Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1274,8 +1274,6 @@ "enter": function() { let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); cmpUnitMotion.MoveToFormationOffset(this.order.data.target, this.order.data.x, this.order.data.z); - - this.SelectAnimation("move"); }, // Occurs when the unit has reached its destination and the controller @@ -1300,12 +1298,12 @@ var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation); if (cmpFormation) cmpFormation.UnsetInPosition(this.entity); + if (!this.MoveTo(this.order.data)) { this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "MovementUpdate": function() { @@ -1465,10 +1463,6 @@ this.RespondToHealableEntities(msg.data.added); }, - "MoveCompleted": function() { - this.SelectAnimation("idle"); - }, - "Timer": function(msg) { if (!this.isIdle) { @@ -1485,11 +1479,9 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function () { - this.SelectAnimation("idle"); this.StopMoving(); }, @@ -1510,7 +1502,6 @@ this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); }, "Timer": function(msg) { @@ -1549,7 +1540,6 @@ this.StartTimer(0, 1000); this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); }, "leave": function() { @@ -1593,7 +1583,6 @@ this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); this.SetHeldPositionOnEntity(this.isGuardOf); return false; }, @@ -1641,7 +1630,6 @@ this.StartTimer(1000, 1000); this.SetHeldPositionOnEntity(this.entity); this.SetAnimationVariant("combat"); - this.SelectAnimation("idle"); return false; }, @@ -1700,7 +1688,6 @@ this.PlaySound("panic"); // Run quickly - this.SelectAnimation("move"); this.SetSpeedMultiplier(this.GetRunMultiplier()); }, @@ -1746,7 +1733,6 @@ // Show weapons rather than carried resources. this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -1882,6 +1868,7 @@ cmpBuildingAI.SetUnitAITarget(0); this.StopTimer(); this.SetDefaultAnimationVariant(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -2001,7 +1988,6 @@ // Show weapons rather than carried resources. this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); if (cmpUnitAI && cmpUnitAI.IsFleeing()) { @@ -2055,8 +2041,6 @@ this.SetNextState("GATHERING"); return true; } - - this.SelectAnimation("move"); return false; }, @@ -2088,7 +2072,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2175,6 +2158,7 @@ delete this.gatheringTarget; // Show the carried resource, if we've gathered anything. + this.ResetAnimation(); this.SetDefaultAnimationVariant(); }, @@ -2347,7 +2331,6 @@ return true; } - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -2398,6 +2381,7 @@ }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -2458,13 +2442,10 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { - // Switch back to idle animation to guarantee we won't - // get stuck with the carry animation after stopping moving - this.SelectAnimation("idle"); + this.StopMoving(); }, "MovementUpdate": function() { @@ -2523,7 +2504,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2563,7 +2543,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2625,6 +2604,7 @@ cmpBuilderList.RemoveBuilder(this.entity); delete this.repairTarget; this.StopTimer(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -2769,7 +2749,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2892,6 +2871,7 @@ "leave": function() { this.StopTimer(); + this.ResetAnimation(); var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver); cmpDamageReceiver.SetInvulnerability(false); }, @@ -2949,7 +2929,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2969,7 +2948,6 @@ "LOADING": { "enter": function() { this.StopMoving(); - this.SelectAnimation("idle"); var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) { @@ -3030,7 +3008,6 @@ "ROAMING": { "enter": function() { // Walk in a random direction - this.SelectAnimation("move", false, 1); this.SetFacePointAfterMove(false); this.MoveRandomly(+this.template.RoamDistance); // Set a random timer to switch to feeding state @@ -3083,6 +3060,7 @@ }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -4106,20 +4084,20 @@ this.SetAnimationVariant(""); }; -UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) +UnitAI.prototype.ResetAnimation = function() { let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (!cmpVisual) return; - // Special case: the "move" animation gets turned into a special - // movement mode that deals with speeds and walk/run automatically - if (name == "move") - { - // Speed to switch from walking to running animations - cmpVisual.SelectMovementAnimation(this.GetWalkSpeed()); + cmpVisual.SelectAnimation("idle", false, 1.0); +}; + +UnitAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0) +{ + let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) return; - } cmpVisual.SelectAnimation(name, once, speed); }; Index: ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp +++ ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp @@ -27,6 +27,7 @@ #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpValueModificationManager.h" +#include "simulation2/components/ICmpVisual.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Render.h" #include "simulation2/MessageTypes.h" @@ -517,18 +518,26 @@ void UpdateMovementState(entity_pos_t speed) { CmpPtr cmpObstruction(GetEntityHandle()); + CmpPtr cmpVisual(GetEntityHandle()); // Moved last turn, didn't this turn. if (speed == fixed::Zero() && m_CurSpeed > fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(false); + if (cmpVisual) + cmpVisual->SelectMovementAnimation("idle", fixed::FromInt(1)); } // Moved this turn, didn't last turn else if (speed > fixed::Zero() && m_CurSpeed == fixed::Zero()) { if (cmpObstruction) cmpObstruction->SetMovingFlag(true); + if (cmpVisual) + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); } + // Speed change, update the visual actor if necessary. + else if (speed != m_CurSpeed && cmpVisual) + cmpVisual->SelectMovementAnimation(m_Speed > m_WalkSpeed ? "run" : "walk", m_Speed); m_CurSpeed = speed; } Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp +++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp @@ -55,7 +55,6 @@ public: static void ClassInit(CComponentManager& componentManager) { - componentManager.SubscribeToMessageType(MT_Update_Final); componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged); componentManager.SubscribeToMessageType(MT_OwnershipChanged); componentManager.SubscribeToMessageType(MT_ValueModification); @@ -75,7 +74,6 @@ fixed m_R, m_G, m_B; // shading color // Current animation state - fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode std::string m_AnimName; bool m_AnimOnce; fixed m_AnimSpeed; @@ -226,7 +224,6 @@ serialize.NumberFixed_Unbounded("g", m_G); serialize.NumberFixed_Unbounded("b", m_B); - serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold); serialize.StringASCII("anim name", m_AnimName, 0, 256); serialize.Bool("anim once", m_AnimOnce); serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed); @@ -281,12 +278,6 @@ { switch (msg.GetType()) { - case MT_Update_Final: - { - const CMessageUpdate_Final& msgData = static_cast (msg); - Update(msgData.turnLength); - break; - } case MT_OwnershipChanged: { if (!m_Unit) @@ -437,7 +428,6 @@ virtual void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1)) { - m_AnimRunThreshold = fixed::Zero(); m_AnimName = name; m_AnimOnce = once; m_AnimSpeed = speed; @@ -458,9 +448,12 @@ m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); } - virtual void SelectMovementAnimation(fixed runThreshold) + virtual void SelectMovementAnimation(const std::string& name, fixed speed) { - m_AnimRunThreshold = runThreshold; + ENSURE(name == "idle" || name == "walk" || name == "run"); + if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run") + return; + SelectAnimation(name, false, speed); } virtual void SetAnimationSyncRepeat(fixed repeattime) @@ -541,8 +534,6 @@ // ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical. // It is also used by ReloadActor. void ReloadUnitAnimation(); - - void Update(fixed turnLength); }; REGISTER_COMPONENT_TYPE(VisualActor) @@ -747,42 +738,3 @@ if (!m_AnimSyncOffsetTime.IsZero()) m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat()); } - -void CCmpVisualActor::Update(fixed UNUSED(turnLength)) -{ - // This function is currently only used to update the animation if the speed in - // CCmpUnitMotion changes. This also only happens in the "special movement mode" - // triggered by SelectMovementAnimation. - - // TODO: This should become event based, in order to save performance and to make the code - // far less hacky. We should also take into account the speed when the animation is different - // from the "special movement mode" walking animation. - - // If we're not in the special movement mode, nothing to do. - if (m_AnimRunThreshold.IsZero()) - return; - - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition || !cmpPosition->IsInWorld()) - return; - - CmpPtr cmpUnitMotion(GetEntityHandle()); - if (!cmpUnitMotion) - return; - - fixed speed = cmpUnitMotion->GetCurrentSpeed(); - std::string name; - - if (speed.IsZero()) - { - speed = fixed::FromFloat(1.f); - name = "idle"; - } - else - name = speed < m_AnimRunThreshold ? "walk" : "run"; - - // Selecting the animation is going to reset the anim run threshold, so save it - fixed runThreshold = m_AnimRunThreshold; - SelectAnimation(name, false, speed); - m_AnimRunThreshold = runThreshold; -} Index: ps/trunk/source/simulation2/components/ICmpVisual.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpVisual.h +++ ps/trunk/source/simulation2/components/ICmpVisual.h @@ -96,20 +96,22 @@ /** * Start playing the given animation. If there are multiple possible animations then it will * pick one at random (not network-synchronised). - * If @p soundgroup is specified, then the sound will be played at each 'event' point in the - * animation cycle. * @param name animation name (e.g. "idle", "walk", "melee"; the names are determined by actor XML files) * @param once if true then the animation will play once and freeze at the final frame, else it will loop * @param speed animation speed multiplier (typically 1.0 for the default speed) - * @param soundgroup VFS path of sound group .xml, relative to audio/, or empty string for none */ virtual void SelectAnimation(const std::string& name, bool once, fixed speed) = 0; /** - * Start playing the walk/run animations, scaled to the unit's movement speed. - * @param runThreshold movement speed at which to switch to the run animation + * Start playing the given movement animation unless we are currently playing a non-movement animation. + * This is necessary so UnitMotion can set the movement animations without overwriting specific animations + * that might have been set by other components. + * TODO: Non-movement animations should probably be made into variants, defining "idle" (really "default"), "walk" and "run" as appropriate, + * and this would no longer be necessary. + * @param name animation name (i.e. one of "idle", "walk", "run"). + * @param speed animation speed multiplier (typically 1.0 for the default speed) */ - virtual void SelectMovementAnimation(fixed runThreshold) = 0; + virtual void SelectMovementAnimation(const std::string& name, fixed speed) = 0; /** * Adjust the speed of the current animation, so it can match simulation events. Index: ps/trunk/source/simulation2/components/ICmpVisual.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpVisual.cpp +++ ps/trunk/source/simulation2/components/ICmpVisual.cpp @@ -27,7 +27,6 @@ DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileActor", std::wstring, ICmpVisual, GetProjectileActor) DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileLaunchPoint", CFixedVector3D, ICmpVisual, GetProjectileLaunchPoint) DEFINE_INTERFACE_METHOD_3("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed) -DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed) DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed)