1
0
forked from 0ad/0ad

# Health decay for buildings not in a civ center's territory.

This was SVN commit r10034.
This commit is contained in:
Ykkrosh 2011-08-18 20:28:53 +00:00
parent 6b26820090
commit 308cb26dd4
32 changed files with 359 additions and 56 deletions

View File

@ -15,6 +15,6 @@ TEX los, fragment.texcoord[1], texture[2], 2D;
MUL result.color.rgb, color, los.a; MUL result.color.rgb, color, los.a;
// Use alpha from base texture // Use alpha from base texture
MOV result.color.a, base.a; MUL result.color.a, objectColor.a, base.a;
END END

View File

@ -0,0 +1,74 @@
function TerritoryDecay() {}
TerritoryDecay.prototype.Schema =
"<element name='HealthDecayRate' a:help='Decay rate in hitpoints per second'>" +
"<data type='positiveInteger'/>" +
"</element>";
TerritoryDecay.prototype.Init = function()
{
this.timer = undefined;
};
TerritoryDecay.prototype.IsConnected = function()
{
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;
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())
return false;
// TODO: this should probably use the same territory restriction
// logic as BuildRestrictions, to handle allies etc
return cmpTerritoryManager.IsConnected(pos.x, pos.y);
};
TerritoryDecay.prototype.UpdateDecayState = function()
{
var connected = this.IsConnected();
if (!connected && !this.timer)
{
// Start decaying
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetInterval(this.entity, IID_TerritoryDecay, "Decay", 1000, 1000, {});
}
else if (connected && this.timer)
{
// Stop decaying
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
}
};
TerritoryDecay.prototype.OnTerritoriesChanged = function(msg)
{
this.UpdateDecayState();
};
TerritoryDecay.prototype.OnOwnershipChanged = function(msg)
{
this.UpdateDecayState();
};
TerritoryDecay.prototype.Decay = function()
{
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
if (!cmpHealth)
return; // error
cmpHealth.Reduce(+this.template.HealthDecayRate);
};
Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay);

View File

@ -82,7 +82,11 @@ Timer.prototype.OnUpdate = function(msg)
var cmp = Engine.QueryInterface(t[0], t[1]); var cmp = Engine.QueryInterface(t[0], t[1]);
if (!cmp) if (!cmp)
continue; // the entity was probably destroyed {
// The entity was probably destroyed; clean up the timer
delete this.timers[id];
continue;
}
try { try {
var lateness = this.time - t[3]; var lateness = this.time - t[3];

View File

@ -0,0 +1 @@
Engine.RegisterInterface("TerritoryDecay");

View File

@ -44,6 +44,7 @@
<Static width="26.0" depth="30.0"/> <Static width="26.0" depth="30.0"/>
</Obstruction> </Obstruction>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>40</Radius> <Radius>40</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -44,6 +44,7 @@
<Static width="28.0" depth="20.0"/> <Static width="28.0" depth="20.0"/>
</Obstruction> </Obstruction>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>40</Radius> <Radius>40</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -44,6 +44,7 @@
<Static width="26.0" depth="10.5"/> <Static width="26.0" depth="10.5"/>
</Obstruction> </Obstruction>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>36</Radius> <Radius>36</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -24,6 +24,7 @@
<Selectable/> <Selectable/>
<TerritoryInfluence> <TerritoryInfluence>
<OverrideCost>64</OverrideCost> <OverrideCost>64</OverrideCost>
<Root>false</Root>
<Weight>0</Weight> <Weight>0</Weight>
<Radius>0</Radius> <Radius>0</Radius>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -24,6 +24,7 @@
<Selectable/> <Selectable/>
<TerritoryInfluence> <TerritoryInfluence>
<OverrideCost>0</OverrideCost> <OverrideCost>0</OverrideCost>
<Root>false</Root>
<Weight>0</Weight> <Weight>0</Weight>
<Radius>0</Radius> <Radius>0</Radius>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -66,6 +66,9 @@
<BarHeight>0.6</BarHeight> <BarHeight>0.6</BarHeight>
<HeightOffset>12.0</HeightOffset> <HeightOffset>12.0</HeightOffset>
</StatusBars> </StatusBars>
<TerritoryDecay>
<HealthDecayRate>20</HealthDecayRate>
</TerritoryDecay>
<Vision> <Vision>
<Range>40</Range> <Range>40</Range>
<RetainInFog>true</RetainInFog> <RetainInFog>true</RetainInFog>

View File

@ -76,6 +76,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>true</Root>
<Radius>180</Radius> <Radius>180</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -45,6 +45,7 @@
<HeightOffset>8.0</HeightOffset> <HeightOffset>8.0</HeightOffset>
</StatusBars> </StatusBars>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>20</Radius> <Radius>20</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -51,6 +51,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>40</Radius> <Radius>40</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -66,6 +66,7 @@
<HeightOffset>21.0</HeightOffset> <HeightOffset>21.0</HeightOffset>
</StatusBars> </StatusBars>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>32</Radius> <Radius>32</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -42,6 +42,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>20</Radius> <Radius>20</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -38,6 +38,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>40</Radius> <Radius>40</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -42,6 +42,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>20</Radius> <Radius>20</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -46,6 +46,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>60</Radius> <Radius>60</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -62,6 +62,7 @@
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>100</Radius> <Radius>100</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -20,6 +20,7 @@
<Static width="24.0" depth="24.0"/> <Static width="24.0" depth="24.0"/>
</Obstruction> </Obstruction>
<TerritoryInfluence> <TerritoryInfluence>
<Root>false</Root>
<Radius>40</Radius> <Radius>40</Radius>
<Weight>65536</Weight> <Weight>65536</Weight>
</TerritoryInfluence> </TerritoryInfluence>

View File

@ -192,7 +192,7 @@ void CTerritoryTexture::GenerateBitmap(const Grid<u8>& territories, u8* bitmap,
{ {
for (ssize_t i = 0; i < w; ++i) for (ssize_t i = 0; i < w; ++i)
{ {
u8 val = territories.get(i, j); u8 val = territories.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
CColor color(1, 0, 1, 1); CColor color(1, 0, 1, 1);
if (val < colors.size()) if (val < colors.size())
@ -202,14 +202,10 @@ void CTerritoryTexture::GenerateBitmap(const Grid<u8>& territories, u8* bitmap,
*p++ = (int)(color.g*255.f); *p++ = (int)(color.g*255.f);
*p++ = (int)(color.r*255.f); *p++ = (int)(color.r*255.f);
if ((i > 0 && territories.get(i-1, j) != val) if ((i > 0 && (territories.get(i-1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)
|| (i < w-1 && territories.get(i+1, j) != val) || (i < w-1 && (territories.get(i+1, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)
|| (j > 0 && territories.get(i, j-1) != val) || (j > 0 && (territories.get(i, j-1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)
|| (j < h-1 && territories.get(i, j+1) != val) || (j < h-1 && (territories.get(i, j+1) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK) != val)
// || (i > 0 && j > 0 && territories.get(i-1, j-1) != val)
// || (i < w-1 && j > 0 && territories.get(i+1, j-1) != val)
// || (i > 0 && j > h-1 && territories.get(i-1, j+1) != val)
// || (i < w-1 && j < h-1 && territories.get(i+1, j+1) != val)
) )
{ {
*p++ = alphaMax; *p++ = alphaMax;

View File

@ -286,6 +286,19 @@ public:
int32_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles int32_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles
}; };
/**
* Sent when territory assignments have changed.
*/
class CMessageTerritoriesChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(TerritoriesChanged)
CMessageTerritoriesChanged()
{
}
};
/** /**
* Sent by CCmpRangeManager at most once per turn, when an active range query * Sent by CCmpRangeManager at most once per turn, when an active range query
* has had matching units enter/leave the range since the last RangeUpdate. * has had matching units enter/leave the range since the last RangeUpdate.

View File

@ -45,6 +45,7 @@ MESSAGE(PositionChanged)
MESSAGE(MotionChanged) MESSAGE(MotionChanged)
MESSAGE(RangeUpdate) MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged) MESSAGE(TerrainChanged)
MESSAGE(TerritoriesChanged)
MESSAGE(PathResult) MESSAGE(PathResult)
// TemplateManager must come before all other (non-test) components, // TemplateManager must come before all other (non-test) components,

View File

@ -916,7 +916,7 @@ public:
{ {
for (u16 i = 0; i < grid.m_W; ++i) for (u16 i = 0; i < grid.m_W; ++i)
{ {
u8 p = grid.get(i, j); u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
if (p > 0 && p <= MAX_LOS_PLAYER_ID) if (p > 0 && p <= MAX_LOS_PLAYER_ID)
{ {
m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));

View File

@ -60,6 +60,11 @@ public:
Init(paramNode); Init(paramNode);
} }
virtual bool IsLoaded()
{
return m_Terrain->GetVerticesPerSide() != 0;
}
virtual CFixedVector3D CalcNormal(entity_pos_t x, entity_pos_t z) virtual CFixedVector3D CalcNormal(entity_pos_t x, entity_pos_t z)
{ {
CFixedVector3D normal; CFixedVector3D normal;

View File

@ -30,6 +30,7 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence) DEFAULT_COMPONENT_ALLOCATOR(TerritoryInfluence)
i32 m_Cost; i32 m_Cost;
bool m_Root;
u32 m_Weight; u32 m_Weight;
u32 m_Radius; u32 m_Radius;
@ -43,6 +44,9 @@ public:
"</data>" "</data>"
"</element>" "</element>"
"</optional>" "</optional>"
"<element name='Root'>"
"<data type='boolean'/>"
"</element>"
"<element name='Weight'>" "<element name='Weight'>"
"<data type='nonNegativeInteger'/>" "<data type='nonNegativeInteger'/>"
"</element>" "</element>"
@ -58,6 +62,7 @@ public:
else else
m_Cost = -1; m_Cost = -1;
m_Root = paramNode.GetChild("Root").ToBool();
m_Weight = paramNode.GetChild("Weight").ToInt(); m_Weight = paramNode.GetChild("Weight").ToInt();
m_Radius = paramNode.GetChild("Radius").ToInt(); m_Radius = paramNode.GetChild("Radius").ToInt();
} }
@ -80,6 +85,11 @@ public:
return m_Cost; return m_Cost;
} }
virtual bool IsRoot()
{
return m_Root;
}
virtual u32 GetWeight() virtual u32 GetWeight()
{ {
return m_Weight; return m_Weight;

View File

@ -66,6 +66,8 @@ public:
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged); componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged); componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeToMessageType(MT_TerrainChanged); componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit); componentManager.SubscribeToMessageType(MT_RenderSubmit);
} }
@ -80,20 +82,39 @@ public:
float m_BorderThickness; float m_BorderThickness;
float m_BorderSeparation; float m_BorderSeparation;
// Player ID in lower 7 bits; connected flag in high bit
Grid<u8>* m_Territories; Grid<u8>* m_Territories;
TerritoryOverlay* m_DebugOverlay;
std::vector<SOverlayTexturedLine> m_BoundaryLines; // 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<SBoundaryLine> m_BoundaryLines;
bool m_BoundaryLinesDirty; bool m_BoundaryLinesDirty;
double m_AnimTime; // time since start of rendering, in seconds
TerritoryOverlay* m_DebugOverlay;
virtual void Init(const CParamNode& UNUSED(paramNode)) virtual void Init(const CParamNode& UNUSED(paramNode))
{ {
m_Territories = NULL; m_Territories = NULL;
m_DebugOverlay = NULL; m_DebugOverlay = NULL;
// m_DebugOverlay = new TerritoryOverlay(*this); // m_DebugOverlay = new TerritoryOverlay(*this);
m_BoundaryLinesDirty = true; m_BoundaryLinesDirty = true;
m_TriggerEvent = true;
m_DirtyID = 1; m_DirtyID = 1;
m_AnimTime = 0.0;
CParamNode externalParamNode; CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml"); CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml");
@ -141,6 +162,22 @@ public:
MakeDirty(); MakeDirty();
break; 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<const CMessageInterpolate&> (msg);
Interpolate(msgData.frameTime, msgData.offset);
break;
}
case MT_RenderSubmit: case MT_RenderSubmit:
{ {
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
@ -166,10 +203,12 @@ public:
virtual const Grid<u8>& GetTerritoryGrid() virtual const Grid<u8>& GetTerritoryGrid()
{ {
CalculateTerritories(); CalculateTerritories();
ENSURE(m_Territories);
return *m_Territories; return *m_Territories;
} }
virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z); virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z);
virtual bool IsConnected(entity_pos_t x, entity_pos_t z);
// To support lazy updates of territory render data, // To support lazy updates of territory render data,
// we maintain a DirtyID here and increment it whenever territories change; // we maintain a DirtyID here and increment it whenever territories change;
@ -182,6 +221,7 @@ public:
SAFE_DELETE(m_Territories); SAFE_DELETE(m_Territories);
++m_DirtyID; ++m_DirtyID;
m_BoundaryLinesDirty = true; m_BoundaryLinesDirty = true;
m_TriggerEvent = true;
} }
virtual bool NeedUpdate(size_t* dirtyID) virtual bool NeedUpdate(size_t* dirtyID)
@ -205,6 +245,7 @@ public:
struct TerritoryBoundary struct TerritoryBoundary
{ {
bool connected;
player_id_t owner; player_id_t owner;
std::vector<CVector2D> points; std::vector<CVector2D> points;
}; };
@ -213,6 +254,8 @@ public:
void UpdateBoundaryLines(); void UpdateBoundaryLines();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector); void RenderSubmit(SceneCollector& collector);
}; };
@ -250,8 +293,8 @@ static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff) static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff)
{ {
u32 tilesW = grid.m_W; u16 tilesW = grid.m_W;
u32 tilesH = grid.m_H; u16 tilesH = grid.m_H;
while (!openTiles.empty()) while (!openTiles.empty())
{ {
@ -281,16 +324,21 @@ static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles,
void CCmpTerritoryManager::CalculateTerritories() void CCmpTerritoryManager::CalculateTerritories()
{ {
PROFILE("CalculateTerritories");
if (m_Territories) if (m_Territories)
return; return;
PROFILE("CalculateTerritories");
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
// 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 tilesW = cmpTerrain->GetTilesPerSide();
u16 tilesH = cmpTerrain->GetTilesPerSide(); u16 tilesH = cmpTerrain->GetTilesPerSide();
SAFE_DELETE(m_Territories);
m_Territories = new Grid<u8>(tilesW, tilesH); m_Territories = new Grid<u8>(tilesW, tilesH);
// Compute terrain-passability-dependent costs per tile // Compute terrain-passability-dependent costs per tile
@ -324,6 +372,7 @@ void CCmpTerritoryManager::CalculateTerritories()
// Split influence entities into per-player lists, ignoring any with invalid properties // Split influence entities into per-player lists, ignoring any with invalid properties
std::map<player_id_t, std::vector<entity_id_t> > influenceEntities; std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
std::vector<entity_id_t> rootInfluenceEntities;
for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it) for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
{ {
// Ignore any with no weight or radius (to avoid divide-by-zero later) // Ignore any with no weight or radius (to avoid divide-by-zero later)
@ -340,12 +389,19 @@ void CCmpTerritoryManager::CalculateTerritories()
if (owner <= 0) if (owner <= 0)
continue; continue;
// We only have 7 bits to store tile ownership, so ignore unrepresentable players
if (owner > TERRITORY_PLAYER_MASK)
continue;
// Ignore if invalid position // Ignore if invalid position
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
if (cmpPosition.null() || !cmpPosition->IsInWorld()) if (cmpPosition.null() || !cmpPosition->IsInWorld())
continue; continue;
influenceEntities[owner].push_back(it->first); influenceEntities[owner].push_back(it->first);
if (cmpTerritoryInfluence->IsRoot())
rootInfluenceEntities.push_back(it->first);
} }
// For each player, store the sum of influences on each tile // For each player, store the sum of influences on each tile
@ -413,6 +469,64 @@ void CCmpTerritoryManager::CalculateTerritories()
} }
} }
} }
// 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<entity_id_t>::iterator it = rootInfluenceEntities.begin(); it != rootInfluenceEntities.end(); ++it)
{
// (These components must be valid else the entities wouldn't be added to this list)
CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), *it);
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
CFixedVector2D pos = cmpPosition->GetPosition2D();
u16 i = (u16)clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
u16 j = (u16)clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
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<u8>& grid = *m_Territories;
u16 maxi = (u16)(grid.m_W-1);
u16 maxj = (u16)(grid.m_H-1);
std::vector<std::pair<u16, u16> > 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
}
} }
/** /**
@ -481,6 +595,7 @@ std::vector<CCmpTerritoryManager::TerritoryBoundary> CCmpTerritoryManager::Compu
std::vector<CCmpTerritoryManager::TerritoryBoundary> boundaries; std::vector<CCmpTerritoryManager::TerritoryBoundary> boundaries;
CalculateTerritories(); CalculateTerritories();
ENSURE(m_Territories);
// Copy the territories grid so we can mess with it // Copy the territories grid so we can mess with it
Grid<u8> grid (*m_Territories); Grid<u8> grid (*m_Territories);
@ -506,7 +621,8 @@ std::vector<CCmpTerritoryManager::TerritoryBoundary> CCmpTerritoryManager::Compu
// we reach the starting point again // we reach the starting point again
boundaries.push_back(TerritoryBoundary()); boundaries.push_back(TerritoryBoundary());
boundaries.back().owner = owner; boundaries.back().connected = (owner & TERRITORY_CONNECTED_MASK);
boundaries.back().owner = (owner & TERRITORY_PLAYER_MASK);
std::vector<CVector2D>& points = boundaries.back().points; std::vector<CVector2D>& points = boundaries.back().points;
u8 dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left u8 dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left
@ -585,9 +701,9 @@ std::vector<CCmpTerritoryManager::TerritoryBoundary> CCmpTerritoryManager::Compu
// process it a second time // process it a second time
std::vector<std::pair<u16, u16> > tileStack; std::vector<std::pair<u16, u16> > tileStack;
#define ZERO_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); ) #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); )
ZERO_AND_PUSH(i, j); MARK_AND_PUSH(i, j);
while (!tileStack.empty()) while (!tileStack.empty())
{ {
int ti = tileStack.back().first; int ti = tileStack.back().first;
@ -595,25 +711,25 @@ std::vector<CCmpTerritoryManager::TerritoryBoundary> CCmpTerritoryManager::Compu
tileStack.pop_back(); tileStack.pop_back();
if (ti > 0 && grid.get(ti-1, tj) == owner) if (ti > 0 && grid.get(ti-1, tj) == owner)
ZERO_AND_PUSH(ti-1, tj); MARK_AND_PUSH(ti-1, tj);
if (ti < maxi && grid.get(ti+1, tj) == owner) if (ti < maxi && grid.get(ti+1, tj) == owner)
ZERO_AND_PUSH(ti+1, tj); MARK_AND_PUSH(ti+1, tj);
if (tj > 0 && grid.get(ti, tj-1) == owner) if (tj > 0 && grid.get(ti, tj-1) == owner)
ZERO_AND_PUSH(ti, tj-1); MARK_AND_PUSH(ti, tj-1);
if (tj < maxj && grid.get(ti, tj+1) == owner) if (tj < maxj && grid.get(ti, tj+1) == owner)
ZERO_AND_PUSH(ti, tj+1); MARK_AND_PUSH(ti, tj+1);
if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner) if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
ZERO_AND_PUSH(ti-1, tj-1); MARK_AND_PUSH(ti-1, tj-1);
if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner) if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner)
ZERO_AND_PUSH(ti-1, tj+1); MARK_AND_PUSH(ti-1, tj+1);
if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner) if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner)
ZERO_AND_PUSH(ti+1, tj-1); MARK_AND_PUSH(ti+1, tj-1);
if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner) if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner)
ZERO_AND_PUSH(ti+1, tj+1); MARK_AND_PUSH(ti+1, tj+1);
} }
#undef ZERO_AND_PUSH #undef MARK_AND_PUSH
} }
} }
} }
@ -658,18 +774,21 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
if (!cmpPlayer.null()) if (!cmpPlayer.null())
color = cmpPlayer->GetColour(); color = cmpPlayer->GetColour();
m_BoundaryLines.push_back(SOverlayTexturedLine()); m_BoundaryLines.push_back(SBoundaryLine());
m_BoundaryLines.back().m_Terrain = terrain; m_BoundaryLines.back().connected = boundaries[i].connected;
m_BoundaryLines.back().m_TextureBase = textureBase; m_BoundaryLines.back().color = color;
m_BoundaryLines.back().m_TextureMask = textureMask;
m_BoundaryLines.back().m_Color = color; m_BoundaryLines.back().overlay.m_Terrain = terrain;
m_BoundaryLines.back().m_Thickness = m_BorderThickness; 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;
SimRender::SmoothPointsAverage(boundaries[i].points, true); SimRender::SmoothPointsAverage(boundaries[i].points, true);
SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation); SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
std::vector<float>& points = m_BoundaryLines.back().m_Coords; std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
for (size_t j = 0; j < boundaries[i].points.size(); ++j) 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].X);
@ -678,8 +797,10 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
} }
} }
void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) void CCmpTerritoryManager::Interpolate(float frameTime, float UNUSED(frameOffset))
{ {
m_AnimTime += frameTime;
if (m_BoundaryLinesDirty) if (m_BoundaryLinesDirty)
{ {
UpdateBoundaryLines(); UpdateBoundaryLines();
@ -687,15 +808,42 @@ void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
} }
for (size_t i = 0; i < m_BoundaryLines.size(); ++i) for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[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;
}
}
} }
int32_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z) void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
{
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[i].overlay);
}
player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
{ {
u16 i, j; u16 i, j;
CalculateTerritories(); CalculateTerritories();
if (!m_Territories)
return 0;
NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
return m_Territories->get(i, j); return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
}
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;
} }
void TerritoryOverlay::StartRender() void TerritoryOverlay::StartRender()

View File

@ -29,6 +29,8 @@ class CTerrain;
class ICmpTerrain : public IComponent class ICmpTerrain : public IComponent
{ {
public: public:
virtual bool IsLoaded() = 0;
virtual CFixedVector3D CalcNormal(entity_pos_t x, entity_pos_t z) = 0; virtual CFixedVector3D CalcNormal(entity_pos_t x, entity_pos_t z) = 0;
virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z) = 0; virtual entity_pos_t GetGroundLevel(entity_pos_t x, entity_pos_t z) = 0;

View File

@ -30,6 +30,8 @@ public:
*/ */
virtual i32 GetCost() = 0; virtual i32 GetCost() = 0;
virtual bool IsRoot() = 0;
virtual u32 GetWeight() = 0; virtual u32 GetWeight() = 0;
virtual u32 GetRadius() = 0; virtual u32 GetRadius() = 0;

View File

@ -22,5 +22,6 @@
#include "simulation2/system/InterfaceScripted.h" #include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(TerritoryManager) BEGIN_INTERFACE_WRAPPER(TerritoryManager)
DEFINE_INTERFACE_METHOD_2("GetOwner", int32_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t)
END_INTERFACE_WRAPPER(TerritoryManager) END_INTERFACE_WRAPPER(TerritoryManager)

View File

@ -18,8 +18,10 @@
#ifndef INCLUDED_ICMPTERRITORYMANAGER #ifndef INCLUDED_ICMPTERRITORYMANAGER
#define INCLUDED_ICMPTERRITORYMANAGER #define INCLUDED_ICMPTERRITORYMANAGER
#include "simulation2/helpers/Grid.h"
#include "simulation2/system/Interface.h" #include "simulation2/system/Interface.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/helpers/Player.h"
#include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpPosition.h"
class ICmpTerritoryManager : public IComponent class ICmpTerritoryManager : public IComponent
@ -27,14 +29,27 @@ class ICmpTerritoryManager : public IComponent
public: public:
virtual bool NeedUpdate(size_t* dirtyID) = 0; virtual bool NeedUpdate(size_t* dirtyID) = 0;
static const int TERRITORY_PLAYER_MASK = 0x7F;
static const int TERRITORY_CONNECTED_MASK = 0x80;
/**
* 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<u8>& GetTerritoryGrid() = 0; virtual const Grid<u8>& GetTerritoryGrid() = 0;
/** /**
* Get owner of territory at given position * Get owner of territory at given position.
*
* @return player ID of owner; 0 if neutral territory * @return player ID of owner; 0 if neutral territory
*/ */
virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z) = 0; virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 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) DECLARE_INTERFACE_TYPE(TerritoryManager)
}; };

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games. /* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -26,20 +26,20 @@
#define TOJSVAL_SETUP() \ #define TOJSVAL_SETUP() \
JSObject* obj = JS_NewObject(scriptInterface.GetContext(), NULL, NULL, NULL); \ JSObject* obj = JS_NewObject(scriptInterface.GetContext(), NULL, NULL, NULL); \
if (! obj) \ if (! obj) \
return JSVAL_VOID return JSVAL_VOID;
#define SET_MSG_PROPERTY(name) \ #define SET_MSG_PROPERTY(name) \
do { \ do { \
jsval prop = ScriptInterface::ToJSVal(scriptInterface.GetContext(), this->name); \ jsval prop = ScriptInterface::ToJSVal(scriptInterface.GetContext(), this->name); \
if (! JS_SetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ if (! JS_SetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \
return JSVAL_VOID; \ return JSVAL_VOID; \
} while (0) } while (0);
#define FROMJSVAL_SETUP() \ #define FROMJSVAL_SETUP() \
if (! JSVAL_IS_OBJECT(val)) \ if (! JSVAL_IS_OBJECT(val)) \
return NULL; \ return NULL; \
JSObject* obj = JSVAL_TO_OBJECT(val) JSObject* obj = JSVAL_TO_OBJECT(val); \
jsval prop; jsval prop;
#define GET_MSG_PROPERTY(type, name) \ #define GET_MSG_PROPERTY(type, name) \
if (! JS_GetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ if (! JS_GetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \
@ -58,10 +58,10 @@ jsval CMessage::ToJSValCached(ScriptInterface& scriptInterface) const
//////////////////////////////// ////////////////////////////////
jsval CMessageTurnStart::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const jsval CMessageTurnStart::ToJSVal(ScriptInterface& scriptInterface) const
{ {
LOGWARNING(L"CMessageTurnStart::ToJSVal not implemented"); TOJSVAL_SETUP();
return JSVAL_VOID; return OBJECT_TO_JSVAL(obj);
} }
CMessage* CMessageTurnStart::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val)) CMessage* CMessageTurnStart::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
@ -254,6 +254,19 @@ CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& scriptInterface, js
//////////////////////////////// ////////////////////////////////
jsval CMessageTerritoriesChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return OBJECT_TO_JSVAL(obj);
}
CMessage* CMessageTerritoriesChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
return new CMessageTerritoriesChanged();
}
////////////////////////////////
jsval CMessageRangeUpdate::ToJSVal(ScriptInterface& scriptInterface) const jsval CMessageRangeUpdate::ToJSVal(ScriptInterface& scriptInterface) const
{ {
TOJSVAL_SETUP(); TOJSVAL_SETUP();