Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 7762) +++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.cpp (revision 7763) @@ -1,357 +1,357 @@ /* 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 "ActorViewer.h" #include "View.h" #include "graphics/ColladaManager.h" #include "graphics/Model.h" #include "graphics/ObjectManager.h" #include "graphics/Patch.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/Terrain.h" #include "graphics/TextureEntry.h" #include "graphics/TextureManager.h" #include "graphics/UnitManager.h" #include "maths/MathUtil.h" #include "ps/Font.h" #include "ps/GameSetup/Config.h" #include "ps/ProfileViewer.h" #include "renderer/Renderer.h" #include "renderer/Scene.h" #include "renderer/SkyManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpVisual.h" struct ActorViewerImpl : public Scene { NONCOPYABLE(ActorViewerImpl); public: ActorViewerImpl() : Entity(INVALID_ENTITY), Terrain(), ColladaManager(), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager), UnitManager(), Simulation2(&UnitManager, &Terrain), ObjectManager(MeshManager, SkeletonAnimManager, Simulation2) { UnitManager.SetObjectManager(ObjectManager); } entity_id_t Entity; CStrW CurrentUnitID; CStrW CurrentUnitAnim; float CurrentSpeed; bool WalkEnabled; bool GroundEnabled; bool ShadowsEnabled; SColor4ub Background; CTerrain Terrain; CColladaManager ColladaManager; CMeshManager MeshManager; CSkeletonAnimManager SkeletonAnimManager; CObjectManager ObjectManager; CUnitManager UnitManager; CSimulation2 Simulation2; // Simplistic implementation of the Scene interface void EnumerateObjects(const CFrustum& frustum, SceneCollector* c) { if (GroundEnabled) c->Submit(Terrain.GetPatch(0, 0)); Simulation2.RenderSubmit(*c, frustum, false); } }; ActorViewer::ActorViewer() : m(*new ActorViewerImpl()) { m.WalkEnabled = false; m.GroundEnabled = true; m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS); m.Background = SColor4ub(255, 255, 255, 255); // Create a tiny empty piece of terrain, just so we can put shadows // on it without having to think too hard m.Terrain.Initialize(1, NULL); CTextureEntry* tex = g_TexMan.FindTexture("whiteness"); if (tex) { CPatch* patch = m.Terrain.GetPatch(0, 0); for (ssize_t i = 0; i < PATCH_SIZE; ++i) { for (ssize_t j = 0; j < PATCH_SIZE; ++j) { CMiniPatch& mp = patch->m_MiniPatches[i][j]; mp.Tex = tex; mp.Priority = 0; } } } else { debug_warn(L"Failed to load whiteness texture"); } // Start the simulation m.Simulation2.LoadDefaultScripts(); m.Simulation2.ResetState(); } ActorViewer::~ActorViewer() { delete &m; } CSimulation2* ActorViewer::GetSimulation2() { return &m.Simulation2; } entity_id_t ActorViewer::GetEntity() { return m.Entity; } void ActorViewer::UnloadObjects() { m.ObjectManager.UnloadObjects(); } void ActorViewer::SetActor(const CStrW& name, const CStrW& animation) { bool needsAnimReload = false; CStrW id = name; // Recreate the entity, if we don't have one or if the new one is different if (m.Entity == INVALID_ENTITY || id != m.CurrentUnitID) { // Delete the old entity (if any) if (m.Entity != INVALID_ENTITY) { m.Simulation2.DestroyEntity(m.Entity); m.Simulation2.FlushDestroyedEntities(); m.Entity = INVALID_ENTITY; } // If there's no actor to display, return with nothing loaded if (id.empty()) return; m.Entity = m.Simulation2.AddEntity(L"preview|" + id); if (m.Entity == INVALID_ENTITY) return; CmpPtr cmpPosition(m.Simulation2, m.Entity); if (!cmpPosition.null()) { cmpPosition->JumpTo(entity_pos_t::FromInt(CELL_SIZE*PATCH_SIZE/2), entity_pos_t::FromInt(CELL_SIZE*PATCH_SIZE/2)); cmpPosition->SetYRotation(entity_angle_t::FromFloat((float)M_PI)); } needsAnimReload = true; } if (animation != m.CurrentUnitAnim) needsAnimReload = true; if (needsAnimReload) { CStr anim = CStr(animation).LowerCase(); // Emulate the typical simulation animation behaviour float speed; float repeattime = 0.f; if (anim == "walk") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (!cmpUnitMotion.null()) - speed = cmpUnitMotion->GetSpeed().ToFloat(); + speed = cmpUnitMotion->GetWalkSpeed().ToFloat(); else speed = 7.f; // typical unit speed m.CurrentSpeed = speed; } else if (anim == "run") { CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); if (!cmpUnitMotion.null()) speed = cmpUnitMotion->GetRunSpeed().ToFloat(); else speed = 12.f; // typical unit speed m.CurrentSpeed = speed; } else if (anim == "melee") { speed = 1.f; // speed will be ignored if we have a repeattime m.CurrentSpeed = 0.f; CStr code = "var cmp = Engine.QueryInterface("+CStr(m.Entity)+", IID_Attack); if (cmp) cmp.GetTimers(cmp.GetBestAttack()).repeat; else 0;"; m.Simulation2.GetScriptInterface().Eval(code.c_str(), repeattime); } else { // Play the animation at normal speed, but movement speed is zero speed = 1.f; m.CurrentSpeed = 0.f; } CStr sound; if (anim == "melee") sound = "attack"; else if (anim == "build") sound = "build"; else if (anim.Find("gather_") == 0) sound = anim; std::wstring soundgroup; if (!sound.empty()) { CStr code = "var cmp = Engine.QueryInterface("+CStr(m.Entity)+", IID_Sound); if (cmp) cmp.GetSoundGroup('"+sound+"'); else '';"; m.Simulation2.GetScriptInterface().Eval(code.c_str(), soundgroup); } CmpPtr cmpVisual(m.Simulation2, m.Entity); if (!cmpVisual.null()) { // TODO: SetEntitySelection(anim) cmpVisual->SelectAnimation(anim, false, speed, soundgroup); if (repeattime) cmpVisual->SetAnimationSyncRepeat(repeattime); } } m.CurrentUnitID = id; m.CurrentUnitAnim = animation; } void ActorViewer::SetBackgroundColour(const SColor4ub& colour) { m.Background = colour; m.Terrain.SetBaseColour(colour); } void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; } void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; } void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; } void ActorViewer::SetStatsEnabled(bool enabled) { if (enabled) g_ProfileViewer.ShowTable("renderer"); else g_ProfileViewer.ShowTable(""); } void ActorViewer::Render() { m.Terrain.MakeDirty(RENDERDATA_UPDATE_COLOR); g_Renderer.SetClearColor(m.Background); // Disable shadows locally (avoid clobbering global state) bool oldShadows = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, m.ShadowsEnabled); bool oldSky = g_Renderer.GetSkyManager()->m_RenderSky; g_Renderer.GetSkyManager()->m_RenderSky = false; g_Renderer.BeginFrame(); // Find the centre of the interesting region, in the middle of the patch // and half way up the model (assuming there is one) CVector3D centre; CmpPtr cmpVisual(m.Simulation2, m.Entity); if (!cmpVisual.null()) cmpVisual->GetBounds().GetCentre(centre); else centre.Y = 0.f; centre.X = centre.Z = CELL_SIZE * PATCH_SIZE/2; CCamera camera = View::GetView_Actor()->GetCamera(); camera.m_Orientation.Translate(centre.X, centre.Y, centre.Z); camera.UpdateFrustum(); g_Renderer.SetSceneCamera(camera, camera); g_Renderer.RenderScene(&m); // .... glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); g_ProfileViewer.RenderProfile(); glPopAttrib(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); g_Renderer.EndFrame(); // Restore the old renderer state g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS, oldShadows); g_Renderer.GetSkyManager()->m_RenderSky = oldSky; ogl_WarnIfError(); } void ActorViewer::Update(float dt) { m.Simulation2.Update((int)(dt*1000)); m.Simulation2.Interpolate(dt, 0); if (m.WalkEnabled && m.CurrentSpeed) { CmpPtr cmpPosition(m.Simulation2, m.Entity); if (!cmpPosition.null()) { // Move the model by speed*dt forwards float z = cmpPosition->GetPosition().Z.ToFloat(); z -= m.CurrentSpeed*dt; // Wrap at the edges, so it doesn't run off into the horizon if (z < CELL_SIZE*PATCH_SIZE * 0.4f) z = CELL_SIZE*PATCH_SIZE * 0.6f; cmpPosition->JumpTo(cmpPosition->GetPosition().X, entity_pos_t::FromFloat(z)); } } } Index: ps/trunk/source/simulation2/MessageTypes.h =================================================================== --- ps/trunk/source/simulation2/MessageTypes.h (revision 7762) +++ ps/trunk/source/simulation2/MessageTypes.h (revision 7763) @@ -1,178 +1,195 @@ /* 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_MESSAGETYPES #define INCLUDED_MESSAGETYPES #include "simulation2/system/Components.h" #include "simulation2/system/Entity.h" #include "simulation2/system/Message.h" #include "simulation2/helpers/Position.h" #define DEFAULT_MESSAGE_IMPL(name) \ - virtual EMessageTypeId GetType() const { return MT_##name; } \ + virtual int GetType() const { return MT_##name; } \ virtual const char* GetScriptHandlerName() const { return "On" #name; } \ virtual const char* GetScriptGlobalHandlerName() const { return "OnGlobal" #name; } \ virtual jsval ToJSVal(ScriptInterface& scriptInterface) const; \ static CMessage* FromJSVal(ScriptInterface&, jsval val); class SceneCollector; class CFrustum; class CMessageTurnStart : public CMessage { public: DEFAULT_MESSAGE_IMPL(TurnStart) CMessageTurnStart() { } }; class CMessageUpdate : public CMessage { public: DEFAULT_MESSAGE_IMPL(Update) CMessageUpdate(fixed turnLength) : turnLength(turnLength) { } fixed turnLength; }; class CMessageInterpolate : public CMessage { public: DEFAULT_MESSAGE_IMPL(Interpolate) CMessageInterpolate(float frameTime, float offset) : frameTime(frameTime), offset(offset) { } float frameTime; // time in seconds since previous interpolate float offset; // range [0, 1] (inclusive); fractional time of current frame between previous/next simulation turns }; class CMessageRenderSubmit : public CMessage { public: DEFAULT_MESSAGE_IMPL(RenderSubmit) CMessageRenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) : collector(collector), frustum(frustum), culling(culling) { } SceneCollector& collector; const CFrustum& frustum; bool culling; }; /** + * This is send immediately after a new entity's components have all be created + * and initialised. + */ +class CMessageCreate : public CMessage +{ +public: + DEFAULT_MESSAGE_IMPL(Create) + + CMessageCreate(entity_id_t entity) : + entity(entity) + { + } + + entity_id_t entity; +}; + +/** * This is sent immediately before a destroyed entity is flushed and really destroyed. * (That is, after CComponentManager::DestroyComponentsSoon and inside FlushDestroyedComponents). * The entity will still exist at the time this message is sent. * It's possible for this message to be sent multiple times for one entity, but all its components * will have been deleted after the first time. */ class CMessageDestroy : public CMessage { public: DEFAULT_MESSAGE_IMPL(Destroy) CMessageDestroy(entity_id_t entity) : entity(entity) { } entity_id_t entity; }; class CMessageOwnershipChanged : public CMessage { public: DEFAULT_MESSAGE_IMPL(OwnershipChanged) CMessageOwnershipChanged(entity_id_t entity, int32_t from, int32_t to) : entity(entity), from(from), to(to) { } entity_id_t entity; int32_t from; int32_t to; }; /** * Sent during TurnStart. * * If @c inWorld is false, then the other fields are invalid and meaningless. * Otherwise they represent the current position. */ class CMessagePositionChanged : public CMessage { public: DEFAULT_MESSAGE_IMPL(PositionChanged) CMessagePositionChanged(bool inWorld, entity_pos_t x, entity_pos_t z, entity_angle_t a) : inWorld(inWorld), x(x), z(z), a(a) { } bool inWorld; entity_pos_t x, z; entity_angle_t a; }; /** * Sent by CCmpUnitMotion during Update, whenever the motion status has changed * since the previous update. */ class CMessageMotionChanged : public CMessage { public: DEFAULT_MESSAGE_IMPL(MotionChanged) CMessageMotionChanged(fixed speed) : speed(speed) { } fixed speed; // metres per second, or 0 if not moving }; /** * Sent when terrain (texture or elevation) has been changed. */ class CMessageTerrainChanged : public CMessage { public: DEFAULT_MESSAGE_IMPL(TerrainChanged) CMessageTerrainChanged(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) : i0(i0), j0(j0), i1(i1), j1(j1) { } ssize_t i0, j0, i1, j1; // inclusive lower bound, exclusive upper bound, in tiles }; #endif // INCLUDED_MESSAGETYPES Index: ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 7762) +++ ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 7763) @@ -1,203 +1,219 @@ /* 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 "scriptinterface/ScriptInterface.h" #include "simulation2/MessageTypes.h" #include "js/jsapi.h" #define TOJSVAL_SETUP() \ ScriptInterface::LocalRootScope scope(scriptInterface.GetContext()); \ if (! scope.OK()) \ return JSVAL_VOID; \ JSObject* obj = JS_NewObject(scriptInterface.GetContext(), NULL, NULL, NULL); \ if (! obj) \ return JSVAL_VOID #define SET_MSG_PROPERTY(name) \ do { \ jsval prop = ScriptInterface::ToJSVal(scriptInterface.GetContext(), this->name); \ if (! JS_SetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ return JSVAL_VOID; \ } while (0) #define FROMJSVAL_SETUP() \ if (! JSVAL_IS_OBJECT(val)) \ return NULL; \ JSObject* obj = JSVAL_TO_OBJECT(val) jsval prop; #define GET_MSG_PROPERTY(type, name) \ if (! JS_GetProperty(scriptInterface.GetContext(), obj, #name, &prop)) \ return NULL; \ type name; \ if (! ScriptInterface::FromJSVal(scriptInterface.GetContext(), prop, name)) \ return NULL; //////////////////////////////// jsval CMessageTurnStart::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return JSVAL_VOID; } CMessage* CMessageTurnStart::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val)) { return new CMessageTurnStart(); } //////////////////////////////// jsval CMessageUpdate::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(turnLength); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageUpdate::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(fixed, turnLength); return new CMessageUpdate(turnLength); } //////////////////////////////// jsval CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(frameTime); SET_MSG_PROPERTY(offset); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageInterpolate::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(float, frameTime); GET_MSG_PROPERTY(float, offset); return new CMessageInterpolate(frameTime, offset); } //////////////////////////////// jsval CMessageRenderSubmit::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return JSVAL_VOID; } CMessage* CMessageRenderSubmit::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val)) { return NULL; } //////////////////////////////// +jsval CMessageCreate::ToJSVal(ScriptInterface& scriptInterface) const +{ + TOJSVAL_SETUP(); + SET_MSG_PROPERTY(entity); + return OBJECT_TO_JSVAL(obj); +} + +CMessage* CMessageCreate::FromJSVal(ScriptInterface& scriptInterface, jsval val) +{ + FROMJSVAL_SETUP(); + GET_MSG_PROPERTY(entity_id_t, entity); + return new CMessageCreate(entity); +} + +//////////////////////////////// + jsval CMessageDestroy::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageDestroy::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); return new CMessageDestroy(entity); } //////////////////////////////// jsval CMessageOwnershipChanged::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(entity); SET_MSG_PROPERTY(from); SET_MSG_PROPERTY(to); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageOwnershipChanged::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(entity_id_t, entity); GET_MSG_PROPERTY(int32_t, from); GET_MSG_PROPERTY(int32_t, to); return new CMessageOwnershipChanged(entity, from, to); } //////////////////////////////// jsval CMessagePositionChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return JSVAL_VOID; } CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val)) { return NULL; } //////////////////////////////// jsval CMessageMotionChanged::ToJSVal(ScriptInterface& scriptInterface) const { TOJSVAL_SETUP(); SET_MSG_PROPERTY(speed); return OBJECT_TO_JSVAL(obj); } CMessage* CMessageMotionChanged::FromJSVal(ScriptInterface& scriptInterface, jsval val) { FROMJSVAL_SETUP(); GET_MSG_PROPERTY(fixed, speed); return new CMessageMotionChanged(speed); } //////////////////////////////// jsval CMessageTerrainChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return JSVAL_VOID; } CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val)) { return NULL; } //////////////////////////////////////////////////////////////// CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, jsval val) { switch (mtid) { #define MESSAGE(name) case MT_##name: return CMessage##name::FromJSVal(scriptingInterface, val); #define INTERFACE(name) #define COMPONENT(name) #include "simulation2/TypeList.h" #undef COMPONENT #undef INTERFACE #undef MESSAGE } return NULL; } Index: ps/trunk/source/simulation2/TypeList.h =================================================================== --- ps/trunk/source/simulation2/TypeList.h (revision 7762) +++ ps/trunk/source/simulation2/TypeList.h (revision 7763) @@ -1,113 +1,114 @@ /* 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 . */ // MESSAGE: message types // INTERFACE: component interface types // COMPONENT: component types // Components intended only for use in test cases: // (The tests rely on the enum IDs, so don't change the order of these) INTERFACE(Test1) COMPONENT(Test1A) COMPONENT(Test1B) COMPONENT(Test1Scripted) INTERFACE(Test2) COMPONENT(Test2A) COMPONENT(Test2Scripted) // Message types: MESSAGE(TurnStart) MESSAGE(Update) MESSAGE(Interpolate) // non-deterministic (use with caution) MESSAGE(RenderSubmit) // non-deterministic (use with caution) +MESSAGE(Create) MESSAGE(Destroy) MESSAGE(OwnershipChanged) MESSAGE(PositionChanged) MESSAGE(MotionChanged) MESSAGE(TerrainChanged) // TemplateManager must come before all other (non-test) components, // so that it is the first to be (de)serialized INTERFACE(TemplateManager) COMPONENT(TemplateManager) // Special component for script component types with no native interface INTERFACE(UnknownScript) COMPONENT(UnknownScript) // In alphabetical order: INTERFACE(CommandQueue) COMPONENT(CommandQueue) INTERFACE(Footprint) COMPONENT(Footprint) INTERFACE(GuiInterface) COMPONENT(GuiInterfaceScripted) INTERFACE(Minimap) COMPONENT(Minimap) INTERFACE(Motion) COMPONENT(MotionBall) COMPONENT(MotionScripted) INTERFACE(Obstruction) COMPONENT(Obstruction) INTERFACE(ObstructionManager) COMPONENT(ObstructionManager) INTERFACE(Ownership) COMPONENT(Ownership) INTERFACE(Pathfinder) COMPONENT(Pathfinder) INTERFACE(Player) COMPONENT(PlayerScripted) INTERFACE(PlayerManager) COMPONENT(PlayerManagerScripted) INTERFACE(Position) COMPONENT(Position) // must be before VisualActor INTERFACE(ProjectileManager) COMPONENT(ProjectileManager) INTERFACE(Selectable) COMPONENT(Selectable) INTERFACE(SoundManager) COMPONENT(SoundManager) INTERFACE(StatusBars) COMPONENT(StatusBars) INTERFACE(Terrain) COMPONENT(Terrain) INTERFACE(UnitMotion) COMPONENT(UnitMotion) // must be after Obstruction INTERFACE(Visual) COMPONENT(VisualActor) INTERFACE(WaterManager) COMPONENT(WaterManager) Index: ps/trunk/source/simulation2/system/ComponentManager.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.h (revision 7762) +++ ps/trunk/source/simulation2/system/ComponentManager.h (revision 7763) @@ -1,260 +1,264 @@ /* 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_COMPONENTMANAGER #define INCLUDED_COMPONENTMANAGER #include "Entity.h" #include "Components.h" #include "scriptinterface/ScriptInterface.h" #include #include class IComponent; class CParamNode; class CMessage; class CSimContext; class CComponentManager { NONCOPYABLE(CComponentManager); public: // We can't use EInterfaceId/etc directly, since scripts dynamically generate new IDs // and casting arbitrary ints to enums is undefined behaviour, so use 'int' typedefs typedef int InterfaceId; typedef int ComponentTypeId; typedef int MessageTypeId; private: // Component allocation types typedef IComponent* (*AllocFunc)(ScriptInterface& scriptInterface, jsval ctor); typedef void (*DeallocFunc)(IComponent*); // ComponentTypes come in three types: // Native: normal C++ component // ScriptWrapper: C++ component that wraps a JS component implementation // Script: a ScriptWrapper linked to a specific JS component implementation enum EComponentTypeType { CT_Native, CT_ScriptWrapper, CT_Script }; // Representation of a component type, to be used when instantiating components struct ComponentType { EComponentTypeType type; InterfaceId iid; AllocFunc alloc; DeallocFunc dealloc; std::string name; std::string schema; // RelaxNG fragment jsval ctor; // only valid if type == CT_Script }; public: CComponentManager(CSimContext&, bool skipScriptFunctions = false); ~CComponentManager(); void LoadComponentTypes(); /** * Load a script and execute it in a new function scope. * @param filename VFS path to load * @param hotload set to true if this script has been loaded before, and redefinitions of * existing components should not be considered errors */ bool LoadScript(const std::wstring& filename, bool hotload = false); void RegisterMessageType(MessageTypeId mtid, const char* name); void RegisterComponentType(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); void RegisterComponentTypeScriptWrapper(InterfaceId, ComponentTypeId, AllocFunc, DeallocFunc, const char*, const std::string& schema); /** * Subscribe the current component type to the given message type. * Each component's HandleMessage will be called on any BroadcastMessage of this message type, * or on any PostMessage of this type targeted at the component's entity. * Must only be called by a component type's ClassInit. */ void SubscribeToMessageType(MessageTypeId mtid); /** * Subscribe the current component type to all messages of the given message type. * Each component's HandleMessage will be called on any BroadcastMessage or PostMessage of this message type, * regardless of the entity. * Must only be called by a component type's ClassInit. */ void SubscribeGloballyToMessageType(MessageTypeId mtid); /** * @param cname Requested component type name (not including any "CID" or "CCmp" prefix) * @return The component type id, or CID__Invalid if not found */ ComponentTypeId LookupCID(const std::string& cname) const; /** * @return The name of the given component type, or "" if not found */ std::string LookupComponentTypeName(ComponentTypeId cid) const; /** * Returns a new entity ID that has never been used before. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(); /** * Returns a new local entity ID that has never been used before. * This entity will not be synchronised over the network, stored in saved games, etc. */ entity_id_t AllocateNewLocalEntity(); /** * Returns a new entity ID that has never been used before. * If possible, returns preferredId, and ensures this ID won't be allocated again. * This affects the simulation state so it must only be called in network-synchronised ways. */ entity_id_t AllocateNewEntity(entity_id_t preferredId); /** * Constructs a component of type 'cid', initialised with data 'paramNode', * and attaches it to entity 'ent'. * * @return true on success; false on failure, and logs an error message */ bool AddComponent(entity_id_t ent, ComponentTypeId cid, const CParamNode& paramNode); /** * Adds an externally-created component, so that it is returned by QueryInterface * but does not get destroyed and does not receive messages from the component manager. * (This is intended for unit tests that need to add mock objects the tested components * expect to exist.) */ void AddMockComponent(entity_id_t ent, InterfaceId iid, IComponent& component); /** * Allocates a component object of type 'cid', and attaches it to entity 'ent'. * (The component's Init is not called here - either Init or Deserialize must be called * before using the returned object.) */ IComponent* ConstructComponent(entity_id_t ent, ComponentTypeId cid); /** * Constructs an entity based on the given template, and adds it the world with * entity ID @p ent. There should not be any existing components with that entity ID. * @return ent, or INVALID_ENTITY on error */ entity_id_t AddEntity(const std::wstring& templateName, entity_id_t ent); /** * Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called. * Has no effect if the entity does not exist, or has already been added to the destruction queue. */ void DestroyComponentsSoon(entity_id_t ent); /** * Does the actual destruction of components from DestroyComponentsSoon. * This must not be called if the component manager is on the call stack (since it * will break internal iterators). */ void FlushDestroyedComponents(); IComponent* QueryInterface(entity_id_t ent, InterfaceId iid) const; const std::map& GetEntitiesWithInterface(InterfaceId iid) const; /** * Send a message, targeted at a particular entity. The message will be received by any * components of that entity which subscribed to the message type, and by any other components * that subscribed globally to the message type. */ void PostMessage(entity_id_t ent, const CMessage& msg) const; /** * Send a message, not targeted at any particular entity. The message will be received by any * components that subscribed (either globally or not) to the message type. */ void BroadcastMessage(const CMessage& msg) const; /** * Resets the dynamic simulation state (deletes all entities, resets entity ID counters; * doesn't unload/reload component scripts). */ void ResetState(); // Various state serialization functions: bool ComputeStateHash(std::string& outHash); bool DumpDebugState(std::ostream& stream); // FlushDestroyedComponents must be called before SerializeState (since the destruction queue // won't get serialized) bool SerializeState(std::ostream& stream); bool DeserializeState(std::istream& stream); std::string GenerateSchema(); ScriptInterface& GetScriptInterface() { return m_ScriptInterface; } private: // Implementations of functions exposed to scripts static void Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor); static void Script_RegisterInterface(void* cbdata, std::string name); + static void Script_RegisterMessageType(void* cbdata, std::string name); static void Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value); static IComponent* Script_QueryInterface(void* cbdata, int ent, int iid); static void Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data); static void Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data); static int Script_AddEntity(void* cbdata, std::string templateName); static int Script_AddLocalEntity(void* cbdata, std::string templateName); static void Script_DestroyEntity(void* cbdata, int ent); + CMessage* ConstructMessage(int mtid, CScriptVal data); void SendGlobalMessage(const CMessage& msg) const; ComponentTypeId GetScriptWrapper(InterfaceId iid); ScriptInterface m_ScriptInterface; const CSimContext& m_SimContext; ComponentTypeId m_CurrentComponent; // used when loading component types bool m_CurrentlyHotloading; // TODO: some of these should be vectors std::map m_ComponentTypesById; std::map > m_ComponentsByInterface; std::map > m_ComponentsByTypeId; std::map > m_LocalMessageSubscriptions; std::map > m_GlobalMessageSubscriptions; std::map m_ComponentTypeIdsByName; std::map m_MessageTypeIdsByName; + std::map m_MessageTypeNamesById; std::map m_InterfaceIdsByName; + // TODO: maintaining both ComponentsBy* is nasty; can we get rid of one, // while keeping QueryInterface and PostMessage sufficiently efficient? std::vector m_DestructionQueue; ComponentTypeId m_NextScriptComponentTypeId; entity_id_t m_NextEntityId; entity_id_t m_NextLocalEntityId; boost::rand48 m_RNG; friend class TestComponentManager; }; #endif // INCLUDED_COMPONENTMANAGER Index: ps/trunk/source/simulation2/system/Message.h =================================================================== --- ps/trunk/source/simulation2/system/Message.h (revision 7762) +++ ps/trunk/source/simulation2/system/Message.h (revision 7763) @@ -1,40 +1,40 @@ /* 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_MESSAGE #define INCLUDED_MESSAGE #include "scriptinterface/ScriptTypes.h" class CMessage { NONCOPYABLE(CMessage); protected: CMessage() { } public: virtual ~CMessage() { } - virtual EMessageTypeId GetType() const = 0; + virtual int GetType() const = 0; virtual const char* GetScriptHandlerName() const = 0; virtual const char* GetScriptGlobalHandlerName() const = 0; virtual jsval ToJSVal(ScriptInterface&) const = 0; }; // TODO: GetType could be replaced with a plain member variable to avoid some // virtual calls, if that turns out to be worthwhile CMessage* CMessageFromJSVal(int mtid, ScriptInterface&, jsval); #endif // INCLUDED_MESSAGE Index: ps/trunk/source/simulation2/system/ComponentManager.cpp =================================================================== --- ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 7762) +++ ps/trunk/source/simulation2/system/ComponentManager.cpp (revision 7763) @@ -1,805 +1,870 @@ /* 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 "ComponentManager.h" #include "IComponent.h" #include "ParamNode.h" #include "SimContext.h" #include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/MessageTypes.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" +/** + * Used for script-only message types. + */ +class CMessageScripted : public CMessage +{ +public: + virtual int GetType() const { return mtid; } + virtual const char* GetScriptHandlerName() const { return handlerName.c_str(); } + virtual const char* GetScriptGlobalHandlerName() const { return globalHandlerName.c_str(); } + virtual jsval ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const { return msg.get(); } + + CMessageScripted(int mtid, const std::string& name, const CScriptValRooted& msg) : + mtid(mtid), handlerName("On" + name), globalHandlerName("OnGlobal" + name), msg(msg) + { + } + + int mtid; + std::string handlerName; + std::string globalHandlerName; + CScriptValRooted msg; +}; + CComponentManager::CComponentManager(CSimContext& context, bool skipScriptFunctions) : m_NextScriptComponentTypeId(CID__LastNative), m_ScriptInterface("Engine"), m_SimContext(context), m_CurrentlyHotloading(false) { context.SetComponentManager(this); m_ScriptInterface.SetCallbackData(static_cast (this)); // TODO: ought to seed the RNG (in a network-synchronised way) before we use it m_ScriptInterface.ReplaceNondeterministicFunctions(m_RNG); // For component script tests, the test system sets up its own scripted implementation of // these functions, so we skip registering them here in those cases if (!skipScriptFunctions) { m_ScriptInterface.RegisterFunction ("RegisterComponentType"); m_ScriptInterface.RegisterFunction ("RegisterInterface"); + m_ScriptInterface.RegisterFunction ("RegisterMessageType"); m_ScriptInterface.RegisterFunction ("RegisterGlobal"); m_ScriptInterface.RegisterFunction ("QueryInterface"); m_ScriptInterface.RegisterFunction ("PostMessage"); m_ScriptInterface.RegisterFunction ("BroadcastMessage"); m_ScriptInterface.RegisterFunction ("AddEntity"); m_ScriptInterface.RegisterFunction ("AddLocalEntity"); m_ScriptInterface.RegisterFunction ("DestroyEntity"); } // Define MT_*, IID_* as script globals, and store their names #define MESSAGE(name) m_ScriptInterface.SetGlobal("MT_" #name, (int)MT_##name); #define INTERFACE(name) \ m_ScriptInterface.SetGlobal("IID_" #name, (int)IID_##name); \ m_InterfaceIdsByName[#name] = IID_##name; #define COMPONENT(name) #include "simulation2/TypeList.h" #undef MESSAGE #undef INTERFACE #undef COMPONENT m_ScriptInterface.SetGlobal("SYSTEM_ENTITY", (int)SYSTEM_ENTITY); ResetState(); } CComponentManager::~CComponentManager() { ResetState(); // Release GC roots std::map::iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.type == CT_Script) m_ScriptInterface.RemoveRoot(&it->second.ctor); } void CComponentManager::LoadComponentTypes() { #define MESSAGE(name) \ RegisterMessageType(MT_##name, #name); #define INTERFACE(name) \ extern void RegisterComponentInterface_##name(ScriptInterface&); \ RegisterComponentInterface_##name(m_ScriptInterface); #define COMPONENT(name) \ extern void RegisterComponentType_##name(CComponentManager&); \ m_CurrentComponent = CID_##name; \ RegisterComponentType_##name(*this); #include "simulation2/TypeList.h" m_CurrentComponent = CID__Invalid; #undef MESSAGE #undef INTERFACE #undef COMPONENT } bool CComponentManager::LoadScript(const std::wstring& filename, bool hotload) { m_CurrentlyHotloading = hotload; CVFSFile file; PSRETURN loadOk = file.Load(g_VFS, filename); debug_assert(loadOk == PSRETURN_OK); // TODO std::wstring content(file.GetBuffer(), file.GetBuffer() + file.GetBufferSize()); // TODO: encodings etc bool ok = m_ScriptInterface.LoadScript(filename, content); return ok; } void CComponentManager::Script_RegisterComponentType(void* cbdata, int iid, std::string cname, CScriptVal ctor) { CComponentManager* componentManager = static_cast (cbdata); // Find the C++ component that wraps the interface int cidWrapper = componentManager->GetScriptWrapper(iid); if (cidWrapper == CID__Invalid) { componentManager->m_ScriptInterface.ReportError("Invalid interface id"); return; } const ComponentType& ctWrapper = componentManager->m_ComponentTypesById[cidWrapper]; bool mustReloadComponents = false; // for hotloading ComponentTypeId cid = componentManager->LookupCID(cname); if (cid == CID__Invalid) { // Allocate a new cid number cid = componentManager->m_NextScriptComponentTypeId++; componentManager->m_ComponentTypeIdsByName[cname] = cid; } else { // Component type is already loaded, so do hotloading: if (!componentManager->m_CurrentlyHotloading) { componentManager->m_ScriptInterface.ReportError("Registering component type with already-registered name"); // TODO: report the actual name return; } const ComponentType& ctPrevious = componentManager->m_ComponentTypesById[cid]; // We can only replace scripted component types, not native ones if (ctPrevious.type != CT_Script) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type with same name as native component"); return; } // We don't support changing the IID of a component type (it would require fiddling // around with m_ComponentsByInterface and being careful to guarantee uniqueness per entity) if (ctPrevious.iid != iid) { // ...though it only matters if any components exist with this type if (!componentManager->m_ComponentsByTypeId[cid].empty()) { componentManager->m_ScriptInterface.ReportError("Hotloading script component type mustn't change interface ID"); return; } } // Clean up the old component type componentManager->m_ScriptInterface.RemoveRoot(&componentManager->m_ComponentTypesById[cid].ctor); // Remove its old message subscriptions std::map >::iterator it; for (it = componentManager->m_LocalMessageSubscriptions.begin(); it != componentManager->m_LocalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } for (it = componentManager->m_GlobalMessageSubscriptions.begin(); it != componentManager->m_GlobalMessageSubscriptions.end(); ++it) { std::vector& types = it->second; std::vector::iterator ctit = find(types.begin(), types.end(), cid); if (ctit != types.end()) types.erase(ctit); } mustReloadComponents = true; } std::string schema = ""; { CScriptValRooted prototype; if (componentManager->m_ScriptInterface.GetProperty(ctor.get(), "prototype", prototype) && componentManager->m_ScriptInterface.HasProperty(prototype.get(), "Schema")) { componentManager->m_ScriptInterface.GetProperty(prototype.get(), "Schema", schema); } } // Construct a new ComponentType, using the wrapper's alloc functions ComponentType ct = { CT_Script, iid, ctWrapper.alloc, ctWrapper.dealloc, cname, schema, ctor.get() }; componentManager->m_ComponentTypesById[cid] = ct; componentManager->m_CurrentComponent = cid; // needed by Subscribe // Stop the ctor getting GCed componentManager->m_ScriptInterface.AddRoot(&componentManager->m_ComponentTypesById[cid].ctor, "ComponentType ctor"); // TODO: check carefully that roots will never get leaked etc // Find all the ctor prototype's On* methods, and subscribe to the appropriate messages: CScriptVal proto; if (!componentManager->m_ScriptInterface.GetProperty(ctor.get(), "prototype", proto)) return; // error std::vector methods; if (!componentManager->m_ScriptInterface.EnumeratePropertyNamesWithPrefix(proto.get(), "On", methods)) return; // error for (std::vector::const_iterator it = methods.begin(); it != methods.end(); ++it) { std::string name = (*it).substr(2); // strip the "On" prefix // Handle "OnGlobalFoo" functions specially bool isGlobal = false; if (name.substr(0, 6) == "Global") { isGlobal = true; name = name.substr(6); } std::map::const_iterator mit = componentManager->m_MessageTypeIdsByName.find(name); if (mit == componentManager->m_MessageTypeIdsByName.end()) { std::string msg = "Registered component has unrecognised '" + *it + "' message handler method"; componentManager->m_ScriptInterface.ReportError(msg.c_str()); return; } if (isGlobal) componentManager->SubscribeGloballyToMessageType(mit->second); else componentManager->SubscribeToMessageType(mit->second); } componentManager->m_CurrentComponent = CID__Invalid; if (mustReloadComponents) { // For every script component with this cid, we need to switch its // prototype from the old constructor's prototype property to the new one's const std::map& comps = componentManager->m_ComponentsByTypeId[cid]; std::map::const_iterator eit = comps.begin(); for (; eit != comps.end(); ++eit) { jsval instance = eit->second->GetJSInstance(); if (instance) componentManager->m_ScriptInterface.SetPrototype(instance, proto.get()); } } } void CComponentManager::Script_RegisterInterface(void* cbdata, std::string name) { CComponentManager* componentManager = static_cast (cbdata); std::map::iterator it = componentManager->m_InterfaceIdsByName.find(name); if (it != componentManager->m_InterfaceIdsByName.end()) { // Redefinitions are fine (and just get ignored) when hotloading; otherwise // they're probably unintentional and should be reported if (!componentManager->m_CurrentlyHotloading) componentManager->m_ScriptInterface.ReportError("Registering interface with already-registered name"); // TODO: report the actual name return; } // IIDs start at 1, so size+1 is the next unused one size_t id = componentManager->m_InterfaceIdsByName.size() + 1; componentManager->m_InterfaceIdsByName[name] = id; - componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int )id); + componentManager->m_ScriptInterface.SetGlobal(("IID_" + name).c_str(), (int)id); +} + +void CComponentManager::Script_RegisterMessageType(void* cbdata, std::string name) +{ + CComponentManager* componentManager = static_cast (cbdata); + + std::map::iterator it = componentManager->m_MessageTypeIdsByName.find(name); + if (it != componentManager->m_MessageTypeIdsByName.end()) + { + // Redefinitions are fine (and just get ignored) when hotloading; otherwise + // they're probably unintentional and should be reported + if (!componentManager->m_CurrentlyHotloading) + componentManager->m_ScriptInterface.ReportError("Registering message type with already-registered name"); // TODO: report the actual name + return; + } + + // MTIDs start at 1, so size+1 is the next unused one + size_t id = componentManager->m_MessageTypeIdsByName.size() + 1; + componentManager->RegisterMessageType(id, name.c_str()); + componentManager->m_ScriptInterface.SetGlobal(("MT_" + name).c_str(), (int)id); } void CComponentManager::Script_RegisterGlobal(void* cbdata, std::string name, CScriptVal value) { CComponentManager* componentManager = static_cast (cbdata); // Set the value, and accept duplicates only if hotloading (otherwise it's an error, // in order to detect accidental duplicate definitions of globals) componentManager->m_ScriptInterface.SetGlobal(name.c_str(), value, componentManager->m_CurrentlyHotloading); } IComponent* CComponentManager::Script_QueryInterface(void* cbdata, int ent, int iid) { CComponentManager* componentManager = static_cast (cbdata); IComponent* component = componentManager->QueryInterface((entity_id_t)ent, iid); return component; } +CMessage* CComponentManager::ConstructMessage(int mtid, CScriptVal data) +{ + if (mtid == MT__Invalid || mtid > (int)m_MessageTypeIdsByName.size()) // (IDs start at 1 so use '>' here) + LOGERROR(L"PostMessage with invalid message type ID '%d'", mtid); + + if (mtid < MT__LastNative) + { + return CMessageFromJSVal(mtid, m_ScriptInterface, data.get()); + } + else + { + return new CMessageScripted(mtid, m_MessageTypeNamesById[mtid], + CScriptValRooted(m_ScriptInterface.GetContext(), data)); + } +} + void CComponentManager::Script_PostMessage(void* cbdata, int ent, int mtid, CScriptVal data) { CComponentManager* componentManager = static_cast (cbdata); - CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get()); + + CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->PostMessage(ent, *msg); delete msg; } void CComponentManager::Script_BroadcastMessage(void* cbdata, int mtid, CScriptVal data) { CComponentManager* componentManager = static_cast (cbdata); - CMessage* msg = CMessageFromJSVal(mtid, componentManager->m_ScriptInterface, data.get()); + + CMessage* msg = componentManager->ConstructMessage(mtid, data); if (!msg) return; // error componentManager->BroadcastMessage(*msg); delete msg; } int CComponentManager::Script_AddEntity(void* cbdata, std::string templateName) { CComponentManager* componentManager = static_cast (cbdata); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewEntity()); return (int)ent; } int CComponentManager::Script_AddLocalEntity(void* cbdata, std::string templateName) { CComponentManager* componentManager = static_cast (cbdata); std::wstring name(templateName.begin(), templateName.end()); // TODO: should validate the string to make sure it doesn't contain scary characters // that will let it access non-component-template files entity_id_t ent = componentManager->AddEntity(name, componentManager->AllocateNewLocalEntity()); return (int)ent; } void CComponentManager::Script_DestroyEntity(void* cbdata, int ent) { CComponentManager* componentManager = static_cast (cbdata); componentManager->DestroyComponentsSoon(ent); } void CComponentManager::ResetState() { // Delete all IComponents std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.begin(); for (; eit != iit->second.end(); ++eit) { eit->second->Deinit(m_SimContext); m_ComponentTypesById[iit->first].dealloc(eit->second); } } m_ComponentsByInterface.clear(); m_ComponentsByTypeId.clear(); m_DestructionQueue.clear(); // Reset IDs m_NextEntityId = SYSTEM_ENTITY + 1; m_NextLocalEntityId = FIRST_LOCAL_ENTITY; } void CComponentManager::RegisterComponentType(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c = { CT_Native, iid, alloc, dealloc, name, schema, 0 }; m_ComponentTypesById.insert(std::make_pair(cid, c)); m_ComponentTypeIdsByName[name] = cid; } void CComponentManager::RegisterComponentTypeScriptWrapper(InterfaceId iid, ComponentTypeId cid, AllocFunc alloc, DeallocFunc dealloc, const char* name, const std::string& schema) { ComponentType c = { CT_ScriptWrapper, iid, alloc, dealloc, name, schema, 0 }; m_ComponentTypesById.insert(std::make_pair(cid, c)); m_ComponentTypeIdsByName[name] = cid; // TODO: merge with RegisterComponentType } void CComponentManager::RegisterMessageType(MessageTypeId mtid, const char* name) { m_MessageTypeIdsByName[name] = mtid; + m_MessageTypeNamesById[mtid] = name; } void CComponentManager::SubscribeToMessageType(MessageTypeId mtid) { // TODO: verify mtid debug_assert(m_CurrentComponent != CID__Invalid); std::vector& types = m_LocalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } void CComponentManager::SubscribeGloballyToMessageType(MessageTypeId mtid) { // TODO: verify mtid debug_assert(m_CurrentComponent != CID__Invalid); std::vector& types = m_GlobalMessageSubscriptions[mtid]; types.push_back(m_CurrentComponent); std::sort(types.begin(), types.end()); // TODO: just sort once at the end of LoadComponents } CComponentManager::ComponentTypeId CComponentManager::LookupCID(const std::string& cname) const { std::map::const_iterator it = m_ComponentTypeIdsByName.find(cname); if (it == m_ComponentTypeIdsByName.end()) return CID__Invalid; return it->second; } std::string CComponentManager::LookupComponentTypeName(ComponentTypeId cid) const { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) return ""; return it->second.name; } CComponentManager::ComponentTypeId CComponentManager::GetScriptWrapper(InterfaceId iid) { if (iid >= IID__LastNative && iid <= (int)m_InterfaceIdsByName.size()) // use <= since IDs start at 1 return CID_UnknownScript; std::map::const_iterator it = m_ComponentTypesById.begin(); for (; it != m_ComponentTypesById.end(); ++it) if (it->second.iid == iid && it->second.type == CT_ScriptWrapper) return it->first; LOGERROR(L"No script wrapper found for interface id %d", iid); // TODO: report name (if iid is valid at all) return CID__Invalid; } entity_id_t CComponentManager::AllocateNewEntity() { entity_id_t id = m_NextEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewLocalEntity() { entity_id_t id = m_NextLocalEntityId++; // TODO: check for overflow return id; } entity_id_t CComponentManager::AllocateNewEntity(entity_id_t preferredId) { // TODO: ensure this ID hasn't been allocated before // (this might occur with broken map files) entity_id_t id = preferredId; // Ensure this ID won't be allocated again if (id >= m_NextEntityId) m_NextEntityId = id+1; // TODO: check for overflow return id; } bool CComponentManager::AddComponent(entity_id_t ent, ComponentTypeId cid, const CParamNode& paramNode) { IComponent* component = ConstructComponent(ent, cid); if (!component) return false; component->Init(m_SimContext, paramNode); return true; } IComponent* CComponentManager::ConstructComponent(entity_id_t ent, ComponentTypeId cid) { std::map::const_iterator it = m_ComponentTypesById.find(cid); if (it == m_ComponentTypesById.end()) { LOGERROR(L"Invalid component id %d", cid); return NULL; } const ComponentType& ct = it->second; std::map& emap1 = m_ComponentsByInterface[ct.iid]; if (emap1.find(ent) != emap1.end()) { LOGERROR(L"Multiple components for interface %d", ct.iid); return NULL; } std::map& emap2 = m_ComponentsByTypeId[cid]; // If this is a scripted component, construct the appropriate JS object first jsval obj = 0; if (ct.type == CT_Script) { obj = m_ScriptInterface.CallConstructor(ct.ctor); if (!obj) { LOGERROR(L"Script component constructor failed"); return NULL; } } // Construct the new component IComponent* component = ct.alloc(m_ScriptInterface, obj); debug_assert(component); component->SetEntityId(ent); component->SetSimContext(m_SimContext); // Store a reference to the new component emap1.insert(std::make_pair(ent, component)); emap2.insert(std::make_pair(ent, component)); // TODO: We need to more careful about this - if an entity is constructed by a component // while we're iterating over all components, this will invalidate the iterators and everything // will break. // We probably need some kind of delayed addition, so they get pushed onto a queue and then // inserted into the world later on. (Be careful about immediation deletion in that case, too.) return component; } void CComponentManager::AddMockComponent(entity_id_t ent, InterfaceId iid, IComponent& component) { // Just add it into the by-interface map, not the by-component-type map, // so it won't be considered for messages or deletion etc std::map& emap1 = m_ComponentsByInterface[iid]; if (emap1.find(ent) != emap1.end()) debug_warn(L"Multiple components for interface"); emap1.insert(std::make_pair(ent, &component)); } entity_id_t CComponentManager::AddEntity(const std::wstring& templateName, entity_id_t ent) { ICmpTemplateManager *tempMan = static_cast (QueryInterface(SYSTEM_ENTITY, IID_TemplateManager)); if (!tempMan) { debug_warn(L"No ICmpTemplateManager loaded"); return INVALID_ENTITY; } // TODO: should assert that ent doesn't exist const CParamNode* tmpl = tempMan->LoadTemplate(ent, CStr8(templateName), -1); if (!tmpl) return INVALID_ENTITY; // LoadTemplate will have reported the error // Construct a component for each child of the root element const CParamNode::ChildrenMap& tmplChilds = tmpl->GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = tmplChilds.begin(); it != tmplChilds.end(); ++it) { // Ignore attributes on the root element if (it->first.length() && it->first[0] == '@') continue; CComponentManager::ComponentTypeId cid = LookupCID(it->first); if (cid == CID__Invalid) { LOGERROR(L"Unrecognised component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str()); return INVALID_ENTITY; } if (!AddComponent(ent, cid, it->second)) { LOGERROR(L"Failed to construct component type name '%hs' in entity template '%ls'", it->first.c_str(), templateName.c_str()); return INVALID_ENTITY; } // TODO: maybe we should delete already-constructed components if one of them fails? } + CMessageCreate msg(ent); + PostMessage(ent, msg); + return ent; } void CComponentManager::DestroyComponentsSoon(entity_id_t ent) { m_DestructionQueue.push_back(ent); } void CComponentManager::FlushDestroyedComponents() { // Make a copy of the destruction queue, so that the iterators won't be invalidated if the // CMessageDestroy handlers try to destroy more entities themselves std::vector queue; queue.swap(m_DestructionQueue); for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { entity_id_t ent = *it; CMessageDestroy msg(ent); PostMessage(ent, msg); // Destroy the components, and remove from m_ComponentsByTypeId: std::map >::iterator iit = m_ComponentsByTypeId.begin(); for (; iit != m_ComponentsByTypeId.end(); ++iit) { std::map::iterator eit = iit->second.find(ent); if (eit != iit->second.end()) { eit->second->Deinit(m_SimContext); m_ComponentTypesById[iit->first].dealloc(eit->second); iit->second.erase(ent); } } // Remove from m_ComponentsByInterface std::map >::iterator ifcit = m_ComponentsByInterface.begin(); for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) { ifcit->second.erase(ent); } } } IComponent* CComponentManager::QueryInterface(entity_id_t ent, InterfaceId iid) const { std::map >::const_iterator iit = m_ComponentsByInterface.find(iid); if (iit == m_ComponentsByInterface.end()) { // Invalid iid, or no entities implement this interface return NULL; } std::map::const_iterator eit = iit->second.find(ent); if (eit == iit->second.end()) { // This entity doesn't implement this interface return NULL; } return eit->second; } static std::map g_EmptyEntityMap; const std::map& CComponentManager::GetEntitiesWithInterface(InterfaceId iid) const { std::map >::const_iterator iit = m_ComponentsByInterface.find(iid); if (iit == m_ComponentsByInterface.end()) { // Invalid iid, or no entities implement this interface return g_EmptyEntityMap; } return iit->second; } void CComponentManager::PostMessage(entity_id_t ent, const CMessage& msg) const { // Send the message to components of ent, that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; std::map::const_iterator eit = emap->second.find(ent); if (eit != emap->second.end()) eit->second->HandleMessage(m_SimContext, msg, false); } } SendGlobalMessage(msg); } void CComponentManager::BroadcastMessage(const CMessage& msg) const { // Send the message to components of all entities that subscribed locally to this message std::map >::const_iterator it; it = m_LocalMessageSubscriptions.find(msg.GetType()); if (it != m_LocalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(m_SimContext, msg, false); } } SendGlobalMessage(msg); } void CComponentManager::SendGlobalMessage(const CMessage& msg) const { // (Common functionality for PostMessage and BroadcastMessage) // Send the message to components of all entities that subscribed globally to this message std::map >::const_iterator it; it = m_GlobalMessageSubscriptions.find(msg.GetType()); if (it != m_GlobalMessageSubscriptions.end()) { std::vector::const_iterator ctit = it->second.begin(); for (; ctit != it->second.end(); ++ctit) { std::map >::const_iterator emap = m_ComponentsByTypeId.find(*ctit); if (emap == m_ComponentsByTypeId.end()) continue; std::map::const_iterator eit = emap->second.begin(); for (; eit != emap->second.end(); ++eit) eit->second->HandleMessage(m_SimContext, msg, true); } } } std::string CComponentManager::GenerateSchema() { std::string schema = "" "" "0" "" "" "0" "" "" "" "" "" "" "" "" "" "" "" "" ""; std::map > interfaceComponentTypes; std::vector componentTypes; for (std::map::const_iterator it = m_ComponentTypesById.begin(); it != m_ComponentTypesById.end(); ++it) { schema += "" "" "" + it->second.schema + "" "" ""; interfaceComponentTypes[it->second.iid].push_back(it->second.name); componentTypes.push_back(it->second.name); } // Declare the implementation of each interface, for documentation for (std::map::const_iterator it = m_InterfaceIdsByName.begin(); it != m_InterfaceIdsByName.end(); ++it) { schema += ""; std::vector& cts = interfaceComponentTypes[it->second]; for (size_t i = 0; i < cts.size(); ++i) schema += ""; schema += ""; } // List all the component types, in alphabetical order (to match the reordering performed by CParamNode). // (We do it this way, rather than ing all the interface definitions (which would additionally perform // a check that we don't use multiple component types of the same interface in one file), because libxml2 gives // useless error messages in the latter case; this way lets it report the real error.) std::sort(componentTypes.begin(), componentTypes.end()); schema += "" "" ""; for (std::vector::const_iterator it = componentTypes.begin(); it != componentTypes.end(); ++it) schema += ""; schema += "" ""; schema += ""; // TODO: pretty-print return schema; } Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp (revision 7762) +++ ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp (revision 7763) @@ -1,30 +1,32 @@ /* 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 "ICmpUnitMotion.h" #include "simulation2/system/InterfaceScripted.h" BEGIN_INTERFACE_WRAPPER(UnitMotion) DEFINE_INTERFACE_METHOD_2("MoveToPoint", bool, ICmpUnitMotion, MoveToPoint, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_3("IsInAttackRange", bool, ICmpUnitMotion, IsInAttackRange, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_3("MoveToAttackRange", bool, ICmpUnitMotion, MoveToAttackRange, entity_id_t, entity_pos_t, entity_pos_t) -DEFINE_INTERFACE_METHOD_0("GetSpeed", fixed, ICmpUnitMotion, GetSpeed) +DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) +DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving) +DEFINE_INTERFACE_METHOD_1("SetSpeedFactor", void, ICmpUnitMotion, SetSpeedFactor, fixed) DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitMotion, SetDebugOverlay, bool) END_INTERFACE_WRAPPER(UnitMotion) Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpUnitMotion.h (revision 7762) +++ ps/trunk/source/simulation2/components/ICmpUnitMotion.h (revision 7763) @@ -1,81 +1,96 @@ /* 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_ICMPUNITMOTION #define INCLUDED_ICMPUNITMOTION #include "simulation2/system/Interface.h" #include "ICmpPosition.h" // for entity_pos_t /** * Motion interface for entities with complex movement capabilities. * (Simpler motion is handled by ICmpMotion instead.) * * Currently this is limited to telling the entity to walk to a point. * Eventually it should support different movement speeds, moving to areas * instead of points, moving as part of a group, moving as part of a formation, * etc. */ class ICmpUnitMotion : public IComponent { public: /** * Attempt to walk to a given point, or as close as possible. * If the unit cannot move anywhere at all, or if there is some other error, then * returns false. * Otherwise, sends a MotionChanged message and returns true; it will send another * MotionChanged message (with speed 0) once it has reached the target or otherwise * given up trying to reach it. */ virtual bool MoveToPoint(entity_pos_t x, entity_pos_t z) = 0; /** * Determine whether the target is within the given range, using the same measurement * as MoveToAttackRange. */ virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0; /** - * Attempt to walk into range of a given target, or as close as possible. + * Attempt to walk into range of a given target entity, or as close as possible. * If the unit is already in range, or cannot move anywhere at all, or if there is * some other error, then returns false. * Otherwise, sends a MotionChanged message and returns true; it will send another * MotionChanged message (with speed 0) once it has reached the target range (such that * IsInAttackRange should return true) or otherwise given up trying to reach it. */ virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0; /** + * See MoveToAttackRange, but the target is the given point. + */ + virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0; + + /** + * Stop moving immediately. + */ + virtual void StopMoving() = 0; + + /** + * Set the current movement speed to be the default multiplied by the given factor. + */ + virtual void SetSpeedFactor(fixed factor) = 0; + + /** * Get the default speed that this unit will have when walking, in metres per second. */ - virtual fixed GetSpeed() = 0; + virtual fixed GetWalkSpeed() = 0; /** * Get the default speed that this unit will have when running, in metres per second. */ virtual fixed GetRunSpeed() = 0; /** * Toggle the rendering of debug info. */ virtual void SetDebugOverlay(bool enabled) = 0; DECLARE_INTERFACE_TYPE(UnitMotion) }; #endif // INCLUDED_ICMPUNITMOTION Index: ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp (revision 7762) +++ ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp (revision 7763) @@ -1,941 +1,975 @@ /* 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 "ICmpUnitMotion.h" #include "ICmpObstruction.h" #include "ICmpObstructionManager.h" #include "ICmpPosition.h" #include "ICmpPathfinder.h" #include "simulation2/MessageTypes.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Render.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "maths/FixedVector2D.h" #include "ps/Profile.h" #include "renderer/Scene.h" static const entity_pos_t WAYPOINT_ADVANCE_MIN = entity_pos_t::FromInt(CELL_SIZE*4); static const entity_pos_t WAYPOINT_ADVANCE_MAX = entity_pos_t::FromInt(CELL_SIZE*8); static const entity_pos_t SHORT_PATH_SEARCH_RANGE = entity_pos_t::FromInt(CELL_SIZE*12); static const CColor OVERLAY_COLOUR_PATH(1, 1, 1, 1); static const CColor OVERLAY_COLOUR_PATH_ACTIVE(1, 1, 0, 1); static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1); class CCmpUnitMotion : public ICmpUnitMotion { public: static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_Update); componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays } DEFAULT_COMPONENT_ALLOCATOR(UnitMotion) bool m_DebugOverlayEnabled; std::vector m_DebugOverlayLines; std::vector m_DebugOverlayShortPathLines; // Template state: - fixed m_Speed; // in metres per second + fixed m_WalkSpeed; // in metres per second fixed m_RunSpeed; entity_pos_t m_Radius; u8 m_PassClass; u8 m_CostClass; // Dynamic state: + fixed m_Speed; bool m_HasTarget; // whether we currently have valid paths and targets // These values contain undefined junk if !HasTarget: ICmpPathfinder::Path m_Path; ICmpPathfinder::Path m_ShortPath; entity_pos_t m_ShortTargetX, m_ShortTargetZ; ICmpPathfinder::Goal m_FinalGoal; enum { IDLE, WALKING, STOPPING }; int m_State; static std::string GetSchema() { return "Provides the unit with the ability to move around the world by itself." "" "7.0" "default" "infantry" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; } /* * TODO: the running/charging thing needs to be designed and implemented */ virtual void Init(const CSimContext& context, const CParamNode& paramNode) { m_HasTarget = false; - m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); + m_WalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed(); + m_Speed = m_WalkSpeed; if (paramNode.GetChild("Run").IsOk()) { m_RunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed(); } else { - m_RunSpeed = m_Speed; + m_RunSpeed = m_WalkSpeed; } CmpPtr cmpObstruction(context, GetEntityId()); if (!cmpObstruction.null()) m_Radius = cmpObstruction->GetUnitRadius(); CmpPtr cmpPathfinder(context, SYSTEM_ENTITY); if (!cmpPathfinder.null()) { m_PassClass = cmpPathfinder->GetPassabilityClass(paramNode.GetChild("PassabilityClass").ToASCIIString()); m_CostClass = cmpPathfinder->GetCostClass(paramNode.GetChild("CostClass").ToASCIIString()); } m_State = IDLE; m_DebugOverlayEnabled = false; } virtual void Deinit(const CSimContext& UNUSED(context)) { } virtual void Serialize(ISerializer& serialize) { serialize.Bool("has target", m_HasTarget); if (m_HasTarget) { // TODO: m_Path // TODO: m_FinalTargetAngle } // TODO: m_State } virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize) { Init(context, paramNode); deserialize.Bool(m_HasTarget); if (m_HasTarget) { } } virtual void HandleMessage(const CSimContext& context, const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Update: { fixed dt = static_cast (msg).turnLength; if (m_State == STOPPING) { m_State = IDLE; CMessageMotionChanged msg(fixed::Zero()); context.GetComponentManager().PostMessage(GetEntityId(), msg); } Move(dt); break; } case MT_RenderSubmit: { const CMessageRenderSubmit& msgData = static_cast (msg); RenderSubmit(msgData.collector); break; } } } - virtual fixed GetSpeed() + virtual fixed GetWalkSpeed() { - return m_Speed; + return m_WalkSpeed; } virtual fixed GetRunSpeed() { return m_RunSpeed; } + virtual void SetSpeedFactor(fixed factor) + { + m_Speed = m_WalkSpeed.Multiply(factor); + } + virtual void SetDebugOverlay(bool enabled) { m_DebugOverlayEnabled = enabled; if (enabled) { RenderPath(m_Path, m_DebugOverlayLines, OVERLAY_COLOUR_PATH); RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOUR_SHORT_PATH); } } virtual bool MoveToPoint(entity_pos_t x, entity_pos_t z); virtual bool MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); virtual bool IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); + virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange); + + virtual void StopMoving() + { + SwitchState(IDLE); + } private: /** * Check whether moving from pos to target is safe (won't hit anything). * If safe, returns true (the caller should do cmpPosition->MoveTo). * Otherwise returns false, and either computes a new path to use on the * next turn or makes the unit stop. */ bool CheckMovement(CFixedVector2D pos, CFixedVector2D target); /** * Do the per-turn movement and other updates */ void Move(fixed dt); void StopAndFaceGoal(CFixedVector2D pos); /** * Rotate to face towards the target point, given the current pos */ void FaceTowardsPoint(CFixedVector2D pos, entity_pos_t x, entity_pos_t z); /** * Change between idle/walking states; automatically sends MotionChanged messages when appropriate */ void SwitchState(int state); bool ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t hw, entity_pos_t hh, entity_pos_t circleRadius); /** * Recompute the whole path to the current goal. * Returns false on error or if the unit can't move anywhere at all. */ bool RegeneratePath(CFixedVector2D pos, bool avoidMovingUnits); /** * Maybe select a new long waypoint if we're getting too close to the * current one. */ void MaybePickNextWaypoint(const CFixedVector2D& pos); /** * Select a next long waypoint, given the current unit position. * Also recomputes the short path to use that waypoint. * Returns false on error, or if there is no waypoint to pick. */ bool PickNextWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits); /** * Select a new short waypoint as the current target, * which possibly involves first selecting a new long waypoint. * Returns false on error, or if there is no waypoint to pick. */ bool PickNextShortWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits); /** * Convert a path into a renderable list of lines */ void RenderPath(const ICmpPathfinder::Path& path, std::vector& lines, CColor color); void RenderSubmit(SceneCollector& collector); }; REGISTER_COMPONENT_TYPE(UnitMotion) bool CCmpUnitMotion::CheckMovement(CFixedVector2D pos, CFixedVector2D target) { CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; NullObstructionFilter filter; if (cmpObstructionManager->TestLine(filter, pos.X, pos.Y, target.X, target.Y, m_Radius)) { // Oops, hit something // TODO: we ought to wait for obstructions to move away instead of immediately throwing away the whole path // TODO: actually a whole proper collision resolution thing needs to be designed and written if (!RegeneratePath(pos, true)) { // Oh dear, we can't find the path any more; give up StopAndFaceGoal(pos); return false; } // NOTE: it's theoretically possible that we will generate a waypoint we can reach without // colliding with anything, but multiplying the movement vector by the timestep will result // in a line that does collide (given numerical inaccuracies), so we'll get stuck in a loop // of generating a new path and colliding whenever we try to follow it, and the unit will // move nowhere. // Hopefully this isn't common. // Wait for the next Update before we try moving again return false; } // NOTE: we ignore terrain here - we assume the pathfinder won't give us a path that crosses impassable // terrain (which is a valid assumption) and that the terrain will never change (which is not). // Probably not worth fixing since it'll happen very rarely. return true; } void CCmpUnitMotion::Move(fixed dt) { PROFILE("Move"); if (!m_HasTarget) return; CmpPtr cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); if (cmpPathfinder.null()) return; CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null()) return; CFixedVector3D pos3 = cmpPosition->GetPosition(); CFixedVector2D pos (pos3.X, pos3.Z); // We want to move (at most) m_Speed*dt units from pos towards the next waypoint while (dt > fixed::Zero()) { CFixedVector2D target(m_ShortTargetX, m_ShortTargetZ); CFixedVector2D offset = target - pos; // Face towards the target entity_angle_t angle = atan2_approx(offset.X, offset.Y); cmpPosition->TurnTo(angle); // Find the speed factor of the underlying terrain // (We only care about the tile we start on - it doesn't matter if we're moving // partially onto a much slower/faster tile) fixed terrainSpeed = cmpPathfinder->GetMovementSpeed(pos.X, pos.Y, m_CostClass); // Work out how far we can travel in dt fixed maxdist = m_Speed.Multiply(terrainSpeed).Multiply(dt); // If the target is close, we can move there directly fixed offsetLength = offset.Length(); if (offsetLength <= maxdist) { if (!CheckMovement(pos, target)) return; pos = target; cmpPosition->MoveTo(pos.X, pos.Y); // Spend the rest of the time heading towards the next waypoint dt = dt - (offset.Length() / m_Speed); MaybePickNextWaypoint(pos); if (PickNextShortWaypoint(pos, false)) continue; // We ran out of usable waypoints, so stop now StopAndFaceGoal(pos); return; } else { // Not close enough, so just move in the right direction offset.Normalize(maxdist); target = pos + offset; if (!CheckMovement(pos, target)) return; pos = target; cmpPosition->MoveTo(pos.X, pos.Y); MaybePickNextWaypoint(pos); return; } } } void CCmpUnitMotion::StopAndFaceGoal(CFixedVector2D pos) { SwitchState(IDLE); FaceTowardsPoint(pos, m_FinalGoal.x, m_FinalGoal.z); // TODO: if the goal was a square building, we ought to point towards the // nearest point on the square, not towards its center } void CCmpUnitMotion::FaceTowardsPoint(CFixedVector2D pos, entity_pos_t x, entity_pos_t z) { CFixedVector2D target(x, z); CFixedVector2D offset = target - pos; if (!offset.IsZero()) { entity_angle_t angle = atan2_approx(offset.X, offset.Y); CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null()) return; cmpPosition->TurnTo(angle); } } void CCmpUnitMotion::SwitchState(int state) { debug_assert(state == IDLE || state == WALKING); if (state == IDLE) m_HasTarget = false; // IDLE -> IDLE -- no change // IDLE -> WALKING -- send a MotionChanged(speed) message // WALKING -> IDLE -- set to STOPPING, so we'll send MotionChanged(0) in the next Update // WALKING -> WALKING -- send a MotionChanged(speed) message // STOPPING -> IDLE -- stay in STOPPING // STOPPING -> WALKING -- set to WALKING, send MotionChanged(speed) if (state == WALKING) { CMessageMotionChanged msg(m_Speed); GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg); } if (m_State == IDLE && state == WALKING) { m_State = WALKING; return; } if (m_State == WALKING && state == IDLE) { m_State = STOPPING; return; } if (m_State == STOPPING && state == IDLE) { return; } if (m_State == STOPPING && state == WALKING) { m_State = WALKING; return; } } bool CCmpUnitMotion::MoveToPoint(entity_pos_t x, entity_pos_t z) { PROFILE("MoveToPoint"); CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null() || !cmpPosition->IsInWorld()) return false; CFixedVector3D pos3 = cmpPosition->GetPosition(); CFixedVector2D pos (pos3.X, pos3.Z); // Reset any current movement m_HasTarget = false; ICmpPathfinder::Goal goal; CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; ICmpObstructionManager::ObstructionSquare obstruction; if (cmpObstructionManager->FindMostImportantObstruction(x, z, m_Radius, obstruction)) { // If we're aiming inside a building, then aim for the outline of the building instead // TODO: if we're aiming at a unit then maybe a circle would look nicer? goal.type = ICmpPathfinder::Goal::SQUARE; goal.x = obstruction.x; goal.z = obstruction.z; goal.u = obstruction.u; goal.v = obstruction.v; entity_pos_t delta = entity_pos_t::FromInt(1) / 4; // nudge the goal outwards so it doesn't intersect the building itself goal.hw = obstruction.hw + m_Radius + delta; goal.hh = obstruction.hh + m_Radius + delta; } else { // Unobstructed - head directly for the goal goal.type = ICmpPathfinder::Goal::POINT; goal.x = x; goal.z = z; } m_FinalGoal = goal; if (!RegeneratePath(pos, false)) return false; SwitchState(WALKING); return true; } bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t hw, entity_pos_t hh, entity_pos_t circleRadius) { // Given a square, plus a target range we should reach, the shape at that distance // is a round-cornered square which we can approximate as either a circle or as a square. // Choose the shape that will minimise the worst-case error: // For a square, error is (sqrt(2)-1) * range at the corners entity_pos_t errSquare = (entity_pos_t::FromInt(4142)/10000).Multiply(range); // For a circle, error is radius-hw at the sides and radius-hh at the top/bottom entity_pos_t errCircle = circleRadius - std::min(hw, hh); return (errCircle < errSquare); } +static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(CELL_SIZE)/4; // for extending the goal outwards/inwards a little bit + bool CCmpUnitMotion::MoveToAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) { PROFILE("MoveToAttackRange"); CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null() || !cmpPosition->IsInWorld()) return false; CFixedVector3D pos3 = cmpPosition->GetPosition(); CFixedVector2D pos (pos3.X, pos3.Z); // Reset any current movement m_HasTarget = false; - ICmpPathfinder::Goal goal; - CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; ICmpObstructionManager::tag_t tag; CmpPtr cmpObstruction(GetSimContext(), target); if (!cmpObstruction.null()) tag = cmpObstruction->GetObstruction(); /* * If we're starting outside the maxRange, we need to move closer in. * If we're starting inside the minRange, we need to move further out. * These ranges are measured from the center of this entity to the edge of the target; * we add the goal range onto the size of the target shape to get the goal shape. * (Then we extend it outwards/inwards by a little bit to be sure we'll end up * within the right range, in case of minor numerical inaccuracies.) * * There's a bit of a problem with large square targets: * the pathfinder only lets us move to goals that are squares, but the points an equal * distance from the target make a rounded square shape instead. * * When moving closer, we could shrink the goal radius to 1/sqrt(2) so the goal shape fits entirely * within the desired rounded square, but that gives an unfair advantage to attackers who approach * the target diagonally. * * If the target is small relative to the range (e.g. archers attacking anything), * then we cheat and pretend the target is actually a circle. * (TODO: that probably looks rubbish for things like walls?) * * If the target is large relative to the range (e.g. melee units attacking buildings), * then we multiply maxRange by approx 1/sqrt(2) to guarantee they'll always aim close enough. * (Those units should set minRange to 0 so they'll never be considered *too* close.) */ - const entity_pos_t goalDelta = entity_pos_t::FromInt(CELL_SIZE)/4; // for extending the goal outwards/inwards a little bit - if (tag.valid()) { ICmpObstructionManager::ObstructionSquare obstruction = cmpObstructionManager->GetObstruction(tag); CFixedVector2D halfSize(obstruction.hw, obstruction.hh); + ICmpPathfinder::Goal goal; goal.x = obstruction.x; goal.z = obstruction.z; entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize); if (distance < minRange) { // Too close to the square - need to move away - entity_pos_t goalDistance = minRange + goalDelta; + entity_pos_t goalDistance = minRange + g_GoalDelta; goal.type = ICmpPathfinder::Goal::SQUARE; goal.u = obstruction.u; goal.v = obstruction.v; entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(CELL_SIZE)/16); // ensure it's far enough to not intersect the building itself goal.hw = obstruction.hw + delta; goal.hh = obstruction.hh + delta; } else if (distance < maxRange) { // We're already in range - no need to move anywhere FaceTowardsPoint(pos, goal.x, goal.z); return false; } else { // We might need to move closer: // Circumscribe the square entity_pos_t circleRadius = halfSize.Length(); if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius)) { // The target is small relative to our range, so pretend it's a circle // Note that the distance to the circle will always be less than // the distance to the square, so the previous "distance < maxRange" // check is still valid (though not sufficient) entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; if (circleDistance < maxRange) { // We're already in range - no need to move anywhere FaceTowardsPoint(pos, goal.x, goal.z); return false; } - entity_pos_t goalDistance = maxRange - goalDelta; + entity_pos_t goalDistance = maxRange - g_GoalDelta; goal.type = ICmpPathfinder::Goal::CIRCLE; goal.hw = circleRadius + goalDistance; } else { // The target is large relative to our range, so treat it as a square and // get close enough that the diagonals come within range - entity_pos_t goalDistance = (maxRange - goalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2) + entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2) goal.type = ICmpPathfinder::Goal::SQUARE; goal.u = obstruction.u; goal.v = obstruction.v; entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(CELL_SIZE)/16); // ensure it's far enough to not intersect the building itself goal.hw = obstruction.hw + delta; goal.hh = obstruction.hh + delta; } } + + m_FinalGoal = goal; + if (!RegeneratePath(pos, false)) + return false; + + SwitchState(WALKING); + return true; } else { // The target didn't have an obstruction or obstruction shape, so treat it as a point instead CmpPtr cmpTargetPosition(GetSimContext(), target); if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld()) return false; CFixedVector3D targetPos = cmpTargetPosition->GetPosition(); - entity_pos_t distance = (pos - CFixedVector2D(targetPos.X, targetPos.Z)).Length(); + return MoveToPointRange(targetPos.X, targetPos.Z, minRange, maxRange); + } +} - entity_pos_t goalDistance; - if (distance < minRange) - { - goalDistance = minRange + goalDelta; - } - else if (distance > maxRange) - { - goalDistance = maxRange - goalDelta; - } - else - { - // We're already in range - no need to move anywhere - FaceTowardsPoint(pos, goal.x, goal.z); - return false; - } +bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) +{ + PROFILE("MoveToPointRange"); + + CmpPtr cmpPosition(GetSimContext(), GetEntityId()); + if (cmpPosition.null() || !cmpPosition->IsInWorld()) + return false; - // TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target) + CFixedVector3D pos3 = cmpPosition->GetPosition(); + CFixedVector2D pos (pos3.X, pos3.Z); - goal.type = ICmpPathfinder::Goal::CIRCLE; - goal.x = targetPos.X; - goal.z = targetPos.Z; - goal.hw = m_Radius + goalDistance; + entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); + + entity_pos_t goalDistance; + if (distance < minRange) + { + goalDistance = minRange + g_GoalDelta; + } + else if (distance > maxRange) + { + goalDistance = maxRange - g_GoalDelta; } + else + { + // We're already in range - no need to move anywhere + FaceTowardsPoint(pos, x, z); + return false; + } + + // TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target) + + ICmpPathfinder::Goal goal; + goal.type = ICmpPathfinder::Goal::CIRCLE; + goal.x = x; + goal.z = z; + goal.hw = m_Radius + goalDistance; m_FinalGoal = goal; if (!RegeneratePath(pos, false)) return false; SwitchState(WALKING); return true; } bool CCmpUnitMotion::IsInAttackRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) { // This function closely mirrors MoveToAttackRange - it needs to return true // after that Move has completed CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null() || !cmpPosition->IsInWorld()) return false; CFixedVector3D pos3 = cmpPosition->GetPosition(); CFixedVector2D pos (pos3.X, pos3.Z); CmpPtr cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; ICmpObstructionManager::tag_t tag; CmpPtr cmpObstruction(GetSimContext(), target); if (!cmpObstruction.null()) tag = cmpObstruction->GetObstruction(); entity_pos_t distance; if (tag.valid()) { ICmpObstructionManager::ObstructionSquare obstruction = cmpObstructionManager->GetObstruction(tag); CFixedVector2D halfSize(obstruction.hw, obstruction.hh); entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize); // See if we're too close to the target square if (distance < minRange) return false; // See if we're close enough to the target square if (distance <= maxRange) return true; entity_pos_t circleRadius = halfSize.Length(); if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius)) { // The target is small relative to our range, so pretend it's a circle // and see if we're close enough to that entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; if (circleDistance <= maxRange) return true; } return false; } else { CmpPtr cmpTargetPosition(GetSimContext(), target); if (cmpTargetPosition.null() || !cmpTargetPosition->IsInWorld()) return false; CFixedVector3D targetPos = cmpTargetPosition->GetPosition(); entity_pos_t distance = (pos - CFixedVector2D(targetPos.X, targetPos.Z)).Length(); if (minRange <= distance && distance <= maxRange) return true; return false; } } bool CCmpUnitMotion::RegeneratePath(CFixedVector2D pos, bool avoidMovingUnits) { CmpPtr cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); if (cmpPathfinder.null()) return false; m_Path.m_Waypoints.clear(); m_ShortPath.m_Waypoints.clear(); // TODO: if it's close then just do a short path, not a long path cmpPathfinder->SetDebugPath(pos.X, pos.Y, m_FinalGoal, m_PassClass, m_CostClass); cmpPathfinder->ComputePath(pos.X, pos.Y, m_FinalGoal, m_PassClass, m_CostClass, m_Path); if (m_DebugOverlayEnabled) RenderPath(m_Path, m_DebugOverlayLines, OVERLAY_COLOUR_PATH); // If there's no waypoints then we've stopped already, otherwise move to the first one if (m_Path.m_Waypoints.empty()) { m_HasTarget = false; return false; } else { return PickNextShortWaypoint(pos, avoidMovingUnits); } } void CCmpUnitMotion::MaybePickNextWaypoint(const CFixedVector2D& pos) { if (m_Path.m_Waypoints.empty()) return; CFixedVector2D w(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z); if ((w - pos).Length() < WAYPOINT_ADVANCE_MIN) PickNextWaypoint(pos, false); // TODO: handle failures? } bool CCmpUnitMotion::PickNextWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits) { if (m_Path.m_Waypoints.empty()) return false; // First try to get the immediate next waypoint entity_pos_t targetX = m_Path.m_Waypoints.back().x; entity_pos_t targetZ = m_Path.m_Waypoints.back().z; m_Path.m_Waypoints.pop_back(); // To smooth the motion and avoid grid-constrained movement and allow dynamic obstacle avoidance, // try skipping some more waypoints if they're close enough while (!m_Path.m_Waypoints.empty()) { CFixedVector2D w(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z); if ((w - pos).Length() > WAYPOINT_ADVANCE_MAX) break; targetX = m_Path.m_Waypoints.back().x; targetZ = m_Path.m_Waypoints.back().z; m_Path.m_Waypoints.pop_back(); } // Highlight the targeted waypoint if (m_DebugOverlayEnabled) m_DebugOverlayLines[m_Path.m_Waypoints.size()].m_Color = OVERLAY_COLOUR_PATH_ACTIVE; // Now we need to recompute a short path to the waypoint m_ShortPath.m_Waypoints.clear(); ICmpPathfinder::Goal goal; if (m_Path.m_Waypoints.empty()) { // This was the last waypoint - head for the exact goal goal = m_FinalGoal; } else { // Head for somewhere near the waypoint (but allow some leeway in case it's obstructed) goal.type = ICmpPathfinder::Goal::CIRCLE; goal.hw = entity_pos_t::FromInt(CELL_SIZE*3/2); goal.x = targetX; goal.z = targetZ; } CmpPtr cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); if (cmpPathfinder.null()) return false; // Set up the filter to avoid/ignore moving units NullObstructionFilter filterNull; StationaryObstructionFilter filterStationary; const IObstructionTestFilter* filter; if (avoidMovingUnits) filter = &filterNull; else filter = &filterStationary; cmpPathfinder->ComputeShortPath(*filter, pos.X, pos.Y, m_Radius, SHORT_PATH_SEARCH_RANGE, goal, m_PassClass, m_ShortPath); if (m_DebugOverlayEnabled) RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOUR_SHORT_PATH); return true; } bool CCmpUnitMotion::PickNextShortWaypoint(const CFixedVector2D& pos, bool avoidMovingUnits) { // If we don't have a short path now if (m_ShortPath.m_Waypoints.empty()) { // Try to pick a new long waypoint (which will also recompute the short path) if (!PickNextWaypoint(pos, avoidMovingUnits)) return false; // no waypoints left if (m_ShortPath.m_Waypoints.empty()) return false; // we can't reach the next long waypoint or are already there } // Head towards the next short waypoint m_ShortTargetX = m_ShortPath.m_Waypoints.back().x; m_ShortTargetZ = m_ShortPath.m_Waypoints.back().z; m_ShortPath.m_Waypoints.pop_back(); m_HasTarget = true; return true; } void CCmpUnitMotion::RenderPath(const ICmpPathfinder::Path& path, std::vector& lines, CColor color) { bool floating = false; CmpPtr cmpPosition(GetSimContext(), GetEntityId()); if (!cmpPosition.null()) floating = cmpPosition->IsFloating(); lines.clear(); std::vector waypointCoords; for (size_t i = 0; i < path.m_Waypoints.size(); ++i) { float x = path.m_Waypoints[i].x.ToFloat(); float z = path.m_Waypoints[i].z.ToFloat(); waypointCoords.push_back(x); waypointCoords.push_back(z); lines.push_back(SOverlayLine()); lines.back().m_Color = color; SimRender::ConstructSquareOnGround(GetSimContext(), x, z, 1.0f, 1.0f, 0.0f, lines.back(), floating); } lines.push_back(SOverlayLine()); lines.back().m_Color = color; SimRender::ConstructLineOnGround(GetSimContext(), waypointCoords, lines.back(), floating); } void CCmpUnitMotion::RenderSubmit(SceneCollector& collector) { if (!m_DebugOverlayEnabled) return; for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i) collector.Submit(&m_DebugOverlayLines[i]); for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i) collector.Submit(&m_DebugOverlayShortPathLines[i]); } Index: ps/trunk/binaries/data/mods/public/audio/actor/fauna/animal/chickens.xml =================================================================== --- ps/trunk/binaries/data/mods/public/audio/actor/fauna/animal/chickens.xml (revision 7762) +++ ps/trunk/binaries/data/mods/public/audio/actor/fauna/animal/chickens.xml (revision 7763) @@ -1,25 +1,25 @@ 1 1 100 1 360 360 0 0 0 1 0.9 0 1.1 0.9 5 3 chicken_10.ogg - /audio/actor/fauna/animal + audio/actor/fauna/animal chicken_13.ogg chicken_10.ogg chicken_11.ogg chicken_12.ogg Index: ps/trunk/binaries/data/mods/public/gui/session_new/session.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session_new/session.xml (revision 7762) +++ ps/trunk/binaries/data/mods/public/gui/session_new/session.xml (revision 7763) @@ -1,466 +1,471 @@