Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1258,7 +1258,6 @@ cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk")); cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run")); } - this.SelectAnimation("move"); }, // Occurs when the unit has reached its destination and the controller @@ -1290,12 +1289,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"); }, "MoveCompletedHint": function() { @@ -1455,14 +1454,6 @@ this.RespondToHealableEntities(msg.data.added); }, - "MoveStarted": function() { - this.SelectAnimation("move"); - }, - - "MoveCompleted": function() { - this.SelectAnimation("idle"); - }, - "Timer": function(msg) { if (!this.isIdle) { @@ -1482,11 +1473,9 @@ this.FinishOrder(); return; } - this.SelectAnimation("move"); }, "leave": function () { - this.SelectAnimation("idle"); this.StopMoving(); }, @@ -1507,7 +1496,6 @@ this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); }, "Timer": function(msg) { @@ -1546,7 +1534,6 @@ this.StartTimer(0, 1000); this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); }, "leave": function() { @@ -1590,7 +1577,6 @@ this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SelectAnimation("move"); this.SetHeldPositionOnEntity(this.isGuardOf); return false; }, @@ -1638,7 +1624,6 @@ this.StartTimer(1000, 1000); this.SetHeldPositionOnEntity(this.entity); this.SetAnimationVariant("combat"); - this.SelectAnimation("idle"); return false; }, @@ -1692,7 +1677,6 @@ this.PlaySound("panic"); // Run quickly - this.SelectAnimation("move"); this.SetSpeedMultiplier(this.GetRunMultiplier()); }, @@ -1738,7 +1722,6 @@ // Show weapons rather than carried resources. this.SetAnimationVariant("combat"); - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -1851,6 +1834,7 @@ cmpBuildingAI.SetUnitAITarget(0); this.StopTimer(); this.SetDefaultAnimationVariant(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -1982,7 +1966,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()) { @@ -2088,8 +2071,6 @@ return true; } - this.SelectAnimation("move"); - return false; }, @@ -2162,7 +2143,6 @@ this.FinishOrder(); return; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2285,6 +2265,7 @@ delete this.gatheringTarget; // Show the carried resource, if we've gathered anything. + this.ResetAnimation(); this.SetDefaultAnimationVariant(); }, @@ -2444,7 +2425,6 @@ return true; } - this.SelectAnimation("move"); this.StartTimer(1000, 1000); }, @@ -2495,6 +2475,7 @@ }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -2561,13 +2542,10 @@ this.SetNextState("LOOKINGFORDROPSITE"); return; } - - this.SelectAnimation("move"); }, "leave": function() { this.StopMoving(); - this.SelectAnimation("idle"); }, "MoveCompletedHint": function() { @@ -2639,7 +2617,6 @@ this.FinishOrder(); return; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2679,7 +2656,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -2745,6 +2721,7 @@ cmpBuilderList.RemoveBuilder(this.entity); delete this.repairTarget; this.StopTimer(); + this.ResetAnimation(); }, "Timer": function(msg) { @@ -2889,7 +2866,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -3017,6 +2993,7 @@ "leave": function() { this.StopTimer(); + this.ResetAnimation(); var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver); cmpDamageReceiver.SetInvulnerability(false); }, @@ -3080,7 +3057,6 @@ this.FinishOrder(); return true; } - this.SelectAnimation("move"); }, "leave": function() { @@ -3101,7 +3077,6 @@ "LOADING": { "enter": function() { this.StopMoving(); - this.SelectAnimation("idle"); var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull()) { @@ -3165,7 +3140,6 @@ "ROAMING": { "enter": function() { // Walk in a random direction - this.SelectAnimation("walk", false, 1); this.SetFacePointAfterMove(false); this.MoveRandomly(+this.template.RoamDistance); // Set a random timer to switch to feeding state @@ -3218,6 +3192,7 @@ }, "leave": function() { + this.ResetAnimation(); this.StopTimer(); }, @@ -4244,21 +4219,21 @@ this.SetAnimationVariant(""); }; +UnitAI.prototype.ResetAnimation = function() +{ + let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) + return; + + 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; - // 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()); - return; - } - cmpVisual.SelectAnimation(name, once, speed); }; Index: source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotion.cpp +++ 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" @@ -552,18 +553,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: source/simulation2/components/CCmpVisualActor.cpp =================================================================== --- source/simulation2/components/CCmpVisualActor.cpp +++ 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); @@ -77,7 +76,6 @@ std::map m_AnimOverride; // 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; @@ -230,7 +228,6 @@ SerializeMap()(serialize, "anim overrides", m_AnimOverride); - 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); @@ -285,12 +282,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) @@ -441,7 +432,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; @@ -462,6 +452,14 @@ m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); } + virtual void SelectMovementAnimation(const std::string& name, fixed speed) + { + ENSURE(name == "idle" || name == "walk" || name == "run"); + if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run") + return; + SelectAnimation(name, false, speed); + } + virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace) { m_AnimOverride[name] = replace; @@ -474,11 +472,6 @@ m_AnimOverride.erase(name); } - virtual void SelectMovementAnimation(fixed runThreshold) - { - m_AnimRunThreshold = runThreshold; - } - virtual void SetAnimationSyncRepeat(fixed repeattime) { m_AnimSyncRepeatTime = repeattime; @@ -557,8 +550,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) @@ -763,46 +754,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"; - - std::map::const_iterator it = m_AnimOverride.find(name); - if (it != m_AnimOverride.end()) - name = it->second; - - // 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: source/simulation2/components/ICmpVisual.h =================================================================== --- source/simulation2/components/ICmpVisual.h +++ source/simulation2/components/ICmpVisual.h @@ -96,15 +96,23 @@ /** * 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 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(const std::string& name, fixed speed) = 0; + /** * Replaces a specified animation with another. Only affects the special speed-based * animation determination behaviour. @@ -120,12 +128,6 @@ */ virtual void ResetMoveAnimation(const std::string& name) = 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 - */ - virtual void SelectMovementAnimation(fixed runThreshold) = 0; - /** * Adjust the speed of the current animation, so it can match simulation events. * @param repeattime time for complete loop of animation, in msec Index: source/simulation2/components/ICmpVisual.cpp =================================================================== --- source/simulation2/components/ICmpVisual.cpp +++ 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("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string) DEFINE_INTERFACE_METHOD_2("ReplaceMoveAnimation", void, ICmpVisual, ReplaceMoveAnimation, std::string, std::string) DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed)