Ticket #3204: territory_neighbours.5.diff
File territory_neighbours.5.diff, 39.3 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/simulation/components/Capturable.js
16 16 // Cache this value 17 17 this.maxCp = +this.template.CapturePoints; 18 18 this.cp = []; 19 this.StartRegenTimer();20 19 }; 21 20 22 21 //// Interface functions //// … … 56 55 */ 57 56 Capturable.prototype.Reduce = function(amount, playerID) 58 57 { 58 if (amount <= 0) 59 return 0; 60 59 61 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 60 62 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 61 63 return 0; … … 69 71 if (cmpFogging) 70 72 cmpFogging.Activate(); 71 73 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; 74 75 75 76 if (numberOfEnemies == 0) 76 77 return 0; … … 88 89 } 89 90 90 91 // 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); 92 93 this.cp[playerID] += takenCp; 93 94 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(); 109 97 return takenCp; 110 98 }; 111 99 … … 128 116 129 117 //// Private functions //// 130 118 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 */ 123 Capturable.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 131 143 Capturable.prototype.GetRegenRate = function() 132 144 { 133 145 var regenRate = +this.template.RegenRate; 134 146 regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity); 135 147 136 var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);137 if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())138 var territoryDecayRate = cmpTerritoryDecay.GetDecayRate();139 else140 var territoryDecayRate = 0;141 142 148 var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); 143 149 if (cmpGarrisonHolder) 144 150 var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length; … … 145 151 else 146 152 var garrisonRegenRate = 0; 147 153 148 return regenRate + garrisonRegenRate - territoryDecayRate;154 return regenRate + garrisonRegenRate; 149 155 }; 150 156 151 Capturable.prototype. RegenCapturePoints= function()157 Capturable.prototype.TimerTick = function() 152 158 { 153 159 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 154 160 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 155 161 return; 156 162 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 157 181 var regenRate = this.GetRegenRate(); 158 182 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) 162 186 163 if ( takenCp > 0)187 if (modifiedCp) 164 188 return; 165 189 166 // no capture points taken, stop the timer190 // nothing changed, stop the timer 167 191 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, "r ate": 0});192 cmpTimer.CancelTimer(this.timer); 193 this.timer = 0; 194 Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "regenRate": 0, "territoryDecay": 0}); 171 195 }; 172 196 173 197 /** 174 * 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), 177 * the timer stops automatically after one execution. 198 * Start the regeneration timer when no timer exists. 199 * When nothing can be modified (f.e. because it is fully regenerated), the 200 * timer stops automatically after one execution. 178 201 */ 179 Capturable.prototype. StartRegenTimer = function()202 Capturable.prototype.CheckTimer = function() 180 203 { 181 if (this. regenTimer)204 if (this.timer) 182 205 return; 183 206 184 var rate = this.GetRegenRate(); 185 if (rate == 0) 207 var regenRate = this.GetRegenRate(); 208 var cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay); 209 var decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0; 210 if (regenRate == 0 && decay == 0) 186 211 return; 187 212 188 213 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});214 this.timer = cmpTimer.SetInterval(this.entity, IID_Capturable, "TimerTick", 1000, 1000, null); 215 Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"ticking": true, "regenRate": regenRate, "territoryDecay": decay}); 191 216 }; 192 217 193 218 //// Message Listeners //// … … 206 231 for (let i in this.cp) 207 232 this.cp[i] *= scale; 208 233 Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); 209 this. StartRegenTimer();234 this.CheckTimer(); 210 235 }; 211 236 212 237 Capturable.prototype.OnGarrisonedUnitsChanged = function(msg) 213 238 { 214 this. StartRegenTimer();239 this.CheckTimer(); 215 240 }; 216 241 217 242 Capturable.prototype.OnTerritoryDecayChanged = function(msg) 218 243 { 219 244 if (msg.to) 220 this. StartRegenTimer();245 this.CheckTimer(); 221 246 }; 222 247 223 248 Capturable.prototype.OnOwnershipChanged = function(msg) 224 249 { 225 this. StartRegenTimer();250 this.CheckTimer(); 226 251 227 252 // if the new owner has no capture points, it means that either 228 253 // * it's being initialised now, or -
binaries/data/mods/public/simulation/components/TerritoryDecay.js
8 8 TerritoryDecay.prototype.Init = function() 9 9 { 10 10 this.decaying = false; 11 this.connectedNeighbours = []; 11 12 }; 12 13 13 14 TerritoryDecay.prototype.IsConnected = function() 14 15 { 16 var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); 17 this.connectedNeighbours.fill(0, 0, numPlayers); 18 15 19 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 16 20 if (!cmpPosition || !cmpPosition.IsInWorld()) 17 21 return false; … … 18 22 19 23 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 20 24 if (!cmpOwnership) 21 return false;25 return true; // something without ownership can't decay 22 26 23 // Prevent special gaia buildings from decaying (e.g. fences, ruins)24 if (cmpOwnership.GetOwner() == 0)25 return true;26 27 27 var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager); 28 if (!cmpTerritoryManager)29 return false;30 31 28 var pos = cmpPosition.GetPosition2D(); 32 29 var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y); 33 30 if (tileOwner != cmpOwnership.GetOwner()) 31 { 32 this.connectedNeighbours[tileOwner] = 1; 34 33 return false; 34 } 35 35 36 return cmpTerritoryManager.IsConnected(pos.x, pos.y); 36 if (tileOwner == 0) 37 return true; // Gaia building on gaia ground -> don't decay 38 39 if (cmpTerritoryManager.IsConnected(pos.x, pos.y)) 40 return true; 41 42 this.connectedNeighbours = cmpTerritoryManager.GetNeighbours(pos.x, pos.y, true); 43 return false; 37 44 }; 38 45 39 46 TerritoryDecay.prototype.IsDecaying = function() … … 49 56 this.entity); 50 57 }; 51 58 59 /** 60 * Get the number of connected bordering tiles to this region 61 * Only valid when this.IsDecaying() 62 */ 63 TerritoryDecay.prototype.GetConnectedNeighbours = function() 64 { 65 return this.connectedNeighbours; 66 }; 67 52 68 TerritoryDecay.prototype.UpdateDecayState = function() 53 69 { 54 70 if (this.IsConnected()) … … 58 74 if (decaying === this.decaying) 59 75 return; 60 76 this.decaying = decaying; 61 Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying });77 Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying, "rate": this.GetDecayRate() }); 62 78 }; 63 79 64 80 TerritoryDecay.prototype.OnTerritoriesChanged = function(msg) … … 73 89 74 90 TerritoryDecay.prototype.OnOwnershipChanged = function(msg) 75 91 { 76 this.UpdateDecayState(); 92 // if it influences the territory, wait until we get a TerritoriesChanged message 93 if (!Engine.QueryInterface(this.entity, IID_TerritoryInfluence)) 94 this.UpdateDecayState(); 77 95 }; 78 96 79 97 Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay); -
binaries/data/mods/public/simulation/templates/special/territory_block.xml
1 <?xml version="1.0" encoding="utf-8"?>2 <Entity>3 <Footprint>4 <Square width="64" depth="8"/>5 <Height>9.0</Height>6 </Footprint>7 <Obstruction>8 <Static width="64" depth="8"/>9 <Active>true</Active>10 <BlockMovement>false</BlockMovement>11 <BlockPathfinding>false</BlockPathfinding>12 <BlockFoundation>false</BlockFoundation>13 <BlockConstruction>false</BlockConstruction>14 <DisableBlockMovement>false</DisableBlockMovement>15 <DisableBlockPathfinding>false</DisableBlockPathfinding>16 </Obstruction>17 <Ownership/>18 <Position>19 <Altitude>0</Altitude>20 <Anchor>upright</Anchor>21 <Floating>false</Floating>22 <TurnRate>6.0</TurnRate>23 </Position>24 <Selectable>25 <EditorOnly/>26 <Overlay>27 <Texture>28 <MainTexture>circle/128x128.png</MainTexture>29 <MainTextureMask>circle/128x128_mask.png</MainTextureMask>30 </Texture>31 </Overlay>32 </Selectable>33 <TerritoryInfluence>34 <OverrideCost>64</OverrideCost>35 <Root>false</Root>36 <Weight>0</Weight>37 <Radius>0</Radius>38 </TerritoryInfluence>39 <VisualActor>40 <SilhouetteDisplay>false</SilhouetteDisplay>41 <SilhouetteOccluder>true</SilhouetteOccluder>42 <Actor>structures/hellenes/wall_medium.xml</Actor>43 <VisibleInAtlasOnly>true</VisibleInAtlasOnly>44 </VisualActor>45 </Entity> -
binaries/data/mods/public/simulation/templates/special/territory_pull.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity> 3 <Footprint> 4 <Square width="64" depth="8"/> 5 <Height>9.0</Height> 6 </Footprint> 7 <Obstruction> 8 <Static width="64" depth="8"/> 9 <Active>true</Active> 10 <BlockMovement>false</BlockMovement> 11 <BlockPathfinding>false</BlockPathfinding> 12 <BlockFoundation>false</BlockFoundation> 13 <BlockConstruction>false</BlockConstruction> 14 <DisableBlockMovement>false</DisableBlockMovement> 15 <DisableBlockPathfinding>false</DisableBlockPathfinding> 16 </Obstruction> 3 <Capturable> 4 <CapturePoints>5</CapturePoints> 5 <RegenRate>0</RegenRate> 6 <GarrisonRegenRate>0</GarrisonRegenRate> 7 </Capturable> 17 8 <Ownership/> 18 9 <Position> 19 10 <Altitude>0</Altitude> … … 30 21 </Texture> 31 22 </Overlay> 32 23 </Selectable> 24 <TerritoryDecay> 25 <DecayRate>5</DecayRate> 26 </TerritoryDecay> 33 27 <TerritoryInfluence> 34 <OverrideCost>0</OverrideCost>35 28 <Root>false</Root> 36 < Weight>0</Weight>37 < Radius>0</Radius>29 <Radius>32</Radius> 30 <Weight>30000</Weight> 38 31 </TerritoryInfluence> 39 32 <VisualActor> 40 33 <SilhouetteDisplay>false</SilhouetteDisplay> 41 34 <SilhouetteOccluder>true</SilhouetteOccluder> 42 <Actor> structures/hellenes/wall_medium.xml</Actor>35 <Actor>props/special/common/waypoint_flag.xml</Actor> 43 36 <VisibleInAtlasOnly>true</VisibleInAtlasOnly> 44 37 </VisualActor> 45 38 </Entity> -
source/simulation2/components/CCmpTerritoryInfluence.cpp
33 33 34 34 DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence) 35 35 36 i32 m_Cost;37 36 bool m_Root; 38 u 32m_Weight;37 u16 m_Weight; 39 38 u32 m_Radius; 40 39 41 40 static std::string GetSchema() 42 41 { 43 42 return 44 "<optional>"45 "<element name='OverrideCost'>"46 "<data type='nonNegativeInteger'>"47 "<param name='maxInclusive'>255</param>"48 "</data>"49 "</element>"50 "</optional>"51 43 "<element name='Root'>" 52 44 "<data type='boolean'/>" 53 45 "</element>" 54 46 "<element name='Weight'>" 55 "<data type='nonNegativeInteger'/>" 47 "<data type='nonNegativeInteger'>" 48 "<param name='maxInclusive'>65536</param>" // Max value 2^16 49 "</data>" 56 50 "</element>" 57 51 "<element name='Radius'>" 58 52 "<data type='nonNegativeInteger'/>" … … 61 55 62 56 virtual void Init(const CParamNode& paramNode) 63 57 { 64 if (paramNode.GetChild("OverrideCost").IsOk())65 m_Cost = paramNode.GetChild("OverrideCost").ToInt();66 else67 m_Cost = -1;68 69 58 m_Root = paramNode.GetChild("Root").ToBool(); 70 m_Weight = paramNode.GetChild("Weight").ToInt();59 m_Weight = (u16)paramNode.GetChild("Weight").ToInt(); 71 60 m_Radius = paramNode.GetChild("Radius").ToInt(); 72 61 } 73 62 … … 84 73 Init(paramNode); 85 74 } 86 75 87 virtual i32 GetCost()88 {89 return m_Cost;90 }91 92 76 virtual bool IsRoot() 93 77 { 94 78 return m_Root; 95 79 } 96 80 97 virtual u 32GetWeight()81 virtual u16 GetWeight() 98 82 { 99 83 return m_Weight; 100 84 } -
source/simulation2/components/CCmpTerritoryManager.cpp
1 /* Copyright (C) 201 2Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 25 25 #include "graphics/TextureManager.h" 26 26 #include "graphics/TerritoryBoundary.h" 27 27 #include "maths/MathUtil.h" 28 #include "maths/Vector2D.h"29 28 #include "renderer/Renderer.h" 30 29 #include "renderer/Scene.h" 31 30 #include "renderer/TerrainOverlay.h" 32 31 #include "simulation2/MessageTypes.h" 33 #include "simulation2/components/ICmpObstruction.h"34 #include "simulation2/components/ICmpObstructionManager.h"35 32 #include "simulation2/components/ICmpOwnership.h" 36 33 #include "simulation2/components/ICmpPathfinder.h" 37 34 #include "simulation2/components/ICmpPlayer.h" 38 35 #include "simulation2/components/ICmpPlayerManager.h" 39 36 #include "simulation2/components/ICmpPosition.h" 40 #include "simulation2/components/ICmpSettlement.h"41 37 #include "simulation2/components/ICmpTerrain.h" 42 38 #include "simulation2/components/ICmpTerritoryInfluence.h" 43 39 #include "simulation2/helpers/Geometry.h" 44 40 #include "simulation2/helpers/Grid.h" 45 #include "simulation2/helpers/PriorityQueue.h"46 41 #include "simulation2/helpers/Render.h" 42 #include "ps/CLogger.h" 47 43 48 44 class CCmpTerritoryManager; 49 45 … … 207 203 // ignore any others 208 204 void MakeDirtyIfRelevantEntity(entity_id_t ent) 209 205 { 210 CmpPtr<ICmpSettlement> cmpSettlement(GetSimContext(), ent);211 if (cmpSettlement)212 MakeDirty();213 214 206 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent); 215 207 if (cmpTerritoryInfluence) 216 208 MakeDirty(); … … 224 216 } 225 217 226 218 virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z); 219 virtual std::vector<u32> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected); 227 220 virtual bool IsConnected(entity_pos_t x, entity_pos_t z); 228 221 229 222 // To support lazy updates of territory render data, … … 252 245 253 246 void CalculateTerritories(); 254 247 255 /**256 * Updates @p grid based on the obstruction shapes of all entities with257 * a TerritoryInfluence component. Grid cells are 0 if no influence,258 * or 1+c if the influence have cost c (assumed between 0 and 254).259 */260 void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid);261 262 248 std::vector<STerritoryBoundary> ComputeBoundaries(); 263 249 264 250 void UpdateBoundaryLines(); … … 270 256 271 257 REGISTER_COMPONENT_TYPE(TerritoryManager) 272 258 273 /* 274 We compute the territory influence of an entity with a kind of best-first search, 275 storing an 'open' list of tiles that have not yet been processed, 276 then taking the highest-weight tile (closest to origin) and updating the weight 277 of extending to each neighbour (based on radius-determining 'falloff' value, 278 adjusted by terrain movement cost), and repeating until all tiles are processed. 279 */ 280 281 typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue; 282 283 static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal, 284 Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid) 259 // Tile data type, for easier accessing of coordinates 260 struct Tile 285 261 { 286 u32 dg = falloff * costGrid.get(i, j);287 if (diagonal)288 dg = (dg * 362) / 256;262 Tile(u16 i, u16 j) : x(i), z(j) { } 263 u16 x, z; 264 }; 289 265 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; 266 // Floodfill templates that expand neighbours from a certain source onwards 267 // (x, z) are the coordinates of the currently expanded tile 268 // (nx, nz) are the coordinates of the current neighbour handled 269 // The user of this floodfill should use "continue" on every neighbour that 270 // shouldn't be expanded on its own. (without continue, an infinite loop will happen) 271 # define FLOODFILL(i, j, code)\ 272 do {\ 273 const int NUM_NEIGHBOURS = 8;\ 274 const int NEIGHBOURS_X[NUM_NEIGHBOURS] = {1,-1, 0, 0, 1,-1, 1,-1};\ 275 const int NEIGHBOURS_Z[NUM_NEIGHBOURS] = {0, 0, 1,-1, 1,-1,-1, 1};\ 276 std::queue<Tile> openTiles;\ 277 openTiles.emplace(i, j);\ 278 while (!openTiles.empty())\ 279 {\ 280 u16 x = openTiles.front().x;\ 281 u16 z = openTiles.front().z;\ 282 openTiles.pop();\ 283 for (int n = 0; n < NUM_NEIGHBOURS; ++n)\ 284 {\ 285 u16 nx = x + NEIGHBOURS_X[n];\ 286 u16 nz = z + NEIGHBOURS_Z[n];\ 287 /* Check the bounds, underflow will cause the values to be big again */\ 288 if (nx >= tilesW || nz >= tilesH)\ 289 continue;\ 290 code\ 291 openTiles.emplace(nx, nz);\ 292 }\ 293 }\ 294 }\ 295 while (false) 294 296 295 u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor 296 297 grid.set(i, j, g); 298 OpenQueue::Item tile = { std::make_pair(i, j), g }; 299 queue.push(tile); 300 } 301 302 static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff) 297 /** 298 * Compute the tile indexes on the grid nearest to a given point 299 */ 300 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) 303 301 { 304 u16 tilesW = grid.m_W; 305 u16 tilesH = grid.m_H; 306 307 while (!openTiles.empty()) 308 { 309 OpenQueue::Item tile = openTiles.pop(); 310 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); 330 } 302 i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1); 303 j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1); 331 304 } 332 305 333 306 void CCmpTerritoryManager::CalculateTerritories() … … 344 317 if (!cmpTerrain->IsLoaded()) 345 318 return; 346 319 347 u16 tilesW = cmpTerrain->GetTilesPerSide();348 u16 tilesH = cmpTerrain->GetTilesPerSide();320 const u16 tilesW = cmpTerrain->GetTilesPerSide(); 321 const u16 tilesH = tilesW; 349 322 350 323 m_Territories = new Grid<u8>(tilesW, tilesH); 351 324 352 // Compute terrain-passability-dependent costs per tile353 Grid<u8> influenceGrid(tilesW, tilesH);354 355 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());356 ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");357 ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");358 359 const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();360 for (u16 j = 0; j < tilesH; ++j)361 {362 for (u16 i = 0; i < tilesW; ++i)363 {364 u16 g = passGrid.get(i, j);365 u8 cost;366 if (g & passClassUnrestricted)367 cost = 255; // off the world; use maximum cost368 else if (g & passClassDefault)369 cost = m_ImpassableCost;370 else371 cost = 1;372 influenceGrid.set(i, j, cost);373 }374 }375 376 325 // Find all territory influence entities 377 326 CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence); 378 327 379 // Allow influence entities to override the terrain costs380 RasteriseInfluences(influences, influenceGrid);381 382 328 // Split influence entities into per-player lists, ignoring any with invalid properties 383 329 std::map<player_id_t, std::vector<entity_id_t> > influenceEntities; 384 std::vector<entity_id_t> rootInfluenceEntities; 385 for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it) 330 for (const CComponentManager::InterfacePair& pair : influences) 386 331 { 387 // Ignore any with no weight or radius (to avoid divide-by-zero later) 388 ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second); 389 if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0) 390 continue; 332 entity_id_t ent = pair.first; 391 333 392 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it->first);334 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent); 393 335 if (!cmpOwnership) 394 336 continue; 395 337 396 // Ignore Gaia and unassigned 338 // Ignore Gaia and unassigned or players we can't represent 397 339 player_id_t owner = cmpOwnership->GetOwner(); 398 if (owner <= 0 )340 if (owner <= 0 || owner > TERRITORY_PLAYER_MASK) 399 341 continue; 400 342 401 // We only have so many bits to store tile ownership, so ignore unrepresentable players 402 if (owner > TERRITORY_PLAYER_MASK) 403 continue; 343 influenceEntities[owner].push_back(ent); 344 } 404 345 405 // Ignore if invalid position406 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);407 if (!cmpPosition || !cmpPosition->IsInWorld())408 continue;346 // Store the overall best weight for comparison 347 Grid<u32> bestWeightGrid(tilesW, tilesH); 348 // store the root influences to mark territory as connected 349 std::vector<entity_id_t> rootInfluenceEntities; 409 350 410 influenceEntities[owner].push_back(it->first); 351 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); 352 if (!cmpPathfinder) 353 return; 411 354 412 if (cmpTerritoryInfluence->IsRoot()) 413 rootInfluenceEntities.push_back(it->first); 414 } 355 ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default"); 356 ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); 415 357 416 // For each player, store the sum of influences on each tile 417 std::vector<std::pair<player_id_t, Grid<u32> > > playerGrids; 418 // TODO: this is a large waste of memory; we don't really need to store 419 // all the intermediate grids 358 const Grid<u16>& passibilityGrid = cmpPathfinder->GetPassabilityGrid(); 420 359 421 for ( std::map<player_id_t, std::vector<entity_id_t> >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)360 for (const std::pair<player_id_t, std::vector<entity_id_t> >& pair : influenceEntities) 422 361 { 362 // entityGrid stores the weight for a single entity, and is reset per entity 363 Grid<u32> entityGrid(tilesW, tilesH); 364 // playerGrid stores the combined weight of all entities for this player 423 365 Grid<u32> playerGrid(tilesW, tilesH); 424 366 425 std::vector<entity_id_t>& ents = it->second; 426 for (std::vector<entity_id_t>::iterator eit = ents.begin(); eit != ents.end(); ++eit) 367 u8 owner = (u8)pair.first; 368 const std::vector<entity_id_t>& ents = pair.second; 369 // With 2^16 entities, we're safe against overflows as the weight is also limited to 2^16 370 ENSURE(ents.size() < 1 << 16); 371 // Compute the influence map of the current entity, then add it to the player grid 372 for (entity_id_t ent : ents) 427 373 { 428 // Compute the influence map of the current entity, then add it to the player grid 374 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent); 375 if (!cmpPosition || !cmpPosition->IsInWorld()) 376 continue; 429 377 430 Grid<u32> entityGrid(tilesW, tilesH); 378 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), ent); 379 u32 weight = cmpTerritoryInfluence->GetWeight(); 380 u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE; 381 if (weight == 0 || radius == 0) 382 continue; 383 u32 falloff = weight / radius; 431 384 432 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit);433 385 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);386 u16 i, j; 387 NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH); 436 388 437 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit); 438 u32 weight = cmpTerritoryInfluence->GetWeight(); 439 u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE; 440 u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero 389 if (cmpTerritoryInfluence->IsRoot()) 390 rootInfluenceEntities.push_back(ent); 441 391 442 // TODO: we should have some maximum value on weight, to avoid overflow443 // when doing all the sums444 445 392 // Initialise the tile under the entity 446 393 entityGrid.set(i, j, weight); 447 OpenQueue openTiles; 448 OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), weight }; 449 openTiles.push(tile); 394 if (weight > bestWeightGrid.get(i, j)) 395 { 396 bestWeightGrid.set(i, j, weight); 397 m_Territories->set(i, j, owner); 398 } 450 399 451 400 // Expand influences outwards 452 FloodFill(entityGrid, influenceGrid, openTiles, falloff); 401 FLOODFILL(i, j, 402 u32 dg = falloff; 403 // enlarge the falloff for unpassable tiles 404 u16 g = passibilityGrid.get(nx, nz); 405 if (g & passClassUnrestricted) 406 dg *= 255; // off the world; use maximum cost 407 else if (g & passClassDefault) 408 dg *= m_ImpassableCost; 453 409 454 // TODO: we should do a sparse grid and only add the non-zero regions, for performance455 playerGrid.add(entityGrid);456 }410 // diagonal neighbour -> multiply with approx sqrt(2) 411 if (nx != x && nz != z) 412 dg = (dg * 362) / 256; 457 413 458 playerGrids.push_back(std::make_pair(it->first, playerGrid)); 459 } 414 // Don't expand if new cost is not better than previous value for that tile 415 // (arranged to avoid underflow if entityGrid.get(x, z) < dg) 416 if (entityGrid.get(x, z) <= entityGrid.get(nx, nz) + dg) 417 continue; 460 418 461 // Set m_Territories to the player ID with the highest influence for each tile 462 for (u16 j = 0; j < tilesH; ++j) 463 { 464 for (u16 i = 0; i < tilesW; ++i) 465 { 466 u32 bestWeight = 0; 467 for (size_t k = 0; k < playerGrids.size(); ++k) 468 { 469 u32 w = playerGrids[k].second.get(i, j); 470 if (w > bestWeight) 419 // weight of this tile = weight of predecessor - falloff from predecessor 420 u32 newWeight = entityGrid.get(x, z) - dg; 421 u32 totalWeight = playerGrid.get(nx, nz) - entityGrid.get(nx, nz) + newWeight; 422 playerGrid.set(nx, nz, totalWeight); 423 entityGrid.set(nx, nz, newWeight); 424 // if this weight is better than the best thus far, set the owner 425 if (totalWeight > bestWeightGrid.get(nx, nz)) 471 426 { 472 player_id_t id = playerGrids[k].first; 473 m_Territories->set(i, j, (u8)id); 474 bestWeight = w; 427 bestWeightGrid.set(nx, nz, totalWeight); 428 m_Territories->set(nx, nz, owner); 475 429 } 476 } 430 ); 431 432 entityGrid.reset(); 477 433 } 478 434 } 479 435 480 436 // Detect territories connected to a 'root' influence (typically a civ center) 481 437 // 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)438 for (entity_id_t ent : rootInfluenceEntities) 483 439 { 440 LOGWARNING("%i", ent); 484 441 // (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);442 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), ent); 443 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), ent); 487 444 488 445 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);446 u16 i, j; 447 NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH); 491 448 492 449 u8 owner = (u8)cmpOwnership->GetOwner(); 493 450 … … 494 451 if (m_Territories->get(i, j) != owner) 495 452 continue; 496 453 497 // TODO: would be nice to refactor some of the many flood fill 498 // algorithms in this component 454 m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK); 499 455 500 Grid<u8>& grid = *m_Territories; 501 502 u16 maxi = (u16)(grid.m_W-1); 503 u16 maxj = (u16)(grid.m_H-1); 504 505 std::vector<std::pair<u16, u16> > tileStack; 506 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); 510 while (!tileStack.empty()) 511 { 512 int ti = tileStack.back().first; 513 int tj = tileStack.back().second; 514 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); 533 } 534 535 #undef MARK_AND_PUSH 456 FLOODFILL(i, j, 457 // Don't expand non-owner tiles, or tiles that already have a connected mask 458 if (m_Territories->get(nx, nz) != owner) 459 continue; 460 m_Territories->set(nx, nz, owner | TERRITORY_CONNECTED_MASK); 461 ); 536 462 } 537 463 } 538 464 539 /**540 * Compute the tile indexes on the grid nearest to a given point541 */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 tile550 */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.cpp558 559 560 void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid)561 {562 for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)563 {564 ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);565 566 i32 cost = cmpTerritoryInfluence->GetCost();567 if (cost == -1)568 continue;569 570 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it->first);571 if (!cmpObstruction)572 continue;573 574 ICmpObstructionManager::ObstructionSquare square;575 if (!cmpObstruction->GetObstructionSquare(square))576 continue;577 578 CFixedVector2D halfSize(square.hw, square.hh);579 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);580 581 u16 i0, j0, i1, j1;582 NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H);583 NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H);584 for (u16 j = j0; j <= j1; ++j)585 {586 for (u16 i = i0; i <= i1; ++i)587 {588 entity_pos_t x, z;589 TileCenter(i, j, x, z);590 if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize))591 grid.set(i, j, (u8)cost);592 }593 }594 595 }596 }597 598 465 std::vector<STerritoryBoundary> CCmpTerritoryManager::ComputeBoundaries() 599 466 { 600 467 PROFILE("ComputeBoundaries"); … … 722 589 return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; 723 590 } 724 591 592 std::vector<u32> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) 593 { 594 CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity()); 595 if (!cmpPlayerManager) 596 return std::vector<u32>(); 597 598 std::vector<u32> ret(cmpPlayerManager->GetNumPlayers(), 0); 599 CalculateTerritories(); 600 if (!m_Territories) 601 return ret; 602 603 u16 i, j; 604 NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); 605 606 // calculate the neighbours 607 player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; 608 609 u16 tilesW = m_Territories->m_W; 610 u16 tilesH = m_Territories->m_H; 611 612 // use a flood-fill algorithm that fills up to the borders and remembers the owners 613 Grid<bool> markerGrid(tilesW, tilesH); 614 markerGrid.set(i, j, true); 615 616 FLOODFILL(i, j, 617 if (markerGrid.get(nx, nz)) 618 continue; 619 // mark the tile as visited in any case 620 markerGrid.set(nx, nz, true); 621 int owner = m_Territories->get(nx, nz) & TERRITORY_PLAYER_MASK; 622 if (owner != thisOwner) 623 { 624 if (owner == 0 || !filterConnected || (m_Territories->get(nx, nz) & TERRITORY_CONNECTED_MASK) != 0) 625 ret[owner]++; // add player to the neighbour list when requested 626 continue; // don't expand non-owner tiles further 627 } 628 ); 629 630 return ret; 631 } 632 725 633 bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z) 726 634 { 727 635 u16 i, j; -
source/simulation2/components/ICmpTerritoryInfluence.h
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 23 23 class ICmpTerritoryInfluence : public IComponent 24 24 { 25 25 public: 26 /**27 * Returns either -1 to indicate no special terrain cost, or a value28 * in [0, 255] to indicate overriding the normal cost of the terrain29 * under the entity's obstruction.30 */31 virtual i32 GetCost() = 0;32 33 26 virtual bool IsRoot() = 0; 34 27 35 virtual u 32GetWeight() = 0;28 virtual u16 GetWeight() = 0; 36 29 37 30 virtual u32 GetRadius() = 0; 38 31 -
source/simulation2/components/ICmpTerritoryManager.cpp
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 23 23 24 24 BEGIN_INTERFACE_WRAPPER(TerritoryManager) 25 25 DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t) 26 DEFINE_INTERFACE_METHOD_3("GetNeighbours", std::vector<u32>, ICmpTerritoryManager, GetNeighbours, entity_pos_t, entity_pos_t, bool) 26 27 DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t) 27 28 END_INTERFACE_WRAPPER(TerritoryManager) -
source/simulation2/components/ICmpTerritoryManager.h
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 47 47 virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0; 48 48 49 49 /** 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 /** 50 56 * Get whether territory at given position is connected to a root object 51 57 * (civ center etc) owned by that territory's player. 52 58 */ -
source/simulation2/system/ComponentManager.h
269 269 270 270 IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; 271 271 272 typedef std::vector<std::pair<entity_id_t, IComponent*> > InterfaceList; 272 typedef std::pair<entity_id_t, IComponent*> InterfacePair; 273 typedef std::vector<InterfacePair> InterfaceList; 273 274 typedef boost::unordered_map<entity_id_t, IComponent*> InterfaceListUnordered; 274 275 275 276 InterfaceList GetEntitiesWithInterface(InterfaceId iid) const;