Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_block.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_block.xml (revision 16675)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_block.xml (nonexistent)
@@ -1,45 +0,0 @@
-
-
-
-
- 9.0
-
-
-
- true
- false
- false
- false
- false
- false
- false
-
-
-
- 0
- upright
- false
- 6.0
-
-
-
-
-
- circle/128x128.png
- circle/128x128_mask.png
-
-
-
-
- 64
- false
- 0
- 0
-
-
- false
- true
- structures/hellenes/wall_medium.xml
- true
-
-
Property changes on: ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_block.xml
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Deleted: svn:mime-type
## -1 +0,0 ##
-text/xml
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 16675)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 16676)
@@ -1,243 +1,269 @@
function Capturable() {}
Capturable.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Capturable.prototype.Init = function()
{
// Cache this value
this.maxCp = +this.template.CapturePoints;
this.cp = [];
- this.StartRegenTimer();
};
//// Interface functions ////
/**
* Returns the current capture points array
*/
Capturable.prototype.GetCapturePoints = function()
{
return this.cp;
};
Capturable.prototype.GetMaxCapturePoints = function()
{
return this.maxCp;
};
Capturable.prototype.GetGarrisonRegenRate = function()
{
return ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity);
};
/**
* Set the new capture points, used for cloning entities
* The caller should assure that the sum of capture points
* matches the max.
*/
Capturable.prototype.SetCapturePoints = function(capturePointsArray)
{
this.cp = capturePointsArray;
};
/**
* Reduces the amount of capture points of an entity,
* in favour of the player of the source
* Returns the number of capture points actually taken
*/
Capturable.prototype.Reduce = function(amount, playerID)
{
+ if (amount <= 0)
+ return 0;
+
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
return 0;
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
if (!cmpPlayerSource)
return 0;
// Before changing the value, activate Fogging if necessary to hide changes
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
- var enemiesFilter = function(v, i) { return v > 0 && cmpPlayerSource.IsEnemy(i); };
- var numberOfEnemies = this.cp.filter(enemiesFilter).length;
+ var numberOfEnemies = this.cp.filter((v, i) => v > 0 && cmpPlayerSource.IsEnemy(i)).length;
if (numberOfEnemies == 0)
return 0;
// distribute the capture points over all enemies
var distributedAmount = amount / numberOfEnemies;
for (let i in this.cp)
{
if (!cmpPlayerSource.IsEnemy(i))
continue;
if (this.cp[i] > distributedAmount)
this.cp[i] -= distributedAmount;
else
this.cp[i] = 0;
}
// give all cp taken to the player
- var takenCp = this.maxCp - this.cp.reduce(function(a, b) { return a + b; });
+ var takenCp = this.maxCp - this.cp.reduce((a, b) => a + b);
this.cp[playerID] += takenCp;
- this.StartRegenTimer();
-
- Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
-
- if (this.cp[cmpOwnership.GetOwner()] > 0)
- return takenCp;
-
- // if all cp has been taken from the owner, convert it to the best player
- var bestPlayer = 0;
- for (let i in this.cp)
- if (this.cp[i] >= this.cp[bestPlayer])
- bestPlayer = +i;
-
- cmpOwnership.SetOwner(bestPlayer);
-
+ this.CheckTimer();
+ this.RegisterCapturePointsChanged();
return takenCp;
};
/**
* Check if the source can (re)capture points from this building
*/
Capturable.prototype.CanCapture = function(playerID)
{
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
if (!cmpPlayerSource)
warn(playerID + " has no player component defined on its id");
var cp = this.GetCapturePoints()
var sourceEnemyCp = 0;
for (let i in this.GetCapturePoints())
if (cmpPlayerSource.IsEnemy(i))
sourceEnemyCp += cp[i];
return sourceEnemyCp > 0;
};
//// Private functions ////
+/**
+ * this has to be called whenever the capture points are changed.
+ * It notifies other components of the change, and switches ownership when needed
+ */
+Capturable.prototype.RegisterCapturePointsChanged = function()
+{
+ var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership)
+ return;
+
+ Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
+
+ var owner = cmpOwnership.GetOwner();
+ if (owner == -1 || this.cp[owner] > 0)
+ return;
+
+ // if all cp has been taken from the owner, convert it to the best player
+ var bestPlayer = 0;
+ for (let i in this.cp)
+ if (this.cp[i] >= this.cp[bestPlayer])
+ bestPlayer = +i;
+
+ cmpOwnership.SetOwner(bestPlayer);
+};
+
Capturable.prototype.GetRegenRate = function()
{
var regenRate = +this.template.RegenRate;
regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity);
- var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
- if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())
- var territoryDecayRate = cmpTerritoryDecay.GetDecayRate();
- else
- var territoryDecayRate = 0;
-
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (cmpGarrisonHolder)
var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length;
else
var garrisonRegenRate = 0;
- return regenRate + garrisonRegenRate - territoryDecayRate;
+ return regenRate + garrisonRegenRate;
};
-Capturable.prototype.RegenCapturePoints = function()
+Capturable.prototype.TimerTick = function()
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
return;
+ var owner = cmpOwnership.GetOwner();
+ var modifiedCp = 0;
+
+ // special handle for the territory decay
+ // reduce cp from the owner in favour of all neighbours (also allies)
+ var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
+ if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())
+ {
+ var neighbours = cmpTerritoryDecay.GetConnectedNeighbours();
+ var totalNeighbours = neighbours.reduce((a, b) => a + b);
+ var decay = Math.min(cmpTerritoryDecay.GetDecayRate(), this.cp[owner]);
+ this.cp[owner] -= decay;
+ modifiedCp += decay;
+ for (let p in neighbours)
+ this.cp[p] += decay * neighbours[p] / totalNeighbours;
+ this.RegisterCapturePointsChanged();
+ }
+
var regenRate = this.GetRegenRate();
if (regenRate < 0)
- var takenCp = this.Reduce(-regenRate, 0);
- else
- var takenCp = this.Reduce(regenRate, cmpOwnership.GetOwner())
+ modifiedCp += this.Reduce(-regenRate, 0)
+ else if (regenRate > 0)
+ modifiedCp += this.Reduce(regenRate, owner)
- if (takenCp > 0)
+ if (modifiedCp)
return;
- // no capture points taken, stop the timer
+ // nothing changed, stop the timer
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- cmpTimer.CancelTimer(this.regenTimer);
- this.regenTimer = 0;
- Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "rate": 0});
+ cmpTimer.CancelTimer(this.timer);
+ this.timer = 0;
+ Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "regenRate": 0, "territoryDecay": 0});
};
/**
- * Start the regeneration timer when no timer exists
- * When nothing can be regenerated (f.e. because the
- * rate is 0, or because it is fully regenerated),
- * the timer stops automatically after one execution.
+ * Start the regeneration timer when no timer exists.
+ * When nothing can be modified (f.e. because it is fully regenerated), the
+ * timer stops automatically after one execution.
*/
-Capturable.prototype.StartRegenTimer = function()
+Capturable.prototype.CheckTimer = function()
{
- if (this.regenTimer)
+ if (this.timer)
return;
- var rate = this.GetRegenRate();
- if (rate == 0)
+ var regenRate = this.GetRegenRate();
+ var cmpDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
+ var decay = cmpDecay && cmpDecay.IsDecaying() ? cmpDecay.GetDecayRate() : 0;
+ if (regenRate == 0 && decay == 0)
return;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Capturable, "RegenCapturePoints", 1000, 1000, null);
- Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": true, "rate": rate});
+ this.timer = cmpTimer.SetInterval(this.entity, IID_Capturable, "TimerTick", 1000, 1000, null);
+ Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"ticking": true, "regenRate": regenRate, "territoryDecay": decay});
};
//// Message Listeners ////
Capturable.prototype.OnValueModification = function(msg)
{
if (msg.component != "Capturable")
return;
var oldMaxCp = this.GetMaxCapturePoints();
this.maxCp = ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity);
if (oldMaxCp == this.maxCp)
return;
var scale = this.maxCp / oldMaxCp;
for (let i in this.cp)
this.cp[i] *= scale;
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp });
- this.StartRegenTimer();
+ this.CheckTimer();
};
Capturable.prototype.OnGarrisonedUnitsChanged = function(msg)
{
- this.StartRegenTimer();
+ this.CheckTimer();
};
Capturable.prototype.OnTerritoryDecayChanged = function(msg)
{
if (msg.to)
- this.StartRegenTimer();
+ this.CheckTimer();
};
Capturable.prototype.OnOwnershipChanged = function(msg)
{
- this.StartRegenTimer();
+ this.CheckTimer();
// if the new owner has no capture points, it means that either
// * it's being initialised now, or
// * it changed ownership for a different reason (defeat, atlas, ...)
if (this.cp[msg.to])
return;
// initialise the capture points when created
this.cp = [];
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
if (i == msg.to)
this.cp[i] = this.maxCp;
else
this.cp[i] = 0;
};
Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
Index: ps/trunk/binaries/data/mods/public/simulation/components/TerritoryDecay.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/TerritoryDecay.js (revision 16675)
+++ ps/trunk/binaries/data/mods/public/simulation/components/TerritoryDecay.js (revision 16676)
@@ -1,79 +1,97 @@
function TerritoryDecay() {}
TerritoryDecay.prototype.Schema =
"" +
"" +
"";
TerritoryDecay.prototype.Init = function()
{
this.decaying = false;
+ this.connectedNeighbours = [];
};
TerritoryDecay.prototype.IsConnected = function()
{
+ var numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
+ this.connectedNeighbours.fill(0, 0, numPlayers);
+
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
return false;
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
- return false;
+ return true; // something without ownership can't decay
- // Prevent special gaia buildings from decaying (e.g. fences, ruins)
- if (cmpOwnership.GetOwner() == 0)
- return true;
-
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
- if (!cmpTerritoryManager)
- return false;
-
var pos = cmpPosition.GetPosition2D();
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
if (tileOwner != cmpOwnership.GetOwner())
+ {
+ this.connectedNeighbours[tileOwner] = 1;
return false;
+ }
+
+ if (tileOwner == 0)
+ return true; // Gaia building on gaia ground -> don't decay
- return cmpTerritoryManager.IsConnected(pos.x, pos.y);
+ if (cmpTerritoryManager.IsConnected(pos.x, pos.y))
+ return true;
+
+ this.connectedNeighbours = cmpTerritoryManager.GetNeighbours(pos.x, pos.y, true);
+ return false;
};
TerritoryDecay.prototype.IsDecaying = function()
{
return this.decaying;
};
TerritoryDecay.prototype.GetDecayRate = function()
{
return ApplyValueModificationsToEntity(
"TerritoryDecay/DecayRate",
+this.template.DecayRate,
this.entity);
};
+/**
+ * Get the number of connected bordering tiles to this region
+ * Only valid when this.IsDecaying()
+ */
+TerritoryDecay.prototype.GetConnectedNeighbours = function()
+{
+ return this.connectedNeighbours;
+};
+
TerritoryDecay.prototype.UpdateDecayState = function()
{
if (this.IsConnected())
var decaying = false;
else
var decaying = this.GetDecayRate() > 0;
if (decaying === this.decaying)
return;
this.decaying = decaying;
- Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying });
+ Engine.PostMessage(this.entity, MT_TerritoryDecayChanged, { "entity": this.entity, "to": decaying, "rate": this.GetDecayRate() });
};
TerritoryDecay.prototype.OnTerritoriesChanged = function(msg)
{
this.UpdateDecayState();
};
TerritoryDecay.prototype.OnTerritoryPositionChanged = function(msg)
{
this.UpdateDecayState();
};
TerritoryDecay.prototype.OnOwnershipChanged = function(msg)
{
- this.UpdateDecayState();
+ // if it influences the territory, wait until we get a TerritoriesChanged message
+ if (!Engine.QueryInterface(this.entity, IID_TerritoryInfluence))
+ this.UpdateDecayState();
};
Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_pull.xml (revision 16675)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/special/territory_pull.xml (revision 16676)
@@ -1,45 +1,38 @@
-
-
- 9.0
-
-
-
- true
- false
- false
- false
- false
- false
- false
-
+
+ 5
+ 0
+ 0
+
0
upright
false
6.0
circle/128x128.png
circle/128x128_mask.png
+
+ 5
+
- 0
false
- 0
- 0
+ 32
+ 30000
false
true
- structures/hellenes/wall_medium.xml
+ props/special/common/waypoint_flag.xml
true
Index: ps/trunk/source/simulation2/components/CCmpTerritoryInfluence.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpTerritoryInfluence.cpp (revision 16675)
+++ ps/trunk/source/simulation2/components/CCmpTerritoryInfluence.cpp (revision 16676)
@@ -1,113 +1,97 @@
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpTerritoryInfluence.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
class CCmpTerritoryInfluence : public ICmpTerritoryInfluence
{
public:
static void ClassInit(CComponentManager& UNUSED(componentManager))
{
}
DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence)
- i32 m_Cost;
bool m_Root;
- u32 m_Weight;
+ u16 m_Weight;
u32 m_Radius;
static std::string GetSchema()
{
return
- ""
- ""
- ""
- "255"
- ""
- ""
- ""
""
""
""
""
- ""
+ ""
+ "65536" // Max value 2^16
+ ""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
- if (paramNode.GetChild("OverrideCost").IsOk())
- m_Cost = paramNode.GetChild("OverrideCost").ToInt();
- else
- m_Cost = -1;
-
m_Root = paramNode.GetChild("Root").ToBool();
- m_Weight = paramNode.GetChild("Weight").ToInt();
+ m_Weight = (u16)paramNode.GetChild("Weight").ToInt();
m_Radius = paramNode.GetChild("Radius").ToInt();
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
- virtual i32 GetCost()
- {
- return m_Cost;
- }
-
virtual bool IsRoot()
{
return m_Root;
}
- virtual u32 GetWeight()
+ virtual u16 GetWeight()
{
return m_Weight;
}
virtual u32 GetRadius()
{
CmpPtr cmpValueModificationManager(GetSystemEntity());
if (!cmpValueModificationManager)
return m_Radius;
u32 newRadius = cmpValueModificationManager->ApplyModifications(L"TerritoryInfluence/Radius", m_Radius, GetEntityId());
return newRadius;
}
};
REGISTER_COMPONENT_TYPE(TerritoryInfluence)
Index: ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp (revision 16675)
+++ ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp (revision 16676)
@@ -1,764 +1,672 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpTerritoryManager.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "graphics/TextureManager.h"
#include "graphics/TerritoryBoundary.h"
#include "maths/MathUtil.h"
-#include "maths/Vector2D.h"
#include "renderer/Renderer.h"
#include "renderer/Scene.h"
#include "renderer/TerrainOverlay.h"
#include "simulation2/MessageTypes.h"
-#include "simulation2/components/ICmpObstruction.h"
-#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpPlayer.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpPosition.h"
-#include "simulation2/components/ICmpSettlement.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpTerritoryInfluence.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Grid.h"
-#include "simulation2/helpers/PriorityQueue.h"
#include "simulation2/helpers/Render.h"
class CCmpTerritoryManager;
class TerritoryOverlay : public TerrainOverlay
{
NONCOPYABLE(TerritoryOverlay);
public:
CCmpTerritoryManager& m_TerritoryManager;
TerritoryOverlay(CCmpTerritoryManager& manager);
virtual void StartRender();
virtual void ProcessTile(ssize_t i, ssize_t j);
};
class CCmpTerritoryManager : public ICmpTerritoryManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeGloballyToMessageType(MT_ValueModification);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_WaterChanged);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
static std::string GetSchema()
{
return "";
}
u8 m_ImpassableCost;
float m_BorderThickness;
float m_BorderSeparation;
// Player ID in bits 0-5 (TERRITORY_PLAYER_MASK);
// connected flag in bit 6 (TERRITORY_CONNECTED_MASK);
// processed flag in bit 7 (TERRITORY_PROCESSED_MASK)
Grid* m_Territories;
// Set to true when territories change; will send a TerritoriesChanged message
// during the Update phase
bool m_TriggerEvent;
struct SBoundaryLine
{
bool connected;
CColor color;
SOverlayTexturedLine overlay;
};
std::vector m_BoundaryLines;
bool m_BoundaryLinesDirty;
double m_AnimTime; // time since start of rendering, in seconds
TerritoryOverlay* m_DebugOverlay;
bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines?
std::vector m_DebugBoundaryLineNodes;
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_Territories = NULL;
m_DebugOverlay = NULL;
// m_DebugOverlay = new TerritoryOverlay(*this);
m_BoundaryLinesDirty = true;
m_TriggerEvent = true;
m_EnableLineDebugOverlays = false;
m_DirtyID = 1;
m_AnimTime = 0.0;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml");
int impassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt();
ENSURE(0 <= impassableCost && impassableCost <= 255);
m_ImpassableCost = (u8)impassableCost;
m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat();
m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat();
}
virtual void Deinit()
{
SAFE_DELETE(m_Territories);
SAFE_DELETE(m_DebugOverlay);
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// Territory state can be recomputed as required, so we don't need to serialize any of it.
// TODO: do we ever need to serialize m_TriggerEvent to prevent lost messages?
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast (msg);
MakeDirtyIfRelevantEntity(msgData.entity);
break;
}
case MT_PositionChanged:
{
const CMessagePositionChanged& msgData = static_cast (msg);
MakeDirtyIfRelevantEntity(msgData.entity);
break;
}
case MT_ValueModification:
{
const CMessageValueModification& msgData = static_cast (msg);
if (msgData.component == L"TerritoryInfluence")
MakeDirty();
break;
}
case MT_TerrainChanged:
case MT_WaterChanged:
{
MakeDirty();
break;
}
case MT_Update:
{
if (m_TriggerEvent)
{
m_TriggerEvent = false;
CMessageTerritoriesChanged msg;
GetSimContext().GetComponentManager().BroadcastMessage(msg);
}
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
// Check whether the entity is either a settlement or territory influence;
// ignore any others
void MakeDirtyIfRelevantEntity(entity_id_t ent)
{
- CmpPtr cmpSettlement(GetSimContext(), ent);
- if (cmpSettlement)
- MakeDirty();
-
CmpPtr cmpTerritoryInfluence(GetSimContext(), ent);
if (cmpTerritoryInfluence)
MakeDirty();
}
virtual const Grid& GetTerritoryGrid()
{
CalculateTerritories();
ENSURE(m_Territories);
return *m_Territories;
}
virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z);
+ virtual std::vector GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected);
virtual bool IsConnected(entity_pos_t x, entity_pos_t z);
// To support lazy updates of territory render data,
// we maintain a DirtyID here and increment it whenever territories change;
// if a caller has a lower DirtyID then it needs to be updated.
size_t m_DirtyID;
void MakeDirty()
{
SAFE_DELETE(m_Territories);
++m_DirtyID;
m_BoundaryLinesDirty = true;
m_TriggerEvent = true;
}
virtual bool NeedUpdate(size_t* dirtyID)
{
if (*dirtyID != m_DirtyID)
{
*dirtyID = m_DirtyID;
return true;
}
return false;
}
void CalculateTerritories();
- /**
- * Updates @p grid based on the obstruction shapes of all entities with
- * a TerritoryInfluence component. Grid cells are 0 if no influence,
- * or 1+c if the influence have cost c (assumed between 0 and 254).
- */
- void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid);
-
std::vector ComputeBoundaries();
void UpdateBoundaryLines();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector);
};
REGISTER_COMPONENT_TYPE(TerritoryManager)
-/*
-We compute the territory influence of an entity with a kind of best-first search,
-storing an 'open' list of tiles that have not yet been processed,
-then taking the highest-weight tile (closest to origin) and updating the weight
-of extending to each neighbour (based on radius-determining 'falloff' value,
-adjusted by terrain movement cost), and repeating until all tiles are processed.
-*/
-
-typedef PriorityQueueHeap, u32, std::greater > OpenQueue;
-
-static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
- Grid& grid, OpenQueue& queue, const Grid& costGrid)
-{
- u32 dg = falloff * costGrid.get(i, j);
- if (diagonal)
- dg = (dg * 362) / 256;
-
- // Stop if new cost g=pg-dg is not better than previous value for that tile
- // (arranged to avoid underflow if pg < dg)
- if (pg <= grid.get(i, j) + dg)
- return;
+// Tile data type, for easier accessing of coordinates
+struct Tile
+{
+ Tile(u16 i, u16 j) : x(i), z(j) { }
+ u16 x, z;
+};
- u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
+// Floodfill templates that expand neighbours from a certain source onwards
+// (x, z) are the coordinates of the currently expanded tile
+// (nx, nz) are the coordinates of the current neighbour handled
+// The user of this floodfill should use "continue" on every neighbour that
+// shouldn't be expanded on its own. (without continue, an infinite loop will happen)
+# define FLOODFILL(i, j, code)\
+ do {\
+ const int NUM_NEIGHBOURS = 8;\
+ const int NEIGHBOURS_X[NUM_NEIGHBOURS] = {1,-1, 0, 0, 1,-1, 1,-1};\
+ const int NEIGHBOURS_Z[NUM_NEIGHBOURS] = {0, 0, 1,-1, 1,-1,-1, 1};\
+ std::queue openTiles;\
+ openTiles.emplace(i, j);\
+ while (!openTiles.empty())\
+ {\
+ u16 x = openTiles.front().x;\
+ u16 z = openTiles.front().z;\
+ openTiles.pop();\
+ for (int n = 0; n < NUM_NEIGHBOURS; ++n)\
+ {\
+ u16 nx = x + NEIGHBOURS_X[n];\
+ u16 nz = z + NEIGHBOURS_Z[n];\
+ /* Check the bounds, underflow will cause the values to be big again */\
+ if (nx >= tilesW || nz >= tilesH)\
+ continue;\
+ code\
+ openTiles.emplace(nx, nz);\
+ }\
+ }\
+ }\
+ while (false)
- grid.set(i, j, g);
- OpenQueue::Item tile = { std::make_pair(i, j), g };
- queue.push(tile);
-}
-
-static void FloodFill(Grid& grid, Grid& costGrid, OpenQueue& openTiles, u32 falloff)
-{
- u16 tilesW = grid.m_W;
- u16 tilesH = grid.m_H;
-
- while (!openTiles.empty())
- {
- OpenQueue::Item tile = openTiles.pop();
-
- // Process neighbours (if they're not off the edge of the map)
- u16 x = tile.id.first;
- u16 z = tile.id.second;
- if (x > 0)
- ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid);
- if (x < tilesW-1)
- ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid);
- if (z > 0)
- ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid);
- if (z < tilesH-1)
- ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid);
- if (x > 0 && z > 0)
- ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
- if (x > 0 && z < tilesH-1)
- ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
- if (x < tilesW-1 && z > 0)
- ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
- if (x < tilesW-1 && z < tilesH-1)
- ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
- }
+/**
+ * Compute the tile indexes on the grid nearest to a given point
+ */
+static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
+{
+ i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
+ j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
}
void CCmpTerritoryManager::CalculateTerritories()
{
if (m_Territories)
return;
PROFILE("CalculateTerritories");
CmpPtr cmpTerrain(GetSystemEntity());
// If the terrain hasn't been loaded (e.g. this is called during map initialisation),
// abort the computation (and assume callers can cope with m_Territories == NULL)
if (!cmpTerrain->IsLoaded())
return;
- u16 tilesW = cmpTerrain->GetTilesPerSide();
- u16 tilesH = cmpTerrain->GetTilesPerSide();
+ const u16 tilesW = cmpTerrain->GetTilesPerSide();
+ const u16 tilesH = tilesW;
m_Territories = new Grid(tilesW, tilesH);
- // Compute terrain-passability-dependent costs per tile
- Grid influenceGrid(tilesW, tilesH);
-
- CmpPtr cmpPathfinder(GetSystemEntity());
- ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");
- ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
-
- const Grid& passGrid = cmpPathfinder->GetPassabilityGrid();
- for (u16 j = 0; j < tilesH; ++j)
- {
- for (u16 i = 0; i < tilesW; ++i)
- {
- u16 g = passGrid.get(i, j);
- u8 cost;
- if (g & passClassUnrestricted)
- cost = 255; // off the world; use maximum cost
- else if (g & passClassDefault)
- cost = m_ImpassableCost;
- else
- cost = 1;
- influenceGrid.set(i, j, cost);
- }
- }
-
// Find all territory influence entities
CComponentManager::InterfaceList influences = GetSimContext().GetComponentManager().GetEntitiesWithInterface(IID_TerritoryInfluence);
- // Allow influence entities to override the terrain costs
- RasteriseInfluences(influences, influenceGrid);
-
// Split influence entities into per-player lists, ignoring any with invalid properties
std::map > influenceEntities;
- std::vector rootInfluenceEntities;
- for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
+ for (const CComponentManager::InterfacePair& pair : influences)
{
- // Ignore any with no weight or radius (to avoid divide-by-zero later)
- ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast(it->second);
- if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0)
- continue;
+ entity_id_t ent = pair.first;
- CmpPtr cmpOwnership(GetSimContext(), it->first);
+ CmpPtr cmpOwnership(GetSimContext(), ent);
if (!cmpOwnership)
continue;
- // Ignore Gaia and unassigned
+ // Ignore Gaia and unassigned or players we can't represent
player_id_t owner = cmpOwnership->GetOwner();
- if (owner <= 0)
+ if (owner <= 0 || owner > TERRITORY_PLAYER_MASK)
continue;
- // We only have so many bits to store tile ownership, so ignore unrepresentable players
- if (owner > TERRITORY_PLAYER_MASK)
- continue;
+ influenceEntities[owner].push_back(ent);
+ }
- // Ignore if invalid position
- CmpPtr cmpPosition(GetSimContext(), it->first);
- if (!cmpPosition || !cmpPosition->IsInWorld())
- continue;
+ // Store the overall best weight for comparison
+ Grid bestWeightGrid(tilesW, tilesH);
+ // store the root influences to mark territory as connected
+ std::vector rootInfluenceEntities;
- influenceEntities[owner].push_back(it->first);
+ CmpPtr cmpPathfinder(GetSystemEntity());
+ if (!cmpPathfinder)
+ return;
- if (cmpTerritoryInfluence->IsRoot())
- rootInfluenceEntities.push_back(it->first);
- }
+ ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");
+ ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
- // For each player, store the sum of influences on each tile
- std::vector > > playerGrids;
- // TODO: this is a large waste of memory; we don't really need to store
- // all the intermediate grids
+ const Grid& passibilityGrid = cmpPathfinder->GetPassabilityGrid();
- for (std::map >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)
+ for (const std::pair >& pair : influenceEntities)
{
+ // entityGrid stores the weight for a single entity, and is reset per entity
+ Grid entityGrid(tilesW, tilesH);
+ // playerGrid stores the combined weight of all entities for this player
Grid playerGrid(tilesW, tilesH);
- std::vector& ents = it->second;
- for (std::vector::iterator eit = ents.begin(); eit != ents.end(); ++eit)
- {
- // Compute the influence map of the current entity, then add it to the player grid
+ u8 owner = (u8)pair.first;
+ const std::vector& ents = pair.second;
+ // With 2^16 entities, we're safe against overflows as the weight is also limited to 2^16
+ ENSURE(ents.size() < 1 << 16);
+ // Compute the influence map of the current entity, then add it to the player grid
+ for (entity_id_t ent : ents)
+ {
+ CmpPtr cmpPosition(GetSimContext(), ent);
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ continue;
- Grid entityGrid(tilesW, tilesH);
-
- CmpPtr cmpPosition(GetSimContext(), *eit);
- CFixedVector2D pos = cmpPosition->GetPosition2D();
- u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
- u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
-
- CmpPtr cmpTerritoryInfluence(GetSimContext(), *eit);
+ CmpPtr cmpTerritoryInfluence(GetSimContext(), ent);
u32 weight = cmpTerritoryInfluence->GetWeight();
u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE;
- u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
+ if (weight == 0 || radius == 0)
+ continue;
+ u32 falloff = weight / radius;
- // TODO: we should have some maximum value on weight, to avoid overflow
- // when doing all the sums
+ CFixedVector2D pos = cmpPosition->GetPosition2D();
+ u16 i, j;
+ NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH);
+
+ if (cmpTerritoryInfluence->IsRoot())
+ rootInfluenceEntities.push_back(ent);
// Initialise the tile under the entity
entityGrid.set(i, j, weight);
- OpenQueue openTiles;
- OpenQueue::Item tile = { std::make_pair((u16)i, (i16)j), weight };
- openTiles.push(tile);
+ if (weight > bestWeightGrid.get(i, j))
+ {
+ bestWeightGrid.set(i, j, weight);
+ m_Territories->set(i, j, owner);
+ }
// Expand influences outwards
- FloodFill(entityGrid, influenceGrid, openTiles, falloff);
-
- // TODO: we should do a sparse grid and only add the non-zero regions, for performance
- playerGrid.add(entityGrid);
- }
-
- playerGrids.push_back(std::make_pair(it->first, playerGrid));
- }
-
- // Set m_Territories to the player ID with the highest influence for each tile
- for (u16 j = 0; j < tilesH; ++j)
- {
- for (u16 i = 0; i < tilesW; ++i)
- {
- u32 bestWeight = 0;
- for (size_t k = 0; k < playerGrids.size(); ++k)
- {
- u32 w = playerGrids[k].second.get(i, j);
- if (w > bestWeight)
+ FLOODFILL(i, j,
+ u32 dg = falloff;
+ // enlarge the falloff for unpassable tiles
+ u16 g = passibilityGrid.get(nx, nz);
+ if (g & passClassUnrestricted)
+ dg *= 255; // off the world; use maximum cost
+ else if (g & passClassDefault)
+ dg *= m_ImpassableCost;
+
+ // diagonal neighbour -> multiply with approx sqrt(2)
+ if (nx != x && nz != z)
+ dg = (dg * 362) / 256;
+
+ // Don't expand if new cost is not better than previous value for that tile
+ // (arranged to avoid underflow if entityGrid.get(x, z) < dg)
+ if (entityGrid.get(x, z) <= entityGrid.get(nx, nz) + dg)
+ continue;
+
+ // weight of this tile = weight of predecessor - falloff from predecessor
+ u32 newWeight = entityGrid.get(x, z) - dg;
+ u32 totalWeight = playerGrid.get(nx, nz) - entityGrid.get(nx, nz) + newWeight;
+ playerGrid.set(nx, nz, totalWeight);
+ entityGrid.set(nx, nz, newWeight);
+ // if this weight is better than the best thus far, set the owner
+ if (totalWeight > bestWeightGrid.get(nx, nz))
{
- player_id_t id = playerGrids[k].first;
- m_Territories->set(i, j, (u8)id);
- bestWeight = w;
+ bestWeightGrid.set(nx, nz, totalWeight);
+ m_Territories->set(nx, nz, owner);
}
- }
+ );
+
+ entityGrid.reset();
}
}
// Detect territories connected to a 'root' influence (typically a civ center)
// belonging to their player, and mark them with the connected flag
- for (std::vector::iterator it = rootInfluenceEntities.begin(); it != rootInfluenceEntities.end(); ++it)
+ for (entity_id_t ent : rootInfluenceEntities)
{
// (These components must be valid else the entities wouldn't be added to this list)
- CmpPtr cmpOwnership(GetSimContext(), *it);
- CmpPtr cmpPosition(GetSimContext(), *it);
+ CmpPtr cmpOwnership(GetSimContext(), ent);
+ CmpPtr cmpPosition(GetSimContext(), ent);
CFixedVector2D pos = cmpPosition->GetPosition2D();
- u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
- u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
+ u16 i, j;
+ NearestTile(pos.X, pos.Y, i, j, tilesW, tilesH);
u8 owner = (u8)cmpOwnership->GetOwner();
if (m_Territories->get(i, j) != owner)
continue;
- // TODO: would be nice to refactor some of the many flood fill
- // algorithms in this component
-
- Grid& grid = *m_Territories;
-
- u16 maxi = (u16)(grid.m_W-1);
- u16 maxj = (u16)(grid.m_H-1);
-
- std::vector > tileStack;
-
-#define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair(i, j)); )
-
- MARK_AND_PUSH(i, j);
- while (!tileStack.empty())
- {
- int ti = tileStack.back().first;
- int tj = tileStack.back().second;
- tileStack.pop_back();
-
- if (ti > 0 && grid.get(ti-1, tj) == owner)
- MARK_AND_PUSH(ti-1, tj);
- if (ti < maxi && grid.get(ti+1, tj) == owner)
- MARK_AND_PUSH(ti+1, tj);
- if (tj > 0 && grid.get(ti, tj-1) == owner)
- MARK_AND_PUSH(ti, tj-1);
- if (tj < maxj && grid.get(ti, tj+1) == owner)
- MARK_AND_PUSH(ti, tj+1);
-
- if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
- MARK_AND_PUSH(ti-1, tj-1);
- if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner)
- MARK_AND_PUSH(ti-1, tj+1);
- if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner)
- MARK_AND_PUSH(ti+1, tj-1);
- if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner)
- MARK_AND_PUSH(ti+1, tj+1);
- }
-
-#undef MARK_AND_PUSH
- }
-}
-
-/**
- * Compute the tile indexes on the grid nearest to a given point
- */
-static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
-{
- i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
- j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
-}
-
-/**
- * Returns the position of the center of the given tile
- */
-static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
-{
- x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
- z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
-}
-
-// TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp
-
-
-void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid)
-{
- for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
- {
- ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast(it->second);
-
- i32 cost = cmpTerritoryInfluence->GetCost();
- if (cost == -1)
- continue;
-
- CmpPtr cmpObstruction(GetSimContext(), it->first);
- if (!cmpObstruction)
- continue;
-
- ICmpObstructionManager::ObstructionSquare square;
- if (!cmpObstruction->GetObstructionSquare(square))
- continue;
-
- CFixedVector2D halfSize(square.hw, square.hh);
- CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
-
- u16 i0, j0, i1, j1;
- NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
- NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
- for (u16 j = j0; j <= j1; ++j)
- {
- for (u16 i = i0; i <= i1; ++i)
- {
- entity_pos_t x, z;
- TileCenter(i, j, x, z);
- if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize))
- grid.set(i, j, (u8)cost);
- }
- }
+ m_Territories->set(i, j, owner | TERRITORY_CONNECTED_MASK);
+ FLOODFILL(i, j,
+ // Don't expand non-owner tiles, or tiles that already have a connected mask
+ if (m_Territories->get(nx, nz) != owner)
+ continue;
+ m_Territories->set(nx, nz, owner | TERRITORY_CONNECTED_MASK);
+ );
}
}
std::vector CCmpTerritoryManager::ComputeBoundaries()
{
PROFILE("ComputeBoundaries");
CalculateTerritories();
ENSURE(m_Territories);
return CTerritoryBoundaryCalculator::ComputeBoundaries(m_Territories);
}
void CCmpTerritoryManager::UpdateBoundaryLines()
{
PROFILE("update boundary lines");
m_BoundaryLines.clear();
m_DebugBoundaryLineNodes.clear();
if (!CRenderer::IsInitialised())
return;
std::vector boundaries = ComputeBoundaries();
CTextureProperties texturePropsBase("art/textures/misc/territory_border.png");
texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
texturePropsBase.SetMaxAnisotropy(2.f);
CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png");
texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
texturePropsMask.SetMaxAnisotropy(2.f);
CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
CmpPtr cmpPlayerManager(GetSystemEntity());
if (!cmpPlayerManager)
return;
for (size_t i = 0; i < boundaries.size(); ++i)
{
if (boundaries[i].points.empty())
continue;
CColor color(1, 0, 1, 1);
CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner));
if (cmpPlayer)
color = cmpPlayer->GetColor();
m_BoundaryLines.push_back(SBoundaryLine());
m_BoundaryLines.back().connected = boundaries[i].connected;
m_BoundaryLines.back().color = color;
m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext();
m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
m_BoundaryLines.back().overlay.m_Color = color;
m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
m_BoundaryLines.back().overlay.m_Closed = true;
SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
std::vector& points = m_BoundaryLines.back().overlay.m_Coords;
for (size_t j = 0; j < boundaries[i].points.size(); ++j)
{
points.push_back(boundaries[i].points[j].X);
points.push_back(boundaries[i].points[j].Y);
if (m_EnableLineDebugOverlays)
{
const size_t numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
SOverlayLine overlayNode;
if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
else if (j < numHighlightNodes)
overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
else
overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
overlayNode.m_Thickness = 1;
SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
m_DebugBoundaryLineNodes.push_back(overlayNode);
}
}
}
}
void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset))
{
m_AnimTime += frameTime;
if (m_BoundaryLinesDirty)
{
UpdateBoundaryLines();
m_BoundaryLinesDirty = false;
}
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
{
if (!m_BoundaryLines[i].connected)
{
CColor c = m_BoundaryLines[i].color;
c.a *= 0.2f + 0.8f * fabsf((float)cos(m_AnimTime * M_PI)); // TODO: should let artists tweak this
m_BoundaryLines[i].overlay.m_Color = c;
}
}
}
void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
{
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[i].overlay);
for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
collector.Submit(&m_DebugBoundaryLineNodes[i]);
}
player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
{
u16 i, j;
CalculateTerritories();
if (!m_Territories)
return 0;
NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
}
+std::vector CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
+{
+ CmpPtr cmpPlayerManager(GetSystemEntity());
+ if (!cmpPlayerManager)
+ return std::vector();
+
+ std::vector ret(cmpPlayerManager->GetNumPlayers(), 0);
+ CalculateTerritories();
+ if (!m_Territories)
+ return ret;
+
+ u16 i, j;
+ NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
+
+ // calculate the neighbours
+ player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
+
+ u16 tilesW = m_Territories->m_W;
+ u16 tilesH = m_Territories->m_H;
+
+ // use a flood-fill algorithm that fills up to the borders and remembers the owners
+ Grid markerGrid(tilesW, tilesH);
+ markerGrid.set(i, j, true);
+
+ FLOODFILL(i, j,
+ if (markerGrid.get(nx, nz))
+ continue;
+ // mark the tile as visited in any case
+ markerGrid.set(nx, nz, true);
+ int owner = m_Territories->get(nx, nz) & TERRITORY_PLAYER_MASK;
+ if (owner != thisOwner)
+ {
+ if (owner == 0 || !filterConnected || (m_Territories->get(nx, nz) & TERRITORY_CONNECTED_MASK) != 0)
+ ret[owner]++; // add player to the neighbour list when requested
+ continue; // don't expand non-owner tiles further
+ }
+ );
+
+ return ret;
+}
+
bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
{
u16 i, j;
CalculateTerritories();
if (!m_Territories)
return false;
NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0;
}
TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager)
: TerrainOverlay(manager.GetSimContext()), m_TerritoryManager(manager)
{ }
void TerritoryOverlay::StartRender()
{
m_TerritoryManager.CalculateTerritories();
}
void TerritoryOverlay::ProcessTile(ssize_t i, ssize_t j)
{
if (!m_TerritoryManager.m_Territories)
return;
u8 id = (m_TerritoryManager.m_Territories->get((int) i, (int) j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
float a = 0.2f;
switch (id)
{
case 0: break;
case 1: RenderTile(CColor(1, 0, 0, a), false); break;
case 2: RenderTile(CColor(0, 1, 0, a), false); break;
case 3: RenderTile(CColor(0, 0, 1, a), false); break;
case 4: RenderTile(CColor(1, 1, 0, a), false); break;
case 5: RenderTile(CColor(0, 1, 1, a), false); break;
case 6: RenderTile(CColor(1, 0, 1, a), false); break;
default: RenderTile(CColor(1, 1, 1, a), false); break;
}
}
+
+#undef FOODFILL
Index: ps/trunk/source/simulation2/components/ICmpTerritoryInfluence.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTerritoryInfluence.h (revision 16675)
+++ ps/trunk/source/simulation2/components/ICmpTerritoryInfluence.h (revision 16676)
@@ -1,42 +1,35 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_ICMPTERRITORYINFLUENCE
#define INCLUDED_ICMPTERRITORYINFLUENCE
#include "simulation2/system/Interface.h"
class ICmpTerritoryInfluence : public IComponent
{
public:
- /**
- * Returns either -1 to indicate no special terrain cost, or a value
- * in [0, 255] to indicate overriding the normal cost of the terrain
- * under the entity's obstruction.
- */
- virtual i32 GetCost() = 0;
-
virtual bool IsRoot() = 0;
- virtual u32 GetWeight() = 0;
+ virtual u16 GetWeight() = 0;
virtual u32 GetRadius() = 0;
DECLARE_INTERFACE_TYPE(TerritoryInfluence)
};
#endif // INCLUDED_ICMPTERRITORYINFLUENCE
Index: ps/trunk/source/simulation2/components/ICmpTerritoryManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTerritoryManager.cpp (revision 16675)
+++ ps/trunk/source/simulation2/components/ICmpTerritoryManager.cpp (revision 16676)
@@ -1,27 +1,28 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ICmpTerritoryManager.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(TerritoryManager)
DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
+DEFINE_INTERFACE_METHOD_3("GetNeighbours", std::vector, ICmpTerritoryManager, GetNeighbours, entity_pos_t, entity_pos_t, bool)
DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t)
END_INTERFACE_WRAPPER(TerritoryManager)
Index: ps/trunk/source/simulation2/components/ICmpTerritoryManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 16675)
+++ ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 16676)
@@ -1,58 +1,64 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_ICMPTERRITORYMANAGER
#define INCLUDED_ICMPTERRITORYMANAGER
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/Player.h"
#include "simulation2/components/ICmpPosition.h"
class ICmpTerritoryManager : public IComponent
{
public:
virtual bool NeedUpdate(size_t* dirtyID) = 0;
static const int TERRITORY_PLAYER_MASK = 0x3F;
static const int TERRITORY_CONNECTED_MASK = 0x40;
static const int TERRITORY_PROCESSED_MASK = 0x80; //< For internal use; marks a tile as processed.
/**
* For each tile, the TERRITORY_PLAYER_MASK bits are player ID;
* TERRITORY_CONNECTED_MASK is set if the tile is connected to a root object
* (civ center etc).
*/
virtual const Grid& GetTerritoryGrid() = 0;
/**
* Get owner of territory at given position.
* @return player ID of owner; 0 if neutral territory
*/
virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
/**
+ * get the number of neighbour tiles for per player for the selected position
+ * @return A list with the number of neighbour tiles per player
+ */
+ virtual std::vector GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) = 0;
+
+ /**
* Get whether territory at given position is connected to a root object
* (civ center etc) owned by that territory's player.
*/
virtual bool IsConnected(entity_pos_t x, entity_pos_t z) = 0;
DECLARE_INTERFACE_TYPE(TerritoryManager)
};
#endif // INCLUDED_ICMPTERRITORYMANAGER
Index: ps/trunk/source/simulation2/system/ComponentManager.h
===================================================================
--- ps/trunk/source/simulation2/system/ComponentManager.h (revision 16675)
+++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 16676)
@@ -1,384 +1,385 @@
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_COMPONENTMANAGER
#define INCLUDED_COMPONENTMANAGER
#include "Entity.h"
#include "Components.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/helpers/Player.h"
#include "ps/Filesystem.h"
#include
#include
#include