This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

Changeset 9906 for ps


Ignore:
Timestamp:
07/24/11 13:42:35 (13 years ago)
Author:
philip
Message:

# New dynamic territories design

Location:
ps/trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_block.xml

    r9889 r9906  
    2929  </VisualActor>
    3030  <TerritoryInfluence>
    31     <OverrideCost>100</OverrideCost>
     31    <OverrideCost>64</OverrideCost>
     32    <Weight>0</Weight>
     33    <Radius>0</Radius>
    3234  </TerritoryInfluence>
    3335</Entity>
  • ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_pull.xml

    r9889 r9906  
    3030  <TerritoryInfluence>
    3131    <OverrideCost>0</OverrideCost>
     32    <Weight>0</Weight>
     33    <Radius>0</Radius>
    3234  </TerritoryInfluence>
    3335</Entity>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

    r9670 r9906  
    6868    </Entities>
    6969  </TrainingQueue>
     70  <TerritoryInfluence>
     71    <Radius>256</Radius>
     72    <Weight>65536</Weight>
     73  </TerritoryInfluence>
    7074</Entity>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml

    r9670 r9906  
    5555    </Entities>
    5656  </TrainingQueue>
     57  <TerritoryInfluence>
     58    <Radius>64</Radius>
     59    <Weight>65536</Weight>
     60  </TerritoryInfluence>
    5761</Entity>
  • ps/trunk/source/simulation2/components/CCmpTerritoryInfluence.cpp

    r9889 r9906  
    3030    DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence)
    3131
    32     u8 m_Cost;
     32    int m_Cost;
     33    u32 m_Weight;
     34    u32 m_Radius;
    3335
    3436    static std::string GetSchema()
    3537    {
    3638        return
    37             "<element name='OverrideCost'>"
     39            "<optional>"
     40                "<element name='OverrideCost'>"
     41                    "<data type='nonNegativeInteger'>"
     42                        "<param name='maxInclusive'>255</param>"
     43                    "</data>"
     44                "</element>"
     45            "</optional>"
     46            "<element name='Weight'>"
     47                "<data type='nonNegativeInteger'/>"
     48            "</element>"
     49            "<element name='Radius'>"
    3850                "<data type='nonNegativeInteger'/>"
    3951            "</element>";
     
    4254    virtual void Init(const CParamNode& paramNode)
    4355    {
    44         m_Cost = paramNode.GetChild("OverrideCost").ToInt();
     56        if (paramNode.GetChild("OverrideCost").IsOk())
     57            m_Cost = paramNode.GetChild("OverrideCost").ToInt();
     58        else
     59            m_Cost = -1;
     60
     61        m_Weight = paramNode.GetChild("Weight").ToInt();
     62        m_Radius = paramNode.GetChild("Radius").ToInt();
    4563    }
    4664
     
    5876    }
    5977
    60     virtual u8 GetCost()
     78    virtual int GetCost()
    6179    {
    6280        return m_Cost;
    6381    }
    6482
     83    virtual u32 GetWeight()
     84    {
     85        return m_Weight;
     86    }
     87
     88    virtual u32 GetRadius()
     89    {
     90        return m_Radius;
     91    }
    6592};
    6693
  • ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp

    r9889 r9906  
    2828#include "simulation2/components/ICmpObstruction.h"
    2929#include "simulation2/components/ICmpObstructionManager.h"
     30#include "simulation2/components/ICmpOwnership.h"
    3031#include "simulation2/components/ICmpPathfinder.h"
    3132#include "simulation2/components/ICmpPosition.h"
     
    168169     * or 1+c if the influence have cost c (assumed between 0 and 254).
    169170     */
    170     void RasteriseInfluences(Grid<u8>& grid);
     171    void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid);
    171172};
    172173
     
    175176
    176177/*
    177 
    178 We compute territory influences with a kind of best-first search:
    179  1) Initialise an 'open' list with tiles that contain settlements (annotated with
    180     territory ID) with initial cost 0
    181  2) Pick the lowest cost tile from 'item'
    182  3) For each neighbour which has not already been assigned to a territory,
    183     assign it to this territory and compute its new cost (effectively the
    184     distance from the associated settlement) and add to 'open'
    185  4) Go to 2 until 'open' is empty
    186 
     178We compute the territory influence of an entity with a kind of best-first search,
     179storing an 'open' list of tiles that have not yet been processed,
     180then taking the highest-weight tile (closest to origin) and updating the weight
     181of extending to each neighbour (based on radius-determining 'falloff' value,
     182adjusted by terrain movement cost), and repeating until all tiles are processed.
    187183*/
    188184
    189 typedef PriorityQueueHeap<std::pair<u16, u16>, u32> OpenQueue;
    190 
    191 static void ProcessNeighbour(u8 pid, u16 i, u16 j, u32 pg, bool diagonal,
    192         Grid<u8>& grid, OpenQueue& queue, const Grid<u8>& influenceGrid)
    193 {
    194     // Ignore tiles that are already claimed
    195     u8 id = grid.get(i, j);
    196     if (id)
     185typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue;
     186
     187static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
     188        Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid)
     189{
     190    u32 dg = falloff * costGrid.get(i, j);
     191    if (diagonal)
     192        dg = (dg * 362) / 256;
     193
     194    // Stop if new cost g=pg-dg is not better than previous value for that tile
     195    // (arranged to avoid underflow if pg < dg)
     196    if (pg <= grid.get(i, j) + dg)
    197197        return;
    198198
    199     // Base cost for moving onto this tile
    200     u32 dg = diagonal ? 362 : 256;
    201 
    202     // Adjust cost based on this tile's influences
    203     dg *= influenceGrid.get(i, j);
    204 
    205     u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
    206 
    207     grid.set(i, j, pid);
     199    u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
     200
     201    grid.set(i, j, g);
    208202    OpenQueue::Item tile = { std::make_pair(i, j), g };
    209203    queue.push(tile);
    210204}
    211205
    212 void CCmpTerritoryManager::CalculateTerritories()
    213 {
    214     PROFILE("CalculateTerritories");
    215 
    216     if (m_Territories)
    217         return;
    218 
    219     CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
    220     uint32_t tilesW = cmpTerrain->GetVerticesPerSide() - 1;
    221     uint32_t tilesH = cmpTerrain->GetVerticesPerSide() - 1;
    222 
    223     Grid<u8> influenceGrid(tilesW, tilesH);
    224 
    225     RasteriseInfluences(influenceGrid);
    226 
    227     SAFE_DELETE(m_Territories);
    228     m_Territories = new Grid<u8>(tilesW, tilesH);
    229 
    230     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
    231     ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass("default");
    232     const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();
    233 
    234     // Adjust influenceGrid so it contains terrain-passability-dependent costs,
    235     // unless overridden by existing values in influenceGrid
    236     for (u32 j = 0; j < tilesH; ++j)
    237     {
    238         for (u32 i = 0; i < tilesW; ++i)
    239         {
    240             u8 cost;
    241             u8 inflCost = influenceGrid.get(i, j);
    242             if (inflCost)
    243             {
    244                 cost = inflCost-1; // undo RasteriseInfluences's offset
    245             }
    246             else
    247             {
    248                 if (passGrid.get(i, j) & passClass)
    249                     cost = 100;
    250                 else
    251                     cost = 1;
    252             }
    253             influenceGrid.set(i, j, cost);
    254         }
    255     }
    256 
    257     OpenQueue openTiles;
    258 
    259     // Initialise open list with all settlements
    260 
    261     CComponentManager::InterfaceList settlements = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_Settlement);
    262     u8 id = 1;
    263     for (CComponentManager::InterfaceList::iterator it = settlements.begin(); it != settlements.end(); ++it)
    264     {
    265         entity_id_t settlement = it->first;
    266         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), settlement);
    267         if (cmpPosition.null() || !cmpPosition->IsInWorld())
    268             continue;
    269 
    270         // TODO: maybe we need to ignore settlements with owner -1,
    271         // since they're probably destroyed
    272 
    273         CFixedVector2D pos = cmpPosition->GetPosition2D();
    274         int i = clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesW-1);
    275         int j = clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesH-1);
    276 
    277         // Must avoid duplicates in the priority queue; ignore the settlement
    278         // if there's already one on that tile
    279         if (!m_Territories->get(i, j))
    280         {
    281             m_Territories->set(i, j, id);
    282             OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), 0 };
    283             openTiles.push(tile);
    284         }
    285 
    286         id += 1;
    287     }
     206static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff)
     207{
     208    u32 tilesW = grid.m_W;
     209    u32 tilesH = grid.m_H;
    288210
    289211    while (!openTiles.empty())
    290212    {
    291213        OpenQueue::Item tile = openTiles.pop();
    292 
    293         // Get current tile's territory ID
    294         u8 tid = m_Territories->get(tile.id.first, tile.id.second);
    295214
    296215        // Process neighbours (if they're not off the edge of the map)
     
    298217        u16 z = tile.id.second;
    299218        if (x > 0)
    300             ProcessNeighbour(tid, x-1, z, tile.rank, false, *m_Territories, openTiles, influenceGrid);
     219            ProcessNeighbour(falloff, x-1, z, tile.rank, false, grid, openTiles, costGrid);
    301220        if (x < tilesW-1)
    302             ProcessNeighbour(tid, x+1, z, tile.rank, false, *m_Territories, openTiles, influenceGrid);
     221            ProcessNeighbour(falloff, x+1, z, tile.rank, false, grid, openTiles, costGrid);
    303222        if (z > 0)
    304             ProcessNeighbour(tid, x, z-1, tile.rank, false, *m_Territories, openTiles, influenceGrid);
     223            ProcessNeighbour(falloff, x, z-1, tile.rank, false, grid, openTiles, costGrid);
    305224        if (z < tilesH-1)
    306             ProcessNeighbour(tid, x, z+1, tile.rank, false, *m_Territories, openTiles, influenceGrid);
     225            ProcessNeighbour(falloff, x, z+1, tile.rank, false, grid, openTiles, costGrid);
    307226        if (x > 0 && z > 0)
    308             ProcessNeighbour(tid, x-1, z-1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
     227            ProcessNeighbour(falloff, x-1, z-1, tile.rank, true, grid, openTiles, costGrid);
    309228        if (x > 0 && z < tilesH-1)
    310             ProcessNeighbour(tid, x-1, z+1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
     229            ProcessNeighbour(falloff, x-1, z+1, tile.rank, true, grid, openTiles, costGrid);
    311230        if (x < tilesW-1 && z > 0)
    312             ProcessNeighbour(tid, x+1, z-1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
     231            ProcessNeighbour(falloff, x+1, z-1, tile.rank, true, grid, openTiles, costGrid);
    313232        if (x < tilesW-1 && z < tilesH-1)
    314             ProcessNeighbour(tid, x+1, z+1, tile.rank, true, *m_Territories, openTiles, influenceGrid);
     233            ProcessNeighbour(falloff, x+1, z+1, tile.rank, true, grid, openTiles, costGrid);
     234    }
     235}
     236
     237void CCmpTerritoryManager::CalculateTerritories()
     238{
     239    PROFILE("CalculateTerritories");
     240
     241    if (m_Territories)
     242        return;
     243
     244    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     245    uint32_t tilesW = cmpTerrain->GetVerticesPerSide() - 1;
     246    uint32_t tilesH = cmpTerrain->GetVerticesPerSide() - 1;
     247
     248    SAFE_DELETE(m_Territories);
     249    m_Territories = new Grid<u8>(tilesW, tilesH);
     250
     251    // Compute terrain-passability-dependent costs per tile
     252    Grid<u8> influenceGrid(tilesW, tilesH);
     253
     254    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     255    ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass("default");
     256    const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();
     257    for (u32 j = 0; j < tilesH; ++j)
     258    {
     259        for (u32 i = 0; i < tilesW; ++i)
     260        {
     261            u8 cost;
     262            if (passGrid.get(i, j) & passClass)
     263                cost = 4; // TODO: should come from some XML file
     264            else
     265                cost = 1;
     266            influenceGrid.set(i, j, cost);
     267        }
     268    }
     269
     270    // Find all territory influence entities
     271    CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
     272
     273    // Allow influence entities to override the terrain costs
     274    RasteriseInfluences(influences, influenceGrid);
     275
     276    // Split influence entities into per-player lists, ignoring any with invalid properties
     277    std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
     278    for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
     279    {
     280        // Ignore any with no weight or radius (to avoid divide-by-zero later)
     281        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     282        if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0)
     283            continue;
     284
     285        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it->first);
     286        if (cmpOwnership.null())
     287            continue;
     288
     289        // Ignore Gaia and unassigned
     290        player_id_t owner = cmpOwnership->GetOwner();
     291        if (owner <= 0)
     292            continue;
     293
     294        // Ignore if invalid position
     295        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
     296        if (cmpPosition.null() || !cmpPosition->IsInWorld())
     297            continue;
     298
     299        influenceEntities[owner].push_back(it->first);
     300    }
     301
     302    // For each player, store the sum of influences on each tile
     303    std::vector<std::pair<player_id_t, Grid<u32> > > playerGrids;
     304    // TODO: this is a large waste of memory; we don't really need to store
     305    // all the intermediate grids
     306
     307    for (std::map<player_id_t, std::vector<entity_id_t> >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)
     308    {
     309        Grid<u32> playerGrid(tilesW, tilesH);
     310
     311        std::vector<entity_id_t>& ents = it->second;
     312        for (std::vector<entity_id_t>::iterator eit = ents.begin(); eit != ents.end(); ++eit)
     313        {
     314            // Compute the influence map of the current entity, then add it to the player grid
     315
     316            Grid<u32> entityGrid(tilesW, tilesH);
     317
     318            CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit);
     319            CFixedVector2D pos = cmpPosition->GetPosition2D();
     320            int i = clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesW-1);
     321            int j = clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesH-1);
     322
     323            CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit);
     324            u32 weight = cmpTerritoryInfluence->GetWeight();
     325            u32 radius = cmpTerritoryInfluence->GetRadius() / CELL_SIZE;
     326            u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
     327
     328            // TODO: we should have some maximum value on weight, to avoid overflow
     329            // when doing all the sums
     330
     331            // Initialise the tile under the entity
     332            entityGrid.set(i, j, weight);
     333            OpenQueue openTiles;
     334            OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), weight };
     335            openTiles.push(tile);
     336
     337            // Expand influences outwards
     338            FloodFill(entityGrid, influenceGrid, openTiles, falloff);
     339
     340            // TODO: we should do a sparse grid and only add the non-zero regions, for performance
     341            for (u16 j = 0; j < entityGrid.m_H; ++j)
     342                for (u16 i = 0; i < entityGrid.m_W; ++i)
     343                    playerGrid.set(i, j, playerGrid.get(i, j) + entityGrid.get(i, j));
     344        }
     345
     346        playerGrids.push_back(std::make_pair(it->first, playerGrid));
     347    }
     348
     349    // Set m_Territories to the player ID with the highest influence for each tile
     350    for (u16 j = 0; j < tilesH; ++j)
     351    {
     352        for (u16 i = 0; i < tilesW; ++i)
     353        {
     354            u32 bestWeight = 0;
     355            for (size_t k = 0; k < playerGrids.size(); ++k)
     356            {
     357                u32 w = playerGrids[k].second.get(i, j);
     358                if (w > bestWeight)
     359                {
     360                    player_id_t id = playerGrids[k].first;
     361                    m_Territories->set(i, j, (u8)id);
     362                    bestWeight = w;
     363                }
     364            }
     365        }
    315366    }
    316367}
     
    337388
    338389
    339 void CCmpTerritoryManager::RasteriseInfluences(Grid<u8>& grid)
    340 {
    341     CComponentManager::InterfaceList infls = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
     390void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid)
     391{
    342392    for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
    343393    {
    344394        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     395
     396        int cost = cmpTerritoryInfluence->GetCost();
     397        if (cost == -1)
     398            continue;
    345399
    346400        CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it->first);
     
    351405        if (!cmpObstruction->GetObstructionSquare(square))
    352406            continue;
    353 
    354         u8 cost = cmpTerritoryInfluence->GetCost();
    355407
    356408        CFixedVector2D halfSize(square.hw, square.hh);
     
    367419                TileCenter(i, j, x, z);
    368420                if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize))
    369                     grid.set(i, j, cost+1);
     421                    grid.set(i, j, cost);
    370422            }
    371423        }
  • ps/trunk/source/simulation2/components/ICmpTerritoryInfluence.h

    r9889 r9906  
    2424{
    2525public:
    26     virtual u8 GetCost() = 0;
     26    /**
     27     * Returns either -1 to indicate no special terrain cost, or a value
     28     * in [0, 255] to indicate overriding the normal cost of the terrain
     29     * under the entity's obstruction.
     30     */
     31    virtual int GetCost() = 0;
     32
     33    virtual u32 GetWeight() = 0;
     34
     35    virtual u32 GetRadius() = 0;
    2736
    2837    DECLARE_INTERFACE_TYPE(TerritoryInfluence)
  • ps/trunk/source/simulation2/helpers/PriorityQueue.h

    r9362 r9906  
    3030#endif
    3131
    32 template <typename Item>
     32template <typename Item, typename CMP>
    3333struct QueueItemPriority
    3434{
    3535    bool operator()(const Item& a, const Item& b)
    3636    {
    37         if (a.rank > b.rank) // higher costs are lower priority
     37        if (CMP()(b.rank, a.rank)) // higher costs are lower priority
    3838            return true;
    39         if (a.rank < b.rank)
     39        if (CMP()(a.rank, b.rank))
    4040            return false;
    4141        // Need to tie-break to get a consistent ordering
     
    5858 * so we shouldn't use it unless we reimplement the heap functions more efficiently.
    5959 */
    60 template <typename ID, typename R>
     60template <typename ID, typename R, typename CMP = std::less<R> >
    6161class PriorityQueueHeap
    6262{
     
    7171    {
    7272        m_Heap.push_back(item);
    73         push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item>());
     73        push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>());
    7474    }
    7575
     
    9494#endif
    9595                m_Heap[n].rank = newrank;
    96                 push_heap(m_Heap.begin(), m_Heap.begin()+n+1, QueueItemPriority<Item>());
     96                push_heap(m_Heap.begin(), m_Heap.begin()+n+1, QueueItemPriority<Item, CMP>());
    9797                return;
    9898            }
     
    106106#endif
    107107        Item r = m_Heap.front();
    108         pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item>());
     108        pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>());
    109109        m_Heap.pop_back();
    110110        return r;
     
    131131 * much simpler and less susceptible to MSVC's painfully slow debug STL.
    132132 */
    133 template <typename ID, typename R>
     133template <typename ID, typename R, typename CMP = std::less<R> >
    134134class PriorityQueueList
    135135{
     
    172172        for (ssize_t i = (ssize_t)bestidx-1; i >= 0; --i)
    173173        {
    174             if (QueueItemPriority<Item>()(best, m_List[i]))
     174            if (QueueItemPriority<Item, CMP>()(best, m_List[i]))
    175175            {
    176176                bestidx = i;
Note: See TracChangeset for help on using the changeset viewer.