Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 9969)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 9970)
@@ -1,78 +1,78 @@
10.042.010.000
- standard
- allied
+ land
+ own001000000.04.09.8corpse0falsetrueStructureStructure ConquestCriticalstructuretruetruetruetruetruefalsefalseinterface/complete/building/complete_universal.xmlattack/destruction/building_collapse_large.xml6.00.612.040truefalsefalsetrue
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml (revision 9969)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml (revision 9970)
@@ -1,59 +1,61 @@
15.040.020.0
+ own ally neutral
+ shoreDock52503008.02500DockBuild upon a shoreline to construct naval vessels and to open sea trade.Townstructures/dock.pngtruefood wood stone metalinterface/complete/building/complete_dock.xmlattack/destruction/building_collapse_large.xml
units/{civ}_ship_fishing
units/{civ}_ship_merchant
40structures/fndn_4x4.xml
Index: ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h
===================================================================
--- ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h (revision 9970)
@@ -1,286 +1,303 @@
/* Copyright (C) 2011 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_CCMPPATHFINDER_COMMON
#define INCLUDED_CCMPPATHFINDER_COMMON
/**
* @file
* Declares CCmpPathfinder, whose implementation is split into multiple source files,
* and provides common code needed for more than one of those files.
* CCmpPathfinder includes two pathfinding algorithms (one tile-based, one vertex-based)
* with some shared state and functionality, so the code is split into
* CCmpPathfinder_Vertex.cpp, CCmpPathfinder_Tile.cpp and CCmpPathfinder.cpp
*/
#include "simulation2/system/Component.h"
#include "ICmpPathfinder.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "maths/MathUtil.h"
#include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Grid.h"
class PathfinderOverlay;
class SceneCollector;
struct PathfindTile;
#ifdef NDEBUG
#define PATHFIND_DEBUG 0
#else
#define PATHFIND_DEBUG 1
#endif
/*
* For efficient pathfinding we want to try hard to minimise the per-tile search cost,
* so we precompute the tile passability flags and movement costs for the various different
* types of unit.
* We also want to minimise memory usage (there can easily be 100K tiles so we don't want
* to store many bytes for each).
*
* To handle passability efficiently, we have a small number of passability classes
* (e.g. "infantry", "ship"). Each unit belongs to a single passability class, and
* uses that for all its pathfinding.
* Passability is determined by water depth, terrain slope, forestness, buildingness.
* We need at least one bit per class per tile to represent passability.
*
* We use a separate bit to indicate building obstructions (instead of folding it into
* the class passabilities) so that it can be ignored when doing the accurate short paths.
* We use another bit to indicate tiles near obstructions that block construction,
* for the AI to plan safe building spots.
*
* To handle movement costs, we have an arbitrary number of unit cost classes (e.g. "infantry", "camel"),
* and a small number of terrain cost classes (e.g. "grass", "steep grass", "road", "sand"),
* and a cost mapping table between the classes (e.g. camels are fast on sand).
* We need log2(|terrain cost classes|) bits per tile to represent costs.
*
* We could have one passability bitmap per class, and another array for cost classes,
* but instead (for no particular reason) we'll pack them all into a single u16 array.
*
* We handle dynamic updates currently by recomputing the entire array, which is stupid;
* it should only bother updating the region that has changed.
*/
class PathfinderPassability
{
public:
PathfinderPassability(ICmpPathfinder::pass_class_t mask, const CParamNode& node) :
m_Mask(mask)
{
if (node.GetChild("MinWaterDepth").IsOk())
m_MinDepth = node.GetChild("MinWaterDepth").ToFixed();
else
m_MinDepth = std::numeric_limits::min();
if (node.GetChild("MaxWaterDepth").IsOk())
m_MaxDepth = node.GetChild("MaxWaterDepth").ToFixed();
else
m_MaxDepth = std::numeric_limits::max();
if (node.GetChild("MaxTerrainSlope").IsOk())
m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
else
m_MaxSlope = std::numeric_limits::max();
+
+ if (node.GetChild("MinShoreDistance").IsOk())
+ m_MinShore = node.GetChild("MinShoreDistance").ToFixed();
+ else
+ m_MinShore = std::numeric_limits::min();
+
+ if (node.GetChild("MaxShoreDistance").IsOk())
+ m_MaxShore = node.GetChild("MaxShoreDistance").ToFixed();
+ else
+ m_MaxShore = std::numeric_limits::max();
+
}
- bool IsPassable(fixed waterdepth, fixed steepness)
+ bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
{
- return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope));
+ return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore));
}
ICmpPathfinder::pass_class_t m_Mask;
private:
fixed m_MinDepth;
fixed m_MaxDepth;
fixed m_MaxSlope;
+ fixed m_MinShore;
+ fixed m_MaxShore;
};
typedef u16 TerrainTile;
// 1 bit for pathfinding obstructions,
// 1 bit for construction obstructions (used by AI),
// PASS_CLASS_BITS for terrain passability (allowing PASS_CLASS_BITS classes),
// COST_CLASS_BITS for movement costs (allowing 2^COST_CLASS_BITS classes)
const int PASS_CLASS_BITS = 10;
const int COST_CLASS_BITS = 16 - (PASS_CLASS_BITS + 2);
#define IS_TERRAIN_PASSABLE(item, classmask) (((item) & (classmask)) == 0)
#define IS_PASSABLE(item, classmask) (((item) & ((classmask) | 1)) == 0)
#define GET_COST_CLASS(item) ((item) >> (PASS_CLASS_BITS + 2))
#define COST_CLASS_MASK(id) ( (TerrainTile) ((id) << (PASS_CLASS_BITS + 2)) )
typedef SparseGrid PathfindTileGrid;
struct AsyncLongPathRequest
{
u32 ticket;
entity_pos_t x0;
entity_pos_t z0;
ICmpPathfinder::Goal goal;
ICmpPathfinder::pass_class_t passClass;
ICmpPathfinder::cost_class_t costClass;
entity_id_t notify;
};
struct AsyncShortPathRequest
{
u32 ticket;
entity_pos_t x0;
entity_pos_t z0;
entity_pos_t r;
entity_pos_t range;
ICmpPathfinder::Goal goal;
ICmpPathfinder::pass_class_t passClass;
bool avoidMovingUnits;
entity_id_t group;
entity_id_t notify;
};
/**
* Implementation of ICmpPathfinder
*/
class CCmpPathfinder : public ICmpPathfinder
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_TurnStart);
}
DEFAULT_COMPONENT_ALLOCATOR(Pathfinder)
// Template state:
std::map m_PassClassMasks;
std::vector m_PassClasses;
std::map m_TerrainCostClassTags;
std::map m_UnitCostClassTags;
std::vector > m_MoveCosts; // costs[unitClass][terrainClass]
std::vector > m_MoveSpeeds; // speeds[unitClass][terrainClass]
// Dynamic state:
std::vector m_AsyncLongPathRequests;
std::vector m_AsyncShortPathRequests;
u32 m_NextAsyncTicket; // unique IDs for asynchronous path requests
u16 m_SameTurnMovesCount; // current number of same turn moves we have processed this turn
// Lazily-constructed dynamic state (not serialized):
u16 m_MapSize; // tiles per side
Grid* m_Grid; // terrain/passability information
Grid* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid)
bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
// For responsiveness we will process some moves in the same turn they were generated in
u16 m_MaxSameTurnMoves; // max number of moves that can be created and processed in the same turn
// Debugging - output from last pathfind operation:
PathfindTileGrid* m_DebugGrid;
u32 m_DebugSteps;
Path* m_DebugPath;
PathfinderOverlay* m_DebugOverlay;
pass_class_t m_DebugPassClass;
std::vector m_DebugOverlayShortPathLines;
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& paramNode);
virtual void Deinit();
virtual void Serialize(ISerializer& serialize);
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize);
virtual void HandleMessage(const CMessage& msg, bool global);
virtual pass_class_t GetPassabilityClass(const std::string& name);
virtual std::map GetPassabilityClasses();
virtual cost_class_t GetCostClass(const std::string& name);
virtual const Grid& GetPassabilityGrid();
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret);
virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify);
virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret);
virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify);
virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass);
virtual void ResetDebugPath();
virtual void SetDebugOverlay(bool enabled);
virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass);
virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal);
virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass);
+ virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass);
+
+ virtual bool CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass);
+
virtual void FinishAsyncRequests();
void ProcessLongRequests(const std::vector& longRequests);
void ProcessShortRequests(const std::vector& shortRequests);
virtual void ProcessSameTurnMoves();
/**
* Returns the tile containing the given position
*/
void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j)
{
i = clamp((x / (int)CELL_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1);
j = clamp((z / (int)CELL_SIZE).ToInt_RoundToZero(), 0, m_MapSize-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)CELL_SIZE + CELL_SIZE/2);
z = entity_pos_t::FromInt(j*(int)CELL_SIZE + CELL_SIZE/2);
}
static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal);
/**
* Regenerates the grid based on the current obstruction list, if necessary
*/
void UpdateGrid();
void RenderSubmit(SceneCollector& collector);
};
#endif // INCLUDED_CCMPPATHFINDER_COMMON
Index: ps/trunk/source/simulation2/components/CCmpFootprint.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpFootprint.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpFootprint.cpp (revision 9970)
@@ -1,229 +1,255 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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 "ICmpFootprint.h"
-#include "ICmpObstruction.h"
-#include "ICmpObstructionManager.h"
-#include "ICmpPosition.h"
+#include "simulation2/components/ICmpObstruction.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/MessageTypes.h"
+#include "graphics/Terrain.h" // For CELL_SIZE
#include "maths/FixedVector2D.h"
class CCmpFootprint : public ICmpFootprint
{
public:
static void ClassInit(CComponentManager& UNUSED(componentManager))
{
}
DEFAULT_COMPONENT_ALLOCATOR(Footprint)
EShape m_Shape;
entity_pos_t m_Size0; // width/radius
entity_pos_t m_Size1; // height/radius
entity_pos_t m_Height;
static std::string GetSchema()
{
return
"Approximation of the entity's shape, for collision detection and outline rendering. "
"Shapes are flat horizontal squares or circles, extended vertically to a given height."
""
""
"0.0"
""
""
""
"0.0"
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
if (paramNode.GetChild("Square").IsOk())
{
m_Shape = SQUARE;
m_Size0 = paramNode.GetChild("Square").GetChild("@width").ToFixed();
m_Size1 = paramNode.GetChild("Square").GetChild("@depth").ToFixed();
}
else if (paramNode.GetChild("Circle").IsOk())
{
m_Shape = CIRCLE;
m_Size0 = m_Size1 = paramNode.GetChild("Circle").GetChild("@radius").ToFixed();
}
else
{
// Error - pick some default
m_Shape = CIRCLE;
m_Size0 = m_Size1 = entity_pos_t::FromInt(1);
}
m_Height = paramNode.GetChild("Height").ToFixed();
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// No dynamic state to serialize
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void GetShape(EShape& shape, entity_pos_t& size0, entity_pos_t& size1, entity_pos_t& height)
{
shape = m_Shape;
size0 = m_Size0;
size1 = m_Size1;
height = m_Height;
}
virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned)
{
// Try to find a free space around the building's footprint.
// (Note that we use the footprint, not the obstruction shape - this might be a bit dodgy
// because the footprint might be inside the obstruction, but it hopefully gives us a nicer
// shape.)
- CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
+ const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
CmpPtr cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null() || !cmpPosition->IsInWorld())
return error;
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return error;
entity_pos_t spawnedRadius;
ICmpObstructionManager::tag_t spawnedTag;
CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned);
if (!cmpSpawnedObstruction.null())
{
spawnedRadius = cmpSpawnedObstruction->GetUnitRadius();
spawnedTag = cmpSpawnedObstruction->GetObstruction();
}
// else use zero radius
- // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
- entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2);
+ // Get passability class from UnitMotion
+ CmpPtr cmpUnitMotion(GetSimContext(), spawned);
+ if (cmpUnitMotion.null())
+ return error;
+ ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpPathfinder.null())
+ return error;
+
CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
+ // Max spawning distance in tiles
+ const size_t maxSpawningDistance = 4;
+
if (m_Shape == CIRCLE)
{
- entity_pos_t radius = m_Size0 + clearance;
-
- // Try equally-spaced points around the circle, starting from the front and expanding outwards in alternating directions
- const ssize_t numPoints = 31;
- for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
+ // Expand outwards from foundation
+ for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
{
- entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
+ // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
+ entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
+ entity_pos_t radius = m_Size0 + clearance;
+
+ // Try equally-spaced points around the circle in alternating directions, starting from the front
+ const ssize_t numPoints = 31 + 2*dist;
+ for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
+ {
+ entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
- fixed s, c;
- sincos_approx(angle, s, c);
+ fixed s, c;
+ sincos_approx(angle, s, c);
- CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
+ CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
- SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
- if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius, NULL))
- return pos; // this position is okay, so return it
+ SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
+ if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass))
+ return pos; // this position is okay, so return it
+ }
}
}
else
{
fixed s, c;
sincos_approx(initialAngle, s, c);
- for (size_t edge = 0; edge < 4; ++edge)
+ // Expand outwards from foundation
+ for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
{
- // Try equally-spaced points along the edge, starting from the middle and expanding outwards in alternating directions
- const ssize_t numPoints = 9;
+ // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
+ entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
- // Compute the direction and length of the current edge
- CFixedVector2D dir;
- fixed sx, sy;
- switch (edge)
+ for (size_t edge = 0; edge < 4; ++edge)
{
- case 0:
- dir = CFixedVector2D(c, -s);
- sx = m_Size0;
- sy = m_Size1;
- break;
- case 1:
- dir = CFixedVector2D(-s, -c);
- sx = m_Size1;
- sy = m_Size0;
- break;
- case 2:
- dir = CFixedVector2D(s, c);
- sx = m_Size1;
- sy = m_Size0;
- break;
- case 3:
- dir = CFixedVector2D(-c, s);
- sx = m_Size0;
- sy = m_Size1;
- break;
- }
- CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
- dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
+ // Try equally-spaced points along the edge in alternating directions, starting from the middle
+ const ssize_t numPoints = 9 + 2*dist;
- for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
- {
- CFixedVector2D pos (center + dir*i);
-
- SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
- if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, spawnedRadius, NULL))
- return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
+ // Compute the direction and length of the current edge
+ CFixedVector2D dir;
+ fixed sx, sy;
+ switch (edge)
+ {
+ case 0:
+ dir = CFixedVector2D(c, -s);
+ sx = m_Size0;
+ sy = m_Size1;
+ break;
+ case 1:
+ dir = CFixedVector2D(-s, -c);
+ sx = m_Size1;
+ sy = m_Size0;
+ break;
+ case 2:
+ dir = CFixedVector2D(s, c);
+ sx = m_Size1;
+ sy = m_Size0;
+ break;
+ case 3:
+ dir = CFixedVector2D(-c, s);
+ sx = m_Size0;
+ sy = m_Size1;
+ break;
+ }
+ CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
+ dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
+
+ for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
+ {
+ CFixedVector2D pos (center + dir*i);
+
+ SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
+ if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass))
+ return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
+ }
}
}
}
return error;
}
};
REGISTER_COMPONENT_TYPE(Footprint)
Index: ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp (revision 9970)
@@ -1,711 +1,720 @@
/* Copyright (C) 2011 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 "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/Overlay.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) : m_TerritoryManager(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.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager)
static std::string GetSchema()
{
return "";
}
u8 m_ImpassableCost;
float m_BorderThickness;
float m_BorderSeparation;
Grid* m_Territories;
TerritoryOverlay* m_DebugOverlay;
std::vector m_BoundaryLines;
bool m_BoundaryLinesDirty;
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_Territories = NULL;
m_DebugOverlay = NULL;
// m_DebugOverlay = new TerritoryOverlay(*this);
m_BoundaryLinesDirty = true;
m_DirtyID = 1;
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml");
m_ImpassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt();
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& serialize)
{
// TODO
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& 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_TerrainChanged:
{
MakeDirty();
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.null())
MakeDirty();
CmpPtr cmpTerritoryInfluence(GetSimContext(), ent);
if (!cmpTerritoryInfluence.null())
MakeDirty();
}
virtual const Grid& GetTerritoryGrid()
{
CalculateTerritories();
return *m_Territories;
}
+ virtual int32_t GetOwner(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;
}
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);
struct TerritoryBoundary
{
player_id_t owner;
std::vector points;
};
std::vector ComputeBoundaries();
void UpdateBoundaryLines();
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;
u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
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)
{
u32 tilesW = grid.m_W;
u32 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, x-1, z, tile.rank, false, grid, openTiles, costGrid);
if (x < tilesW-1)
ProcessNeighbour(falloff, x+1, z, tile.rank, false, grid, openTiles, costGrid);
if (z > 0)
ProcessNeighbour(falloff, x, z-1, tile.rank, false, grid, openTiles, costGrid);
if (z < tilesH-1)
ProcessNeighbour(falloff, x, z+1, tile.rank, false, grid, openTiles, costGrid);
if (x > 0 && z > 0)
ProcessNeighbour(falloff, x-1, z-1, tile.rank, true, grid, openTiles, costGrid);
if (x > 0 && z < tilesH-1)
ProcessNeighbour(falloff, x-1, z+1, tile.rank, true, grid, openTiles, costGrid);
if (x < tilesW-1 && z > 0)
ProcessNeighbour(falloff, x+1, z-1, tile.rank, true, grid, openTiles, costGrid);
if (x < tilesW-1 && z < tilesH-1)
ProcessNeighbour(falloff, x+1, z+1, tile.rank, true, grid, openTiles, costGrid);
}
}
void CCmpTerritoryManager::CalculateTerritories()
{
PROFILE("CalculateTerritories");
if (m_Territories)
return;
CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
uint32_t tilesW = cmpTerrain->GetVerticesPerSide() - 1;
uint32_t tilesH = cmpTerrain->GetVerticesPerSide() - 1;
SAFE_DELETE(m_Territories);
m_Territories = new Grid(tilesW, tilesH);
// Compute terrain-passability-dependent costs per tile
Grid influenceGrid(tilesW, tilesH);
CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");
const Grid& passGrid = cmpPathfinder->GetPassabilityGrid();
for (u32 j = 0; j < tilesH; ++j)
{
for (u32 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;
for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
{
// 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;
CmpPtr cmpOwnership(GetSimContext(), it->first);
if (cmpOwnership.null())
continue;
// Ignore Gaia and unassigned
player_id_t owner = cmpOwnership->GetOwner();
if (owner <= 0)
continue;
// Ignore if invalid position
CmpPtr cmpPosition(GetSimContext(), it->first);
if (cmpPosition.null() || !cmpPosition->IsInWorld())
continue;
influenceEntities[owner].push_back(it->first);
}
// 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
for (std::map >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)
{
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
Grid entityGrid(tilesW, tilesH);
CmpPtr cmpPosition(GetSimContext(), *eit);
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = clamp((pos.X / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesW-1);
int j = clamp((pos.Y / (int)CELL_SIZE).ToInt_RoundToNegInfinity(), 0, (int)tilesH-1);
CmpPtr cmpTerritoryInfluence(GetSimContext(), *eit);
u32 weight = cmpTerritoryInfluence->GetWeight();
u32 radius = cmpTerritoryInfluence->GetRadius() / CELL_SIZE;
u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
// TODO: we should have some maximum value on weight, to avoid overflow
// when doing all the sums
// 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);
// Expand influences outwards
FloodFill(entityGrid, influenceGrid, openTiles, falloff);
// TODO: we should do a sparse grid and only add the non-zero regions, for performance
for (u16 j = 0; j < entityGrid.m_H; ++j)
for (u16 i = 0; i < entityGrid.m_W; ++i)
playerGrid.set(i, j, playerGrid.get(i, j) + entityGrid.get(i, j));
}
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)
{
player_id_t id = playerGrids[k].first;
m_Territories->set(i, j, (u8)id);
bestWeight = w;
}
}
}
}
}
/**
* 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 = clamp((x / (int)CELL_SIZE).ToInt_RoundToZero(), 0, w-1);
j = clamp((z / (int)CELL_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)CELL_SIZE + CELL_SIZE/2);
z = entity_pos_t::FromInt(j*(int)CELL_SIZE + CELL_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);
int cost = cmpTerritoryInfluence->GetCost();
if (cost == -1)
continue;
CmpPtr cmpObstruction(GetSimContext(), it->first);
if (cmpObstruction.null())
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, cost);
}
}
}
}
std::vector CCmpTerritoryManager::ComputeBoundaries()
{
PROFILE("ComputeBoundaries");
std::vector boundaries;
CalculateTerritories();
// Copy the territories grid so we can mess with it
Grid grid (*m_Territories);
// Some constants for the border walk
CVector2D edgeOffsets[] = {
CVector2D(0.5f, 0.0f),
CVector2D(1.0f, 0.5f),
CVector2D(0.5f, 1.0f),
CVector2D(0.0f, 0.5f)
};
// Try to find an assigned tile
for (int j = 0; j < grid.m_H; ++j)
{
for (int i = 0; i < grid.m_W; ++i)
{
u8 owner = grid.get(i, j);
if (owner)
{
// Found the first tile (which must be the lowest j value of any non-zero tile);
// start at the bottom edge of it and chase anticlockwise around the border until
// we reach the starting point again
boundaries.push_back(TerritoryBoundary());
boundaries.back().owner = owner;
std::vector& points = boundaries.back().points;
int dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left
int cdir = dir;
int ci = i, cj = j;
while (true)
{
points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * CELL_SIZE);
// Given that we're on an edge on a continuous boundary and aiming anticlockwise,
// we can either carry on straight or turn left or turn right, so examine each
// of the three possible cases (depending on initial direction):
switch (cdir)
{
case 0:
if (ci < grid.m_W-1 && cj > 0 && grid.get(ci+1, cj-1) == owner)
{
++ci;
--cj;
cdir = 3;
}
else if (ci < grid.m_W-1 && grid.get(ci+1, cj) == owner)
++ci;
else
cdir = 1;
break;
case 1:
if (ci < grid.m_W-1 && cj < grid.m_H-1 && grid.get(ci+1, cj+1) == owner)
{
++ci;
++cj;
cdir = 0;
}
else if (cj < grid.m_H-1 && grid.get(ci, cj+1) == owner)
++cj;
else
cdir = 2;
break;
case 2:
if (ci > 0 && cj < grid.m_H-1 && grid.get(ci-1, cj+1) == owner)
{
--ci;
++cj;
cdir = 1;
}
else if (ci > 0 && grid.get(ci-1, cj) == owner)
--ci;
else
cdir = 3;
break;
case 3:
if (ci > 0 && cj > 0 && grid.get(ci-1, cj-1) == owner)
{
--ci;
--cj;
cdir = 2;
}
else if (cj > 0 && grid.get(ci, cj-1) == owner)
--cj;
else
cdir = 0;
break;
}
// Stop when we've reached the starting point again
if (ci == i && cj == j && cdir == dir)
break;
}
// Zero out this whole territory with a simple flood fill, so we don't
// process it a second time
std::vector > tileStack;
#define ZERO_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); )
ZERO_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)
ZERO_AND_PUSH(ti-1, tj);
if (ti < grid.m_W-1 && grid.get(ti+1, tj) == owner)
ZERO_AND_PUSH(ti+1, tj);
if (tj > 0 && grid.get(ti, tj-1) == owner)
ZERO_AND_PUSH(ti, tj-1);
if (tj < grid.m_H-1 && grid.get(ti, tj+1) == owner)
ZERO_AND_PUSH(ti, tj+1);
if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
ZERO_AND_PUSH(ti-1, tj-1);
if (ti > 0 && tj < grid.m_H-1 && grid.get(ti-1, tj+1) == owner)
ZERO_AND_PUSH(ti-1, tj+1);
if (ti < grid.m_W-1 && tj > 0 && grid.get(ti+1, tj-1) == owner)
ZERO_AND_PUSH(ti+1, tj-1);
if (ti < grid.m_W-1 && tj < grid.m_H-1 && grid.get(ti+1, tj+1) == owner)
ZERO_AND_PUSH(ti+1, tj+1);
}
#undef ZERO_AND_PUSH
}
}
}
return boundaries;
}
void CCmpTerritoryManager::UpdateBoundaryLines()
{
PROFILE("update boundary lines");
m_BoundaryLines.clear();
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 cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
if (cmpTerrain.null())
return;
CTerrain* terrain = cmpTerrain->GetCTerrain();
CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpPlayerManager.null())
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.null())
color = cmpPlayer->GetColour();
m_BoundaryLines.push_back(SOverlayTexturedLine());
m_BoundaryLines.back().m_Terrain = terrain;
m_BoundaryLines.back().m_TextureBase = textureBase;
m_BoundaryLines.back().m_TextureMask = textureMask;
m_BoundaryLines.back().m_Color = color;
m_BoundaryLines.back().m_Thickness = m_BorderThickness;
SimRender::SmoothPointsAverage(boundaries[i].points, true);
SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
std::vector& points = m_BoundaryLines.back().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);
}
}
}
void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
{
if (m_BoundaryLinesDirty)
{
UpdateBoundaryLines();
m_BoundaryLinesDirty = false;
}
for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
collector.Submit(&m_BoundaryLines[i]);
}
+int32_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
+{
+ u16 i, j;
+ CalculateTerritories();
+ NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
+ return m_Territories->get(i, j);
+}
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(i, j);
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;
}
}
Index: ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp (revision 9970)
@@ -1,538 +1,539 @@
/* Copyright (C) 2011 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 "ICmpTemplateManager.h"
#include "simulation2/MessageTypes.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/XML/RelaxNG.h"
#include "ps/XML/Xeromyces.h"
static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";
static const wchar_t ACTOR_ROOT[] = L"art/actors/";
class CCmpTemplateManager : public ICmpTemplateManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(TemplateManager)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_DisableValidation = false;
m_Validator.LoadGrammar(GetSimContext().GetComponentManager().GenerateSchema());
// TODO: handle errors loading the grammar here?
// TODO: support hotloading changes to the grammar
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& serialize)
{
size_t count = 0;
for (std::map::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
{
if (ENTITY_IS_LOCAL(it->first))
continue;
++count;
}
serialize.NumberU32_Unbounded("num entities", (u32)count);
for (std::map::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
{
if (ENTITY_IS_LOCAL(it->first))
continue;
serialize.NumberU32_Unbounded("id", it->first);
serialize.StringASCII("template", it->second, 0, 256);
}
// TODO: maybe we should do some kind of interning thing instead of printing so many strings?
// TODO: will need to serialize techs too, because we need to be giving out
// template data before other components (like the tech components) have been deserialized
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
u32 numEntities;
deserialize.NumberU32_Unbounded("num entities", numEntities);
for (u32 i = 0; i < numEntities; ++i)
{
entity_id_t ent;
std::string templateName;
deserialize.NumberU32_Unbounded("id", ent);
deserialize.StringASCII("template", templateName, 0, 256);
m_LatestTemplates[ent] = templateName;
}
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Destroy:
{
const CMessageDestroy& msgData = static_cast (msg);
// Clean up m_LatestTemplates so it doesn't record any data for destroyed entities
m_LatestTemplates.erase(msgData.entity);
break;
}
}
}
virtual void DisableValidation()
{
m_DisableValidation = true;
}
virtual const CParamNode* LoadTemplate(entity_id_t ent, const std::string& templateName, int playerID);
virtual const CParamNode* GetTemplate(std::string templateName);
virtual const CParamNode* GetTemplateWithoutValidation(std::string templateName);
virtual const CParamNode* LoadLatestTemplate(entity_id_t ent);
virtual std::string GetCurrentTemplateName(entity_id_t ent);
virtual std::vector FindAllTemplates(bool includeActors);
virtual std::vector GetEntitiesUsingTemplate(std::string templateName);
private:
// Entity template XML validator
RelaxNGValidator m_Validator;
// Disable validation, for test cases
bool m_DisableValidation;
// Map from template name (XML filename or special |-separated string) to the most recently
// loaded non-broken template data. This includes files that will fail schema validation.
// (Failed loads won't remove existing entries under the same name, so we behave more nicely
// when hotloading broken files)
std::map m_TemplateFileData;
// Map from template name to schema validation status.
// (Some files, e.g. inherited parent templates, may not be valid themselves but we still need to load
// them and use them; we only reject invalid templates that were requested directly by GetTemplate/etc)
std::map m_TemplateSchemaValidity;
// Remember the template used by each entity, so we can return them
// again for deserialization.
// TODO: should store player ID etc.
std::map m_LatestTemplates;
// (Re)loads the given template, regardless of whether it exists already,
// and saves into m_TemplateFileData. Also loads any parents that are not yet
// loaded. Returns false on error.
// @param templateName XML filename to load (not a |-separated string)
bool LoadTemplateFile(const std::string& templateName, int depth);
// Constructs a standard static-decorative-object template for the given actor
void ConstructTemplateActor(const std::string& actorName, CParamNode& out);
// Copy the non-interactive components of an entity template (position, actor, etc) into
// a new entity template
void CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse);
// Copy the components of an entity necessary for a construction foundation
// (position, actor, armour, health, etc) into a new entity template
void CopyFoundationSubset(CParamNode& out, const CParamNode& in);
};
REGISTER_COMPONENT_TYPE(TemplateManager)
const CParamNode* CCmpTemplateManager::LoadTemplate(entity_id_t ent, const std::string& templateName, int UNUSED(playerID))
{
m_LatestTemplates[ent] = templateName;
const CParamNode* templateRoot = GetTemplate(templateName);
if (!templateRoot)
return NULL;
// TODO: Eventually we need to support techs in here, and return a different template per playerID
return templateRoot;
}
const CParamNode* CCmpTemplateManager::GetTemplate(std::string templateName)
{
// Load the template if necessary
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
return NULL;
}
if (!m_DisableValidation)
{
// Compute validity, if it's not computed before
if (m_TemplateSchemaValidity.find(templateName) == m_TemplateSchemaValidity.end())
m_TemplateSchemaValidity[templateName] = m_Validator.Validate(wstring_from_utf8(templateName), m_TemplateFileData[templateName].ToXML());
// Refuse to return invalid templates
if (!m_TemplateSchemaValidity[templateName])
return NULL;
}
const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");
if (!templateRoot.IsOk())
{
// The validator should never let this happen
LOGERROR(L"Invalid root element in entity template '%hs'", templateName.c_str());
return NULL;
}
return &templateRoot;
}
const CParamNode* CCmpTemplateManager::GetTemplateWithoutValidation(std::string templateName)
{
// Load the template if necessary
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
return NULL;
}
const CParamNode& templateRoot = m_TemplateFileData[templateName].GetChild("Entity");
if (!templateRoot.IsOk())
return NULL;
return &templateRoot;
}
const CParamNode* CCmpTemplateManager::LoadLatestTemplate(entity_id_t ent)
{
std::map::const_iterator it = m_LatestTemplates.find(ent);
if (it == m_LatestTemplates.end())
return NULL;
return LoadTemplate(ent, it->second, -1);
}
std::string CCmpTemplateManager::GetCurrentTemplateName(entity_id_t ent)
{
std::map::const_iterator it = m_LatestTemplates.find(ent);
if (it == m_LatestTemplates.end())
return "";
return it->second;
}
bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int depth)
{
// If this file was already loaded, we don't need to do anything
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
return true;
// Handle infinite loops more gracefully than running out of stack space and crashing
if (depth > 100)
{
LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str());
return false;
}
// Handle special case "actor|foo"
if (templateName.find("actor|") == 0)
{
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
return true;
}
// Handle special case "preview|foo"
if (templateName.find("preview|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(8);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
return true;
}
// Handle special case "corpse|foo"
if (templateName.find("corpse|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(7);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
return true;
}
// Handle special case "foundation|foo"
if (templateName.find("foundation|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(11);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Normal case: templateName is an XML file:
VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return false; // (Xeromyces already logged an error with the full filename)
int attr_parent = xero.GetAttributeID("parent");
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
if (!parentName.empty())
{
// To prevent needless complexity in template design, we don't allow |-separated strings as parents
if (parentName.find('|') != parentName.npos)
{
LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str());
return false;
}
// Ensure the parent is loaded
if (!LoadTemplateFile(parentName, depth+1))
{
LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str());
return false;
}
CParamNode& parentData = m_TemplateFileData[parentName];
// Initialise this template with its parent
m_TemplateFileData[templateName] = parentData;
}
// Load the new file into the template data (overriding parent values)
CParamNode::LoadXML(m_TemplateFileData[templateName], xero);
return true;
}
void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
{
// Load the base actor template if necessary
const char* templateName = "special/actor";
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%hs'", templateName);
return;
}
// Copy the actor template
out = m_TemplateFileData[templateName];
// Initialise the actor's name
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(wstring_from_utf8(actorName)));
std::string xml = "" + name + "";
CParamNode::LoadXMLString(out, xml.c_str());
}
static Status AddToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& templates = *(std::vector*)cbData;
// Strip the .xml extension
VfsPath pathstem = pathname.ChangeExtension(L"");
// Strip the root from the path
std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
// We want to ignore template_*.xml templates, since they should never be built in the editor
if (name.substr(0, 9) == L"template_")
return INFO::OK;
templates.push_back(std::string(name.begin(), name.end()));
return INFO::OK;
}
static Status AddActorToTemplates(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& templates = *(std::vector*)cbData;
// Strip the root from the path
std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
templates.push_back("actor|" + std::string(name.begin(), name.end()));
return INFO::OK;
}
std::vector CCmpTemplateManager::FindAllTemplates(bool includeActors)
{
// TODO: eventually this should probably read all the template files and look for flags to
// determine which should be displayed in the editor (and in what categories etc); for now we'll
// just return all the files
std::vector templates;
Status ok;
// Find all the normal entity templates first
ok = vfs::ForEachFile(g_VFS, TEMPLATE_ROOT, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
WARN_IF_ERR(ok);
if (includeActors)
{
// Add all the actors too
ok = vfs::ForEachFile(g_VFS, ACTOR_ROOT, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
WARN_IF_ERR(ok);
}
return templates;
}
/**
* Get the list of entities using the specified template
*/
std::vector CCmpTemplateManager::GetEntitiesUsingTemplate(std::string templateName)
{
std::vector entities;
for (std::map::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it)
{
if(it->second == templateName)
entities.push_back(it->first);
}
return entities;
}
void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
{
// We only want to include components which are necessary (for the visual previewing of an entity)
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
// to this list should be carefully considered
std::set permittedComponentTypes;
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Decay");
+ permittedComponentTypes.insert("BuildRestrictions");
// Need these for the Actor Viewer:
permittedComponentTypes.insert("Attack");
permittedComponentTypes.insert("UnitMotion");
permittedComponentTypes.insert("Sound");
// (This set could be initialised once and reused, but it's not worth the effort)
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
// (but can still be used for testing this entity for collisions against others)
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "false");
if (!corpse)
{
// Previews should always be visible in fog-of-war/etc
CParamNode::LoadXMLString(out, "0falsetrue");
}
if (corpse)
{
// Corpses should include decay components and un-inactivate them
if (out.GetChild("Entity").GetChild("Decay").IsOk())
CParamNode::LoadXMLString(out, "");
// Corpses shouldn't display silhouettes (especially since they're often half underground)
if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
CParamNode::LoadXMLString(out, "false");
}
}
void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
{
// TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
// extensible scriptable way to define these subsets
std::set permittedComponentTypes;
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
permittedComponentTypes.insert("Identity");
permittedComponentTypes.insert("BuildRestrictions");
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Selectable");
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Armour");
permittedComponentTypes.insert("Health");
permittedComponentTypes.insert("StatusBars");
permittedComponentTypes.insert("OverlayRenderer");
permittedComponentTypes.insert("Decay");
permittedComponentTypes.insert("Cost");
permittedComponentTypes.insert("Sound");
permittedComponentTypes.insert("Vision");
permittedComponentTypes.insert("AIProxy");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Switch the actor to foundation mode
CParamNode::LoadXMLString(out, "");
// Add the Foundation component, to deal with the construction process
CParamNode::LoadXMLString(out, "");
// Initialise health to 1
CParamNode::LoadXMLString(out, "1");
// Foundations shouldn't initially block unit movement
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "truetrue");
// Don't provide population bonuses yet (but still do take up population cost)
if (out.GetChild("Entity").GetChild("Cost").IsOk())
CParamNode::LoadXMLString(out, "0");
// Foundations should be visible themselves in fog-of-war if their base template is,
// but shouldn't have any vision range
if (out.GetChild("Entity").GetChild("Vision").IsOk())
CParamNode::LoadXMLString(out, "0");
}
Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp (revision 9970)
@@ -1,101 +1,106 @@
/* Copyright (C) 2011 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 "ICmpUnitMotion.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(UnitMotion)
DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
DEFINE_INTERFACE_METHOD_1("SetSpeed", void, ICmpUnitMotion, SetSpeed, fixed)
DEFINE_INTERFACE_METHOD_0("GetWalkSpeed", fixed, ICmpUnitMotion, GetWalkSpeed)
DEFINE_INTERFACE_METHOD_0("GetRunSpeed", fixed, ICmpUnitMotion, GetRunSpeed)
DEFINE_INTERFACE_METHOD_1("SetUnitRadius", void, ICmpUnitMotion, SetUnitRadius, fixed)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool)
END_INTERFACE_WRAPPER(UnitMotion)
class CCmpUnitMotionScripted : public ICmpUnitMotion
{
public:
DEFAULT_SCRIPT_WRAPPER(UnitMotionScripted)
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange)
{
return m_Script.Call("MoveToPointRange", x, z, minRange, maxRange);
}
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
{
return m_Script.Call("IsInTargetRange", target, minRange, maxRange);
}
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
{
return m_Script.Call("MoveToTargetRange", target, minRange, maxRange);
}
virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
{
m_Script.CallVoid("MoveToFormationOffset", target, x, z);
}
virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
{
m_Script.CallVoid("FaceTowardsPoint", x, z);
}
virtual void StopMoving()
{
m_Script.CallVoid("StopMoving");
}
virtual void SetSpeed(fixed speed)
{
m_Script.CallVoid("SetSpeed", speed);
}
virtual fixed GetWalkSpeed()
{
return m_Script.Call("GetWalkSpeed");
}
virtual fixed GetRunSpeed()
{
return m_Script.Call("GetRunSpeed");
}
+ virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
+ {
+ return m_Script.Call("GetPassabilityClass");
+ }
+
virtual void SetUnitRadius(fixed radius)
{
m_Script.CallVoid("SetUnitRadius", radius);
}
virtual void SetDebugOverlay(bool enabled)
{
m_Script.CallVoid("SetDebugOverlay", enabled);
}
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(UnitMotionScripted)
Index: ps/trunk/source/simulation2/components/CCmpPathfinder.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpPathfinder.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpPathfinder.cpp (revision 9970)
@@ -1,552 +1,723 @@
/* Copyright (C) 2011 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 .
*/
/**
* @file
* Common code and setup code for CCmpPathfinder.
*/
#include "precompiled.h"
#include "CCmpPathfinder_Common.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/serialization/SerializeTemplates.h"
// Default cost to move a single tile is a fairly arbitrary number, which should be big
// enough to be precise when multiplied/divided and small enough to never overflow when
// summing the cost of a whole path.
const int DEFAULT_MOVE_COST = 256;
REGISTER_COMPONENT_TYPE(Pathfinder)
void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode))
{
m_MapSize = 0;
m_Grid = NULL;
m_ObstructionGrid = NULL;
m_TerrainDirty = true;
m_NextAsyncTicket = 1;
m_DebugOverlay = NULL;
m_DebugGrid = NULL;
m_DebugPath = NULL;
m_SameTurnMovesCount = 0;
// Since this is used as a system component (not loaded from an entity template),
// we can't use the real paramNode (it won't get handled properly when deserializing),
// so load the data from a special XML file.
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml");
// Previously all move commands during a turn were
// queued up and processed asynchronously at the start
// of the next turn. Now we are processing queued up
// events several times duing the turn. This improves
// responsiveness and units move more smoothly especially.
// when in formation. There is still a call at the
// beginning of a turn to process all outstanding moves -
// this will handle any moves above the MaxSameTurnMoves
// threshold.
//
// TODO - The moves processed at the beginning of the
// turn do not count against the maximum moves per turn
// currently. The thinking is that this will eventually
// happen in another thread. Either way this probably
// will require some adjustment and rethinking.
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder");
m_MaxSameTurnMoves = pathingSettings.GetChild("MaxSameTurnMoves").ToInt();
const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren();
for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it)
{
std::string name = it->first;
ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS);
pass_class_t mask = (pass_class_t)(1u << (m_PassClasses.size() + 2));
m_PassClasses.push_back(PathfinderPassability(mask, it->second));
m_PassClassMasks[name] = mask;
}
const CParamNode::ChildrenMap& moveClasses = externalParamNode.GetChild("Pathfinder").GetChild("MovementClasses").GetChildren();
// First find the set of unit classes used by any terrain classes,
// and assign unique tags to terrain classes
std::set unitClassNames;
unitClassNames.insert("default"); // must always have costs for default
{
size_t i = 0;
for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
{
std::string terrainClassName = it->first;
m_TerrainCostClassTags[terrainClassName] = (cost_class_t)i;
++i;
const CParamNode::ChildrenMap& unitClasses = it->second.GetChild("UnitClasses").GetChildren();
for (CParamNode::ChildrenMap::const_iterator uit = unitClasses.begin(); uit != unitClasses.end(); ++uit)
unitClassNames.insert(uit->first);
}
}
// For each terrain class, set the costs for every unit class,
// and assign unique tags to unit classes
{
size_t i = 0;
for (std::set::const_iterator nit = unitClassNames.begin(); nit != unitClassNames.end(); ++nit)
{
m_UnitCostClassTags[*nit] = (cost_class_t)i;
++i;
std::vector costs;
std::vector speeds;
for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
{
// Default to the general costs for this terrain class
fixed cost = it->second.GetChild("@Cost").ToFixed();
fixed speed = it->second.GetChild("@Speed").ToFixed();
// Check for specific cost overrides for this unit class
const CParamNode& unitClass = it->second.GetChild("UnitClasses").GetChild(nit->c_str());
if (unitClass.IsOk())
{
cost = unitClass.GetChild("@Cost").ToFixed();
speed = unitClass.GetChild("@Speed").ToFixed();
}
costs.push_back((cost * DEFAULT_MOVE_COST).ToInt_RoundToZero());
speeds.push_back(speed);
}
m_MoveCosts.push_back(costs);
m_MoveSpeeds.push_back(speeds);
}
}
}
void CCmpPathfinder::Deinit()
{
SetDebugOverlay(false); // cleans up memory
ResetDebugPath();
delete m_Grid;
delete m_ObstructionGrid;
}
struct SerializeLongRequest
{
template
void operator()(S& serialize, const char* UNUSED(name), AsyncLongPathRequest& value)
{
serialize.NumberU32_Unbounded("ticket", value.ticket);
serialize.NumberFixed_Unbounded("x0", value.x0);
serialize.NumberFixed_Unbounded("z0", value.z0);
SerializeGoal()(serialize, "goal", value.goal);
serialize.NumberU16_Unbounded("pass class", value.passClass);
serialize.NumberU8_Unbounded("cost class", value.costClass);
serialize.NumberU32_Unbounded("notify", value.notify);
}
};
struct SerializeShortRequest
{
template
void operator()(S& serialize, const char* UNUSED(name), AsyncShortPathRequest& value)
{
serialize.NumberU32_Unbounded("ticket", value.ticket);
serialize.NumberFixed_Unbounded("x0", value.x0);
serialize.NumberFixed_Unbounded("z0", value.z0);
serialize.NumberFixed_Unbounded("r", value.r);
serialize.NumberFixed_Unbounded("range", value.range);
SerializeGoal()(serialize, "goal", value.goal);
serialize.NumberU16_Unbounded("pass class", value.passClass);
serialize.Bool("avoid moving units", value.avoidMovingUnits);
serialize.NumberU32_Unbounded("group", value.group);
serialize.NumberU32_Unbounded("notify", value.notify);
}
};
void CCmpPathfinder::Serialize(ISerializer& serialize)
{
SerializeVector()(serialize, "long requests", m_AsyncLongPathRequests);
SerializeVector()(serialize, "short requests", m_AsyncShortPathRequests);
serialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket);
serialize.NumberU16_Unbounded("same turn moves count", m_SameTurnMovesCount);
}
void CCmpPathfinder::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
SerializeVector()(deserialize, "long requests", m_AsyncLongPathRequests);
SerializeVector()(deserialize, "short requests", m_AsyncShortPathRequests);
deserialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket);
deserialize.NumberU16_Unbounded("same turn moves count", m_SameTurnMovesCount);
}
void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
case MT_TerrainChanged:
{
// TODO: we ought to only bother updating the dirtied region
m_TerrainDirty = true;
break;
}
case MT_TurnStart:
{
m_SameTurnMovesCount = 0;
break;
}
}
}
void CCmpPathfinder::RenderSubmit(SceneCollector& collector)
{
for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
collector.Submit(&m_DebugOverlayShortPathLines[i]);
}
fixed CCmpPathfinder::GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass)
{
UpdateGrid();
u16 i, j;
NearestTile(x0, z0, i, j);
TerrainTile tileTag = m_Grid->get(i, j);
return m_MoveSpeeds.at(costClass).at(GET_COST_CLASS(tileTag));
}
ICmpPathfinder::pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name)
{
if (m_PassClassMasks.find(name) == m_PassClassMasks.end())
{
LOGERROR(L"Invalid passability class name '%hs'", name.c_str());
return 0;
}
return m_PassClassMasks[name];
}
std::map CCmpPathfinder::GetPassabilityClasses()
{
return m_PassClassMasks;
}
ICmpPathfinder::cost_class_t CCmpPathfinder::GetCostClass(const std::string& name)
{
if (m_UnitCostClassTags.find(name) == m_UnitCostClassTags.end())
{
LOGERROR(L"Invalid unit cost class name '%hs'", name.c_str());
return m_UnitCostClassTags["default"];
}
return m_UnitCostClassTags[name];
}
fixed CCmpPathfinder::DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)
{
switch (goal.type)
{
case CCmpPathfinder::Goal::POINT:
return (pos - CFixedVector2D(goal.x, goal.z)).Length();
case CCmpPathfinder::Goal::CIRCLE:
return ((pos - CFixedVector2D(goal.x, goal.z)).Length() - goal.hw).Absolute();
case CCmpPathfinder::Goal::SQUARE:
{
CFixedVector2D halfSize(goal.hw, goal.hh);
CFixedVector2D d(pos.X - goal.x, pos.Y - goal.z);
return Geometry::DistanceToSquare(d, goal.u, goal.v, halfSize);
}
default:
debug_warn(L"invalid type");
return fixed::Zero();
}
}
const Grid& CCmpPathfinder::GetPassabilityGrid()
{
UpdateGrid();
return *m_Grid;
}
void CCmpPathfinder::UpdateGrid()
{
// If the terrain was resized then delete the old grid data
if (m_Grid && m_MapSize != GetSimContext().GetTerrain().GetTilesPerSide())
{
SAFE_DELETE(m_Grid);
SAFE_DELETE(m_ObstructionGrid);
m_TerrainDirty = true;
}
// Initialise the terrain data when first needed
if (!m_Grid)
{
// TOOD: these bits should come from ICmpTerrain
ssize_t size = GetSimContext().GetTerrain().GetTilesPerSide();
ENSURE(size >= 1 && size <= 0xffff); // must fit in 16 bits
m_MapSize = size;
m_Grid = new Grid(m_MapSize, m_MapSize);
m_ObstructionGrid = new Grid(m_MapSize, m_MapSize);
}
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid);
if (obstructionsDirty && !m_TerrainDirty)
{
PROFILE("UpdateGrid obstructions");
// Obstructions changed - we need to recompute passability
// Since terrain hasn't changed we only need to update the obstruction bits
// and can skip the rest of the data
// TODO: if ObstructionManager::SetPassabilityCircular was called at runtime
// (which should probably never happen, but that's not guaranteed),
// then TILE_OUTOFBOUNDS will change and we can't use this fast path, but
// currently it'll just set obstructionsDirty and we won't notice
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
TerrainTile& t = m_Grid->get(i, j);
u8 obstruct = m_ObstructionGrid->get(i, j);
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
t |= 1;
else
t &= ~1;
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
t |= 2;
else
t &= ~2;
}
}
++m_Grid->m_DirtyID;
}
else if (obstructionsDirty || m_TerrainDirty)
{
PROFILE("UpdateGrid full");
// Obstructions or terrain changed - we need to recompute passability
// TODO: only bother recomputing the region that has actually changed
CmpPtr cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
+ // TOOD: these bits should come from ICmpTerrain
CTerrain& terrain = GetSimContext().GetTerrain();
+ // avoid integer overflow in intermediate calculation
+ const u16 shoreMax = 32767;
+
+ // First pass - find underwater tiles
+ Grid waterGrid(m_MapSize, m_MapSize);
+ for (u16 j = 0; j < m_MapSize; ++j)
+ {
+ for (u16 i = 0; i < m_MapSize; ++i)
+ {
+ fixed x, z;
+ TileCenter(i, j, x, z);
+
+ bool underWater = !cmpWaterMan.null() && (cmpWaterMan->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
+ waterGrid.set(i, j, underWater);
+ }
+ }
+ // Second pass - find shore tiles
+ Grid shoreGrid(m_MapSize, m_MapSize);
+ for (u16 j = 0; j < m_MapSize; ++j)
+ {
+ for (u16 i = 0; i < m_MapSize; ++i)
+ {
+ // Find a land tile
+ if (!waterGrid.get(i, j) && (
+ (i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) ||
+ (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) ||
+ (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
+ ))
+ {
+ shoreGrid.set(i, j, 0);
+ }
+ else
+ {
+ shoreGrid.set(i, j, shoreMax);
+ }
+ }
+ }
+
+ // Expand influences to find shore distance
+ for (size_t y = 0; y < m_MapSize; ++y)
+ {
+ u16 min = shoreMax;
+ for (size_t x = 0; x < m_MapSize; ++x)
+ {
+ u16 g = shoreGrid.get(x, y);
+ if (g > min)
+ shoreGrid.set(x, y, min);
+ else if (g < min)
+ min = g;
+
+ ++min;
+ }
+ for (size_t x = m_MapSize; x > 0; --x)
+ {
+ u16 g = shoreGrid.get(x-1, y);
+ if (g > min)
+ shoreGrid.set(x-1, y, min);
+ else if (g < min)
+ min = g;
+
+ ++min;
+ }
+ }
+ for (size_t x = 0; x < m_MapSize; ++x)
+ {
+ u16 min = shoreMax;
+ for (size_t y = 0; y < m_MapSize; ++y)
+ {
+ u16 g = shoreGrid.get(x, y);
+ if (g > min)
+ shoreGrid.set(x, y, min);
+ else if (g < min)
+ min = g;
+
+ ++min;
+ }
+ for (size_t y = m_MapSize; y > 0; --y)
+ {
+ u16 g = shoreGrid.get(x, y-1);
+ if (g > min)
+ shoreGrid.set(x, y-1, min);
+ else if (g < min)
+ min = g;
+
+ ++min;
+ }
+ }
+
+ // Apply passability classes to terrain
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
fixed x, z;
TileCenter(i, j, x, z);
TerrainTile t = 0;
u8 obstruct = m_ObstructionGrid->get(i, j);
- fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre
+ fixed height = terrain.GetExactGroundLevelFixed(x, z);
fixed water;
if (!cmpWaterMan.null())
water = cmpWaterMan->GetWaterLevel(x, z);
fixed depth = water - height;
fixed slope = terrain.GetSlopeFixed(i, j);
+ fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
+
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
t |= 1;
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
t |= 2;
if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS)
{
// If out of bounds, nobody is allowed to pass
for (size_t n = 0; n < m_PassClasses.size(); ++n)
t |= m_PassClasses[n].m_Mask;
}
else
{
for (size_t n = 0; n < m_PassClasses.size(); ++n)
{
- if (!m_PassClasses[n].IsPassable(depth, slope))
+ if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
t |= m_PassClasses[n].m_Mask;
}
}
std::string moveClass = terrain.GetMovementClass(i, j);
if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end())
t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]);
m_Grid->set(i, j, t);
}
}
m_TerrainDirty = false;
++m_Grid->m_DirtyID;
}
}
//////////////////////////////////////////////////////////
// Async path requests:
u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify)
{
AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, costClass, notify };
m_AsyncLongPathRequests.push_back(req);
return req.ticket;
}
u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify)
{
AsyncShortPathRequest req = { m_NextAsyncTicket++, x0, z0, r, range, goal, passClass, avoidMovingUnits, group, notify };
m_AsyncShortPathRequests.push_back(req);
return req.ticket;
}
void CCmpPathfinder::FinishAsyncRequests()
{
// Save the request queue in case it gets modified while iterating
std::vector longRequests;
m_AsyncLongPathRequests.swap(longRequests);
std::vector shortRequests;
m_AsyncShortPathRequests.swap(shortRequests);
// TODO: we should only compute one path per entity per turn
// TODO: this computation should be done incrementally, spread
// across multiple frames (or even multiple turns)
ProcessLongRequests(longRequests);
ProcessShortRequests(shortRequests);
}
void CCmpPathfinder::ProcessLongRequests(const std::vector& longRequests)
{
for (size_t i = 0; i < longRequests.size(); ++i)
{
const AsyncLongPathRequest& req = longRequests[i];
Path path;
ComputePath(req.x0, req.z0, req.goal, req.passClass, req.costClass, path);
CMessagePathResult msg(req.ticket, path);
GetSimContext().GetComponentManager().PostMessage(req.notify, msg);
}
}
void CCmpPathfinder::ProcessShortRequests(const std::vector& shortRequests)
{
for (size_t i = 0; i < shortRequests.size(); ++i)
{
const AsyncShortPathRequest& req = shortRequests[i];
Path path;
ControlGroupMovementObstructionFilter filter(req.avoidMovingUnits, req.group);
ComputeShortPath(filter, req.x0, req.z0, req.r, req.range, req.goal, req.passClass, path);
CMessagePathResult msg(req.ticket, path);
GetSimContext().GetComponentManager().PostMessage(req.notify, msg);
}
}
void CCmpPathfinder::ProcessSameTurnMoves()
{
u32 moveCount;
if (!m_AsyncLongPathRequests.empty())
{
// Figure out how many moves we can do this time
moveCount = m_MaxSameTurnMoves - m_SameTurnMovesCount;
if (moveCount <= 0)
return;
// Copy the long request elements we are going to process into a new array
std::vector longRequests;
if (m_AsyncLongPathRequests.size() <= moveCount)
{
m_AsyncLongPathRequests.swap(longRequests);
moveCount = longRequests.size();
}
else
{
longRequests.resize(moveCount);
copy(m_AsyncLongPathRequests.begin(), m_AsyncLongPathRequests.begin() + moveCount, longRequests.begin());
m_AsyncLongPathRequests.erase(m_AsyncLongPathRequests.begin(), m_AsyncLongPathRequests.begin() + moveCount);
}
ProcessLongRequests(longRequests);
m_SameTurnMovesCount += moveCount;
}
if (!m_AsyncShortPathRequests.empty())
{
// Figure out how many moves we can do now
moveCount = m_MaxSameTurnMoves - m_SameTurnMovesCount;
if (moveCount <= 0)
return;
// Copy the short request elements we are going to process into a new array
std::vector shortRequests;
if (m_AsyncShortPathRequests.size() <= moveCount)
{
m_AsyncShortPathRequests.swap(shortRequests);
moveCount = shortRequests.size();
}
else
{
shortRequests.resize(moveCount);
copy(m_AsyncShortPathRequests.begin(), m_AsyncShortPathRequests.begin() + moveCount, shortRequests.begin());
m_AsyncShortPathRequests.erase(m_AsyncShortPathRequests.begin(), m_AsyncShortPathRequests.begin() + moveCount);
}
ProcessShortRequests(shortRequests);
m_SameTurnMovesCount += moveCount;
}
}
+bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
+ entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass)
+{
+ // Check unit obstruction
+ CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpObstructionManager.null())
+ return false;
+
+ if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
+ return false;
+
+ // Test against terrain:
+
+ UpdateGrid();
+
+ u16 i0, j0, i1, j1;
+ NearestTile(x - r, z - r, i0, j0);
+ NearestTile(x + r, z + r, i1, j1);
+ for (u16 j = j0; j <= j1; ++j)
+ {
+ for (u16 i = i0; i <= i1; ++i)
+ {
+ if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter,
+ entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w,
+ entity_pos_t h, entity_id_t id, pass_class_t passClass)
+{
+ // Check unit obstruction
+ CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpObstructionManager.null())
+ return false;
+
+ if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL))
+ return false;
+
+ // Test against terrain:
+
+ UpdateGrid();
+
+ CmpPtr cmpObstruction(GetSimContext(), id);
+ if (cmpObstruction.null())
+ return false;
+
+ ICmpObstructionManager::ObstructionSquare square;
+ if (!cmpObstruction->GetObstructionSquare(square))
+ return false;
+
+ CFixedVector2D halfSize(square.hw, square.hh);
+ halfSize = halfSize * 1.41421f;
+ 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);
+ NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1);
+ 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)
+ && !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
Index: ps/trunk/source/simulation2/components/ICmpTerritoryManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 9969)
+++ ps/trunk/source/simulation2/components/ICmpTerritoryManager.h (revision 9970)
@@ -1,35 +1,42 @@
/* Copyright (C) 2011 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/system/Interface.h"
+#include "simulation2/components/ICmpPosition.h"
class ICmpTerritoryManager : public IComponent
{
public:
virtual bool NeedUpdate(size_t* dirtyID) = 0;
virtual const Grid& GetTerritoryGrid() = 0;
+ /**
+ * Get owner of territory at given position
+ *
+ * @return player ID of owner; 0 if neutral territory
+ */
+ virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
+
DECLARE_INTERFACE_TYPE(TerritoryManager)
};
#endif // INCLUDED_ICMPTERRITORYMANAGER
Index: ps/trunk/source/simulation2/components/CCmpObstruction.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpObstruction.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/CCmpObstruction.cpp (revision 9970)
@@ -1,409 +1,412 @@
/* Copyright (C) 2011 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 "ICmpObstruction.h"
-#include "ICmpObstructionManager.h"
-#include "ICmpPosition.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpPosition.h"
#include "simulation2/MessageTypes.h"
/**
* Obstruction implementation. This keeps the ICmpPathfinder's model of the world updated when the
* entities move and die, with shapes derived from ICmpFootprint.
*/
class CCmpObstruction : public ICmpObstruction
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_PositionChanged);
componentManager.SubscribeToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(Obstruction)
// Template state:
enum {
STATIC,
UNIT
} m_Type;
entity_pos_t m_Size0; // radius or width
entity_pos_t m_Size1; // radius or depth
u8 m_TemplateFlags;
// Dynamic state:
bool m_Active; // whether the obstruction is obstructing or just an inactive placeholder
bool m_Moving;
entity_id_t m_ControlGroup;
ICmpObstructionManager::tag_t m_Tag;
u8 m_Flags;
static std::string GetSchema()
{
return
""
"Causes this entity to obstruct the motion of other units."
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
if (paramNode.GetChild("Unit").IsOk())
{
m_Type = UNIT;
m_Size0 = m_Size1 = paramNode.GetChild("Unit").GetChild("@radius").ToFixed();
}
else
{
m_Type = STATIC;
m_Size0 = paramNode.GetChild("Static").GetChild("@width").ToFixed();
m_Size1 = paramNode.GetChild("Static").GetChild("@depth").ToFixed();
}
m_TemplateFlags = 0;
if (paramNode.GetChild("BlockMovement").ToBool())
m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
if (paramNode.GetChild("BlockPathfinding").ToBool())
m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
if (paramNode.GetChild("BlockFoundation").ToBool())
m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_FOUNDATION;
if (paramNode.GetChild("BlockConstruction").ToBool())
m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION;
m_Flags = m_TemplateFlags;
if (paramNode.GetChild("DisableBlockMovement").ToBool())
m_Flags &= ~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
if (paramNode.GetChild("DisableBlockPathfinding").ToBool())
m_Flags &= ~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
m_Active = paramNode.GetChild("Active").ToBool();
m_Tag = ICmpObstructionManager::tag_t();
m_Moving = false;
m_ControlGroup = GetEntityId();
}
virtual void Deinit()
{
}
template
void SerializeCommon(S& serialize)
{
serialize.Bool("active", m_Active);
serialize.Bool("moving", m_Moving);
serialize.NumberU32_Unbounded("control group", m_ControlGroup);
serialize.NumberU32_Unbounded("tag", m_Tag.n);
serialize.NumberU8_Unbounded("flags", m_Flags);
}
virtual void Serialize(ISerializer& serialize)
{
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
SerializeCommon(deserialize);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_PositionChanged:
{
if (!m_Active)
break;
const CMessagePositionChanged& data = static_cast (msg);
if (!data.inWorld && !m_Tag.valid())
break; // nothing needs to change
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
break; // error
if (data.inWorld && m_Tag.valid())
{
cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a);
}
else if (data.inWorld && !m_Tag.valid())
{
// Need to create a new pathfinder shape:
if (m_Type == STATIC)
m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
data.x, data.z, data.a, m_Size0, m_Size1, m_Flags);
else
m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
data.x, data.z, m_Size0, m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0), m_ControlGroup);
}
else if (!data.inWorld && m_Tag.valid())
{
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = ICmpObstructionManager::tag_t();
}
break;
}
case MT_Destroy:
{
if (m_Tag.valid())
{
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
break; // error
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = ICmpObstructionManager::tag_t();
}
break;
}
}
}
virtual void SetActive(bool active)
{
if (active && !m_Active)
{
m_Active = true;
// Construct the obstruction shape
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return; // error
CmpPtr cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null())
return; // error
if (!cmpPosition->IsInWorld())
return; // don't need an obstruction
CFixedVector2D pos = cmpPosition->GetPosition2D();
if (m_Type == STATIC)
m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags);
else
m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
pos.X, pos.Y, m_Size0, m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0), m_ControlGroup);
}
else if (!active && m_Active)
{
m_Active = false;
// Delete the obstruction shape
if (m_Tag.valid())
{
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return; // error
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = ICmpObstructionManager::tag_t();
}
}
// else we didn't change the active status
}
virtual void SetDisableBlockMovementPathfinding(bool disabled)
{
if (disabled)
{
// Remove the blocking flags
m_Flags &= ~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
m_Flags &= ~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
}
else
{
// Add the blocking flags if the template had enabled them
m_Flags |= (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
m_Flags |= (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
}
// Reset the shape with the new flags (kind of inefficiently - we
// should have a ICmpObstructionManager::SetFlags function or something)
if (m_Active)
{
SetActive(false);
SetActive(true);
}
}
virtual bool GetBlockMovementFlag()
{
return (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT) != 0;
}
virtual ICmpObstructionManager::tag_t GetObstruction()
{
return m_Tag;
}
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out)
{
CmpPtr cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null())
return false; // error
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return false; // error
if (!cmpPosition->IsInWorld())
return false; // no obstruction square
CFixedVector2D pos = cmpPosition->GetPosition2D();
if (m_Type == STATIC)
out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
else
out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0);
return true;
}
virtual entity_pos_t GetUnitRadius()
{
if (m_Type == UNIT)
return m_Size0;
else
return entity_pos_t::Zero();
}
- virtual bool CheckFoundationCollisions()
+ virtual bool CheckFoundation(std::string className)
{
CmpPtr cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null())
return false; // error
if (!cmpPosition->IsInWorld())
return false; // no obstruction
CFixedVector2D pos = cmpPosition->GetPosition2D();
- CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
- if (cmpObstructionManager.null())
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+ if (cmpPathfinder.null())
return false; // error
+ // Get passability class
+ ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
+
// Ignore collisions with self, or with non-foundation-blocking obstructions
SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
if (m_Type == STATIC)
- return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL);
+ return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
else
- return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL);
+ return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
}
virtual std::vector GetConstructionCollisions()
{
std::vector ret;
CmpPtr cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null())
return ret; // error
if (!cmpPosition->IsInWorld())
return ret; // no obstruction
CFixedVector2D pos = cmpPosition->GetPosition2D();
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return ret; // error
// Ignore collisions with self, or with non-construction-blocking obstructions
SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
if (m_Type == STATIC)
cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
else
cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret);
return ret;
}
virtual void SetMovingFlag(bool enabled)
{
m_Moving = enabled;
if (m_Tag.valid() && m_Type == UNIT)
{
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpObstructionManager.null())
cmpObstructionManager->SetUnitMovingFlag(m_Tag, m_Moving);
}
}
virtual void SetControlGroup(entity_id_t group)
{
m_ControlGroup = group;
if (m_Tag.valid() && m_Type == UNIT)
{
CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpObstructionManager.null())
cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup);
}
}
};
REGISTER_COMPONENT_TYPE(Obstruction)
Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpUnitMotion.h (revision 9969)
+++ ps/trunk/source/simulation2/components/ICmpUnitMotion.h (revision 9970)
@@ -1,110 +1,116 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 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_ICMPUNITMOTION
#define INCLUDED_ICMPUNITMOTION
#include "simulation2/system/Interface.h"
-#include "ICmpPosition.h" // for entity_pos_t
+#include "simulation2/components/ICmpPathfinder.h" // for pass_class_t
+#include "simulation2/components/ICmpPosition.h" // for entity_pos_t
/**
* Motion interface for entities with complex movement capabilities.
* (Simpler motion is handled by ICmpMotion instead.)
*
* Currently this is limited to telling the entity to walk to a point.
* Eventually it should support different movement speeds, moving to areas
* instead of points, moving as part of a group, moving as part of a formation,
* etc.
*/
class ICmpUnitMotion : public IComponent
{
public:
/**
* Attempt to walk into range of a to a given point, or as close as possible.
* If the unit is already in range, or cannot move anywhere at all, or if there is
* some other error, then returns false.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
* If maxRange is negative, then the maximum range is treated as infinity.
*/
virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* Determine whether the target is within the given range, using the same measurement
* as MoveToTargetRange.
*/
virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* Attempt to walk into range of a given target entity, or as close as possible.
* If the unit is already in range, or cannot move anywhere at all, or if there is
* some other error, then returns false.
* Otherwise, returns true and sends a MotionChanged message after starting to move,
* and sends another MotionChanged after finishing moving.
* If maxRange is negative, then the maximum range is treated as infinity.
*/
virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
/**
* Join a formation, and move towards a given offset relative to the formation controller entity.
* Continues following the formation until given a different command.
*/
virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) = 0;
/**
* Turn to look towards the given point.
*/
virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0;
/**
* Stop moving immediately.
*/
virtual void StopMoving() = 0;
/**
* Set the current movement speed.
*/
virtual void SetSpeed(fixed speed) = 0;
/**
* Get the default speed that this unit will have when walking, in metres per second.
*/
virtual fixed GetWalkSpeed() = 0;
/**
* Get the default speed that this unit will have when running, in metres per second.
*/
virtual fixed GetRunSpeed() = 0;
/**
+ * Get the unit's passability class.
+ */
+ virtual ICmpPathfinder::pass_class_t GetPassabilityClass() = 0;
+
+ /**
* Override the default obstruction radius, used for planning paths and checking for collisions.
* Bad things may happen if this entity has an active Obstruction component with a larger
* radius. (This is intended primarily for formation controllers.)
*/
virtual void SetUnitRadius(fixed radius) = 0;
/**
* Toggle the rendering of debug info.
*/
virtual void SetDebugOverlay(bool enabled) = 0;
DECLARE_INTERFACE_TYPE(UnitMotion)
};
#endif // INCLUDED_ICMPUNITMOTION
Index: ps/trunk/source/simulation2/components/ICmpObstruction.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpObstruction.cpp (revision 9969)
+++ ps/trunk/source/simulation2/components/ICmpObstruction.cpp (revision 9970)
@@ -1,32 +1,32 @@
/* Copyright (C) 2011 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 "ICmpObstruction.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Obstruction)
DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
-DEFINE_INTERFACE_METHOD_0("CheckFoundationCollisions", bool, ICmpObstruction, CheckFoundationCollisions)
+DEFINE_INTERFACE_METHOD_1("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string)
DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector, ICmpObstruction, GetConstructionCollisions)
DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)
DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag)
DEFINE_INTERFACE_METHOD_1("SetControlGroup", void, ICmpObstruction, SetControlGroup, entity_id_t)
END_INTERFACE_WRAPPER(Obstruction)
Index: ps/trunk/source/simulation2/components/ICmpPathfinder.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 9969)
+++ ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 9970)
@@ -1,170 +1,184 @@
/* Copyright (C) 2011 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_ICMPPATHFINDER
#define INCLUDED_ICMPPATHFINDER
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Position.h"
#include "maths/FixedVector2D.h"
#include