Index: ps/trunk/source/simulation2/components/ICmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpRangeManager.cpp (revision 8233)
+++ ps/trunk/source/simulation2/components/ICmpRangeManager.cpp (revision 8234)
@@ -1,33 +1,34 @@
/* Copyright (C) 2010 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 "ICmpRangeManager.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(RangeManager)
DEFINE_INTERFACE_METHOD_4("ExecuteQuery", std::vector, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, std::vector, int)
DEFINE_INTERFACE_METHOD_4("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, std::vector, int)
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
DEFINE_INTERFACE_METHOD_1("ResetActiveQuery", std::vector, ICmpRangeManager, ResetActiveQuery, ICmpRangeManager::tag_t)
+DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector, ICmpRangeManager, GetEntitiesByPlayer, int)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
DEFINE_INTERFACE_METHOD_1("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, bool)
END_INTERFACE_WRAPPER(RangeManager)
Index: ps/trunk/source/simulation2/components/ICmpRangeManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpRangeManager.h (revision 8233)
+++ ps/trunk/source/simulation2/components/ICmpRangeManager.h (revision 8234)
@@ -1,236 +1,244 @@
/* Copyright (C) 2010 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_ICMPRANGEMANAGER
#define INCLUDED_ICMPRANGEMANAGER
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Position.h"
#include "graphics/Terrain.h" // for CELL_SIZE
/**
* Provides efficient range-based queries of the game world,
* and also LOS-based effects (fog of war).
*
* (These are somewhat distinct concepts but they share a lot of the implementation,
* so for efficiency they're combined into this class.)
*
* Possible use cases:
* - combat units need to detect targetable enemies entering LOS, so they can choose
* to auto-attack.
* - auras let a unit have some effect on all units (or those of the same player, or of enemies)
* within a certain range.
* - capturable animals need to detect when a player-owned unit is nearby and no units of other
* players are in range.
* - scenario triggers may want to detect when units enter a given area.
* - units gathering from a resource that is exhausted need to find a new resource of the
* same type, near the old one and reachable.
* - projectile weapons with splash damage need to find all units within some distance
* of the target point.
* - ...
*
* In most cases the users are event-based and want notifications when something
* has entered or left the range, and the query can be set up once and rarely changed.
* These queries have to be fast. It's fine to approximate an entity as a point.
*
* Current design:
*
* This class handles just the most common parts of range queries:
* distance, target interface, and player ownership.
* The caller can then apply any more complex filtering that it needs.
*
* There are two types of query:
* Passive queries are performed by ExecuteQuery and immediately return the matching entities.
* Active queries are set up by CreateActiveQuery, and then a CMessageRangeUpdate message will be
* sent to the entity once per turn if anybody has entered or left the range since the last RangeUpdate.
* Queries can be disabled, in which case no message will be sent.
*/
class ICmpRangeManager : public IComponent
{
public:
/**
* External identifiers for active queries.
*/
typedef u32 tag_t;
/**
* Set the bounds of the world.
* Entities should not be outside the bounds (else efficiency will suffer).
* @param x0,z0,x1,z1 Coordinates of the corners of the world
* @param vertices Number of terrain vertices per side
*/
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices) = 0;
/**
* Execute a passive query.
* @param source the entity around which the range will be computed.
* @param maxRange maximum distance in metres (inclusive).
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector ExecuteQuery(entity_id_t source, entity_pos_t maxRange, std::vector owners, int requiredInterface) = 0;
/**
* Construct an active query. The query will be disabled by default.
* @param source the entity around which the range will be computed.
* @param maxRange maximum distance in metres (inclusive).
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t maxRange, std::vector owners, int requiredInterface) = 0;
/**
* Destroy a query and clean up resources. This must be called when an entity no longer needs its
* query (e.g. when the entity is destroyed).
* @param tag identifier of query.
*/
virtual void DestroyActiveQuery(tag_t tag) = 0;
/**
* Re-enable the processing of a query.
* @param tag identifier of query.
*/
virtual void EnableActiveQuery(tag_t tag) = 0;
/**
* Disable the processing of a query (no RangeUpdate messages will be sent).
* @param tag identifier of query.
*/
virtual void DisableActiveQuery(tag_t tag) = 0;
/**
* Immediately execute a query, and re-enable it if disabled.
* The next RangeUpdate message will say who has entered/left since this call,
* so you won't miss any notifications.
* @param tag identifier of query.
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector ResetActiveQuery(tag_t tag) = 0;
/**
+ * Returns list of all entities for specific player.
+ * (This is on this interface because it shares a lot of the implementation.
+ * Maybe it should be extended to be more like ExecuteQuery without
+ * the range parameter.)
+ */
+ virtual std::vector GetEntitiesByPlayer(int playerId) = 0;
+
+ /**
* Toggle the rendering of debug info.
*/
virtual void SetDebugOverlay(bool enabled) = 0;
// LOS interface:
enum ELosState
{
LOS_UNEXPLORED = 0,
LOS_EXPLORED = 1,
LOS_VISIBLE = 2,
LOS_MASK = 3
};
enum ELosVisibility
{
VIS_HIDDEN,
VIS_FOGGED,
VIS_VISIBLE
};
/**
* Object providing efficient abstracted access to the LOS state.
* This depends on some implementation details of CCmpRangeManager.
*
* This *ignores* the GetLosRevealAll flag - callers should check that explicitly.
*/
class CLosQuerier
{
private:
friend class CCmpRangeManager;
CLosQuerier(int player, const std::vector& data, ssize_t verticesPerSide) :
m_Data(data), m_VerticesPerSide(verticesPerSide)
{
if (player > 0 && player <= 16)
m_PlayerMask = LOS_MASK << (2*(player-1));
else
m_PlayerMask = 0;
}
const CLosQuerier& operator=(const CLosQuerier&); // not implemented
public:
/**
* Returns whether the given vertex is visible (i.e. is within a unit's LOS).
* i and j must be in the range [0, verticesPerSide).
*/
inline bool IsVisible(ssize_t i, ssize_t j)
{
#ifndef NDEBUG
debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
#endif
// Check high bit of each bit-pair
if ((m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0xAAAAAAAAu)
return true;
else
return false;
}
/**
* Returns whether the given vertex is explored (i.e. was (or still is) within a unit's LOS).
* i and j must be in the range [0, verticesPerSide).
*/
inline bool IsExplored(ssize_t i, ssize_t j)
{
#ifndef NDEBUG
debug_assert(i >= 0 && j >= 0 && i < m_VerticesPerSide && j < m_VerticesPerSide);
#endif
// Check low bit of each bit-pair
if ((m_Data.at(j*m_VerticesPerSide + i) & m_PlayerMask) & 0x55555555u)
return true;
else
return false;
}
private:
u32 m_PlayerMask;
const std::vector& m_Data;
ssize_t m_VerticesPerSide;
};
/**
* Returns a CLosQuerier for checking whether vertex positions are visible to the given player.
*/
virtual CLosQuerier GetLosQuerier(int player) = 0;
/**
* Returns the visibility status of the given entity, with respect to the given player.
* Returns VIS_HIDDEN if the entity doesn't exist or is not in the world.
* This respects the GetLosRevealAll flag.
*/
virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player) = 0;
/**
* Set globally whether the whole map should be made visible.
*/
virtual void SetLosRevealAll(bool enabled) = 0;
/**
* Returns whether the whole map has been made visible to the given player.
*/
virtual bool GetLosRevealAll(int player) = 0;
DECLARE_INTERFACE_TYPE(RangeManager)
};
#endif // INCLUDED_ICMPRANGEMANAGER
Index: ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 8233)
+++ ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 8234)
@@ -1,791 +1,807 @@
/* Copyright (C) 2010 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 "ICmpRangeManager.h"
#include "ICmpPosition.h"
#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Spatial.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "maths/FixedVector2D.h"
#include "ps/CLogger.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
/**
* Representation of a range query.
*/
struct Query
{
bool enabled;
entity_id_t source;
entity_pos_t maxRange;
u32 ownersMask;
int interface;
std::vector lastMatch;
};
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
* into a 32-bit mask for quick set-membership tests.
*/
static u32 CalcOwnerMask(i32 owner)
{
if (owner >= -1 && owner < 31)
return 1 << (1+owner);
else
return 0; // owner was invalid
}
/**
* Representation of an entity, with the data needed for queries.
*/
struct EntityData
{
EntityData() : retainInFog(0), owner(-1), inWorld(0) { }
entity_pos_t x, z;
entity_pos_t visionRange;
u8 retainInFog; // boolean
i8 owner;
u8 inWorld; // boolean
};
cassert(sizeof(EntityData) == 16);
/**
* Functor for sorting entities by distance from a source point.
* It must only be passed entities that are in 'entities'
* and are currently in the world.
*/
struct EntityDistanceOrdering
{
EntityDistanceOrdering(const std::map& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
{
}
bool operator()(entity_id_t a, entity_id_t b)
{
const EntityData& da = m_EntityData.find(a)->second;
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
}
const std::map& m_EntityData;
CFixedVector2D m_Source;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
};
/**
* Range manager implementation.
* Maintains a list of all entities (and their positions and owners), which is used for
* queries.
*
* LOS implementation is based on the model described in GPG2.
* (TODO: would be nice to make it cleverer, so e.g. mountains and walls
* can block vision)
*/
class CCmpRangeManager : public ICmpRangeManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_Create);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
}
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
bool m_DebugOverlayEnabled;
bool m_DebugOverlayDirty;
std::vector m_DebugOverlayLines;
SpatialSubdivision m_Subdivision;
// Range query state:
tag_t m_QueryNext; // next allocated id
std::map m_Queries;
std::map m_EntityData;
// LOS state:
bool m_LosRevealAll;
ssize_t m_TerrainVerticesPerSide;
// Counts of units seeing vertex, per vertex, per player (starting with player 0).
// Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
// of units in a very small area.
// (Note we use vertexes, not tiles, to better match the renderer.)
// Lazily constructed when it's needed, to save memory in smaller games.
std::vector > m_LosPlayerCounts;
// 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
std::vector m_LosState;
static const int MAX_LOS_PLAYER_ID = 16;
static std::string GetSchema()
{
return "";
}
virtual void Init(const CSimContext& UNUSED(context), const CParamNode& UNUSED(paramNode))
{
m_QueryNext = 1;
m_DebugOverlayEnabled = false;
m_DebugOverlayDirty = true;
// Initialise with bogus values (these will get replaced when
// SetBounds is called)
ResetSubdivisions(entity_pos_t::FromInt(1), entity_pos_t::FromInt(1));
m_LosRevealAll = false;
m_TerrainVerticesPerSide = 0;
}
virtual void Deinit(const CSimContext& UNUSED(context))
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// TODO
}
virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(context, paramNode);
}
virtual void HandleMessage(const CSimContext& UNUSED(context), const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Create:
{
const CMessageCreate& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
// Ignore local entities - we shouldn't let them influence anything
if (ENTITY_IS_LOCAL(ent))
break;
// Ignore non-positional entities
CmpPtr cmpPosition(GetSimContext(), ent);
if (cmpPosition.null())
break;
// The newly-created entity will have owner -1 and position out-of-world
// (any initialisation of those values will happen later), so we can just
// use the default-constructed EntityData here
EntityData entdata;
// Store the LOS data, if any
CmpPtr cmpVision(GetSimContext(), ent);
if (!cmpVision.null())
{
entdata.visionRange = cmpVision->GetRange();
entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0);
}
// Remember this entity
m_EntityData.insert(std::make_pair(ent, entdata));
break;
}
case MT_PositionChanged:
{
const CMessagePositionChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
std::map::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (msgData.inWorld)
{
if (it->second.inWorld)
{
CFixedVector2D from(it->second.x, it->second.z);
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Move(ent, from, to);
LosMove(it->second.owner, it->second.visionRange, from, to);
}
else
{
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Add(ent, to);
LosAdd(it->second.owner, it->second.visionRange, to);
}
it->second.inWorld = 1;
it->second.x = msgData.x;
it->second.z = msgData.z;
}
else
{
if (it->second.inWorld)
{
CFixedVector2D from(it->second.x, it->second.z);
m_Subdivision.Remove(ent, from);
LosRemove(it->second.owner, it->second.visionRange, from);
}
it->second.inWorld = 0;
it->second.x = entity_pos_t::Zero();
it->second.z = entity_pos_t::Zero();
}
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
std::map::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.inWorld)
{
CFixedVector2D pos(it->second.x, it->second.z);
LosRemove(it->second.owner, it->second.visionRange, pos);
LosAdd(msgData.to, it->second.visionRange, pos);
}
it->second.owner = msgData.to;
break;
}
case MT_Destroy:
{
const CMessageDestroy& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
std::map::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.inWorld)
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
m_EntityData.erase(it);
break;
}
case MT_Update:
{
m_DebugOverlayDirty = true;
ExecuteActiveQueries();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
{
debug_assert(x0.IsZero() && z0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(x1, z1);
m_TerrainVerticesPerSide = vertices;
m_LosPlayerCounts.clear();
m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
m_LosState.clear();
m_LosState.resize(vertices*vertices);
for (std::map::iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
}
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
{
// Use 8x8 tile subdivisions
// (TODO: find the optimal number instead of blindly guessing)
m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*CELL_SIZE));
for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (it->second.inWorld)
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z));
}
}
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t maxRange,
std::vector owners, int requiredInterface)
{
size_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, maxRange, owners, requiredInterface);
return (tag_t)id;
}
virtual void DestroyActiveQuery(tag_t tag)
{
if (m_Queries.find(tag) == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DestroyActiveQuery called with invalid tag %d", tag);
return;
}
m_Queries.erase(tag);
}
virtual void EnableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: EnableActiveQuery called with invalid tag %d", tag);
return;
}
Query& q = it->second;
q.enabled = true;
}
virtual void DisableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DisableActiveQuery called with invalid tag %d", tag);
return;
}
Query& q = it->second;
q.enabled = false;
}
virtual std::vector ExecuteQuery(entity_id_t source, entity_pos_t maxRange,
std::vector owners, int requiredInterface)
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, maxRange, owners, requiredInterface);
std::vector r;
CmpPtr cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
return r;
}
PerformQuery(q, r);
// Return the list sorted by distance from the entity
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector ResetActiveQuery(tag_t tag)
{
PROFILE("ResetActiveQuery");
std::vector r;
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: ResetActiveQuery called with invalid tag %d", tag);
return r;
}
Query& q = it->second;
q.enabled = true;
CmpPtr cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
q.lastMatch = r;
return r;
}
PerformQuery(q, r);
q.lastMatch = r;
// Return the list sorted by distance from the entity
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
+ virtual std::vector GetEntitiesByPlayer(int playerId)
+ {
+ std::vector entities;
+
+ u32 ownerMask = CalcOwnerMask(playerId);
+
+ for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
+ {
+ // Check owner and add to list if it matches
+ if (CalcOwnerMask(it->second.owner) & ownerMask)
+ entities.push_back(it->first);
+ }
+
+ return entities;
+ }
+
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
m_DebugOverlayDirty = true;
if (!enabled)
m_DebugOverlayLines.clear();
}
private:
/**
* Update all currently-enabled active queries.
*/
void ExecuteActiveQueries()
{
PROFILE("ExecuteActiveQueries");
// Store a queue of all messages before sending any, so we can assume
// no entities will move until we've finished checking all the ranges
std::vector > messages;
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
if (!q.enabled)
continue;
CmpPtr cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
continue;
std::vector r;
r.reserve(q.lastMatch.size());
PerformQuery(q, r);
// Compute the changes vs the last match
std::vector added;
std::vector removed;
std::set_difference(r.begin(), r.end(), q.lastMatch.begin(), q.lastMatch.end(), std::back_inserter(added));
std::set_difference(q.lastMatch.begin(), q.lastMatch.end(), r.begin(), r.end(), std::back_inserter(removed));
if (added.empty() && removed.empty())
continue;
// Return the 'added' list sorted by distance from the entity
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, pos));
messages.push_back(std::make_pair(q.source, CMessageRangeUpdate(it->first)));
messages.back().second.added.swap(added);
messages.back().second.removed.swap(removed);
it->second.lastMatch.swap(r);
}
for (size_t i = 0; i < messages.size(); ++i)
GetSimContext().GetComponentManager().PostMessage(messages[i].first, messages[i].second);
}
/**
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
*/
void PerformQuery(const Query& q, std::vector& r)
{
CmpPtr cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
return;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
// Get a quick list of entities that are potentially in range
std::vector ents = m_Subdivision.GetNear(pos, q.maxRange);
for (size_t i = 0; i < ents.size(); ++i)
{
std::map::const_iterator it = m_EntityData.find(ents[i]);
debug_assert(it != m_EntityData.end());
// Quick filter to ignore entities with the wrong owner
if (!(CalcOwnerMask(it->second.owner) & q.ownersMask))
continue;
// Restrict based on precise location
if (!it->second.inWorld)
continue;
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
if (distVsMax > 0)
continue;
// Ignore self
if (it->first == q.source)
continue;
// Ignore if it's missing the required interface
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(it->first, q.interface))
continue;
r.push_back(it->first);
}
}
Query ConstructQuery(entity_id_t source, entity_pos_t maxRange, std::vector owners, int requiredInterface)
{
Query q;
q.enabled = false;
q.source = source;
q.maxRange = maxRange;
q.ownersMask = 0;
for (size_t i = 0; i < owners.size(); ++i)
q.ownersMask |= CalcOwnerMask(owners[i]);
q.interface = requiredInterface;
return q;
}
void RenderSubmit(SceneCollector& collector)
{
if (!m_DebugOverlayEnabled)
return;
CColor enabledRingColour(0, 1, 0, 1);
CColor disabledRingColour(1, 0, 0, 1);
CColor rayColour(1, 1, 0, 0.2f);
if (m_DebugOverlayDirty)
{
m_DebugOverlayLines.clear();
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
CmpPtr cmpSourcePosition(GetSimContext(), q.source);
if (cmpSourcePosition.null() || !cmpSourcePosition->IsInWorld())
continue;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
// Draw the range circle
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToDouble(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
// Draw a ray from the source to each matched entity
for (size_t i = 0; i < q.lastMatch.size(); ++i)
{
CmpPtr cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld())
continue;
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
std::vector coords;
coords.push_back(pos.X.ToFloat());
coords.push_back(pos.Y.ToFloat());
coords.push_back(targetPos.X.ToFloat());
coords.push_back(targetPos.Y.ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = rayColour;
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
}
}
m_DebugOverlayDirty = false;
}
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
collector.Submit(&m_DebugOverlayLines[i]);
}
// LOS implementation:
virtual CLosQuerier GetLosQuerier(int player)
{
return CLosQuerier(player, m_LosState, m_TerrainVerticesPerSide);
}
virtual ELosVisibility GetLosVisibility(entity_id_t ent, int player)
{
// (We can't use m_EntityData since this needs to handle LOCAL entities too)
CmpPtr cmpPosition(GetSimContext(), ent);
if (cmpPosition.null() || !cmpPosition->IsInWorld())
return VIS_HIDDEN;
if (m_LosRevealAll)
return VIS_VISIBLE;
CFixedVector2D pos = cmpPosition->GetPosition2D();
CLosQuerier los(player, m_LosState, m_TerrainVerticesPerSide);
int i = (pos.X / (int)CELL_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)CELL_SIZE).ToInt_RoundToNearest();
if (los.IsVisible(i, j))
return VIS_VISIBLE;
if (los.IsExplored(i, j))
{
CmpPtr cmpVision(GetSimContext(), ent);
if (!cmpVision.null() && cmpVision->GetRetainInFog())
return VIS_FOGGED;
}
return VIS_HIDDEN;
}
virtual void SetLosRevealAll(bool enabled)
{
// Eventually we might want this to be a per-player flag (which is why
// GetLosRevealAll takes a player argument), but currently it's just a
// global setting since I can't quite work out where per-player would be useful
m_LosRevealAll = enabled;
}
virtual bool GetLosRevealAll(int UNUSED(player))
{
return m_LosRevealAll;
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
* amount is +1 or -1.
*/
inline void LosUpdateStripHelper(u8 owner, ssize_t i0, ssize_t i1, ssize_t j, int amount, std::vector& counts)
{
for (ssize_t i = i0; i <= i1; ++i)
{
ssize_t idx = j*m_TerrainVerticesPerSide + i;
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
if (counts[idx] == 0 && amount > 0)
{
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
counts[idx] += amount;
// Decreasing from non-zero to zero - move from visible+explored to explored
if (counts[idx] == 0 && amount < 0)
{
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
}
}
}
/**
* Update the LOS state of tiles within a given circular range.
* Assumes owner is in the valid range.
*/
inline void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos, int amount)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelper");
std::vector& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
// Compute the circular region as a series of strips.
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
// to get smoother behaviour as a unit moves rather than jumping a whole tile
// at once.
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
// (so that we never render the sharp edge of the map)
ssize_t j0 = ((pos.Y - visionRange)/(int)CELL_SIZE).ToInt_RoundToInfinity();
ssize_t j1 = ((pos.Y + visionRange)/(int)CELL_SIZE).ToInt_RoundToNegInfinity();
ssize_t j0clamp = std::max(j0, (ssize_t)1);
ssize_t j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
entity_pos_t xscale = pos.X / (int)CELL_SIZE;
entity_pos_t yscale = pos.Y / (int)CELL_SIZE;
entity_pos_t rsquared = (visionRange / (int)CELL_SIZE).Square();
for (ssize_t j = j0clamp; j <= j1clamp; ++j)
{
// Compute values such that (i - x)^2 + (j - y)^2 <= r^2
// (TODO: is this sqrt slow? can we optimise it?)
entity_pos_t di = (rsquared - (entity_pos_t::FromInt(j) - yscale).Square()).Sqrt();
ssize_t i0 = (xscale - di).ToInt_RoundToInfinity();
ssize_t i1 = (xscale + di).ToInt_RoundToNegInfinity();
ssize_t i0clamp = std::max(i0, (ssize_t)1);
ssize_t i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
LosUpdateStripHelper(owner, i0clamp, i1clamp, j, amount, counts);
}
}
void LosAdd(i8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper(owner, visionRange, pos, 1);
}
void LosRemove(i8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper(owner, visionRange, pos, -1);
}
void LosMove(i8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
// TODO: we could optimise this by only modifying tiles that changed
LosRemove(owner, visionRange, from);
LosAdd(owner, visionRange, to);
}
};
REGISTER_COMPONENT_TYPE(RangeManager)
Index: ps/trunk/source/simulation2/Simulation2.cpp
===================================================================
--- ps/trunk/source/simulation2/Simulation2.cpp (revision 8233)
+++ ps/trunk/source/simulation2/Simulation2.cpp (revision 8234)
@@ -1,456 +1,457 @@
/* Copyright (C) 2010 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.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/system/ComponentManager.h"
#include "simulation2/system/ParamNode.h"
#include "simulation2/system/SimContext.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "lib/file/file_system_util.h"
#include "lib/utf8.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "ps/XML/Xeromyces.h"
#include
#if MSC_VERSION
#include
#define getpid _getpid // use the non-deprecated function name
#endif
class CSimulation2Impl
{
public:
CSimulation2Impl(CUnitManager* unitManager, CTerrain* terrain) :
m_SimContext(), m_ComponentManager(m_SimContext), m_EnableOOSLog(false)
{
m_SimContext.m_UnitManager = unitManager;
m_SimContext.m_Terrain = terrain;
m_ComponentManager.LoadComponentTypes();
RegisterFileReloadFunc(ReloadChangedFileCB, this);
// m_EnableOOSLog = true; // TODO: this should be a command-line flag or similar
// (can't call ResetState here since the scripts haven't been loaded yet)
}
~CSimulation2Impl()
{
UnregisterFileReloadFunc(ReloadChangedFileCB, this);
}
CParamNode LoadXML(const std::wstring& name)
{
CParamNode ret;
VfsPath path = VfsPath(L"simulation/templates/") / name;
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return ret; // (Xeromyces already logged an error)
CParamNode::LoadXML(ret, xero);
return ret;
}
void ResetState(bool skipScriptedComponents)
{
m_ComponentManager.ResetState();
m_DeltaTime = 0.0;
m_LastFrameOffset = 0.0f;
m_TurnNumber = 0;
CParamNode noParam;
CComponentManager::ComponentTypeId cid;
// Add native system components:
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_TemplateManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_CommandQueue, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ObstructionManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Pathfinder, LoadXML(L"special/pathfinder.xml").GetChild("Pathfinder"));
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_ProjectileManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_RangeManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_SoundManager, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_Terrain, noParam);
m_ComponentManager.AddComponent(SYSTEM_ENTITY, CID_WaterManager, noParam);
// Add scripted system components:
if (!skipScriptedComponents)
{
#define LOAD_SCRIPTED_COMPONENT(name) \
cid = m_ComponentManager.LookupCID(name); \
if (cid == CID__Invalid) \
LOGERROR(L"Can't find component type " L##name); \
m_ComponentManager.AddComponent(SYSTEM_ENTITY, cid, noParam)
LOAD_SCRIPTED_COMPONENT("GuiInterface");
LOAD_SCRIPTED_COMPONENT("PlayerManager");
LOAD_SCRIPTED_COMPONENT("Timer");
+ LOAD_SCRIPTED_COMPONENT("EndGameManager");
#undef LOAD_SCRIPTED_COMPONENT
}
}
bool LoadScripts(const VfsPath& path);
LibError ReloadChangedFile(const VfsPath& path);
static LibError ReloadChangedFileCB(void* param, const VfsPath& path)
{
return static_cast(param)->ReloadChangedFile(path);
}
bool Update(int turnLength, const std::vector& commands);
void Interpolate(float frameLength, float frameOffset);
void DumpState();
CSimContext m_SimContext;
CComponentManager m_ComponentManager;
double m_DeltaTime;
float m_LastFrameOffset;
std::wstring m_StartupScript;
CScriptValRooted m_MapSettings;
std::set m_LoadedScripts;
uint32_t m_TurnNumber;
bool m_EnableOOSLog;
};
bool CSimulation2Impl::LoadScripts(const VfsPath& path)
{
VfsPaths pathnames;
if (fs_util::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
return false;
bool ok = true;
for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
{
std::wstring filename = it->string();
m_LoadedScripts.insert(filename);
LOGMESSAGE(L"Loading simulation script '%ls'", filename.c_str());
if (! m_ComponentManager.LoadScript(filename))
ok = false;
}
return ok;
}
LibError CSimulation2Impl::ReloadChangedFile(const VfsPath& path)
{
const std::wstring& filename = path.string();
// Ignore if this file wasn't loaded as a script
// (TODO: Maybe we ought to load in any new .js files that are created in the right directories)
if (m_LoadedScripts.find(filename) == m_LoadedScripts.end())
return INFO::OK;
// If the file doesn't exist (e.g. it was deleted), don't bother loading it since that'll give an error message.
// (Also don't bother trying to 'unload' it from the component manager, because that's not possible)
if (!FileExists(path))
return INFO::OK;
LOGMESSAGE(L"Reloading simulation script '%ls'", filename.c_str());
if (!m_ComponentManager.LoadScript(filename, true))
return ERR::FAIL;
return INFO::OK;
}
bool CSimulation2Impl::Update(int turnLength, const std::vector& commands)
{
fixed turnLengthFixed = fixed::FromInt(turnLength) / 1000;
// TODO: the update process is pretty ugly, with lots of messages and dependencies
// between different components. Ought to work out a nicer way to do this.
CMessageTurnStart msgTurnStart;
m_ComponentManager.BroadcastMessage(msgTurnStart);
if (m_TurnNumber == 0)
{
ScriptInterface& scriptInterface = m_ComponentManager.GetScriptInterface();
CScriptVal ret;
scriptInterface.CallFunction(scriptInterface.GetGlobalObject(), "LoadMapSettings", m_MapSettings, ret);
if (!m_StartupScript.empty())
m_ComponentManager.GetScriptInterface().LoadScript(L"map startup script", m_StartupScript);
}
CmpPtr cmpPathfinder(m_SimContext, SYSTEM_ENTITY);
if (!cmpPathfinder.null())
cmpPathfinder->FinishAsyncRequests();
CmpPtr cmpCommandQueue(m_SimContext, SYSTEM_ENTITY);
if (!cmpCommandQueue.null())
cmpCommandQueue->FlushTurn(commands);
// Send all the update phases
{
CMessageUpdate msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_MotionFormation msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_MotionUnit msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
{
CMessageUpdate_Final msgUpdate(turnLengthFixed);
m_ComponentManager.BroadcastMessage(msgUpdate);
}
// Clean up any entities destroyed during the simulation update
m_ComponentManager.FlushDestroyedComponents();
// if (m_TurnNumber == 0)
// m_ComponentManager.GetScriptInterface().DumpHeap();
if (m_EnableOOSLog)
DumpState();
++m_TurnNumber;
return true; // TODO: don't bother with bool return
}
void CSimulation2Impl::Interpolate(float frameLength, float frameOffset)
{
m_LastFrameOffset = frameOffset;
CMessageInterpolate msg(frameLength, frameOffset);
m_ComponentManager.BroadcastMessage(msg);
}
void CSimulation2Impl::DumpState()
{
PROFILE("DumpState");
std::wstringstream name;
name << L"sim_log/" << getpid() << L"/" << std::setw(5) << std::setfill(L'0') << m_TurnNumber << L".txt";
fs::wpath path (psLogDir()/name.str());
CreateDirectories(path.branch_path(), 0700);
std::ofstream file (path.external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc);
file << "State hash: " << std::hex;
std::string hashRaw;
m_ComponentManager.ComputeStateHash(hashRaw);
for (size_t i = 0; i < hashRaw.size(); ++i)
file << std::setfill('0') << std::setw(2) << (int)(unsigned char)hashRaw[i];
file << std::dec << "\n";
file << "\n";
m_ComponentManager.DumpDebugState(file);
std::ofstream binfile (change_extension(path, L".dat").external_file_string().c_str(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary);
m_ComponentManager.SerializeState(binfile);
}
////////////////////////////////////////////////////////////////
CSimulation2::CSimulation2(CUnitManager* unitManager, CTerrain* terrain) :
m(new CSimulation2Impl(unitManager, terrain))
{
}
CSimulation2::~CSimulation2()
{
delete m;
}
// Forward all method calls to the appropriate CSimulation2Impl/CComponentManager methods:
void CSimulation2::EnableOOSLog()
{
m->m_EnableOOSLog = true;
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity());
}
entity_id_t CSimulation2::AddEntity(const std::wstring& templateName, entity_id_t preferredId)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewEntity(preferredId));
}
entity_id_t CSimulation2::AddLocalEntity(const std::wstring& templateName)
{
return m->m_ComponentManager.AddEntity(templateName, m->m_ComponentManager.AllocateNewLocalEntity());
}
void CSimulation2::DestroyEntity(entity_id_t ent)
{
m->m_ComponentManager.DestroyComponentsSoon(ent);
}
void CSimulation2::FlushDestroyedEntities()
{
m->m_ComponentManager.FlushDestroyedComponents();
}
IComponent* CSimulation2::QueryInterface(entity_id_t ent, int iid) const
{
return m->m_ComponentManager.QueryInterface(ent, iid);
}
void CSimulation2::PostMessage(entity_id_t ent, const CMessage& msg) const
{
m->m_ComponentManager.PostMessage(ent, msg);
}
void CSimulation2::BroadcastMessage(const CMessage& msg) const
{
m->m_ComponentManager.BroadcastMessage(msg);
}
const CSimulation2::InterfaceList& CSimulation2::GetEntitiesWithInterface(int iid)
{
return m->m_ComponentManager.GetEntitiesWithInterface(iid);
}
const CSimContext& CSimulation2::GetSimContext() const
{
return m->m_SimContext;
}
ScriptInterface& CSimulation2::GetScriptInterface() const
{
return m->m_ComponentManager.GetScriptInterface();
}
void CSimulation2::InitGame(const CScriptVal& data)
{
CScriptVal ret; // ignored
GetScriptInterface().CallFunction(GetScriptInterface().GetGlobalObject(), "InitGame", data, ret);
}
bool CSimulation2::Update(int turnLength)
{
std::vector commands;
return m->Update(turnLength, commands);
}
bool CSimulation2::Update(int turnLength, const std::vector& commands)
{
return m->Update(turnLength, commands);
}
void CSimulation2::Interpolate(float frameLength, float frameOffset)
{
m->Interpolate(frameLength, frameOffset);
}
void CSimulation2::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
CMessageRenderSubmit msg(collector, frustum, culling);
m->m_ComponentManager.BroadcastMessage(msg);
}
float CSimulation2::GetLastFrameOffset() const
{
return m->m_LastFrameOffset;
}
bool CSimulation2::LoadScripts(const VfsPath& path)
{
return m->LoadScripts(path);
}
bool CSimulation2::LoadDefaultScripts()
{
return (
m->LoadScripts(L"simulation/components/interfaces/") &&
m->LoadScripts(L"simulation/helpers/") &&
m->LoadScripts(L"simulation/components/")
);
}
void CSimulation2::SetStartupScript(const std::wstring& code)
{
m->m_StartupScript = code;
}
const std::wstring& CSimulation2::GetStartupScript()
{
return m->m_StartupScript;
}
void CSimulation2::SetMapSettings(const utf16string& settings)
{
m->m_MapSettings = m->m_ComponentManager.GetScriptInterface().ParseJSON(settings);
}
std::string CSimulation2::GetMapSettings()
{
return m->m_ComponentManager.GetScriptInterface().StringifyJSON(m->m_MapSettings.get());
}
LibError CSimulation2::ReloadChangedFile(const VfsPath& path)
{
return m->ReloadChangedFile(path);
}
void CSimulation2::ResetState(bool skipGui)
{
m->ResetState(skipGui);
}
bool CSimulation2::ComputeStateHash(std::string& outHash)
{
return m->m_ComponentManager.ComputeStateHash(outHash);
}
bool CSimulation2::DumpDebugState(std::ostream& stream)
{
return m->m_ComponentManager.DumpDebugState(stream);
}
bool CSimulation2::SerializeState(std::ostream& stream)
{
return m->m_ComponentManager.SerializeState(stream);
}
bool CSimulation2::DeserializeState(std::istream& stream)
{
// TODO: need to make sure the required SYSTEM_ENTITY components get constructed
return m->m_ComponentManager.DeserializeState(stream);
}
std::string CSimulation2::GenerateSchema()
{
return m->m_ComponentManager.GenerateSchema();
}
Index: ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml (revision 8233)
+++ ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_terrain_demo.xml (revision 8234)
@@ -1,262 +1,263 @@
defaultdefault19.304915080.450
units/hele_ship_bireme
1
units/hele_ship_merchant
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_mechanical_siege_lithobolos
1
units/hele_cavalry_swordsman_b
1
units/hele_cavalry_swordsman_b
1
units/hele_cavalry_swordsman_b
1
structures/hele_barracks
1
structures/hele_dock
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
units/hele_infantry_spearman_b
1
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml (revision 8233)
+++ ps/trunk/binaries/data/mods/public/maps/scenarios/Pathfinding_demo.xml (revision 8234)
@@ -1,635 +1,636 @@
defaultdefault515080.450
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
units/hele_cavalry_swordsman_e
1
units/hele_cavalry_swordsman_e
1
units/hele_cavalry_swordsman_e
1
units/hele_cavalry_swordsman_e
1
units/hele_cavalry_swordsman_e
1
units/hele_cavalry_swordsman_e
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
structures/celt_corral
1
units/celt_infantry_javelinist_e
2
units/celt_infantry_javelinist_e
1
units/hele_infantry_archer_e
1
units/hele_infantry_archer_e
1
structures/celt_temple
2
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
units/hele_infantry_archer_b
1
structures/celt_temple
2
structures/celt_temple
2
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
units/hele_infantry_spearman_e
1
gaia/geology_metal_mediterranean
1
gaia/geology_stone_mediterranean
1
gaia/flora_tree_oak_large
1
gaia/fauna_zebra
1
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/maps/scenarios/Units_demo.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scenarios/Units_demo.xml (revision 8233)
+++ ps/trunk/binaries/data/mods/public/maps/scenarios/Units_demo.xml (revision 8234)
@@ -1,94 +1,95 @@
defaultdefault515080.450
Index: ps/trunk/binaries/data/mods/public/gui/page_summary.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/page_summary.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/page_summary.xml (revision 8234)
@@ -0,0 +1,9 @@
+
+
+ common/setup.xml
+ common/styles.xml
+ common/sprite1.xml
+ common/init.xml
+ summary/summary.xml
+ common/global.xml
+
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js (revision 8234)
@@ -1,35 +1,35 @@
/*
DESCRIPTION : Contains global GUI functions, which will later be accessible from every GUI script/file.
NOTES : So far, only the message box-related functions are implemented.
*/
// *******************************************
// messageBox
// *******************************************
-// @params: int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions
+// @params: int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions, arr mbButtonsCode
// @return: void
// @desc: Displays a new modal message box.
// *******************************************
function messageBox (mbWidth, mbHeight, mbMessage, mbTitle, mbMode, mbButtonCaptions, mbButtonsCode)
{
Engine.PushGuiPage("page_msgbox.xml", {
width: mbWidth,
height: mbHeight,
message: mbMessage,
title: mbTitle,
mode: mbMode,
buttonCaptions: mbButtonCaptions,
buttonCode: mbButtonsCode
});
}
// ====================================================================
function updateFPS()
{
getGUIObjectByName("fpsCounter").caption = "FPS: " + getFPS();
}
// ====================================================================
Index: ps/trunk/binaries/data/mods/public/gui/session_new/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session_new/session.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/gui/session_new/session.js (revision 8234)
@@ -1,186 +1,240 @@
// Network Mode
var g_IsNetworked = false;
// Cache the basic player data (name, civ, color)
var g_Players = [];
var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } };
// Cache dev-mode settings that are frequently or widely used
var g_DevSettings = {
controlAll: false
};
// Indicate when one of the current player's training queues is blocked
// (this is used to support population counter blinking)
var g_IsTrainingQueueBlocked = false;
// Cache EntityStates
var g_EntityStates = {}; // {id:entState}
+// Whether the player has lost/won and reached the end of their game
+var g_GameEnded = false;
+
function GetEntityState(entId)
{
if (!(entId in g_EntityStates))
{
var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
g_EntityStates[entId] = entState;
}
return g_EntityStates[entId];
}
// Cache TemplateData
var g_TemplateData = {}; // {id:template}
function GetTemplateData(templateName)
{
if (!(templateName in g_TemplateData))
{
var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
g_TemplateData[templateName] = template;
}
return g_TemplateData[templateName];
}
// Init
function init(initData, hotloadData)
{
if (hotloadData)
{
g_Selection.selected = hotloadData.selection;
}
else
{
// Starting for the first time:
startMusic();
}
if (initData)
{
g_IsNetworked = initData.isNetworked; // Set network mode
g_PlayerAssignments = initData.playerAssignments;
g_Players = getPlayerData(initData.playerAssignments); // Cache the player data
}
else // Needed for autostart loading option
{
g_Players = getPlayerData(null);
}
getGUIObjectByName("civIcon").sprite = g_Players[Engine.GetPlayerID()].civ+"Icon";
cacheMenuObjects();
onSimulationUpdate();
}
function leaveGame()
{
+ var simState = Engine.GuiInterfaceCall("GetSimulationState");
+ var playerState = simState.players[Engine.GetPlayerID()];
+
+ var gameResult;
+ if (playerState.state == "won")
+ {
+ gameResult = "You have won the battle!";
+ }
+ else if (playerState.state == "defeated")
+ {
+ gameResult = "You have been defeated...";
+ }
+ else // "active"
+ {
+ gameResult = "You have abandoned the game.";
+
+ // Tell other players that we have given up and
+ // been defeated
+ Engine.PostNetworkCommand({
+ "type": "defeat-player",
+ "playerId": Engine.GetPlayerID()
+ });
+
+ }
+
stopMusic();
endGame();
- Engine.SwitchGuiPage("page_pregame.xml");
+
+ Engine.SwitchGuiPage("page_summary.xml", { "gameResult" : gameResult });
+
}
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
return { selection: g_Selection.selected };
}
function onTick()
{
+ checkPlayerState();
+
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
g_DevSettings.controlAll = getGUIObjectByName("devControlAll").checked;
// TODO: at some point this controlAll needs to disable the simulation code's
// player checks (once it has some player checks)
updateCursor();
// If the selection changed, we need to regenerate the sim display
if (g_Selection.dirty)
{
onSimulationUpdate();
// Display rally points for selected buildings
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
}
// Run timers
updateTimers();
// When training is blocked, flash population (alternates colour every 500msec)
if (g_IsTrainingQueueBlocked && (Date.now() % 1000) < 500)
getGUIObjectByName("resourcePop").textcolor = "255 165 0";
else
getGUIObjectByName("resourcePop").textcolor = "white";
}
+function checkPlayerState()
+{
+ var simState = Engine.GuiInterfaceCall("GetSimulationState");
+ var playerState = simState.players[Engine.GetPlayerID()];
+
+ if (!g_GameEnded)
+ {
+ if (playerState.state == "defeated")
+ {
+ g_GameEnded = true;
+ messageBox(400, 200, "You have been defeated... Do you want to leave the game now?",
+ "Defeat", 0, ["Yes", "No!"], [leaveGame, null]);
+ }
+ else if (playerState.state == "won")
+ {
+ g_GameEnded = true;
+ messageBox(400, 200, "You have won the battle! Do you want to leave the game now?",
+ "Victory", 0, ["Yes", "No!"], [leaveGame, null]);
+ }
+ }
+}
+
function onSimulationUpdate()
{
g_Selection.dirty = false;
g_EntityStates = {};
g_TemplateData = {};
var simState = Engine.GuiInterfaceCall("GetSimulationState");
// If we're called during init when the game is first loading, there will be no simulation yet, so do nothing
if (!simState)
return;
handleNotifications();
updateDebug(simState);
updatePlayerDisplay(simState);
updateSelectionDetails();
}
function updateDebug(simState)
{
var debug = getGUIObjectByName("debug");
if (getGUIObjectByName("devDisplayState").checked)
{
debug.hidden = false;
}
else
{
debug.hidden = true;
return;
}
var text = uneval(simState);
var selection = g_Selection.toList();
if (selection.length)
{
var entState = GetEntityState(selection[0]);
if (entState)
{
var template = GetTemplateData(entState.template);
text += "\n\n" + uneval(entState) + "\n\n" + uneval(template);
}
}
debug.caption = text;
}
function updatePlayerDisplay(simState)
{
var playerState = simState.players[Engine.GetPlayerID()];
if (!playerState)
return;
getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food;
getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood;
getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone;
getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal;
getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
g_IsTrainingQueueBlocked = playerState.trainingQueueBlocked;
}
Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 8234)
@@ -0,0 +1,4 @@
+function init(data)
+{
+ getGUIObjectByName("summaryText").caption = data.gameResult;
+}
Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (revision 8234)
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 8234)
@@ -1,24 +1,31 @@
function LoadMapSettings(settings)
{
// Default settings for old maps
if (!settings)
settings = {};
if (settings.DefaultStance)
{
for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SetStance(settings.DefaultStance);
}
}
if (settings.RevealMap)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetLosRevealAll(true);
}
+
+ var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
+ if (settings.GameType)
+ {
+ cmpEndGameManager.SetGameType(settings.GameType);
+ }
+ cmpEndGameManager.Start();
}
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js (revision 8234)
@@ -1,253 +1,261 @@
function ProcessCommand(player, cmd)
{
// print("command: " + player + " " + uneval(cmd) + "\n");
// TODO: all of this stuff needs to do checks for valid arguments
// (e.g. make sure players own the units they're trying to use)
switch (cmd.type)
{
case "debug-print":
print(cmd.message);
break;
case "walk":
var cmpUnitAI = GetFormationUnitAI(cmd.entities);
if (cmpUnitAI)
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
break;
case "attack":
var cmpUnitAI = GetFormationUnitAI(cmd.entities);
if (cmpUnitAI)
cmpUnitAI.Attack(cmd.target, cmd.queued);
break;
case "repair":
// This covers both repairing damaged buildings, and constructing unfinished foundations
var cmpUnitAI = GetFormationUnitAI(cmd.entities);
if (cmpUnitAI)
cmpUnitAI.Repair(cmd.target, cmd.queued);
break;
case "gather":
var cmpUnitAI = GetFormationUnitAI(cmd.entities);
if (cmpUnitAI)
cmpUnitAI.Gather(cmd.target, cmd.queued);
break;
case "train":
var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue);
if (queue)
queue.AddBatch(cmd.template, +cmd.count);
break;
case "stop-train":
var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue);
if (queue)
queue.RemoveBatch(cmd.id);
break;
case "construct":
/*
* Construction process:
* . Take resources away immediately.
* . Create a foundation entity with 1hp, 0% build progress.
* . Increase hp and build progress up to 100% when people work on it.
* . If it's destroyed, an appropriate fraction of the resource cost is refunded.
* . If it's completed, it gets replaced with the real building.
*/
// Find the player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerEnt = cmpPlayerMan.GetPlayerByID(player);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Tentatively create the foundation (we might find later that it's a invalid build command)
var ent = Engine.AddEntity("foundation|" + cmd.template);
// TODO: report errors (e.g. invalid template names)
// Move the foundation to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.JumpTo(cmd.x, cmd.z);
cmpPosition.SetYRotation(cmd.angle);
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction && cmpObstruction.CheckCollisions())
{
// TODO: report error to player (the building site was obstructed)
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
break;
}
var cmpCost = Engine.QueryInterface(ent, IID_Cost);
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
{
// TODO: report error to player (they ran out of resources)
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
break;
}
// Make it owned by the current player
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Initialise the foundation
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
cmpFoundation.InitialiseConstruction(player, cmd.template);
// Tell the units to start building this new entity
ProcessCommand(player, {
"type": "repair",
"entities": cmd.entities,
"target": ent,
"queued": cmd.queued
});
break;
case "delete-entity":
// Verify the player owns the unit
var cmpOwnership = Engine.QueryInterface(cmd.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
break;
var cmpHealth = Engine.QueryInterface(cmd.entity, IID_Health);
if (cmpHealth)
cmpHealth.Kill();
else
Engine.DestroyEntity(cmd.entity);
break;
case "set-rallypoint":
for each (var ent in cmd.entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
cmpRallyPoint.SetPosition(cmd.x, cmd.z);
}
break;
case "unset-rallypoint":
for each (var ent in cmd.entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
cmpRallyPoint.Unset();
}
break;
+
+ case "defeat-player":
+ // Get player entity by playerId
+ var cmpPlayerMananager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ var playerEnt = cmpPlayerManager.GetPlayerByID(cmd.playerId);
+ // Send "OnPlayerDefeated" message to player
+ Engine.PostMessage(playerEnt, MT_PlayerDefeated, null);
+ break;
default:
error("Ignoring unrecognised command type '" + cmd.type + "'");
}
}
/**
* Get some information about the formations used by entities.
*/
function ExtractFormations(ents)
{
var entities = []; // subset of ents that have UnitAI
var members = {}; // { formationentity: [ent, ent, ...], ... }
for each (var ent in ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
var fid = cmpUnitAI.GetFormationController();
if (fid != INVALID_ENTITY)
{
if (!members[fid])
members[fid] = [];
members[fid].push(ent);
}
entities.push(ent);
}
}
var ids = [ id for (id in members) ];
return { "entities": entities, "members": members, "ids": ids };
}
/**
* Remove the given list of entities from their current formations.
*/
function RemoveFromFormation(ents)
{
var formation = ExtractFormations(ents);
for (var fid in formation.members)
{
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
}
}
/**
* Return null or a UnitAI belonging either to the selected unit
* or to a formation entity for the selected group of units.
*/
function GetFormationUnitAI(ents)
{
// If an individual was selected, remove it from any formation
// and command it individually
if (ents.length == 1)
{
RemoveFromFormation(ents);
return Engine.QueryInterface(ents[0], IID_UnitAI);
}
// Find what formations the selected entities are currently in
var formation = ExtractFormations(ents);
if (formation.entities.length == 0)
{
// No units with AI - nothing to do here
return null;
}
var formationEnt = undefined;
if (formation.ids.length == 1)
{
// Selected units all belong to the same formation.
// Check that it doesn't have any other members
var fid = formation.ids[0];
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation && cmpFormation.GetMemberCount() == formation.entities.length)
{
// The whole formation was selected, so reuse its controller for this command
formationEnt = +fid;
}
}
if (!formationEnt)
{
// We need to give the selected units a new formation controller
// Remove selected units from their current formation
for (var fid in formation.members)
{
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
}
// Create the new controller
formationEnt = Engine.AddEntity("special/formation");
var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation);
cmpFormation.SetMembers(formation.entities);
}
return Engine.QueryInterface(formationEnt, IID_UnitAI);
}
Engine.RegisterGlobal("ProcessCommand", ProcessCommand);
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 8234)
@@ -1,415 +1,416 @@
function GuiInterface()
{
this.notifications = [];
}
GuiInterface.prototype.Schema =
"";
GuiInterface.prototype.Serialize = function()
{
return {};
};
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
this.rallyPoints = undefined;
};
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
"players": []
};
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
var playerData = {
"name": cmpPlayer.GetName(),
"civ": cmpPlayer.GetCiv(),
"color": cmpPlayer.GetColour(),
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
- "trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked()
+ "trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(),
+ "state": cmpPlayer.GetState()
};
ret.players.push(playerData);
}
return ret;
};
GuiInterface.prototype.GetEntityState = function(player, ent)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// All units must have a template; if not then it's a nonexistent entity id
var template = cmpTempMan.GetCurrentTemplateName(ent);
if (!template)
return null;
var ret = {
"id": ent,
"template": template
}
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
ret.identity = {
"rank": cmpIdentity.GetRank(),
"classes": cmpIdentity.GetClassesList()
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition)
{
ret.position = cmpPosition.GetPosition();
}
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
{
ret.hitpoints = cmpHealth.GetHitpoints();
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
}
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
if (cmpAttack)
{
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show?
ret.attack = cmpAttack.GetAttackStrengths(type);
}
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
if (cmpArmour)
{
ret.armour = cmpArmour.GetArmourStrengths();
}
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
{
ret.buildEntities = cmpBuilder.GetEntitiesList();
}
var cmpTrainingQueue = Engine.QueryInterface(ent, IID_TrainingQueue);
if (cmpTrainingQueue)
{
ret.training = {
"entities": cmpTrainingQueue.GetEntitiesList(),
"queue": cmpTrainingQueue.GetQueue(),
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
ret.foundation = {
"progress": cmpFoundation.GetBuildPercentage()
};
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
{
ret.player = cmpOwnership.GetOwner();
}
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply)
{
ret.resourceSupply = {
"max": cmpResourceSupply.GetMaxAmount(),
"amount": cmpResourceSupply.GetCurrentAmount(),
"type": cmpResourceSupply.GetType()
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
}
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = { };
}
return ret;
};
GuiInterface.prototype.GetTemplateData = function(player, name)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(name);
if (!template)
return null;
var ret = {};
if (template.Identity)
{
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon_sheet = template.Identity.IconSheet;
ret.icon_cell = template.Identity.IconCell;
ret.tooltip = template.Identity.Tooltip;
}
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food) ret.cost.food = +template.Cost.Resources.food;
if (template.Cost.Resources.wood) ret.cost.wood = +template.Cost.Resources.wood;
if (template.Cost.Resources.stone) ret.cost.stone = +template.Cost.Resources.stone;
if (template.Cost.Resources.metal) ret.cost.metal = +template.Cost.Resources.metal;
}
return ret;
};
GuiInterface.prototype.PushNotification = function(notification)
{
this.notifications.push(notification);
};
GuiInterface.prototype.GetNextNotification = function()
{
if (this.notifications.length)
return this.notifications.pop();
else
return "";
};
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerColours = {}; // cache of owner -> colour map
for each (var ent in cmd.entities)
{
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
if (!cmpSelectable)
continue;
if (cmd.alpha == 0)
{
cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0});
continue;
}
// Find the entity's owner's colour:
var owner = -1;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
var colour = playerColours[owner];
if (!colour)
{
colour = [1, 1, 1];
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
if (cmpPlayer)
colour = cmpPlayer.GetColour();
playerColours[owner] = colour;
}
cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha});
}
};
/**
* Displays the rally point of a building
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
// If there are rally points already displayed, destroy them
for each (var ent in this.rallyPoints)
{
// Hide it first (the destruction won't be instantaneous)
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.MoveOutOfWorld();
Engine.DestroyEntity(ent);
}
this.rallyPoints = [];
var positions = [];
// DisplayRallyPoints is called passing a list of entities for which
// rally points must be displayed
for each (var ent in cmd.entities)
{
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = {"x": cmd.x, "z": cmd.z};
else
pos = cmpRallyPoint.GetPosition();
if (pos)
{
// TODO: it'd probably be nice if we could draw some kind of line
// between the building and pos, to make the marker easy to find even
// if it's a long way from the building
positions.push(pos);
}
}
// Add rally point entity for each building
for each (var pos in positions)
{
var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
cmpPosition.JumpTo(pos.x, pos.z);
this.rallyPoints.push(rallyPoint);
}
};
/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
* Returns true if the placement is okay (everything is valid and the entity is not obstructed by others).
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
// Destroy the old preview if there was one
if (this.placementEntity)
Engine.DestroyEntity(this.placementEntity[1]);
// Load the new template
if (cmd.template == "")
{
this.placementEntity = undefined;
}
else
{
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
}
}
if (this.placementEntity)
{
// Move the preview into the right location
var pos = Engine.QueryInterface(this.placementEntity[1], IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
// Check whether it's obstructed by other entities
var cmpObstruction = Engine.QueryInterface(this.placementEntity[1], IID_Obstruction);
var colliding = (cmpObstruction && cmpObstruction.CheckCollisions());
// Set it to a red shade if this is an obstructed location
var cmpVisual = Engine.QueryInterface(this.placementEntity[1], IID_Visual);
if (cmpVisual)
{
if (colliding)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
if (!colliding)
return true;
}
return false;
};
GuiInterface.prototype.PlaySound = function(player, data)
{
PlaySound(data.name, data.entity);
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
cmpPathfinder.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
{
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
cmpObstructionManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for each (var ent in data.entities)
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetDebugOverlay(enabled);
};
// List the GuiInterface functions that can be safely called by GUI scripts.
// (GUI scripts are non-deterministic and untrusted, so these functions must be
// appropriately careful. They are called with a first argument "player", which is
// trusted and indicates the player associated with the current client; no data should
// be returned unless this player is meant to be able to see it.)
var exposedFunctions = {
"GetSimulationState": 1,
"GetEntityState": 1,
"GetTemplateData": 1,
"GetNextNotification": 1,
"SetSelectionHighlight": 1,
"DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1,
"PlaySound": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
{
if (exposedFunctions[name])
return this[name](player, args);
else
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
};
Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/EndGameManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/EndGameManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/EndGameManager.js (revision 8234)
@@ -0,0 +1,2 @@
+Engine.RegisterInterface("EndGameManager");
+Engine.RegisterMessageType("PlayerDefeated");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js (revision 8234)
@@ -1,97 +1,101 @@
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Identity.js");
Engine.LoadComponentScript("interfaces/RallyPoint.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/TrainingQueue.js");
Engine.LoadComponentScript("GuiInterface.js");
var cmp = ConstructComponent(SYSTEM_ENTITY, "GuiInterface");
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
GetNumPlayers: function() { return 2; },
GetPlayerByID: function(id) { TS_ASSERT(id === 0 || id === 1); return 100+id; }
});
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
GetCurrentTemplateName: function(ent) { return "example"; },
GetTemplate: function(name) { return ""; },
});
AddMock(100, IID_Player, {
GetName: function() { return "Player 1"; },
GetCiv: function() { return "gaia"; },
GetColour: function() { return { r: 1, g: 1, b: 1, a: 1}; },
GetPopulationCount: function() { return 10; },
GetPopulationLimit: function() { return 20; },
GetResourceCounts: function() { return { food: 100 }; },
IsTrainingQueueBlocked: function() { return false; },
+ GetState: function() { return "active"; },
});
AddMock(101, IID_Player, {
GetName: function() { return "Player 2"; },
GetCiv: function() { return "celt"; },
GetColour: function() { return { r: 1, g: 0, b: 0, a: 1}; },
GetPopulationCount: function() { return 40; },
GetPopulationLimit: function() { return 30; },
GetResourceCounts: function() { return { food: 200 }; },
IsTrainingQueueBlocked: function() { return false; },
+ GetState: function() { return "active"; },
});
TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
players: [
{
name: "Player 1",
civ: "gaia",
color: { r:1, g:1, b:1, a:1 },
popCount: 10,
popLimit: 20,
resourceCounts: { food: 100 },
trainingQueueBlocked: false,
+ state: "active",
},
{
name: "Player 2",
civ: "celt",
color: { r:1, g:0, b:0, a:1 },
popCount: 40,
popLimit: 30,
resourceCounts: { food: 200 },
trainingQueueBlocked: false,
+ state: "active",
}
]
});
AddMock(10, IID_Position, {
GetPosition: function() {
return {x:1, y:2, z:3};
}
});
AddMock(10, IID_Health, {
GetHitpoints: function() { return 50; },
GetMaxHitpoints: function() { return 60; },
IsRepairable: function() { return false; },
});
AddMock(10, IID_Builder, {
GetEntitiesList: function() {
return ["test1", "test2"];
}
});
var state = cmp.GetEntityState(-1, 10);
TS_ASSERT_UNEVAL_EQUALS(state, {
id: 10,
template: "example",
position: {x:1, y:2, z:3},
hitpoints: 50,
maxHitpoints: 60,
needsRepair: false,
buildEntities: ["test1", "test2"]
});
Index: ps/trunk/binaries/data/mods/public/simulation/components/Identity.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Identity.js (revision 8234)
@@ -1,121 +1,123 @@
function Identity() {}
Identity.prototype.Schema =
"Specifies various names and values associated with the unit type, typically for GUI display to users." +
"" +
"hele" +
"Infantry Spearman" +
"Hoplite" +
"3" +
"PortraitSheet" +
"" +
"" +
"" +
"gaia" +
"cart" +
"celt" +
"hele" +
"iber" +
"pers" +
"rome" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"Organic" +
"Foot" +
"Mounted" +
"Mechanical" +
"Super" +
"Hero" +
+ "Structure" +
"Civic" +
"Economic" +
"Defensive" +
"Village" +
"Town" +
"City" +
+ "ConquestCritical" +
"Bow" + // TODO: what are these used for?
"Javelin" +
"Spear" +
"Sword" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Identity.prototype.Init = function()
{
};
Identity.prototype.GetCiv = function()
{
return this.template.Civ;
};
Identity.prototype.GetRank = function()
{
if (this.template.Rank)
return this.template.Rank;
return "";
};
Identity.prototype.GetClassesList = function()
{
if (this.template.Classes)
{
var string = this.template.Classes._string;
return string.split(/\s+/);
}
else
{
return [];
}
};
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
Index: ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (revision 8234)
@@ -0,0 +1,110 @@
+// Repetition interval (msecs) for checking end game conditions
+var g_ProgressInterval = 1000;
+
+/**
+ * System component which regularly checks victory/defeat conditions
+ * and if they are satisfied then it marks the player as victorious/defeated.
+ */
+function EndGameManager() {}
+
+EndGameManager.prototype.Schema =
+ "";
+
+EndGameManager.prototype.Init = function()
+{
+ // Game type, initialised from the map settings.
+ // One of: "conquest" (default) and "endless"
+ this.gameType = "conquest";
+};
+
+EndGameManager.prototype.SetGameType = function(newGameType)
+{
+ this.gameType = newGameType;
+};
+
+/**
+ * Begin checking the end-game conditions.
+ * Must be called once, after calling SetGameType.
+ */
+EndGameManager.prototype.Start = function()
+{
+ if (this.gameType != "endless")
+ {
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.timer = cmpTimer.SetTimeout(this.entity, IID_EndGameManager, "ProgressTimeout", g_ProgressInterval, {});
+ }
+};
+
+EndGameManager.prototype.OnDestroy = function()
+{
+ if (this.timer)
+ {
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.timer);
+ }
+};
+
+EndGameManager.prototype.ProgressTimeout = function(data)
+{
+ this.UpdatePlayerStates();
+
+ // Repeat the timer
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.timer = cmpTimer.SetTimeout(this.entity, IID_EndGameManager, "ProgressTimeout", g_ProgressInterval, data);
+};
+
+EndGameManager.prototype.UpdatePlayerStates = function()
+{
+ var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ switch (this.gameType)
+ {
+ case "conquest":
+
+ // If a player is currently active but has no suitable units left,
+ // mark that player as defeated
+ // (Start from player 1 since we ignore Gaia)
+ for (var i = 1; i < cmpPlayerManager.GetNumPlayers(); i++)
+ {
+ var playerEntityId = cmpPlayerManager.GetPlayerByID(i);
+ var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
+ if (cmpPlayer.GetState() == "active")
+ {
+ if (cmpPlayer.GetConquestCriticalEntitiesCount() == 0)
+ {
+ Engine.PostMessage(playerEntityId, MT_PlayerDefeated, null);
+ }
+ }
+ }
+
+ // If there's only player remaining active, mark them as the winner
+ // TODO: update this code for allies
+
+ var alivePlayersCount = 0;
+ var lastAlivePlayerId;
+ // (Start from 1 to ignore Gaia)
+ for (var i = 1; i < cmpPlayerManager.GetNumPlayers(); i++)
+ {
+ var playerEntityId = cmpPlayerManager.GetPlayerByID(i);
+ var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
+ if (cmpPlayer.GetState() == "active")
+ {
+ alivePlayersCount++;
+ lastAlivePlayerId = i;
+ }
+ }
+ if (alivePlayersCount == 1)
+ {
+ var playerEntityId = cmpPlayerManager.GetPlayerByID(lastAlivePlayerId);
+ var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
+ cmpPlayer.SetState("won");
+ }
+
+ break;
+
+ default:
+ error("Invalid game type "+this.gameType);
+ break;
+ }
+};
+
+Engine.RegisterComponentType(IID_EndGameManager, "EndGameManager", EndGameManager);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 8234)
@@ -1,174 +1,222 @@
function Player() {}
Player.prototype.Schema =
"";
Player.prototype.Init = function()
{
this.playerID = undefined;
this.name = "Unknown";
this.civ = "gaia";
this.colour = { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 };
this.popUsed = 0; // population of units owned by this player
this.popReserved = 0; // population of units currently being trained
this.popLimit = 0; // maximum population
this.trainingQueueBlocked = false; // indicates whether any training queue is currently blocked
this.resourceCount = {
"food": 1000,
"wood": 1000,
"metal": 500,
"stone": 1000
};
+ this.state = "active"; // game state - one of "active", "defeated", "won"
+ this.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class
};
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
};
Player.prototype.GetPlayerID = function()
{
return this.playerID;
};
Player.prototype.SetName = function(name)
{
this.name = name;
};
Player.prototype.GetName = function()
{
return this.name;
};
Player.prototype.SetCiv = function(civcode)
{
this.civ = civcode;
};
Player.prototype.GetCiv = function()
{
return this.civ;
};
Player.prototype.SetColour = function(r, g, b)
{
this.colour = { "r": r/255.0, "g": g/255.0, "b": b/255.0, "a": 1.0 };
};
Player.prototype.GetColour = function()
{
return this.colour;
};
Player.prototype.TryReservePopulationSlots = function(num)
{
if (num > this.GetPopulationLimit() - this.GetPopulationCount())
return false;
this.popReserved += num;
return true;
};
Player.prototype.UnReservePopulationSlots = function(num)
{
this.popReserved -= num;
};
Player.prototype.GetPopulationCount = function()
{
return this.popUsed + this.popReserved;
};
Player.prototype.GetPopulationLimit = function()
{
return this.popLimit;
};
Player.prototype.IsTrainingQueueBlocked = function()
{
return this.trainingQueueBlocked;
};
Player.prototype.BlockTrainingQueue = function()
{
this.trainingQueueBlocked = true;
};
Player.prototype.UnBlockTrainingQueue = function()
{
this.trainingQueueBlocked = false;
};
Player.prototype.GetResourceCounts = function()
{
return this.resourceCount;
};
Player.prototype.AddResource = function(type, amount)
{
this.resourceCount[type] += (+amount);
};
Player.prototype.AddResources = function(amounts)
{
for (var type in amounts)
this.resourceCount[type] += (+amounts[type]);
};
Player.prototype.TrySubtractResources = function(amounts)
{
// Check if we can afford it all
var amountsNeeded = {};
for (var type in amounts)
if (amounts[type] > this.resourceCount[type])
amountsNeeded[type] = amounts[type] - this.resourceCount[type];
var formattedAmountsNeeded = [];
for (var type in amountsNeeded)
formattedAmountsNeeded.push(type + ": " + amountsNeeded[type]);
// If we don't have enough resources, send a notification to the player
if (formattedAmountsNeeded.length)
{
var notification = {"player": this.playerID, "message": "Insufficient resources - " + formattedAmountsNeeded.join(", ")};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
return false;
}
else
{
// Subtract the resources
for (var type in amounts)
this.resourceCount[type] -= amounts[type];
}
return true;
};
+Player.prototype.GetState = function()
+{
+ return this.state;
+};
+
+Player.prototype.SetState = function(newState)
+{
+ this.state = newState;
+};
+
+Player.prototype.GetConquestCriticalEntitiesCount = function()
+{
+ return this.conquestCriticalEntitiesCount;
+};
+
// Keep track of population effects of all entities that
// become owned or unowned by this player
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
+ var classes = [];
+
+ // Load class list only if we're going to need it
+ if (msg.from == this.playerID || msg.to == this.playerID)
+ {
+ var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
+ if (cmpIdentity)
+ classes = cmpIdentity.GetClassesList();
+ }
+
if (msg.from == this.playerID)
{
+ if (classes.indexOf("ConquestCritical") != -1)
+ this.conquestCriticalEntitiesCount--;
+
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
this.popUsed -= cost.GetPopCost();
this.popLimit -= cost.GetPopBonus();
}
}
if (msg.to == this.playerID)
{
+ if (classes.indexOf("ConquestCritical") != -1)
+ this.conquestCriticalEntitiesCount++;
+
var cost = Engine.QueryInterface(msg.entity, IID_Cost);
if (cost)
{
this.popUsed += cost.GetPopCost();
this.popLimit += cost.GetPopBonus();
}
}
};
+Player.prototype.OnPlayerDefeated = function()
+{
+ this.state = "defeated";
+
+ // Reassign all player's entities to Gaia
+ var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ var entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID);
+ for each (var entity in entities)
+ {
+ // Note: maybe we need to reassign units and buildings only?
+ var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
+ cmpOwnership.SetOwner(0);
+ }
+}
+
Engine.RegisterComponentType(IID_Player, "Player", Player);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 8234)
@@ -1,56 +1,60 @@
Structure
- snPortraitSheetBuildings
+
+ Structure
+ ConquestCritical
+
+ snPortraitSheetBuildingsstandardalliedstructure00100000corpse0falsetrue0.04.09.810.042.010.012.036trueattack/destruction/explode_debris.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 8233)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 8234)
@@ -1,68 +1,71 @@
Unit
+
+ ConquestCritical
+ unitfalse1010000corpse1000truefalse60.00.020.00.00.01.0false7.015.050.00.00.10.2defaultdefault5.02.524false