Ticket #3204: territory_neighbours.3.diff

File territory_neighbours.3.diff, 26.1 KB (added by sanderd17, 9 years ago)
  • binaries/data/mods/public/simulation/components/Capturable.js

     
    1616    // Cache this value
    1717    this.maxCp = +this.template.CapturePoints;
    1818    this.cp = [];
    19     this.StartRegenTimer();
    2019};
    2120
    2221//// Interface functions ////
     
    5655 */
    5756Capturable.prototype.Reduce = function(amount, playerID)
    5857{
     58    if (amount <= 0)
     59        return 0;
     60
    5961    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    6062    if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
    6163        return 0;
     
    6971    if (cmpFogging)
    7072        cmpFogging.Activate();
    7173
    72     var enemiesFilter = function(v, i) { return v > 0 && cmpPlayerSource.IsEnemy(i); };
    73     var numberOfEnemies = this.cp.filter(enemiesFilter).length;
     74    var numberOfEnemies = this.cp.filter((v, i) => v > 0 && cmpPlayerSource.IsEnemy(i)).length;
    7475
    7576    if (numberOfEnemies == 0)
    7677        return 0;
     
    8889    }
    8990
    9091    // give all cp taken to the player
    91     var takenCp = this.maxCp - this.cp.reduce(function(a, b) { return a + b; });
     92    var takenCp = this.maxCp - this.cp.reduce((a, b) => a + b);
    9293    this.cp[playerID] += takenCp;
    9394
    94     this.StartRegenTimer();
    95 
    96     Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
    97 
    98     if (this.cp[cmpOwnership.GetOwner()] > 0)
    99         return takenCp;
    100 
    101     // if all cp has been taken from the owner, convert it to the best player
    102     var bestPlayer = 0;
    103     for (let i in this.cp)
    104         if (this.cp[i] >= this.cp[bestPlayer])
    105             bestPlayer = +i;
    106 
    107     cmpOwnership.SetOwner(bestPlayer);
    108 
     95    this.CheckTimer();
     96    this.RegisterCapturePointsChanged();
    10997    return takenCp;
    11098};
    11199
     
    128116
    129117//// Private functions ////
    130118
     119/**
     120 * this has to be called whenever the capture points are changed.
     121 * It notifies other components of the change, and switches ownership when needed
     122 */
     123Capturable.prototype.RegisterCapturePointsChanged = function()
     124{
     125    Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
     126
     127    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     128    if (!cmpOwnership)
     129        return
     130    var owner = cmpOwnership.GetOwner();
     131    if (owner == -1 || this.cp[owner] > 0)
     132        return;
     133
     134    // if all cp has been taken from the owner, convert it to the best player
     135    var bestPlayer = 0;
     136    for (let i in this.cp)
     137        if (this.cp[i] >= this.cp[bestPlayer])
     138            bestPlayer = +i;
     139
     140    cmpOwnership.SetOwner(bestPlayer);
     141};
     142
    131143Capturable.prototype.GetRegenRate = function()
    132144{
    133145    var regenRate = +this.template.RegenRate;
    134146    regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity);
    135147
    136     var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
    137     if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())
    138         var territoryDecayRate = cmpTerritoryDecay.GetDecayRate();
    139     else
    140         var territoryDecayRate = 0;
    141 
    142148    var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
    143149    if (cmpGarrisonHolder)
    144150        var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length;
     
    145151    else
    146152        var garrisonRegenRate = 0;
    147153
    148     return regenRate + garrisonRegenRate - territoryDecayRate;
     154    return regenRate + garrisonRegenRate;
    149155};
    150156
    151 Capturable.prototype.RegenCapturePoints = function()
     157Capturable.prototype.TimerTick = function()
    152158{
    153159    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    154160    if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
    155161        return;
    156162
     163    var owner = cmpOwnership.GetOwner();
     164    var modifiedCp = 0;
     165
     166    // special handle for the territory decay
     167    // reduce cp from the owner in favour of all neighbours (also allies)
     168    var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
     169    if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())
     170    {
     171        var neighbours = cmpTerritoryDecay.GetConnectedNeighbours();
     172        var totalNeighbours = neighbours.reduce((a, b) => a + b);
     173        var decay = Math.min(cmpTerritoryDecay.GetDecayRate(), this.cp[owner]);
     174        this.cp[owner] -= decay;
     175        modifiedCp += decay;
     176        for (let p in neighbours)
     177            this.cp[p] += decay * neighbours[p] / totalNeighbours;
     178        this.RegisterCapturePointsChanged();
     179    }
     180
    157181    var regenRate = this.GetRegenRate();
    158182    if (regenRate < 0)
    159         var takenCp = this.Reduce(-regenRate, 0);
    160     else
    161         var takenCp = this.Reduce(regenRate, cmpOwnership.GetOwner())
     183        modifiedCp += this.Reduce(-regenRate, 0)
     184    else if (regenRate > 0)
     185        modifiedCp += this.Reduce(regenRate, owner)
    162186
    163     if (takenCp > 0)
     187    if (modifiedCp)
    164188        return;
    165189
    166     // no capture points taken, stop the timer
     190    // nothing changed, stop the timer
    167191    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    168     cmpTimer.CancelTimer(this.regenTimer);
    169     this.regenTimer = 0;
    170     Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "rate": 0});
     192    cmpTimer.CancelTimer(this.timer);
     193    this.timer = 0;
     194    Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "regenRate": 0, "territoryDecay": 0});
    171195};
    172196
    173197/**
    174198 * Start the regeneration timer when no timer exists
    175  * When nothing can be regenerated (f.e. because the
    176  * rate is 0, or because it is fully regenerated),
     199 * When nothing can be modified (f.e. because it
     200 * is fully regenerated),
    177201 * the timer stops automatically after one execution.
    178202 */
    179 Capturable.prototype.StartRegenTimer = function()
     203Capturable.prototype.CheckTimer = function()
    180204{
    181     if (this.regenTimer)
     205    if (this.timer)
    182206        return;
    183207
    184     var rate = this.GetRegenRate();
    185     if (rate == 0)
     208    var regenRate = this.GetRegenRate();
     209    var cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
     210    var decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0;
     211    if (regenRate == 0 && decay == 0)
    186212        return;
    187213
    188214    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    189     this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Capturable, "RegenCapturePoints", 1000, 1000, null);
    190     Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": true, "rate": rate});
     215    this.timer = cmpTimer.SetInterval(this.entity, IID_Capturable, "TimerTick", 1000, 1000, null);
     216    Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"ticking": true, "regenRate": regenRate, "territoryDecay": decay});
    191217};
    192218
    193219//// Message Listeners ////
     
    206232    for (let i in this.cp)
    207233        this.cp[i] *= scale;
    208234    Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp });
    209     this.StartRegenTimer();
     235    this.CheckTimer();
    210236};
    211237
    212238Capturable.prototype.OnGarrisonedUnitsChanged = function(msg)
    213239{
    214     this.StartRegenTimer();
     240    this.CheckTimer();
    215241};
    216242
    217243Capturable.prototype.OnTerritoryDecayChanged = function(msg)
    218244{
    219245    if (msg.to)
    220         this.StartRegenTimer();
     246        this.CheckTimer();
    221247};
    222248
    223249Capturable.prototype.OnOwnershipChanged = function(msg)
    224250{
    225     this.StartRegenTimer();
     251    this.CheckTimer();
    226252
    227253    // if the new owner has no capture points, it means that either
    228254    // * it's being initialised now, or
  • binaries/data/mods/public/simulation/components/TerritoryDecay.js

     
    88TerritoryDecay.prototype.Init = function()
    99{
    1010    this.decaying = false;
     11    this.connectedNeighbours = [];
    1112};
    1213
    1314TerritoryDecay.prototype.IsConnected = function()
    1415{
     16    var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
     17    this.connectedNeighbours.fill(0, 0, numPlayers);
     18
    1519    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    1620    if (!cmpPosition || !cmpPosition.IsInWorld())
    1721        return false;
     
    3135    var pos = cmpPosition.GetPosition2D();
    3236    var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
    3337    if (tileOwner != cmpOwnership.GetOwner())
     38    {
     39        this.connectedNeighbours[tileOwner] = 1;
    3440        return false;
     41    }
    3542
    36     return cmpTerritoryManager.IsConnected(pos.x, pos.y);
     43    if (cmpTerritoryManager.IsConnected(pos.x, pos.y))
     44        return true;
     45
     46    this.connectedNeighbours = cmpTerritoryManager.GetNeighbours(pos.x, pos.y, true);
     47    return false;
    3748};
    3849
    3950TerritoryDecay.prototype.IsDecaying = function()
     
    4960        this.entity);
    5061};
    5162
     63/**
     64 * Get the number of connected bordering tiles to this region
     65 * Only valid when this.IsDecaying()
     66 */
     67TerritoryDecay.prototype.GetConnectedNeighbours = function()
     68{
     69    return this.connectedNeighbours;
     70};
     71
    5272TerritoryDecay.prototype.UpdateDecayState = function()
    5373{
    5474    if (this.IsConnected())
     
    5878    if (decaying === this.decaying)
    5979        return;
    6080    this.decaying = decaying;
    61     Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying });
     81    Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying, "rate": this.GetDecayRate() });
    6282};
    6383
    6484TerritoryDecay.prototype.OnTerritoriesChanged = function(msg)
  • source/simulation2/components/CCmpTerritoryManager.cpp

     
    224224    }
    225225
    226226    virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z);
     227    virtual std::vector<u32> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected);
    227228    virtual bool IsConnected(entity_pos_t x, entity_pos_t z);
    228229
    229230    // To support lazy updates of territory render data,
     
    280281
    281282typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue;
    282283
    283 static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
    284         Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid)
     284/**
     285 * Compute the tile indexes on the grid nearest to a given point
     286 */
     287static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
    285288{
    286     u32 dg = falloff * costGrid.get(i, j);
    287     if (diagonal)
    288         dg = (dg * 362) / 256;
     289    i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
     290    j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
     291}
    289292
    290     // Stop if new cost g=pg-dg is not better than previous value for that tile
    291     // (arranged to avoid underflow if pg < dg)
    292     if (pg <= grid.get(i, j) + dg)
    293         return;
     293/**
     294 * Returns the position of the center of the given tile
     295 */
     296static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
     297{
     298    x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
     299    z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
     300}
    294301
    295     u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
     302// TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp
    296303
    297     grid.set(i, j, g);
    298     OpenQueue::Item tile = { std::make_pair(i, j), g };
    299     queue.push(tile);
    300 }
    301 
    302304static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff)
    303305{
    304306    u16 tilesW = grid.m_W;
     
    308310    {
    309311        OpenQueue::Item tile = openTiles.pop();
    310312
    311         // Process neighbours (if they're not off the edge of the map)
    312         u16 x = tile.id.first;
    313         u16 z = tile.id.second;
    314         if (x > 0)
    315             ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid);
    316         if (x < tilesW-1)
    317             ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid);
    318         if (z > 0)
    319             ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid);
    320         if (z < tilesH-1)
    321             ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid);
    322         if (x > 0 && z > 0)
    323             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
    324         if (x > 0 && z < tilesH-1)
    325             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
    326         if (x < tilesW-1 && z > 0)
    327             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
    328         if (x < tilesW-1 && z < tilesH-1)
    329             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
     313        // Process neighbours
     314        int x = tile.id.first;
     315        int z = tile.id.second;
     316        u32 pg = tile.rank;
     317        for (int i = -1; i < 2; ++i)
     318        {
     319            int ti = x + i;
     320            if (ti < 0 || ti >= tilesW)
     321                continue;
     322            for (int j = -1; j < 2; ++j)
     323            {
     324                if (i == 0 && j == 0)
     325                    continue;
     326                int tj = z + j;
     327                if (tj < 0 || tj >= tilesH)
     328                    continue;
     329
     330                u32 dg = falloff * costGrid.get(ti, tj);
     331                if (i * j != 0) // diagonal tile
     332                    dg = (dg * 362) / 256;
     333
     334                // Stop if new cost g=pg-dg is not better than previous value for that tile
     335                // (arranged to avoid underflow if pg < dg)
     336                if (pg <= grid.get(ti, tj) + dg)
     337                    continue;
     338
     339                u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
     340
     341                grid.set(ti, tj, g);
     342                OpenQueue::Item tile = { std::make_pair(ti, tj), g };
     343                openTiles.push(tile);
     344            }
     345        }
    330346    }
    331347}
    332348
     
    382398    // Split influence entities into per-player lists, ignoring any with invalid properties
    383399    std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
    384400    std::vector<entity_id_t> rootInfluenceEntities;
    385     for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
     401    for (auto& it : influences)
    386402    {
    387403        // Ignore any with no weight or radius (to avoid divide-by-zero later)
    388         ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     404        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it.second);
    389405        if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0)
    390406            continue;
    391407
    392         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it->first);
     408        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it.first);
    393409        if (!cmpOwnership)
    394410            continue;
    395411
     
    403419            continue;
    404420
    405421        // Ignore if invalid position
    406         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
     422        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it.first);
    407423        if (!cmpPosition || !cmpPosition->IsInWorld())
    408424            continue;
    409425
    410         influenceEntities[owner].push_back(it->first);
     426        influenceEntities[owner].push_back(it.first);
    411427
    412428        if (cmpTerritoryInfluence->IsRoot())
    413             rootInfluenceEntities.push_back(it->first);
     429            rootInfluenceEntities.push_back(it.first);
    414430    }
    415431
    416432    // For each player, store the sum of influences on each tile
     
    418434    // TODO: this is a large waste of memory; we don't really need to store
    419435    // all the intermediate grids
    420436
    421     for (std::map<player_id_t, std::vector<entity_id_t> >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)
     437    for (auto& it : influenceEntities)
    422438    {
    423439        Grid<u32> playerGrid(tilesW, tilesH);
     440        Grid<u32> entityGrid(tilesW, tilesH);
    424441
    425         std::vector<entity_id_t>& ents = it->second;
    426         for (std::vector<entity_id_t>::iterator eit = ents.begin(); eit != ents.end(); ++eit)
     442        std::vector<entity_id_t>& ents = it.second;
     443        // Compute the influence map of the current entity, then add it to the player grid
     444        for (auto& eit : ents)
    427445        {
    428             // Compute the influence map of the current entity, then add it to the player grid
    429 
    430             Grid<u32> entityGrid(tilesW, tilesH);
    431 
    432             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit);
     446            CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), eit);
    433447            CFixedVector2D pos = cmpPosition->GetPosition2D();
    434             u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
    435             u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
     448            u16 i, j;
     449            NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH);
    436450
    437             CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit);
     451            CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), eit);
    438452            u32 weight = cmpTerritoryInfluence->GetWeight();
    439453            u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE;
    440             u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
     454            u32 falloff = weight / radius + 1; // earlier check for GetRadius() == 0 prevents divide-by-zero
    441455
    442             // TODO: we should have some maximum value on weight, to avoid overflow
    443             // when doing all the sums
    444456
    445457            // Initialise the tile under the entity
    446458            entityGrid.set(i, j, weight);
    447459            OpenQueue openTiles;
    448             OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), weight };
     460            OpenQueue::Item tile = { std::make_pair(i, j), weight };
    449461            openTiles.push(tile);
    450462
    451463            // Expand influences outwards
    452464            FloodFill(entityGrid, influenceGrid, openTiles, falloff);
    453465
    454             // TODO: we should do a sparse grid and only add the non-zero regions, for performance
    455             playerGrid.add(entityGrid);
     466            // TODO: we should have overflow checks when adding, as the number of added
     467            // entities can be arbitrary large
     468            playerGrid.addRegion(entityGrid, i-radius, j-radius, i+radius, j+radius);
     469            entityGrid.reset();
    456470        }
    457471
    458         playerGrids.push_back(std::make_pair(it->first, playerGrid));
     472        playerGrids.emplace_back(it.first, playerGrid);
    459473    }
    460474
    461475    // Set m_Territories to the player ID with the highest influence for each tile
     
    467481            for (size_t k = 0; k < playerGrids.size(); ++k)
    468482            {
    469483                u32 w = playerGrids[k].second.get(i, j);
    470                 if (w > bestWeight)
    471                 {
    472                     player_id_t id = playerGrids[k].first;
    473                     m_Territories->set(i, j, (u8)id);
    474                     bestWeight = w;
    475                 }
     484                if (w <= bestWeight)
     485                    continue;
     486                m_Territories->set(i, j, (u8)playerGrids[k].first);
     487                bestWeight = w;
    476488            }
    477489        }
    478490    }
     
    479491
    480492    // Detect territories connected to a 'root' influence (typically a civ center)
    481493    // belonging to their player, and mark them with the connected flag
    482     for (std::vector<entity_id_t>::iterator it = rootInfluenceEntities.begin(); it != rootInfluenceEntities.end(); ++it)
     494    for (auto& it : rootInfluenceEntities)
    483495    {
    484496        // (These components must be valid else the entities wouldn't be added to this list)
    485         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), *it);
    486         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
     497        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it);
     498        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it);
    487499
    488500        CFixedVector2D pos = cmpPosition->GetPosition2D();
    489         u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
    490         u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
     501        u16 i, j;
     502        NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH);
    491503
    492504        u8 owner = (u8)cmpOwnership->GetOwner();
    493505
     
    499511
    500512        Grid<u8>& grid = *m_Territories;
    501513
    502         u16 maxi = (u16)(grid.m_W-1);
    503         u16 maxj = (u16)(grid.m_H-1);
    504 
    505514        std::vector<std::pair<u16, u16> > tileStack;
     515        tileStack.emplace_back(i, j);
    506516
    507 #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair(i, j)); )
    508 
    509         MARK_AND_PUSH(i, j);
    510517        while (!tileStack.empty())
    511518        {
    512             int ti = tileStack.back().first;
    513             int tj = tileStack.back().second;
     519            int x = tileStack.back().first;
     520            int z = tileStack.back().second;
    514521            tileStack.pop_back();
    515 
    516             if (ti > 0 && grid.get(ti-1, tj) == owner)
    517                 MARK_AND_PUSH(ti-1, tj);
    518             if (ti < maxi && grid.get(ti+1, tj) == owner)
    519                 MARK_AND_PUSH(ti+1, tj);
    520             if (tj > 0 && grid.get(ti, tj-1) == owner)
    521                 MARK_AND_PUSH(ti, tj-1);
    522             if (tj < maxj && grid.get(ti, tj+1) == owner)
    523                 MARK_AND_PUSH(ti, tj+1);
    524 
    525             if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
    526                 MARK_AND_PUSH(ti-1, tj-1);
    527             if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner)
    528                 MARK_AND_PUSH(ti-1, tj+1);
    529             if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner)
    530                 MARK_AND_PUSH(ti+1, tj-1);
    531             if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner)
    532                 MARK_AND_PUSH(ti+1, tj+1);
     522            for (int i = -1; i < 2; ++i)
     523            {
     524                int ti = x + i;
     525                if (ti < 0 || ti >= tilesW) // check grid bounds
     526                    continue;
     527                for (int j = -1; j < 2; ++j)
     528                {
     529                    if (i == 0 && j == 0) // don't process the same tile
     530                        continue;
     531                    int tj = z + j;
     532                    if (tj < 0 || tj >= tilesH) // check grid bounds
     533                        continue;
     534                    if (grid.get(ti, tj) != owner)
     535                        continue;
     536                    grid.set(ti, tj, owner | TERRITORY_CONNECTED_MASK);
     537                    tileStack.emplace_back(ti, tj);
     538                }
     539            }
    533540        }
    534 
    535 #undef MARK_AND_PUSH
    536541    }
    537542}
    538543
    539 /**
    540  * Compute the tile indexes on the grid nearest to a given point
    541  */
    542 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
    543 {
    544     i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
    545     j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
    546 }
    547 
    548 /**
    549  * Returns the position of the center of the given tile
    550  */
    551 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
    552 {
    553     x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    554     z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    555 }
    556 
    557 // TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp
    558 
    559 
    560544void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid)
    561545{
    562     for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
     546    for (auto& it : infls)
    563547    {
    564         ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     548        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it.second);
    565549
    566550        i32 cost = cmpTerritoryInfluence->GetCost();
    567551        if (cost == -1)
    568552            continue;
    569553
    570         CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it->first);
     554        CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it.first);
    571555        if (!cmpObstruction)
    572556            continue;
    573557
     
    722706    return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
    723707}
    724708
     709std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
     710{
     711    CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     712    if (!cmpPlayerManager)
     713        return std::vector<u32>();
     714
     715    std::vector<u32> ret(cmpPlayerManager->GetNumPlayers(), 0);
     716    CalculateTerritories();
     717    if (!m_Territories)
     718        return ret;
     719
     720    u16 i, j;
     721    NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     722
     723    // calculate the neighbours
     724    player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
     725
     726    u16 tilesW = m_Territories->m_W;
     727    u16 tilesH = m_Territories->m_H;
     728
     729    // use a flood-fill algorithm that fills up to the borders and remembers the owners
     730    Grid<bool>* markerGrid = new Grid<bool>(tilesW, tilesH);
     731    std::vector<std::pair<u16, u16> > tileStack;
     732    tileStack.emplace_back(i, j);
     733    markerGrid->set(i, j, true);
     734    while (!tileStack.empty())
     735    {
     736        int x = tileStack.back().first;
     737        int z = tileStack.back().second;
     738        tileStack.pop_back();
     739        for (int i = -1; i < 2; ++i)
     740        {
     741            int ti = x + i;
     742            if (ti < 0 || ti >= tilesW)
     743                continue;
     744            for (int j = -1; j < 2; ++j)
     745            {
     746                if (i == 0 && j == 0)
     747                    continue;
     748                int tj = z + j;
     749                if (tj < 0 || tj >= tilesH)
     750                    continue;
     751
     752                if (markerGrid->get(ti, tj))
     753                    continue;
     754                // mark the tile as visited in any case
     755                markerGrid->set(ti, tj, true);
     756                int owner = m_Territories->get(ti, tj) & TERRITORY_PLAYER_MASK;
     757                if (owner == thisOwner)
     758                    tileStack.emplace_back(ti, tj); // expand the tile
     759                else if (owner == 0 || !filterConnected || (m_Territories->get(ti, tj) & TERRITORY_CONNECTED_MASK) != 0)
     760                    ret[owner]++; // add player to the neighbours when requested
     761            }
     762        }
     763    }
     764    return ret;
     765}
     766
    725767bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
    726768{
    727769    u16 i, j;
  • source/simulation2/components/ICmpTerritoryManager.cpp

     
    2323
    2424BEGIN_INTERFACE_WRAPPER(TerritoryManager)
    2525DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
     26DEFINE_INTERFACE_METHOD_3("GetNeighbours", std::vector<u32>, ICmpTerritoryManager, GetNeighbours, entity_pos_t, entity_pos_t, bool)
    2627DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t)
    2728END_INTERFACE_WRAPPER(TerritoryManager)
  • source/simulation2/components/ICmpTerritoryManager.h

     
    4747    virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
    4848
    4949    /**
     50     * get the number of neighbour tiles for per player for the selected position
     51     * @return A list with the number of neighbour tiles per player
     52     */
     53    virtual std::vector<u32> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) = 0;
     54
     55    /**
    5056     * Get whether territory at given position is connected to a root object
    5157     * (civ center etc) owned by that territory's player.
    5258     */
  • source/simulation2/helpers/Grid.h

     
    9292            m_Data[i] += g.m_Data[i];
    9393    }
    9494
     95    // Add a region of a grid to this grid
     96    void addRegion(const Grid& g, int minI, int minJ, int maxI, int maxJ)
     97    {
     98#if GRID_BOUNDS_DEBUG
     99        ENSURE(g.m_W == m_W && g.m_H == m_H);
     100        ENSURE(minI <= maxI && minJ <= maxJ);
     101#endif
     102        minI = std::min(std::max(minI, 0), (int)g.m_W);
     103        maxI = std::min(std::max(maxI, 0), (int)g.m_W);
     104        minJ = std::min(std::max(minJ, 0), (int)g.m_H);
     105        maxJ = std::min(std::max(maxJ, 0), (int)g.m_H);
     106        for (int j = minJ; j <= maxJ; ++j)
     107        {
     108            for (int i = minI; i <= maxI; ++i)
     109            {
     110                m_Data[j*m_W + i] += g.m_Data[j*m_W + i];
     111            }
     112        }
     113    }
     114
    95115    void set(int i, int j, const T& value)
    96116    {
    97117#if GRID_BOUNDS_DEBUG