Ticket #4420: unitMotionRewrite.patch

File unitMotionRewrite.patch, 89.3 KB (added by wraitii, 7 years ago)

Basic version - working but needs refinements.

  • binaries/data/mods/public/simulation/components/Formation.js

    diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js
    index 4533497..b70a59f 100644
    a b Formation.prototype.ComputeMotionParameters = function()  
    865865    {
    866866        var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
    867867        if (cmpUnitMotion)
    868             minSpeed = Math.min(minSpeed, cmpUnitMotion.GetWalkSpeed());
     868            minSpeed = Math.min(minSpeed, cmpUnitMotion.GetSpeed());
    869869    }
    870870    minSpeed *= this.GetSpeedMultiplier();
    871871
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
    index 932c8cd..eb24ed0 100644
    a b GuiInterface.prototype.GetExtendedEntityState = function(player, ent)  
    596596    let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
    597597    if (cmpUnitMotion)
    598598        ret.speed = {
    599             "walk": cmpUnitMotion.GetWalkSpeed(),
    600             "run": cmpUnitMotion.GetRunSpeed()
     599            "walk": cmpUnitMotion.GetSpeed(),
     600            "run": cmpUnitMotion.GetSpeed() * cmpUnitMotion.GetTopSpeedRatio()
    601601        };
    602602
    603603    return ret;
  • binaries/data/mods/public/simulation/components/UnitAI.js

    diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js
    index cbb0428..764f619 100644
    a b  
     1const WALKING_SPEED = 1.0
     2
    13function UnitAI() {}
    24
    35UnitAI.prototype.Schema =
    UnitAI.prototype.UnitFsmSpec = {  
    13911393            "enter": function () {
    13921394                var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
    13931395                var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    1394                 if (cmpFormation && cmpVisual)
     1396/* TOREPLACE                if (cmpFormation && cmpVisual)
    13951397                {
    13961398                    cmpVisual.ReplaceMoveAnimation("walk", cmpFormation.GetFormationAnimation(this.entity, "walk"));
    13971399                    cmpVisual.ReplaceMoveAnimation("run", cmpFormation.GetFormationAnimation(this.entity, "run"));
    13981400                }
     1401                */
    13991402                this.SelectAnimation("move");
    14001403            },
    14011404
    UnitAI.prototype.UnitFsmSpec = {  
    14051408                // We can only finish this order if the move was really completed.
    14061409                if (!msg.data.error && this.FinishOrder())
    14071410                    return;
    1408                 var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     1411/* TOREPLACE                var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
    14091412                if (cmpVisual)
    14101413                {
    14111414                    cmpVisual.ResetMoveAnimation("walk");
    14121415                    cmpVisual.ResetMoveAnimation("run");
    14131416                }
    1414 
     1417*/
    14151418                var cmpFormation = Engine.QueryInterface(this.formationController, IID_Formation);
    14161419                if (cmpFormation)
    14171420                    cmpFormation.SetInPosition(this.entity);
    UnitAI.prototype.UnitFsmSpec = {  
    16991702                },
    17001703
    17011704                "leave": function(msg) {
    1702                     this.SetMoveSpeed(this.GetWalkSpeed());
     1705                    this.SetMoveSpeed(WALKING_SPEED);
    17031706                    this.StopTimer();
    17041707                },
    17051708
    17061709                "MoveStarted": function(msg) {
    17071710                    // Adapt the speed to the one of the target if needed
    1708                     var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     1711                    let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    17091712                    if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange))
    17101713                    {
    1711                         var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI);
    1712                         if (cmpUnitAI)
     1714                        var cmpOtherMotion = Engine.QueryInterface(this.isGuardOf, IID_UnitMotion);
     1715                        if (cmpOtherMotion)
    17131716                        {
    1714                             var speed = cmpUnitAI.GetWalkSpeed();
    1715                             if (speed < this.GetWalkSpeed())
     1717                            let otherSpeed = cmpOtherMotion.GetSpeed();
     1718                            let mySpeed = cmpUnitMotion.GetSpeed();
     1719                            let speed = otherSpeed / mySpeed;
     1720                            if (speed < WALKING_SPEED)
    17161721                                this.SetMoveSpeed(speed);
    17171722                        }
    17181723                    }
    17191724                },
    17201725
    17211726                "MoveCompleted": function() {
    1722                     this.SetMoveSpeed(this.GetWalkSpeed());
     1727                    this.SetMoveSpeed(WALKING_SPEED);
    17231728                    if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange))
    17241729                        this.SetNextState("GUARDING");
    17251730                },
    UnitAI.prototype.UnitFsmSpec = {  
    17741779                this.PlaySound("panic");
    17751780
    17761781                // Run quickly
    1777                 var speed = this.GetRunSpeed();
    1778                 this.SelectAnimation("move");
    1779                 this.SetMoveSpeed(speed);
    1780             },
    1781 
    1782             "HealthChanged": function() {
    1783                 var speed = this.GetRunSpeed();
    1784                 this.SetMoveSpeed(speed);
     1782                this.SetMoveSpeed(this.GetRunSpeed());
    17851783            },
    17861784
    17871785            "leave": function() {
    17881786                // Reset normal speed
    1789                 this.SetMoveSpeed(this.GetWalkSpeed());
     1787                this.SetMoveSpeed(WALKING_SPEED);
    17901788            },
    17911789
    17921790            "MoveCompleted": function() {
    UnitAI.prototype.UnitFsmSpec = {  
    21002098
    21012099                    this.SelectAnimation("move");
    21022100                    var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
     2101                    // Run after a fleeing target
    21032102                    if (cmpUnitAI && cmpUnitAI.IsFleeing())
    2104                     {
    2105                         // Run after a fleeing target
    2106                         var speed = this.GetRunSpeed();
    2107                         this.SetMoveSpeed(speed);
    2108                     }
     2103                        this.SetMoveSpeed(this.GetRunSpeed());
    21092104                    this.StartTimer(1000, 1000);
    21102105                },
    21112106
    UnitAI.prototype.UnitFsmSpec = {  
    21132108                    var cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
    21142109                    if (!cmpUnitAI || !cmpUnitAI.IsFleeing())
    21152110                        return;
    2116                     var speed = this.GetRunSpeed();
    2117                     this.SetMoveSpeed(speed);
     2111                    // TODO: figure out what to do with fleeing
     2112                    //var speed = this.GetRunSpeed();
     2113                    //this.SetMoveSpeed(speed);
    21182114                },
    21192115
    21202116                "leave": function() {
    21212117                    // Reset normal speed in case it was changed
    2122                     this.SetMoveSpeed(this.GetWalkSpeed());
     2118                    this.SetMoveSpeed(WALKING_SPEED);
    21232119                    // Show carried resources when walking.
    21242120                    this.SetGathererAnimationOverride();
    21252121
    UnitAI.prototype.UnitFsmSpec = {  
    32273223        "ROAMING": {
    32283224            "enter": function() {
    32293225                // Walk in a random direction
    3230                 this.SelectAnimation("walk", false, this.GetWalkSpeed());
    32313226                this.MoveRandomly(+this.template.RoamDistance);
    32323227                // Set a random timer to switch to feeding state
    32333228                this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));
    UnitAI.prototype.StopTimer = function()  
    40104005
    40114006//// Message handlers /////
    40124007
    4013 UnitAI.prototype.OnMotionChanged = function(msg)
     4008UnitAI.prototype.OnBeginMove = function(msg)
     4009{
     4010    this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
     4011};
     4012
     4013UnitAI.prototype.OnFinishedMove = function(msg)
    40144014{
    4015     if (msg.starting && !msg.error)
    4016         this.UnitFsm.ProcessMessage(this, {"type": "MoveStarted", "data": msg});
    4017     else if (!msg.starting || msg.error)
    4018         this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
     4015    this.UnitFsm.ProcessMessage(this, {"type": "MoveCompleted", "data": msg});
    40194016};
    40204017
    40214018UnitAI.prototype.OnGlobalConstructionFinished = function(msg)
    UnitAI.prototype.OnPackFinished = function(msg)  
    40764073
    40774074//// Helper functions to be called by the FSM ////
    40784075
    4079 UnitAI.prototype.GetWalkSpeed = function()
    4080 {
    4081     var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    4082     return cmpUnitMotion.GetWalkSpeed();
    4083 };
    4084 
    40854076UnitAI.prototype.GetRunSpeed = function()
    40864077{
    40874078    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    4088     var runSpeed = cmpUnitMotion.GetRunSpeed();
    4089     var walkSpeed = cmpUnitMotion.GetWalkSpeed();
    4090     if (runSpeed <= walkSpeed)
    4091         return runSpeed;
    4092     var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    4093     var health = cmpHealth.GetHitpoints()/cmpHealth.GetMaxHitpoints();
    4094     return (health*runSpeed + (1-health)*walkSpeed);
     4079    return cmpUnitMotion.GetTopSpeedRatio();
    40954080};
    40964081
    40974082/**
    UnitAI.prototype.SetGathererAnimationOverride = function(disable)  
    42904275    // Remove the animation override, so that weapons are shown again.
    42914276    if (disable)
    42924277    {
    4293         cmpVisual.ResetMoveAnimation("walk");
     4278//TOREPLACE     cmpVisual.ResetMoveAnimation("walk");
    42944279        return;
    42954280    }
    42964281
    42974282    // Work out what we're carrying, in order to select an appropriate animation
     4283    /*
    42984284    var type = cmpResourceGatherer.GetLastCarriedType();
    42994285    if (type)
    43004286    {
    UnitAI.prototype.SetGathererAnimationOverride = function(disable)  
    43044290        if (type.specific == "meat")
    43054291            typename = "carry_" + type.specific;
    43064292
    4307         cmpVisual.ReplaceMoveAnimation("walk", typename);
     4293//  TOREPLACE   cmpVisual.ReplaceMoveAnimation("walk", typename);
    43084294    }
    43094295    else
    4310         cmpVisual.ResetMoveAnimation("walk");
     4296        cmpVisual.ResetMoveAnimation("idle");
     4297    */
    43114298};
    43124299
    43134300UnitAI.prototype.SelectAnimation = function(name, once, speed, sound)
    UnitAI.prototype.SelectAnimation = function(name, once, speed, sound)  
    43164303    if (!cmpVisual)
    43174304        return;
    43184305
    4319     // Special case: the "move" animation gets turned into a special
    4320     // movement mode that deals with speeds and walk/run automatically
    4321     if (name == "move")
    4322     {
    4323         // Speed to switch from walking to running animations
    4324         var runThreshold = (this.GetWalkSpeed() + this.GetRunSpeed()) / 2;
    4325 
    4326         cmpVisual.SelectMovementAnimation(runThreshold);
    4327         return;
    4328     }
    4329 
    43304306    var soundgroup;
    43314307    if (sound)
    43324308    {
    UnitAI.prototype.SetAnimationSync = function(actiontime, repeattime)  
    43594335UnitAI.prototype.StopMoving = function()
    43604336{
    43614337    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    4362     cmpUnitMotion.StopMoving();
     4338    cmpUnitMotion.DiscardMove();
    43634339};
    43644340
    43654341UnitAI.prototype.MoveToPoint = function(x, z)
    43664342{
    43674343    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4344    cmpUnitMotion.SetAbortIfStuck(3);
    43684345    return cmpUnitMotion.MoveToPointRange(x, z, 0, 0);
    43694346};
    43704347
    43714348UnitAI.prototype.MoveToPointRange = function(x, z, rangeMin, rangeMax)
    43724349{
    43734350    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4351    cmpUnitMotion.SetAbortIfStuck(3);
    43744352    return cmpUnitMotion.MoveToPointRange(x, z, rangeMin, rangeMax);
    43754353};
    43764354
    UnitAI.prototype.MoveToTarget = function(target)  
    43804358        return false;
    43814359
    43824360    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4361    cmpUnitMotion.SetAbortIfStuck(5);
    43834362    return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
    43844363};
    43854364
    UnitAI.prototype.MoveToTargetRange = function(target, iid, type)  
    43944373    var range = cmpRanged.GetRange(type);
    43954374
    43964375    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4376    cmpUnitMotion.SetAbortIfStuck(5);
    43974377    return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
    43984378};
    43994379
    UnitAI.prototype.MoveToTargetAttackRange = function(target, type)  
    44504430    var guessedMaxRange = (range.max + parabolicMaxRange)/2;
    44514431
    44524432    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4433    cmpUnitMotion.SetAbortIfStuck(9);
    44534434    if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange))
    44544435        return true;
    44554436
    UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)  
    44634444        return false;
    44644445
    44654446    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4447    cmpUnitMotion.SetAbortIfStuck(5);
    44664448    return cmpUnitMotion.MoveToTargetRange(target, min, max);
    44674449};
    44684450
    UnitAI.prototype.MoveToGarrisonRange = function(target)  
    44774459    var range = cmpGarrisonHolder.GetLoadingRange();
    44784460
    44794461    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     4462    cmpUnitMotion.SetAbortIfStuck(5);
    44804463    return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
    44814464};
    44824465
    UnitAI.prototype.GetStanceName = function()  
    56945677UnitAI.prototype.SetMoveSpeed = function(speed)
    56955678{
    56965679    var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    5697     cmpMotion.SetSpeed(speed);
     5680    cmpMotion.SetSpeed(1.0);
    56985681};
    56995682
    57005683UnitAI.prototype.SetHeldPosition = function(x, z)
  • binaries/data/mods/public/simulation/components/UnitMotionFlying.js

    diff --git a/binaries/data/mods/public/simulation/components/UnitMotionFlying.js b/binaries/data/mods/public/simulation/components/UnitMotionFlying.js
    index a03c3b5..3c42cf6 100644
    a b UnitMotionFlying.prototype.IsInTargetRange = function(target, minRange, maxRange  
    295295    return this.IsInPointRange(targetPos.x, targetPos.y, minRange, maxRange);
    296296};
    297297
    298 UnitMotionFlying.prototype.GetWalkSpeed = function()
     298UnitMotionFlying.prototype.GetSpeed = function()
    299299{
    300300    return +this.template.MaxSpeed;
    301301};
  • binaries/data/mods/public/simulation/data/pathfinder.xml

    diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml
    index eb912f1..aeed241 100644
    a b  
    1111      <Obstructions>pathfinding</Obstructions>
    1212      <MaxWaterDepth>2</MaxWaterDepth>
    1313      <MaxTerrainSlope>1.0</MaxTerrainSlope>
    14       <Clearance>0.8</Clearance>
     14      <Clearance>0.7</Clearance>
    1515    </default>
    1616    <large>
    1717      <Obstructions>pathfinding</Obstructions>
  • source/simulation2/MessageTypes.h

    diff --git a/source/simulation2/MessageTypes.h b/source/simulation2/MessageTypes.h
    index af194d6..36984a3 100644
    a b public:  
    317317};
    318318
    319319/**
    320  * Sent by CCmpUnitMotion during Update, whenever the motion status has changed
    321  * since the previous update.
     320 * Sent by CCmpUnitMotion during Update,
     321 * whenever we have started actually moving and were not moving before.
     322 * We may or may not already have been trying to move
    322323 */
    323 class CMessageMotionChanged : public CMessage
     324class CMessageBeginMove : public CMessage
    324325{
    325326public:
    326     DEFAULT_MESSAGE_IMPL(MotionChanged)
     327    DEFAULT_MESSAGE_IMPL(BeginMove)
    327328
    328     CMessageMotionChanged(bool starting, bool error) :
    329         starting(starting), error(error)
     329    CMessageBeginMove()
     330    {
     331    }
     332};
     333
     334/**
     335 * Sent by CCmpUnitMotion during Update,
     336 * whenever we were actually moving before, and cannot continue
     337 * this can be because we've arrived (failed=false) or we failed moving (failed=true)
     338 * After this message is sent, the unit won't remove/repath without orders.
     339 * Will never be sent on the same turn as MT_BeginMove.
     340 */
     341class CMessageFinishedMove : public CMessage
     342{
     343public:
     344    DEFAULT_MESSAGE_IMPL(FinishedMove)
     345
     346    CMessageFinishedMove(bool fail) : failed(fail)
    330347    {
    331348    }
    332349
    333     bool starting; // whether this is a start or end of movement
    334     bool error; // whether we failed to start moving (couldn't find any path)
     350    bool failed; // move failed
     351};
     352
     353/**
     354 * Sent by CCmpUnitMotion during Update,
     355 * whenever we were actually moving before, and now stopped
     356 * In this case, we will retry moving/pathing in the future on our own
     357 * Unless ordered otherwise.
     358 * We are just possibly stuck short-term, or must repath.
     359 * Will never be sent on the same turn as MT_BeginMove.
     360 */
     361class CMessagePausedMove : public CMessage
     362{
     363public:
     364    DEFAULT_MESSAGE_IMPL(PausedMove)
     365
     366    CMessagePausedMove()
     367    {
     368    }
    335369};
    336370
    337371/**
  • source/simulation2/TypeList.h

    diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
    index 773c39b..c1f6473 100644
    a b MESSAGE(OwnershipChanged)  
    4545MESSAGE(PositionChanged)
    4646MESSAGE(InterpolatedPositionChanged)
    4747MESSAGE(TerritoryPositionChanged)
    48 MESSAGE(MotionChanged)
     48MESSAGE(BeginMove)
     49MESSAGE(FinishedMove)
     50MESSAGE(PausedMove)
    4951MESSAGE(RangeUpdate)
    5052MESSAGE(TerrainChanged)
    5153MESSAGE(VisibilityChanged)
  • source/simulation2/components/CCmpObstructionManager.cpp

    diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp
    index 553ad91..8dc6829 100644
    a b void CCmpObstructionManager::RenderSubmit(SceneCollector& collector)  
    10841084            m_DebugOverlayLines.push_back(SOverlayLine());
    10851085            m_DebugOverlayLines.back().m_Color = defaultColor;
    10861086            float a = atan2f(it->second.v.X.ToFloat(), it->second.v.Y.ToFloat());
    1087             SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2, it->second.hh.ToFloat()*2, a, m_DebugOverlayLines.back(), true);
     1087            SimRender::ConstructSquareOnGround(GetSimContext(), it->second.x.ToFloat(), it->second.z.ToFloat(), it->second.hw.ToFloat()*2 + 1, it->second.hh.ToFloat()*2 + 1, a, m_DebugOverlayLines.back(), true);
    10881088        }
    10891089
    10901090        m_DebugOverlayDirty = false;
  • source/simulation2/components/CCmpPathfinder_Vertex.cpp

    diff --git a/source/simulation2/components/CCmpPathfinder_Vertex.cpp b/source/simulation2/components/CCmpPathfinder_Vertex.cpp
    index a6f2a43..6442db0 100644
    a b void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,  
    610610        // Check whether this is an axis-aligned square
    611611        bool aa = (u.X == fixed::FromInt(1) && u.Y == fixed::Zero() && v.X == fixed::Zero() && v.Y == fixed::FromInt(1));
    612612
     613        bool add = true;
    613614        Vertex vert;
    614615        vert.status = Vertex::UNEXPLORED;
    615616        vert.quadInward = QUADRANT_NONE;
    616617        vert.quadOutward = QUADRANT_ALL;
    617618        vert.p.X = center.X - hd0.Dot(u); vert.p.Y = center.Y + hd0.Dot(v);
    618         if (vert.p.X < rangeXMin) continue;
    619         if (vert.p.Y < rangeZMin) continue;
    620         if (vert.p.X > rangeXMax) continue;
    621         if (vert.p.Y > rangeZMax) continue;
    622         if (aa) vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
     619        if (vert.p.X <= rangeXMin) add = false;
     620        else if (vert.p.X >= rangeXMax) add = false;
     621        else if (vert.p.Y <= rangeZMin) add = false;
     622        else if (vert.p.Y >= rangeZMax) add = false;
     623        if (aa) vert.quadInward = QUADRANT_BR;
     624        if (add) vertexes.push_back(vert);
     625        add = true;
    623626        vert.p.X = center.X - hd1.Dot(u); vert.p.Y = center.Y + hd1.Dot(v);
    624         if (vert.p.X < rangeXMin) continue;
    625         if (vert.p.Y < rangeZMin) continue;
    626         if (vert.p.X > rangeXMax) continue;
    627         if (vert.p.Y > rangeZMax) continue;
    628         if (aa) vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
     627        if (vert.p.X <= rangeXMin) add = false;
     628        else if (vert.p.X >= rangeXMax) add = false;
     629        else if (vert.p.Y <= rangeZMin) add = false;
     630        else if (vert.p.Y >= rangeZMax) add = false;
     631        if (aa) vert.quadInward = QUADRANT_TR;
     632        if (add) vertexes.push_back(vert);
     633        add = true;
    629634        vert.p.X = center.X + hd0.Dot(u); vert.p.Y = center.Y - hd0.Dot(v);
    630         if (vert.p.X < rangeXMin) continue;
    631         if (vert.p.Y < rangeZMin) continue;
    632         if (vert.p.X > rangeXMax) continue;
    633         if (vert.p.Y > rangeZMax) continue;
    634         if (aa) vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
     635        if (vert.p.X <= rangeXMin) add = false;
     636        else if (vert.p.X >= rangeXMax) add = false;
     637        else if (vert.p.Y <= rangeZMin) add = false;
     638        else if (vert.p.Y >= rangeZMax) add = false;
     639        if (aa) vert.quadInward = QUADRANT_TL;
     640        if (add) vertexes.push_back(vert);
     641        add = true;
    635642        vert.p.X = center.X + hd1.Dot(u); vert.p.Y = center.Y - hd1.Dot(v);
    636         if (vert.p.X < rangeXMin) continue;
    637         if (vert.p.Y < rangeZMin) continue;
    638         if (vert.p.X > rangeXMax) continue;
    639         if (vert.p.Y > rangeZMax) continue;
    640         if (aa) vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
     643        if (vert.p.X <= rangeXMin) add = false;
     644        else if (vert.p.X >= rangeXMax) add = false;
     645        else if (vert.p.Y <= rangeZMin) add = false;
     646        else if (vert.p.Y >= rangeZMax) add = false;
     647        if (aa) vert.quadInward = QUADRANT_BL;
     648        if (add) vertexes.push_back(vert);
    641649
    642650        // Add the edges:
    643651
    void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,  
    658666            edges.emplace_back(Edge{ ev3, ev0 });
    659667        }
    660668
    661         // TODO: should clip out vertexes and edges that are outside the range,
    662         // to reduce the search space
    663669    }
    664670
    665671    // Add terrain obstructions
  • source/simulation2/components/CCmpUnitMotion.cpp

    diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp
    index 04a7a00..b66c726 100644
    a b  
    2727#include "simulation2/components/ICmpPathfinder.h"
    2828#include "simulation2/components/ICmpRangeManager.h"
    2929#include "simulation2/components/ICmpValueModificationManager.h"
     30#include "simulation2/components/ICmpVisual.h"
    3031#include "simulation2/helpers/Geometry.h"
    3132#include "simulation2/helpers/Render.h"
    3233#include "simulation2/MessageTypes.h"
    static const entity_pos_t CHECK_TARGET_MOVEMENT_AT_MAX_DIST = entity_pos_t::From  
    106107 */
    107108static const fixed CHECK_TARGET_MOVEMENT_MIN_COS = fixed::FromInt(866)/1000;
    108109
     110/**
     111 * See unitmotion logic for details. Higher means units will retry more often before potentially failing.
     112 */
     113static const size_t MAX_PATH_REATTEMPS = 8;
     114
    109115static const CColor OVERLAY_COLOR_LONG_PATH(1, 1, 1, 1);
    110116static const CColor OVERLAY_COLOR_SHORT_PATH(1, 0, 0, 1);
    111117
    112118class CCmpUnitMotion : public ICmpUnitMotion
    113119{
     120private:
     121    struct SMotionGoal
     122    {
     123    private:
     124        bool m_Valid = false;
     125
     126        entity_pos_t m_TargetMinRange;
     127        entity_pos_t m_TargetMaxRange;
     128
     129        entity_id_t m_TargetEntity;
     130        // pathfinder-compliant goal.
     131        PathGoal m_Goal;
     132    public:
     133        SMotionGoal() : m_Valid(false) {};
     134       
     135        SMotionGoal(PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange)
     136        {
     137            m_TargetEntity = INVALID_ENTITY;
     138
     139            m_TargetMinRange = minRange;
     140            m_TargetMaxRange = maxRange;
     141
     142            m_Goal = goal;
     143            m_Valid = true;
     144        }
     145
     146        SMotionGoal(const CSimContext& context, entity_id_t target, PathGoal& goal, entity_pos_t minRange, entity_pos_t maxRange)
     147        {
     148            m_TargetEntity = target;
     149            m_TargetMinRange = minRange;
     150            m_TargetMaxRange = maxRange;
     151
     152            m_Goal = goal;
     153            m_Valid = true;
     154
     155            UpdateTargetPosition(context);
     156        }
     157
     158        const PathGoal& Goal() const { return m_Goal; };
     159
     160        bool TargetIsEntity() const { return m_TargetEntity != INVALID_ENTITY; }
     161        entity_id_t GetEntity() const { return m_TargetEntity; }
     162
     163        bool Valid() const { return m_Valid; }
     164        void Clear() { m_Valid = false; }
     165
     166        entity_pos_t MinRange() const { return m_TargetMinRange; };
     167        entity_pos_t MaxRange() const { return m_TargetMaxRange; };
     168
     169        CFixedVector2D Pos() const { return CFixedVector2D(m_Goal.x, m_Goal.z); }
     170        entity_pos_t X() const { return m_Goal.x; }
     171        entity_pos_t Z() const { return m_Goal.z; }
     172
     173        void UpdateTargetPosition(const CSimContext& context)
     174        {
     175            if (!TargetIsEntity())
     176                return;
     177
     178            CmpPtr<ICmpPosition> cmpPosition(context, m_TargetEntity);
     179            if (!cmpPosition || !cmpPosition->IsInWorld())
     180                return;
     181
     182            m_Goal.x = cmpPosition->GetPosition2D().X;
     183            m_Goal.z = cmpPosition->GetPosition2D().Y;
     184        }
     185
     186        bool IsAPoint() const
     187        {
     188            return m_Goal.type == PathGoal::POINT && !TargetIsEntity();
     189        }
     190    };
     191
    114192public:
    115193    static void ClassInit(CComponentManager& componentManager)
    116194    {
    117         componentManager.SubscribeToMessageType(MT_Update_MotionFormation);
    118195        componentManager.SubscribeToMessageType(MT_Update_MotionUnit);
    119196        componentManager.SubscribeToMessageType(MT_PathResult);
    120197        componentManager.SubscribeToMessageType(MT_OwnershipChanged);
    public:  
    128205    std::vector<SOverlayLine> m_DebugOverlayLongPathLines;
    129206    std::vector<SOverlayLine> m_DebugOverlayShortPathLines;
    130207
    131     // Template state:
    132 
    133     bool m_FormationController;
    134     fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second
    135     fixed m_RunSpeed, m_OriginalRunSpeed;
     208    // Template state, never changed after init.
     209    fixed m_TemplateSpeed, m_TopSpeedRatio;
    136210    pass_class_t m_PassClass;
    137211    std::string m_PassClassName;
    138 
    139     // Dynamic state:
    140 
    141212    entity_pos_t m_Clearance;
    142     bool m_Moving;
    143     bool m_FacePointAfterMove;
    144 
    145     enum State
    146     {
    147         /*
    148          * Not moving at all.
    149          */
    150         STATE_IDLE,
    151 
    152         /*
    153          * Not moving at all. Will go to IDLE next turn.
    154          * (This one-turn delay is a hack to fix animation timings.)
    155          */
    156         STATE_STOPPING,
    157 
    158         /*
    159          * Member of a formation.
    160          * Pathing to the target (depending on m_PathState).
    161          * Target is m_TargetEntity plus m_TargetOffset.
    162          */
    163         STATE_FORMATIONMEMBER_PATH,
    164 
    165         /*
    166          * Individual unit or formation controller.
    167          * Pathing to the target (depending on m_PathState).
    168          * Target is m_TargetPos, m_TargetMinRange, m_TargetMaxRange;
    169          * if m_TargetEntity is not INVALID_ENTITY then m_TargetPos is tracking it.
    170          */
    171         STATE_INDIVIDUAL_PATH,
    172 
    173         STATE_MAX
    174     };
    175     u8 m_State;
    176 
    177     enum PathState
    178     {
    179         /*
    180          * There is no path.
    181          * (This should only happen in IDLE and STOPPING.)
    182          */
    183         PATHSTATE_NONE,
    184 
    185         /*
    186          * We have an outstanding long path request.
    187          * No paths are usable yet, so we can't move anywhere.
    188          */
    189         PATHSTATE_WAITING_REQUESTING_LONG,
    190 
    191         /*
    192          * We have an outstanding short path request.
    193          * m_LongPath is valid.
    194          * m_ShortPath is not yet valid, so we can't move anywhere.
    195          */
    196         PATHSTATE_WAITING_REQUESTING_SHORT,
    197 
    198         /*
    199          * We are following our path, and have no path requests.
    200          * m_LongPath and m_ShortPath are valid.
    201          */
    202         PATHSTATE_FOLLOWING,
    203 
    204         /*
    205          * We are following our path, and have an outstanding long path request.
    206          * (This is because our target moved a long way and we need to recompute
    207          * the whole path).
    208          * m_LongPath and m_ShortPath are valid.
    209          */
    210         PATHSTATE_FOLLOWING_REQUESTING_LONG,
    211 
    212         /*
    213          * We are following our path, and have an outstanding short path request.
    214          * (This is because our target moved and we've got a new long path
    215          * which we need to follow).
    216          * m_LongPath is valid; m_ShortPath is valid but obsolete.
    217          */
    218         PATHSTATE_FOLLOWING_REQUESTING_SHORT,
    219 
    220         PATHSTATE_MAX
    221     };
    222     u8 m_PathState;
    223 
    224     u32 m_ExpectedPathTicket; // asynchronous request ID we're waiting for, or 0 if none
    225 
    226     entity_id_t m_TargetEntity;
    227     CFixedVector2D m_TargetPos;
    228     CFixedVector2D m_TargetOffset;
    229     entity_pos_t m_TargetMinRange;
    230     entity_pos_t m_TargetMaxRange;
    231213
     214    // TARGET
     215    // As long as we have a valid target, the unit is considered "on the move".
     216    // It may not be actually moving for a variety of reasons (no path, blocked path)… but it will shortly.
     217    SMotionGoal m_FinalGoal;
     218   
     219    // MOTION PLANNING
     220    // We will abort if we are stuck after X tries.
     221    u8 m_AbortIfStuck;
     222    // turn towards our target at the end
     223    bool m_FacePointAfterMove;
     224    // actual unit speed, after technology and ratio
    232225    fixed m_Speed;
     226    // cached for convenience
     227    fixed m_SpeedRatio;
    233228
    234     // Current mean speed (over the last turn).
    235     fixed m_CurSpeed;
     229    // asynchronous request ID we're waiting for, or 0 if none
     230    u32 m_ExpectedPathTicket;
    236231
    237232    // Currently active paths (storing waypoints in reverse order).
    238233    // The last item in each path is the point we're currently heading towards.
    239     WaypointPath m_LongPath;
    240     WaypointPath m_ShortPath;
    241 
    242     // Motion planning
    243     u8 m_Tries; // how many tries we've done to get to our current Final Goal.
    244 
    245     PathGoal m_FinalGoal;
     234    WaypointPath m_Path;
     235    // used for the short pathfinder, incremented on each unsuccessful try.
     236    u8 m_Tries;
     237    // Turns to wait before a certain action.
     238    u8 m_WaitingTurns;
     239    // if we actually started moving at some point.
     240    bool m_StartedMoving;
     241
     242    // Speed over the last turn
     243    // cached for components that want it
     244    fixed m_ActualSpeed;
    246245
    247246    static std::string GetSchema()
    248247    {
    public:  
    276275
    277276    virtual void Init(const CParamNode& paramNode)
    278277    {
    279         m_FormationController = paramNode.GetChild("FormationController").ToBool();
    280 
    281         m_Moving = false;
    282278        m_FacePointAfterMove = true;
    283279
    284         m_WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
    285         m_Speed = m_WalkSpeed;
    286         m_CurSpeed = fixed::Zero();
     280        m_TemplateSpeed = m_Speed = paramNode.GetChild("WalkSpeed").ToFixed();
     281        m_ActualSpeed = fixed::Zero();
     282        m_SpeedRatio = fixed::FromInt(1);
    287283
    288         if (paramNode.GetChild("Run").IsOk())
    289             m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
    290         else
    291             m_RunSpeed = m_OriginalRunSpeed = m_WalkSpeed;
     284        m_TopSpeedRatio = fixed::FromInt(1);
     285        if (paramNode.GetChild("RunMultiplier").IsOk())
     286            m_TopSpeedRatio = paramNode.GetChild("WalkSpeed").ToFixed();
    292287
    293288        CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    294289        if (cmpPathfinder)
    public:  
    302297                cmpObstruction->SetUnitClearance(m_Clearance);
    303298        }
    304299
    305         m_State = STATE_IDLE;
    306         m_PathState = PATHSTATE_NONE;
    307 
    308300        m_ExpectedPathTicket = 0;
    309301
    310302        m_Tries = 0;
    311 
    312         m_TargetEntity = INVALID_ENTITY;
    313 
    314         m_FinalGoal.type = PathGoal::POINT;
     303        m_WaitingTurns = 0;
    315304
    316305        m_DebugOverlayEnabled = false;
     306        m_AbortIfStuck = 0;
    317307    }
    318308
    319309    virtual void Deinit()
    public:  
    323313    template<typename S>
    324314    void SerializeCommon(S& serialize)
    325315    {
    326         serialize.NumberU8("state", m_State, 0, STATE_MAX-1);
     316        /*serialize.NumberU8("state", m_State, 0, STATE_MAX-1);
    327317        serialize.NumberU8("path state", m_PathState, 0, PATHSTATE_MAX-1);
    328318
    329319        serialize.StringASCII("pass class", m_PassClassName, 0, 64);
    public:  
    339329        serialize.NumberFixed_Unbounded("target max range", m_TargetMaxRange);
    340330
    341331        serialize.NumberFixed_Unbounded("speed", m_Speed);
    342         serialize.NumberFixed_Unbounded("current speed", m_CurSpeed);
     332        serialize.NumberFixed_Unbounded("current speed", m_ActualSpeed);
    343333
    344334        serialize.Bool("moving", m_Moving);
    345335        serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
    public:  
    349339        SerializeVector<SerializeWaypoint>()(serialize, "long path", m_LongPath.m_Waypoints);
    350340        SerializeVector<SerializeWaypoint>()(serialize, "short path", m_ShortPath.m_Waypoints);
    351341
    352         SerializeGoal()(serialize, "goal", m_FinalGoal);
     342        SerializeGoal()(serialize, "goal", m_FinalGoal);*/
    353343    }
    354344
    355345    virtual void Serialize(ISerializer& serialize)
    public:  
    359349
    360350    virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
    361351    {
    362         Init(paramNode);
     352        /*Init(paramNode);
    363353
    364354        SerializeCommon(deserialize);
    365355
    366356        CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    367357        if (cmpPathfinder)
    368             m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName);
     358            m_PassClass = cmpPathfinder->GetPassabilityClass(m_PassClassName);*/
    369359    }
    370360
    371361    virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
    372362    {
    373363        switch (msg.GetType())
    374364        {
    375         case MT_Update_MotionFormation:
    376         {
    377             if (m_FormationController)
    378             {
    379                 fixed dt = static_cast<const CMessageUpdate_MotionFormation&> (msg).turnLength;
    380                 Move(dt);
    381             }
    382             break;
    383         }
    384365        case MT_Update_MotionUnit:
    385366        {
    386             if (!m_FormationController)
    387             {
    388                 fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength;
    389                 Move(dt);
    390             }
     367            fixed dt = static_cast<const CMessageUpdate_MotionUnit&> (msg).turnLength;
     368            Move(dt);
    391369            break;
    392370        }
    393371        case MT_RenderSubmit:
    public:  
    417395            if (!cmpValueModificationManager)
    418396                break;
    419397
    420             fixed newWalkSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_OriginalWalkSpeed, GetEntityId());
    421             fixed newRunSpeed = cmpValueModificationManager->ApplyModifications(L"UnitMotion/Run/Speed", m_OriginalRunSpeed, GetEntityId());
    422 
    423             // update m_Speed (the actual speed) if set to one of the variables
    424             if (m_Speed == m_WalkSpeed)
    425                 m_Speed = newWalkSpeed;
    426             else if (m_Speed == m_RunSpeed)
    427                 m_Speed = newRunSpeed;
     398            m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId()));
    428399
    429             m_WalkSpeed = newWalkSpeed;
    430             m_RunSpeed = newRunSpeed;
    431400            break;
    432401        }
    433402        }
    public:  
    439408        GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
    440409    }
    441410
    442     virtual bool IsMoving()
     411    virtual bool IsActuallyMoving()
     412    {
     413        return m_StartedMoving;
     414    }
     415
     416    virtual bool IsTryingToMove()
     417    {
     418        // speed check as sanity check to avoid infinite loops.
     419        return m_FinalGoal.Valid() && m_Speed > fixed::Zero();
     420    }
     421
     422    virtual fixed GetTemplateSpeed()
     423    {
     424        return m_TemplateSpeed;
     425    }
     426
     427    virtual fixed GetSpeed()
    443428    {
    444         return m_Moving;
     429        return m_Speed;
    445430    }
    446431
    447     virtual fixed GetWalkSpeed()
     432    virtual fixed GetActualSpeed()
    448433    {
    449         return m_WalkSpeed;
     434        return m_ActualSpeed;
    450435    }
    451436
    452     virtual fixed GetRunSpeed()
     437    virtual fixed GetSpeedRatio()
    453438    {
    454         return m_RunSpeed;
     439        return m_SpeedRatio;
     440    }
     441
     442    virtual fixed GetTopSpeedRatio()
     443    {
     444        return m_TopSpeedRatio;
     445    }
     446
     447    // don't call this all the time
     448    // it's voluntarily too slow, because you shouldn't be doing this.
     449    virtual void SetSpeed(fixed ratio)
     450    {
     451        m_SpeedRatio = ratio;
     452        CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity());
     453        if (cmpValueModificationManager)
     454        {
     455            m_Speed = m_SpeedRatio.Multiply(cmpValueModificationManager->ApplyModifications(L"UnitMotion/WalkSpeed", m_TemplateSpeed, GetEntityId()));
     456            return;
     457        }
     458
     459        m_Speed = m_SpeedRatio.Multiply(m_TemplateSpeed);
    455460    }
    456461
    457462    virtual pass_class_t GetPassabilityClass()
    public:  
    472477            m_PassClass = cmpPathfinder->GetPassabilityClass(passClassName);
    473478    }
    474479
    475     virtual fixed GetCurrentSpeed()
    476     {
    477         return m_CurSpeed;
    478     }
    479 
    480     virtual void SetSpeed(fixed speed)
    481     {
    482         m_Speed = speed;
    483     }
    484 
    485480    virtual void SetFacePointAfterMove(bool facePointAfterMove)
    486481    {
    487482        m_FacePointAfterMove = facePointAfterMove;
    public:  
    493488        UpdateMessageSubscriptions();
    494489    }
    495490
     491    virtual entity_pos_t GetUnitClearance()
     492    {
     493        return m_Clearance;
     494    }
     495
    496496    virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
    497497    virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
    498498    virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
    499499    virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
    500     virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
    501500
    502501    virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z);
    503502
    504     virtual void StopMoving()
     503    virtual void SetAbortIfStuck(u8 shouldAbort)
    505504    {
    506         m_Moving = false;
    507         m_ExpectedPathTicket = 0;
    508         m_State = STATE_STOPPING;
    509         m_PathState = PATHSTATE_NONE;
    510         m_LongPath.m_Waypoints.clear();
    511         m_ShortPath.m_Waypoints.clear();
     505        m_AbortIfStuck = shouldAbort;
    512506    }
    513507
    514     virtual entity_pos_t GetUnitClearance()
     508    virtual void DiscardMove()
    515509    {
    516         return m_Clearance;
     510        StopMovingQuietly();
    517511    }
    518512
    519 private:
    520     bool ShouldAvoidMovingUnits() const
     513    // stop moving and send message
     514    virtual void CompleteMove()
    521515    {
    522         return !m_FormationController;
     516        // highlight bugs.
     517        if (!IsTryingToMove())
     518        {
     519            LOGERROR("Entity %i trying to stop moving but has not actually started", GetEntityId());
     520            return;
     521        }
     522
     523        StopMovingQuietly();
     524
     525        if (m_FacePointAfterMove)
     526        {
     527            CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     528            if (cmpPosition && cmpPosition->IsInWorld())
     529                FaceTowardsPointFromPos(cmpPosition->GetPosition2D(), m_FinalGoal.X(), m_FinalGoal.Z());
     530        }
     531
     532        CMessageFinishedMove msg(false);
     533        GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
    523534    }
    524535
     536private:
     537/*
     538 TODO: reimplement
    525539    bool IsFormationMember() const
    526540    {
    527541        return m_State == STATE_FORMATIONMEMBER_PATH;
    528542    }
    529 
     543*/
    530544    entity_id_t GetGroup() const
    531545    {
    532         return IsFormationMember() ? m_TargetEntity : GetEntityId();
     546        //return IsFormationMember() ? m_TargetEntity : GetEntityId();
     547        return GetEntityId();
    533548    }
    534549
    535550    bool HasValidPath() const
    536551    {
    537         return m_PathState == PATHSTATE_FOLLOWING
    538             || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG
    539             || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT;
     552        return !m_Path.m_Waypoints.empty();
    540553    }
    541554
    542     void StartFailed()
     555    void StopMovingQuietly()
    543556    {
    544         StopMoving();
    545         m_State = STATE_IDLE; // don't go through the STOPPING state since we never even started
     557        // sanity
     558        m_Tries = 0;
     559        m_WaitingTurns = 0;
     560        m_StartedMoving = false;
     561
     562        // reset state.
     563        m_ExpectedPathTicket = 0;
     564        m_FinalGoal.Clear();
     565        m_Path.m_Waypoints.clear();
    546566
    547567        CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    548568        if (cmpObstruction)
    549569            cmpObstruction->SetMovingFlag(false);
    550570
    551         CMessageMotionChanged msg(true, true);
    552         GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     571        CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle());
     572        if (cmpVisualActor)
     573            cmpVisualActor->SetMoving(true);
    553574    }
    554575
    555576    void MoveFailed()
    556577    {
    557         StopMoving();
     578        StopMovingQuietly();
    558579
    559         CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    560         if (cmpObstruction)
    561             cmpObstruction->SetMovingFlag(false);
    562 
    563         CMessageMotionChanged msg(false, true);
     580        CMessageFinishedMove msg(true);
    564581        GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     582        CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle());
     583        if (cmpVisualActor)
     584            cmpVisualActor->SetMoving(false);
    565585    }
    566586
    567     void StartSucceeded()
     587    void MovePaused()
    568588    {
    569         CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    570         if (cmpObstruction)
    571             cmpObstruction->SetMovingFlag(true);
     589        m_StartedMoving = false;
    572590
    573         m_Moving = true;
    574 
    575         CMessageMotionChanged msg(true, false);
     591        CMessagePausedMove msg;
    576592        GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     593        CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle());
     594        if (cmpVisualActor)
     595            cmpVisualActor->SetMoving(false);
    577596    }
    578597
    579     void MoveSucceeded()
     598    void MoveStarted()
    580599    {
    581         m_Moving = false;
    582 
    583         CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    584         if (cmpObstruction)
    585             cmpObstruction->SetMovingFlag(false);
     600        m_StartedMoving = true;
    586601
    587         // No longer moving, so speed is 0.
    588         m_CurSpeed = fixed::Zero();
    589 
    590         CMessageMotionChanged msg(false, false);
     602        CMessageBeginMove msg;
    591603        GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
     604        CmpPtr<ICmpVisual> cmpVisualActor(GetEntityHandle());
     605        if (cmpVisualActor)
     606            cmpVisualActor->SetMoving(true);
    592607    }
    593608
    594609    bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target);
    private:  
    634649    bool CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta);
    635650
    636651    /**
    637      * Update goal position if moving target
    638      */
    639     void UpdateFinalGoal();
    640 
    641     /**
    642652     * Returns whether we are close enough to the target to assume it's a good enough
    643653     * position to stop.
    644654     */
    645     bool ShouldConsiderOurselvesAtDestination(const CFixedVector2D& from);
     655    bool ShouldConsiderOurselvesAtDestination();
    646656
    647657    /**
    648658     * Returns whether the length of the given path, plus the distance from
    private:  
    663673    ControlGroupMovementObstructionFilter GetObstructionFilter(bool noTarget = false) const;
    664674
    665675    /**
    666      * Start moving to the given goal, from our current position 'from'.
     676     * Dump current paths and request a new one.
    667677     * Might go in a straight line immediately, or might start an asynchronous
    668678     * path request.
    669679     */
    670     void BeginPathing(const CFixedVector2D& from, const PathGoal& goal);
     680    void RequestNewPath();
    671681
    672682    /**
    673683     * Start an asynchronous long path query.
    REGISTER_COMPONENT_TYPE(UnitMotion)  
    691701
    692702void CCmpUnitMotion::PathResult(u32 ticket, const WaypointPath& path)
    693703{
    694     // reset our state for sanity.
    695     CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    696     if (cmpObstruction)
    697         cmpObstruction->SetMovingFlag(false);
    698 
    699     m_Moving = false;
    700 
    701704    // Ignore obsolete path requests
    702705    if (ticket != m_ExpectedPathTicket)
    703706        return;
    704707
    705708    m_ExpectedPathTicket = 0; // we don't expect to get this result again
    706709
    707     // Check that we are still able to do something with that path
    708     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    709     if (!cmpPosition || !cmpPosition->IsInWorld())
    710     {
    711         if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT)
    712             StartFailed();
    713         else if (m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)
    714             StopMoving();
     710    if (!m_FinalGoal.Valid())
    715711        return;
    716     }
    717712
    718     if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_LONG)
     713    if (path.m_Waypoints.empty())
    719714    {
    720         m_LongPath = path;
     715        // no waypoints, path failed.
     716        // if we have some room, pop waypoint
     717        // TODO: this isn't particularly bright.
     718        if (!m_Path.m_Waypoints.empty())
     719            m_Path.m_Waypoints.pop_back();
    721720
    722         // If we are following a path, leave the old m_ShortPath so we can carry on following it
    723         // until a new short path has been computed
    724         if (m_PathState == PATHSTATE_WAITING_REQUESTING_LONG)
    725             m_ShortPath.m_Waypoints.clear();
    726 
    727         // If there's no waypoints then we couldn't get near the target.
    728         // Sort of hack: Just try going directly to the goal point instead
    729         // (via the short pathfinder), so if we're stuck and the user clicks
    730         // close enough to the unit then we can probably get unstuck
    731         if (m_LongPath.m_Waypoints.empty())
    732             m_LongPath.m_Waypoints.emplace_back(Waypoint{ m_FinalGoal.x, m_FinalGoal.z });
    733 
    734         if (!HasValidPath())
    735             StartSucceeded();
    736 
    737         m_PathState = PATHSTATE_FOLLOWING;
    738 
    739         if (cmpObstruction)
    740             cmpObstruction->SetMovingFlag(true);
    741 
    742         m_Moving = true;
     721        // we will then deal with this on the next Move() call.
     722        return;
    743723    }
    744     else if (m_PathState == PATHSTATE_WAITING_REQUESTING_SHORT || m_PathState == PATHSTATE_FOLLOWING_REQUESTING_SHORT)
    745     {
    746         m_ShortPath = path;
    747 
    748         // If there's no waypoints then we couldn't get near the target
    749         if (m_ShortPath.m_Waypoints.empty())
    750         {
    751             // If we're globally following a long path, try to remove the next waypoint, it might be obstructed
    752             // If not, and we are not in a formation, retry
    753             // unless we are close to our target and we don't have a target entity.
    754             // This makes sure that units don't clump too much when they are not in a formation and tasked to move.
    755             if (m_LongPath.m_Waypoints.size() > 1)
    756                 m_LongPath.m_Waypoints.pop_back();
    757             else if (IsFormationMember())
    758             {
    759                 m_Moving = false;
    760                 CMessageMotionChanged msg(true, true);
    761                 GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
    762                 return;
    763             }
    764 
    765             CMessageMotionChanged msg(false, false);
    766             GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
    767 
    768             CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    769             if (!cmpPosition || !cmpPosition->IsInWorld())
    770                 return;
    771724
    772             CFixedVector2D pos = cmpPosition->GetPosition2D();
    773 
    774             if (ShouldConsiderOurselvesAtDestination(pos))
    775                 return;
    776 
    777             UpdateFinalGoal();
    778             RequestLongPath(pos, m_FinalGoal);
    779             m_PathState = PATHSTATE_WAITING_REQUESTING_LONG;
    780             return;
    781         }
    782 
    783         // else we could, so reset our number of tries.
    784         m_Tries = 0;
    785 
    786         // Now we've got a short path that we can follow
    787         if (!HasValidPath())
    788             StartSucceeded();
    789 
    790         m_PathState = PATHSTATE_FOLLOWING;
    791 
    792         if (cmpObstruction)
    793             cmpObstruction->SetMovingFlag(true);
    794 
    795         m_Moving = true;
    796     }
    797     else
    798         LOGWARNING("unexpected PathResult (%u %d %d)", GetEntityId(), m_State, m_PathState);
     725    // add to the top of our current waypoints
     726    m_Path.m_Waypoints.insert(m_Path.m_Waypoints.end(), path.m_Waypoints.begin(), path.m_Waypoints.end());
    799727}
    800728
    801729void CCmpUnitMotion::Move(fixed dt)
    802730{
    803731    PROFILE("Move");
    804732
    805     if (m_State == STATE_STOPPING)
     733    /**
     734     * TODO: the visual actor doesn't interpolate, it merely changes things on update
     735     * This means if a unit wants to change animation between turns (because it stops…)
     736     * It will look slightly glitchy for a very short while
     737     */
     738
     739    if (!IsTryingToMove())
    806740    {
    807         m_State = STATE_IDLE;
    808         MoveSucceeded();
     741        m_ActualSpeed = fixed::Zero();
    809742        return;
    810743    }
    811744
    812     if (m_State == STATE_IDLE)
     745    m_FinalGoal.UpdateTargetPosition(GetSimContext());
     746
     747    // TODO: units will look at each other's position in an arbitrary order that must be the same for any simulation
     748    // In particular this means no threading. Maybe we should update this someday if it's possible.
     749
     750    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
     751    if (!cmpPathfinder)
    813752        return;
    814753
    815     switch (m_PathState)
    816     {
    817     case PATHSTATE_NONE:
    818     {
    819         // If we're not pathing, do nothing
     754    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     755    if (!cmpPosition || !cmpPosition->IsInWorld())
    820756        return;
    821     }
    822757
    823     case PATHSTATE_WAITING_REQUESTING_LONG:
    824     case PATHSTATE_WAITING_REQUESTING_SHORT:
     758    CFixedVector2D initialPos = cmpPosition->GetPosition2D();
     759
     760    // Preliminary check: our target may be an entity and may have moved before us
     761    if (ShouldConsiderOurselvesAtDestination())
    825762    {
    826         // If we're waiting for a path and don't have one yet, do nothing
     763        CompleteMove();
    827764        return;
    828765    }
    829766
    830     case PATHSTATE_FOLLOWING:
    831     case PATHSTATE_FOLLOWING_REQUESTING_SHORT:
    832     case PATHSTATE_FOLLOWING_REQUESTING_LONG:
    833     {
    834         // TODO: there's some asymmetry here when units look at other
    835         // units' positions - the result will depend on the order of execution.
    836         // Maybe we should split the updates into multiple phases to minimise
    837         // that problem.
     767    // TODO: here should go things such as:
     768    // - has our target moved enough that we should re-path?
     769    // end TODO
    838770
    839         CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    840         if (!cmpPathfinder)
    841             return;
     771    // Keep track of the current unit's position during the update
     772    CFixedVector2D pos = initialPos;
    842773
    843         CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    844         if (!cmpPosition || !cmpPosition->IsInWorld())
    845             return;
     774    // Find the speed factor of the underlying terrain
     775    // (We only care about the tile we start on - it doesn't matter if we're moving
     776    // partially onto a much slower/faster tile)
     777    // TODO: Terrain-dependent speeds are not currently supported
     778    // TODO: note that this is also linked to pathfinding so maybe never supported
     779    // fixed terrainSpeed = fixed::FromInt(1);
    846780
    847         CFixedVector2D initialPos = cmpPosition->GetPosition2D();
     781    bool wasObstructed = false;
    848782
    849         // If we're chasing a potentially-moving unit and are currently close
    850         // enough to its current position, and we can head in a straight line
    851         // to it, then throw away our current path and go straight to it
    852         if (m_PathState == PATHSTATE_FOLLOWING)
    853             TryGoingStraightToTargetEntity(initialPos);
     783    // We want to move (at most) m_Speed*dt units from pos towards the next waypoint
    854784
    855         // Keep track of the current unit's position during the update
    856         CFixedVector2D pos = initialPos;
     785    fixed timeLeft = dt;
    857786
    858         // If in formation, run to keep up; otherwise just walk
    859         fixed basicSpeed;
    860         if (IsFormationMember())
    861             basicSpeed = GetRunSpeed();
    862         else
    863             basicSpeed = m_Speed; // (typically but not always WalkSpeed)
     787    while (timeLeft > fixed::Zero())
     788    {
     789        // If we ran out of path, we have to stop
     790        if (!HasValidPath())
     791            break;
    864792
    865         // Find the speed factor of the underlying terrain
    866         // (We only care about the tile we start on - it doesn't matter if we're moving
    867         // partially onto a much slower/faster tile)
    868         // TODO: Terrain-dependent speeds are not currently supported
    869         fixed terrainSpeed = fixed::FromInt(1);
     793        CFixedVector2D target;
     794        target = CFixedVector2D(m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z);
    870795
    871         fixed maxSpeed = basicSpeed.Multiply(terrainSpeed);
     796        CFixedVector2D offset = target - pos;
     797        fixed offsetLength = offset.Length();
     798        // Work out how far we can travel in timeLeft
     799        fixed maxdist = m_Speed.Multiply(timeLeft);
    872800
    873         bool wasObstructed = false;
     801        CFixedVector2D destination;
     802        if (offsetLength <= maxdist)
     803            destination = target;
     804        else
     805        {
     806            offset.Normalize(maxdist);
     807            destination = pos + offset;
     808        }
    874809
    875         // We want to move (at most) maxSpeed*dt units from pos towards the next waypoint
     810        // TODO: try moving as much as we can still?
     811        // TODO: get more information about what blocked us.
     812        if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
     813        {
     814            pos = destination;
    876815
    877         fixed timeLeft = dt;
    878         fixed zero = fixed::Zero();
     816            timeLeft = (timeLeft.Multiply(m_Speed) - offsetLength) / m_Speed;
    879817
    880         while (timeLeft > zero)
     818            if (destination == target)
     819                m_Path.m_Waypoints.pop_back();
     820            continue;
     821        }
     822        else
    881823        {
    882             // If we ran out of path, we have to stop
    883             if (m_ShortPath.m_Waypoints.empty() && m_LongPath.m_Waypoints.empty())
    884                 break;
    885 
    886             CFixedVector2D target;
    887             if (m_ShortPath.m_Waypoints.empty())
    888                 target = CFixedVector2D(m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z);
    889             else
    890                 target = CFixedVector2D(m_ShortPath.m_Waypoints.back().x, m_ShortPath.m_Waypoints.back().z);
     824            // Error - path was obstructed
     825            wasObstructed = true;
     826            break;
     827        }
     828    }
    891829
    892             CFixedVector2D offset = target - pos;
     830    if (!m_StartedMoving && wasObstructed)
     831        // If this is the turn we start moving, and we're already obstructed,
     832        // fail the move entirely to avoid weirdness.
     833        // (we would need to send a "move started" and a "move failed" message in the same turn)
     834        pos = initialPos;
    893835
    894             // Work out how far we can travel in timeLeft
    895             fixed maxdist = maxSpeed.Multiply(timeLeft);
     836    // Update the Position component after our movement (if we actually moved anywhere)
     837    if (pos != initialPos)
     838    {
     839        CFixedVector2D offset = pos - initialPos;
    896840
    897             // If the target is close, we can move there directly
    898             fixed offsetLength = offset.Length();
    899             if (offsetLength <= maxdist)
    900             {
    901                 if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
    902                 {
    903                     pos = target;
    904 
    905                     // Spend the rest of the time heading towards the next waypoint
    906                     timeLeft = timeLeft - (offsetLength / maxSpeed);
    907 
    908                     if (m_ShortPath.m_Waypoints.empty())
    909                         m_LongPath.m_Waypoints.pop_back();
    910                     else
    911                         m_ShortPath.m_Waypoints.pop_back();
    912 
    913                     continue;
    914                 }
    915                 else
    916                 {
    917                     // Error - path was obstructed
    918                     wasObstructed = true;
    919                     break;
    920                 }
    921             }
    922             else
    923             {
    924                 // Not close enough, so just move in the right direction
    925                 offset.Normalize(maxdist);
    926                 target = pos + offset;
     841        // Face towards the target
     842        entity_angle_t angle = atan2_approx(offset.X, offset.Y);
     843        cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle);
    927844
    928                 if (cmpPathfinder->CheckMovement(GetObstructionFilter(), pos.X, pos.Y, target.X, target.Y, m_Clearance, m_PassClass))
    929                     pos = target;
    930                 else
    931                     wasObstructed = true; // Error - path was obstructed
     845        // Calculate the mean speed over this past turn.
     846        m_ActualSpeed = cmpPosition->GetDistanceTravelled() / dt;
    932847
    933                 break;
    934             }
    935         }
     848        // tell other components and visual actor we are moving.
     849        if (!m_StartedMoving)
     850            MoveStarted();
    936851
    937         // Update the Position component after our movement (if we actually moved anywhere)
    938         if (pos != initialPos)
     852        // Check if we are at our destination
     853        // since we're already checking in the general case at the beginning of this function,
     854        // no need to do this outside this if block.
     855        if (ShouldConsiderOurselvesAtDestination())
    939856        {
    940             CFixedVector2D offset = pos - initialPos;
    941 
    942             // Face towards the target
    943             entity_angle_t angle = atan2_approx(offset.X, offset.Y);
    944             cmpPosition->MoveAndTurnTo(pos.X,pos.Y, angle);
    945 
    946             // Calculate the mean speed over this past turn.
    947             m_CurSpeed = cmpPosition->GetDistanceTravelled() / dt;
     857            CompleteMove();
     858            return;
    948859        }
    949860
    950         if (wasObstructed)
     861        if (!wasObstructed)
    951862        {
    952             // Oops, we hit something (very likely another unit).
    953             // This is when we might easily get stuck wrongly.
    954 
    955             // check if we've arrived.
    956             if (ShouldConsiderOurselvesAtDestination(pos))
    957                 return;
    958 
    959             // If we still have long waypoints, try and compute a short path
    960             // This will get us around units, amongst others.
    961             // However in some cases a long waypoint will be in located in the obstruction of
    962             // an idle unit. In that case, we need to scrap that waypoint or we might never be able to reach it.
    963             // I am not sure why this happens but the following code seems to work.
    964             if (!m_LongPath.m_Waypoints.empty())
    965             {
    966                 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
    967                 if (cmpObstructionManager)
    968                 {
    969                     // create a fake obstruction to represent our waypoint.
    970                     ICmpObstructionManager::ObstructionSquare square;
    971                     square.hh = m_Clearance;
    972                     square.hw = m_Clearance;
    973                     square.u = CFixedVector2D(entity_pos_t::FromInt(1),entity_pos_t::FromInt(0));
    974                     square.v = CFixedVector2D(entity_pos_t::FromInt(0),entity_pos_t::FromInt(1));
    975                     square.x = m_LongPath.m_Waypoints.back().x;
    976                     square.z = m_LongPath.m_Waypoints.back().z;
    977                     std::vector<entity_id_t> unitOnGoal;
    978                     // don't ignore moving units as those might be units like us, ie not really moving.
    979                     cmpObstructionManager->GetUnitsOnObstruction(square, unitOnGoal, GetObstructionFilter(), true);
    980                     if (!unitOnGoal.empty())
    981                         m_LongPath.m_Waypoints.pop_back();
    982                 }
    983                 if (!m_LongPath.m_Waypoints.empty())
    984                 {
    985                     PathGoal goal;
    986                     if (m_LongPath.m_Waypoints.size() > 1 || m_FinalGoal.DistanceToPoint(pos) > LONG_PATH_MIN_DIST)
    987                         goal = { PathGoal::POINT, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z };
    988                     else
    989                     {
    990                         UpdateFinalGoal();
    991                         goal = m_FinalGoal;
    992                         m_LongPath.m_Waypoints.clear();
    993                         CFixedVector2D target = goal.NearestPointOnGoal(pos);
    994                         m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y });
    995                     }
    996                     RequestShortPath(pos, goal, true);
    997                     m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT;
    998                     return;
    999                 }
    1000             }
    1001             // Else, just entirely recompute
    1002             UpdateFinalGoal();
    1003             BeginPathing(pos, m_FinalGoal);
    1004 
    1005             // potential TODO: We could switch the short-range pathfinder for something else entirely.
     863            // everything is going smoothly, return.
     864            m_Tries = 0;
     865            m_WaitingTurns = 0;
    1006866            return;
    1007867        }
     868    }
    1008869
    1009         // We successfully moved along our path, until running out of
    1010         // waypoints or time.
     870    // tell relevant components we have paused if necessary
     871    if (m_StartedMoving)
     872        MovePaused();
    1011873
    1012         if (m_PathState == PATHSTATE_FOLLOWING)
    1013         {
    1014             // If we're not currently computing any new paths:
    1015             if (m_LongPath.m_Waypoints.empty() && m_ShortPath.m_Waypoints.empty())
    1016             {
    1017                 if (IsFormationMember())
    1018                 {
    1019                     // We've reached our assigned position. If the controller
    1020                     // is idle, send a notification in case it should disband,
    1021                     // otherwise continue following the formation next turn.
    1022                     CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
    1023                     if (cmpUnitMotion && !cmpUnitMotion->IsMoving())
    1024                     {
    1025                         CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    1026                         if (cmpObstruction)
    1027                             cmpObstruction->SetMovingFlag(false);
    1028 
    1029                         m_Moving = false;
    1030                         CMessageMotionChanged msg(false, false);
    1031                         GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
    1032                     }
    1033                 }
    1034                 else
    1035                 {
    1036                     // check if target was reached in case of a moving target
    1037                     CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
    1038                     if (cmpUnitMotion && cmpUnitMotion->IsMoving() &&
    1039                         MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange))
    1040                         return;
    1041 
    1042                     // Not in formation, so just finish moving
    1043                     StopMoving();
    1044                     m_State = STATE_IDLE;
    1045                     MoveSucceeded();
    1046 
    1047                     if (m_FacePointAfterMove)
    1048                         FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z);
    1049                     // TODO: if the goal was a square building, we ought to point towards the
    1050                     // nearest point on the square, not towards its center
    1051                 }
    1052             }
     874    // Oops, we've had a problem. Either we were obstructed, or we ran out of path (but still have a goal).
     875    // Handle it.
     876    // Failure to handle it will result in stuckness and players complaining.
    1053877
    1054             // If we have a target entity, and we're not miles away from the end of
    1055             // our current path, and the target moved enough, then recompute our
    1056             // whole path
    1057             if (IsFormationMember())
    1058                 CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA_FORMATION);
    1059             else
    1060                 CheckTargetMovement(pos, CHECK_TARGET_MOVEMENT_MIN_DELTA);
    1061         }
    1062     }
     878    if (m_ExpectedPathTicket != 0)
     879        // wait until we get our path to see where that leads us.
     880        return;
     881
     882    // give us some turns to recover.
     883    // TODO: only do this if we ran into a moving unit and not something else, because something else won't move
     884    if (m_WaitingTurns == 0)
     885    {
     886        if (HasValidPath())
     887            m_WaitingTurns = MAX_PATH_REATTEMPS + 1;
     888        else
     889            m_WaitingTurns = 3;
    1063890    }
    1064 }
    1065891
    1066 bool CCmpUnitMotion::ComputeTargetPosition(CFixedVector2D& out)
    1067 {
    1068     if (m_TargetEntity == INVALID_ENTITY)
    1069         return false;
     892    --m_WaitingTurns;
    1070893
    1071     CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TargetEntity);
    1072     if (!cmpPosition || !cmpPosition->IsInWorld())
    1073         return false;
     894    // Try again next turn, no changes
     895    if (m_WaitingTurns >= MAX_PATH_REATTEMPS)
     896        return;
    1074897
    1075     if (m_TargetOffset.IsZero())
     898    // already waited one turn, no changes, so try computing a short path.
     899    if (m_WaitingTurns >= 3)
    1076900    {
    1077         // No offset, just return the position directly
    1078         out = cmpPosition->GetPosition2D();
     901        PathGoal goal;
     902        if (m_Path.m_Waypoints.empty())
     903            goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() };
     904        else
     905        {
     906            goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z };
     907            m_Path.m_Waypoints.pop_back();
     908        }
     909        RequestShortPath(pos, goal, true);
     910        return;
    1079911    }
    1080     else
     912
     913    // Last resort, compute a long path
     914    if (m_WaitingTurns == 2)
    1081915    {
    1082         // There is an offset, so compute it relative to orientation
    1083         entity_angle_t angle = cmpPosition->GetRotation().Y;
    1084         CFixedVector2D offset = m_TargetOffset.Rotate(angle);
    1085         out = cmpPosition->GetPosition2D() + offset;
     916        PathGoal goal;
     917        if (m_Path.m_Waypoints.empty())
     918            goal = { PathGoal::POINT, m_FinalGoal.X(), m_FinalGoal.Z() };
     919        else
     920        {
     921            goal = { PathGoal::POINT, m_Path.m_Waypoints.back().x, m_Path.m_Waypoints.back().z };
     922            m_Path.m_Waypoints.pop_back();
     923        }
     924        RequestLongPath(pos, goal);
     925        return;
    1086926    }
    1087     return true;
    1088 }
    1089927
    1090 bool CCmpUnitMotion::TryGoingStraightToGoalPoint(const CFixedVector2D& from)
    1091 {
    1092     // Make sure the goal is a point (and not a point-like target like a formation controller)
    1093     if (m_FinalGoal.type != PathGoal::POINT || m_TargetEntity != INVALID_ENTITY)
    1094         return false;
    1095928
    1096     // Fail if the goal is too far away
    1097     CFixedVector2D goalPos(m_FinalGoal.x, m_FinalGoal.z);
    1098     if ((goalPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
    1099         return false;
     929    // m_waitingTurns == 1 here
    1100930
    1101     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    1102     if (!cmpPathfinder)
    1103         return false;
     931    // we tried getting a renewed path and still got stuck
     932    if (m_AbortIfStuck == 0)
     933    {
     934        MoveFailed();
     935        return;
     936    }
    1104937
    1105     // Check if there's any collisions on that route
    1106     if (!cmpPathfinder->CheckMovement(GetObstructionFilter(), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
    1107         return false;
     938    --m_AbortIfStuck;
    1108939
    1109     // That route is okay, so update our path
    1110     m_LongPath.m_Waypoints.clear();
    1111     m_ShortPath.m_Waypoints.clear();
    1112     m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
     940    // Recompute a new path, but wait a dozen turns.
     941    m_WaitingTurns = 12 + MAX_PATH_REATTEMPS;
    1113942
    1114     return true;
     943    return;
    1115944}
    1116945
    1117 bool CCmpUnitMotion::TryGoingStraightToTargetEntity(const CFixedVector2D& from)
     946// TODO: this should care about target movement
     947bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta)
    1118948{
    1119     CFixedVector2D targetPos;
    1120     if (!ComputeTargetPosition(targetPos))
     949    if (!m_FinalGoal.TargetIsEntity())
    1121950        return false;
    1122951
    1123     // Fail if the target is too far away
    1124     if ((targetPos - from).CompareLength(DIRECT_PATH_RANGE) > 0)
    1125         return false;
     952    if (!HasValidPath())
     953        return true;
    1126954
    1127     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
    1128     if (!cmpPathfinder)
    1129         return false;
    1130 
    1131     // Move the goal to match the target entity's new position
    1132     PathGoal goal = m_FinalGoal;
    1133     goal.x = targetPos.X;
    1134     goal.z = targetPos.Y;
    1135     // (we ignore changes to the target's rotation, since only buildings are
    1136     // square and buildings don't move)
    1137 
    1138     // Find the point on the goal shape that we should head towards
    1139     CFixedVector2D goalPos = goal.NearestPointOnGoal(from);
    1140 
    1141     // Check if there's any collisions on that route
    1142     if (!cmpPathfinder->CheckMovement(GetObstructionFilter(true), from.X, from.Y, goalPos.X, goalPos.Y, m_Clearance, m_PassClass))
    1143         return false;
    1144 
    1145     // That route is okay, so update our path
    1146     m_FinalGoal = goal;
    1147     m_LongPath.m_Waypoints.clear();
    1148     m_ShortPath.m_Waypoints.clear();
    1149     m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
    1150 
    1151     return true;
    1152 }
     955    // Fail unless the target has moved enough
     956    CFixedVector2D oldTargetPos = CFixedVector2D(m_Path.m_Waypoints[0].x,m_Path.m_Waypoints[0].z);
    1153957
    1154 bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_t minDelta)
    1155 {
    1156     CFixedVector2D targetPos;
    1157     if (!ComputeTargetPosition(targetPos))
     958    if ((m_FinalGoal.Pos() - oldTargetPos).CompareLength(minDelta) < 0)
    1158959        return false;
    1159960
    1160     // Fail unless the target has moved enough
    1161     CFixedVector2D oldTargetPos(m_FinalGoal.x, m_FinalGoal.z);
    1162     if ((targetPos - oldTargetPos).CompareLength(minDelta) < 0)
    1163         return false;
    1164961
    1165962    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    1166963    if (!cmpPosition || !cmpPosition->IsInWorld())
    1167964        return false;
    1168965    CFixedVector2D pos = cmpPosition->GetPosition2D();
    1169966    CFixedVector2D oldDir = (oldTargetPos - pos);
    1170     CFixedVector2D newDir = (targetPos - pos);
     967    CFixedVector2D newDir = (m_FinalGoal.Pos() - pos);
    1171968    oldDir.Normalize();
    1172969    newDir.Normalize();
    1173970
    1174971    // Fail unless we're close enough to the target to care about its movement
    1175972    // and the angle between the (straight-line) directions of the previous and new target positions is small
    1176     if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_LongPath, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
     973    if (oldDir.Dot(newDir) > CHECK_TARGET_MOVEMENT_MIN_COS && !PathIsShort(m_Path, from, CHECK_TARGET_MOVEMENT_AT_MAX_DIST))
    1177974        return false;
    1178975
    1179976    // Fail if the target is no longer visible to this entity's owner
    bool CCmpUnitMotion::CheckTargetMovement(const CFixedVector2D& from, entity_pos_  
    1183980    if (cmpOwnership)
    1184981    {
    1185982        CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
    1186         if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_TargetEntity, cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
     983        if (cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FinalGoal.GetEntity(), cmpOwnership->GetOwner()) == ICmpRangeManager::VIS_HIDDEN)
    1187984            return false;
    1188985    }
    1189986
    1190987    // The target moved and we need to update our current path;
    1191     // change the goal here and expect our caller to start the path request
    1192     m_FinalGoal.x = targetPos.X;
    1193     m_FinalGoal.z = targetPos.Y;
    1194     RequestLongPath(from, m_FinalGoal);
    1195     m_PathState = PATHSTATE_FOLLOWING_REQUESTING_LONG;
     988    // Expect our caller to recompute
     989    // Dump our current path.
     990    m_Path.m_Waypoints.clear();
    1196991
    1197992    return true;
    1198993}
    1199994
    1200 void CCmpUnitMotion::UpdateFinalGoal()
     995// TODO: ought to be cleverer here.
     996// In particular maybe we should support some "margin" for error.
     997bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination()
    1201998{
    1202     if (m_TargetEntity == INVALID_ENTITY)
    1203         return;
    1204     CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
    1205     if (!cmpUnitMotion)
    1206         return;
    1207     if (IsFormationMember())
    1208         return;
    1209     CFixedVector2D targetPos;
    1210     if (!ComputeTargetPosition(targetPos))
    1211         return;
    1212     m_FinalGoal.x = targetPos.X;
    1213     m_FinalGoal.z = targetPos.Y;
    1214 }
    1215 
    1216 bool CCmpUnitMotion::ShouldConsiderOurselvesAtDestination(const CFixedVector2D& from)
    1217 {
    1218     if (m_TargetEntity != INVALID_ENTITY || m_FinalGoal.DistanceToPoint(from) > SHORT_PATH_GOAL_RADIUS)
    1219         return false;
    1220 
    1221     StopMoving();
    1222     MoveSucceeded();
    1223 
    1224     if (m_FacePointAfterMove)
    1225         FaceTowardsPointFromPos(from, m_FinalGoal.x, m_FinalGoal.z);
    1226     return true;
     999    if (m_FinalGoal.TargetIsEntity())
     1000        return IsInTargetRange(m_FinalGoal.GetEntity(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
     1001    else
     1002        return IsInPointRange(m_FinalGoal.X(),m_FinalGoal.Z(), m_FinalGoal.MinRange(), m_FinalGoal.MaxRange());
    12271003}
    12281004
    12291005bool CCmpUnitMotion::PathIsShort(const WaypointPath& path, const CFixedVector2D& from, entity_pos_t minDistance) const
    void CCmpUnitMotion::FaceTowardsPointFromPos(const CFixedVector2D& pos, entity_p  
    12751051
    12761052ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter(bool noTarget) const
    12771053{
    1278     entity_id_t group = noTarget ? m_TargetEntity : GetGroup();
    1279     return ControlGroupMovementObstructionFilter(ShouldAvoidMovingUnits(), group);
     1054    entity_id_t group = noTarget ? m_FinalGoal.GetEntity() : GetGroup();
     1055    // TODO: if we sometimes want to consider moving units, change here.
     1056    return ControlGroupMovementObstructionFilter(false, group);
    12801057}
    12811058
    1282 
    1283 
    1284 void CCmpUnitMotion::BeginPathing(const CFixedVector2D& from, const PathGoal& goal)
     1059// TODO: this should be improved, it's a little limited
     1060// EG use of hierarchical pathfinder,…
     1061// also it should probably make the goal passable directly, to avoid conflict with the paths returned.
     1062void CCmpUnitMotion::RequestNewPath()
    12851063{
    1286     // reset our state for sanity.
    1287     m_ExpectedPathTicket = 0;
     1064    ENSURE(m_ExpectedPathTicket == 0);
    12881065
    1289     CmpPtr<ICmpObstruction> cmpObstruction(GetEntityHandle());
    1290     if (cmpObstruction)
    1291         cmpObstruction->SetMovingFlag(false);
     1066    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     1067    if (!cmpPosition)
     1068        return;
    12921069
    1293     m_Moving = false;
     1070    // dump current path
     1071    m_Path.m_Waypoints.clear();
    12941072
    1295     m_PathState = PATHSTATE_NONE;
     1073    CFixedVector2D position = cmpPosition->GetPosition2D();
    12961074
    12971075#if DISABLE_PATHFINDER
    12981076    {
    12991077        CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY);
    1300         CFixedVector2D goalPos = m_FinalGoal.NearestPointOnGoal(from);
     1078        CFixedVector2D goalPos = m_FinalGoal.Goal().NearestPointOnGoal(position);
    13011079        m_LongPath.m_Waypoints.clear();
    13021080        m_ShortPath.m_Waypoints.clear();
    13031081        m_ShortPath.m_Waypoints.emplace_back(Waypoint{ goalPos.X, goalPos.Y });
    1304         m_PathState = PATHSTATE_FOLLOWING;
    13051082        return;
    13061083    }
    13071084#endif
    13081085
    1309     // If we're aiming at a target entity and it's close and we can reach
    1310     // it in a straight line, then we'll just go along the straight line
    1311     // instead of computing a path.
    1312     if (TryGoingStraightToTargetEntity(from))
    1313     {
    1314         if (!HasValidPath())
    1315             StartSucceeded();
    1316         m_PathState = PATHSTATE_FOLLOWING;
    1317         return;
    1318     }
    1319 
    1320     // Same thing applies to non-entity points
    1321     if (TryGoingStraightToGoalPoint(from))
    1322     {
    1323         if (!HasValidPath())
    1324             StartSucceeded();
    1325         m_PathState = PATHSTATE_FOLLOWING;
    1326         return;
    1327     }
    1328 
    1329     // Otherwise we need to compute a path.
    1330 
    13311086    // If it's close then just do a short path, not a long path
    13321087    // TODO: If it's close on the opposite side of a river then we really
    13331088    // need a long path, so we shouldn't simply check linear distance
    13341089    // the check is arbitrary but should be a reasonably small distance.
    1335     if (goal.DistanceToPoint(from) < LONG_PATH_MIN_DIST)
    1336     {
    1337         // add our final goal as a long range waypoint so we don't forget
    1338         // where we are going if the short-range pathfinder returns
    1339         // an aborted path.
    1340         m_LongPath.m_Waypoints.clear();
    1341         CFixedVector2D target = m_FinalGoal.NearestPointOnGoal(from);
    1342         m_LongPath.m_Waypoints.emplace_back(Waypoint{ target.X, target.Y });
    1343         m_PathState = PATHSTATE_WAITING_REQUESTING_SHORT;
    1344         RequestShortPath(from, goal, true);
    1345     }
     1090    // Maybe use PathIsShort?
     1091    if (m_FinalGoal.Goal().DistanceToPoint(position) < LONG_PATH_MIN_DIST)
     1092        RequestShortPath(position, m_FinalGoal.Goal(), true);
    13461093    else
    1347     {
    1348         m_PathState = PATHSTATE_WAITING_REQUESTING_LONG;
    1349         RequestLongPath(from, goal);
    1350     }
     1094        RequestLongPath(position, m_FinalGoal.Goal());
    13511095}
    13521096
    13531097void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal& goal)
    bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos  
    13911135{
    13921136    PROFILE("MoveToPointRange");
    13931137
     1138    DiscardMove();
     1139
    13941140    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    13951141    if (!cmpPosition || !cmpPosition->IsInWorld())
    13961142        return false;
    bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos  
    14421188        }
    14431189    }
    14441190
    1445     m_State = STATE_INDIVIDUAL_PATH;
    1446     m_TargetEntity = target;
    1447     m_TargetOffset = CFixedVector2D();
    1448     m_TargetMinRange = minRange;
    1449     m_TargetMaxRange = maxRange;
    1450     m_FinalGoal = goal;
    1451     m_Tries = 0;
     1191    if (target == INVALID_ENTITY)
     1192        m_FinalGoal = SMotionGoal(goal, minRange, maxRange);
     1193    else
     1194        m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange);
    14521195
    1453     BeginPathing(pos, goal);
     1196    RequestNewPath();
    14541197
    14551198    return true;
    14561199}
    bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t  
    14631206
    14641207    CFixedVector2D pos = cmpPosition->GetPosition2D();
    14651208
    1466     bool hasObstruction = false;
    1467     CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
    1468     ICmpObstructionManager::ObstructionSquare obstruction;
    1469 //TODO  if (cmpObstructionManager)
    1470 //      hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(), x, z, m_Radius, obstruction);
    1471 
    1472     if (minRange.IsZero() && maxRange.IsZero() && hasObstruction)
    1473     {
    1474         // Handle the non-ranged mode:
    1475         CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
    1476         entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
    1477 
    1478         // See if we're too close to the target square
    1479         if (distance < minRange)
    1480             return false;
    1481 
    1482         // See if we're close enough to the target square
    1483         if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
    1484             return true;
     1209    entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
    14851210
     1211    if (distance < minRange)
     1212        return false;
     1213    else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
    14861214        return false;
    1487     }
    14881215    else
    1489     {
    1490         entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
    1491 
    1492         if (distance < minRange)
    1493             return false;
    1494         else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
    1495             return false;
    1496         else
    1497             return true;
    1498     }
     1216        return true;
    14991217}
    15001218
    15011219bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const
    bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange  
    15121230{
    15131231    PROFILE("MoveToTargetRange");
    15141232
     1233    DiscardMove();
     1234
    15151235    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    15161236    if (!cmpPosition || !cmpPosition->IsInWorld())
    15171237        return false;
    bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange  
    16551375        }
    16561376    }
    16571377
    1658     m_State = STATE_INDIVIDUAL_PATH;
    1659     m_TargetEntity = target;
    1660     m_TargetOffset = CFixedVector2D();
    1661     m_TargetMinRange = minRange;
    1662     m_TargetMaxRange = maxRange;
    1663     m_FinalGoal = goal;
    1664     m_Tries = 0;
     1378    if (target == INVALID_ENTITY)
     1379        m_FinalGoal = SMotionGoal(goal, minRange, maxRange);
     1380    else
     1381        m_FinalGoal = SMotionGoal(GetSimContext(), target, goal, minRange, maxRange);
    16651382
    1666     BeginPathing(pos, goal);
     1383    RequestNewPath();
    16671384
    16681385    return true;
    16691386}
    bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange,  
    17381455    }
    17391456}
    17401457
    1741 void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
    1742 {
    1743     CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), target);
    1744     if (!cmpPosition || !cmpPosition->IsInWorld())
    1745         return;
    1746 
    1747     CFixedVector2D pos = cmpPosition->GetPosition2D();
    1748 
    1749     PathGoal goal;
    1750     goal.type = PathGoal::POINT;
    1751     goal.x = pos.X;
    1752     goal.z = pos.Y;
    1753 
    1754     m_State = STATE_FORMATIONMEMBER_PATH;
    1755     m_TargetEntity = target;
    1756     m_TargetOffset = CFixedVector2D(x, z);
    1757     m_TargetMinRange = entity_pos_t::Zero();
    1758     m_TargetMaxRange = entity_pos_t::Zero();
    1759     m_FinalGoal = goal;
    1760     m_Tries = 0;
    1761 
    1762     BeginPathing(pos, goal);
    1763 }
    1764 
    1765 
    1766 
    1767 
    17681458
    17691459void CCmpUnitMotion::RenderPath(const WaypointPath& path, std::vector<SOverlayLine>& lines, CColor color)
    17701460{
    void CCmpUnitMotion::RenderSubmit(SceneCollector& collector)  
    18001490    if (!m_DebugOverlayEnabled)
    18011491        return;
    18021492
    1803     RenderPath(m_LongPath, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH);
    1804     RenderPath(m_ShortPath, m_DebugOverlayShortPathLines, OVERLAY_COLOR_SHORT_PATH);
     1493    RenderPath(m_Path, m_DebugOverlayLongPathLines, OVERLAY_COLOR_LONG_PATH);
    18051494
    18061495    for (size_t i = 0; i < m_DebugOverlayLongPathLines.size(); ++i)
    18071496        collector.Submit(&m_DebugOverlayLongPathLines[i]);
  • source/simulation2/components/CCmpVisualActor.cpp

    diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
    index f84dc51..9f586fc 100644
    a b private:  
    7171
    7272    fixed m_R, m_G, m_B; // shading color
    7373
    74     std::map<std::string, std::string> m_AnimOverride;
    75 
    7674    // Current animation state
    77     fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
    7875    std::string m_AnimName;
    7976    bool m_AnimOnce;
    8077    fixed m_AnimSpeed;
    private:  
    8380    fixed m_AnimSyncRepeatTime; // 0.0 if not synced
    8481    fixed m_AnimSyncOffsetTime;
    8582
     83    std::string m_MovingPrefix;
     84    fixed m_MovingSpeed;
     85    bool m_MovingNow, m_MovingLastTurn;
     86
    8687    std::map<CStr, CStr> m_VariantSelections;
    8788
    8889    u32 m_Seed; // seed used for random variations
    public:  
    206207
    207208        InitModel(paramNode);
    208209
     210        m_MovingLastTurn = m_MovingNow = false;
     211
    209212        SelectAnimation("idle", false, fixed::FromInt(1), L"");
    210213    }
    211214
    public:  
    225228        serialize.NumberFixed_Unbounded("g", m_G);
    226229        serialize.NumberFixed_Unbounded("b", m_B);
    227230
    228         SerializeMap<SerializeString, SerializeString>()(serialize, "anim overrides", m_AnimOverride);
    229 
    230         serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold);
    231231        serialize.StringASCII("anim name", m_AnimName, 0, 256);
    232232        serialize.Bool("anim once", m_AnimOnce);
    233233        serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
    public:  
    236236        serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
    237237        serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime);
    238238
     239        serialize.Bool("moving now", m_MovingNow);
     240        serialize.Bool("moving last turn", m_MovingLastTurn);
     241
    239242        SerializeMap<SerializeString, SerializeString>()(serialize, "variation", m_VariantSelections);
    240243
    241244        serialize.NumberU32_Unbounded("seed", m_Seed);
    public:  
    423426
    424427    virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup)
    425428    {
    426         m_AnimRunThreshold = fixed::Zero();
    427429        m_AnimName = name;
    428430        m_AnimOnce = once;
    429431        m_AnimSpeed = speed;
    public:  
    432434        m_AnimSyncRepeatTime = fixed::Zero();
    433435        m_AnimSyncOffsetTime = fixed::Zero();
    434436
    435         SetVariant("animation", m_AnimName);
    436 
    437         if (m_Unit && m_Unit->GetAnimation())
    438             m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
    439     }
     437        // TODO: change this once we support walk/run-anims
     438        std::string animName = name;
     439        /*if (!m_MovingPrefix.empty() && m_AnimName != "idle")
     440            animName = m_MovingPrefix + "-" + m_AnimName;
     441        else */if (!m_MovingPrefix.empty())
     442            animName = m_MovingPrefix;
    440443
    441     virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace)
    442     {
    443         m_AnimOverride[name] = replace;
    444     }
     444        SetVariant("animation", animName);
    445445
    446     virtual void ResetMoveAnimation(const std::string& name)
    447     {
    448         std::map<std::string, std::string>::const_iterator it = m_AnimOverride.find(name);
    449         if (it != m_AnimOverride.end())
    450             m_AnimOverride.erase(name);
     446        if (m_Unit && m_Unit->GetAnimation())
     447            m_Unit->GetAnimation()->SetAnimationState(animName, m_AnimOnce, m_MovingSpeed.Multiply(m_AnimSpeed).ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
    451448    }
    452449
    453     virtual void SelectMovementAnimation(fixed runThreshold)
     450    virtual void SetMoving(bool moving)
    454451    {
    455         SelectAnimation("walk", false, fixed::FromFloat(1.f), L"");
    456         m_AnimRunThreshold = runThreshold;
     452        m_MovingNow = moving;
    457453    }
    458454
    459455    virtual void SetAnimationSyncRepeat(fixed repeattime)
    void CCmpVisualActor::Update(fixed UNUSED(turnLength))  
    751747    // far less hacky. We should also take into account the speed when the animation is different
    752748    // from the "special movement mode" walking animation.
    753749
    754     // If we're not in the special movement mode, nothing to do.
    755     if (m_AnimRunThreshold.IsZero())
    756         return;
     750    //if (!m_MovingNow && !m_MovingLastTurn)
     751    //  return;
    757752
    758753    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    759754    if (!cmpPosition || !cmpPosition->IsInWorld())
    void CCmpVisualActor::Update(fixed UNUSED(turnLength))  
    763758    if (!cmpUnitMotion)
    764759        return;
    765760
    766     fixed speed = cmpUnitMotion->GetCurrentSpeed();
    767     std::string name;
     761    // TODO: change this to make us run as fast but stop earlier instead of this hack.
     762    // Probably should subscribe dynamically too.
     763    fixed speed = cmpUnitMotion->GetActualSpeed();
     764    std::string prefix;
    768765
    769     if (speed.IsZero())
     766    if (!m_MovingNow || speed.IsZero())
    770767    {
    771768        speed = fixed::FromFloat(1.f);
    772         name = "idle";
     769        prefix = "";
    773770    }
    774771    else
    775         name = speed < m_AnimRunThreshold ? "walk" : "run";
     772        prefix = cmpUnitMotion->GetSpeedRatio() <= fixed::FromInt(1) ? "walk" : "run";
     773
     774    m_MovingPrefix = prefix;
     775    m_MovingSpeed = speed;
    776776
    777     std::map<std::string, std::string>::const_iterator it = m_AnimOverride.find(name);
    778     if (it != m_AnimOverride.end())
    779         name = it->second;
     777    SelectAnimation(m_AnimName, m_AnimOnce, m_AnimSpeed, m_SoundGroup);
    780778
    781     // Selecting the animation is going to reset the anim run threshold, so save it
    782     fixed runThreshold = m_AnimRunThreshold;
    783     SelectAnimation(name, false, speed, L"");
    784     m_AnimRunThreshold = runThreshold;
     779    m_MovingLastTurn = m_MovingNow;
    785780}
  • source/simulation2/components/ICmpUnitMotion.cpp

    diff --git a/source/simulation2/components/ICmpUnitMotion.cpp b/source/simulation2/components/ICmpUnitMotion.cpp
    index 28956ce..9099683 100644
    a b DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointR  
    2727DEFINE_INTERFACE_METHOD_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
    2828DEFINE_INTERFACE_METHOD_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
    2929DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
    30 DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
    3130DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
    32 DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
    33 DEFINE_INTERFACE_METHOD_0("GetCurrentSpeed", fixed, ICmpUnitMotion, GetCurrentSpeed)
     31DEFINE_INTERFACE_METHOD_1("SetAbortIfStuck", void, ICmpUnitMotion, SetAbortIfStuck, u8)
     32DEFINE_INTERFACE_METHOD_0("DiscardMove", void, ICmpUnitMotion, DiscardMove)
     33DEFINE_INTERFACE_METHOD_0("CompleteMove", void, ICmpUnitMotion, CompleteMove)
     34DEFINE_INTERFACE_METHOD_0("GetActualSpeed", fixed, ICmpUnitMotion, GetActualSpeed)
     35DEFINE_INTERFACE_METHOD_0("GetTopSpeedRatio", fixed, ICmpUnitMotion, GetTopSpeedRatio)
    3436DEFINE_INTERFACE_METHOD_1("SetSpeed", void, ICmpUnitMotion, SetSpeed, fixed)
    35 DEFINE_INTERFACE_METHOD_0("IsMoving", bool, ICmpUnitMotion, IsMoving)
    36 DEFINE_INTERFACE_METHOD_0("GetWalkSpeed", fixed, ICmpUnitMotion, GetWalkSpeed)
    37 DEFINE_INTERFACE_METHOD_0("GetRunSpeed", fixed, ICmpUnitMotion, GetRunSpeed)
     37DEFINE_INTERFACE_METHOD_0("IsActuallyMoving", bool, ICmpUnitMotion, IsActuallyMoving)
     38DEFINE_INTERFACE_METHOD_0("IsTryingToMove", bool, ICmpUnitMotion, IsTryingToMove)
     39DEFINE_INTERFACE_METHOD_0("GetSpeed", fixed, ICmpUnitMotion, GetSpeed)
     40DEFINE_INTERFACE_METHOD_0("GetTemplateSpeed", fixed, ICmpUnitMotion, GetTemplateSpeed)
    3841DEFINE_INTERFACE_METHOD_0("GetPassabilityClassName", std::string, ICmpUnitMotion, GetPassabilityClassName)
    3942DEFINE_INTERFACE_METHOD_0("GetUnitClearance", entity_pos_t, ICmpUnitMotion, GetUnitClearance)
    4043DEFINE_INTERFACE_METHOD_1("SetFacePointAfterMove", void, ICmpUnitMotion, SetFacePointAfterMove, bool)
    public:  
    6669        return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange);
    6770    }
    6871
    69     virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
     72    virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
    7073    {
    71         m_Script.CallVoid("MoveToFormationOffset", target, x, z);
     74        m_Script.CallVoid("FaceTowardsPoint", x, z);
    7275    }
    7376
    74     virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z)
     77    virtual void DiscardMove()
    7578    {
    76         m_Script.CallVoid("FaceTowardsPoint", x, z);
     79        m_Script.CallVoid("DiscardMove");
    7780    }
    7881
    79     virtual void StopMoving()
     82    virtual void CompleteMove()
    8083    {
    81         m_Script.CallVoid("StopMoving");
     84        m_Script.CallVoid("CompleteMove");
    8285    }
    8386
    84     virtual fixed GetCurrentSpeed()
     87    virtual void SetAbortIfStuck(u8 shouldAbort)
    8588    {
    86         return m_Script.Call<fixed>("GetCurrentSpeed");
     89        m_Script.CallVoid("SetAbortIfStuck", shouldAbort);
     90    }
     91
     92    virtual fixed GetActualSpeed()
     93    {
     94        return m_Script.Call<fixed>("GetActualSpeed");
    8795    }
    8896
    8997    virtual void SetSpeed(fixed speed)
    public:  
    9199        m_Script.CallVoid("SetSpeed", speed);
    92100    }
    93101
    94     virtual bool IsMoving()
     102    virtual fixed GetTopSpeedRatio()
     103    {
     104        return m_Script.Call<fixed>("GetTopSpeedRatio");
     105    }
     106
     107    virtual bool IsActuallyMoving()
     108    {
     109        return m_Script.Call<bool>("IsActuallyMoving");
     110    }
     111
     112    virtual bool IsTryingToMove()
    95113    {
    96         return m_Script.Call<bool>("IsMoving");
     114        return m_Script.Call<bool>("IsTryingToMove");
    97115    }
    98116
    99     virtual fixed GetWalkSpeed()
     117    virtual fixed GetSpeed()
    100118    {
    101         return m_Script.Call<fixed>("GetWalkSpeed");
     119        return m_Script.Call<fixed>("GetSpeed");
    102120    }
    103121
    104     virtual fixed GetRunSpeed()
     122    virtual fixed GetTemplateSpeed()
    105123    {
    106         return m_Script.Call<fixed>("GetRunSpeed");
     124        return m_Script.Call<fixed>("GetTemplateSpeed");
    107125    }
    108126
    109127    virtual void SetFacePointAfterMove(bool facePointAfterMove)
    public:  
    116134        return m_Script.Call<pass_class_t>("GetPassabilityClass");
    117135    }
    118136
     137    virtual fixed GetSpeedRatio()
     138    {
     139        return fixed::FromInt(1);
     140    }
     141
    119142    virtual std::string GetPassabilityClassName()
    120143    {
    121144        return m_Script.Call<std::string>("GetPassabilityClassName");
  • source/simulation2/components/ICmpUnitMotion.h

    diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h
    index de5c674..dd40777 100644
    a b public:  
    7171    virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
    7272
    7373    /**
    74      * Join a formation, and move towards a given offset relative to the formation controller entity.
    75      * Continues following the formation until given a different command.
     74     * Turn to look towards the given point.
    7675     */
    77     virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) = 0;
     76    virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0;
    7877
    7978    /**
    80      * Turn to look towards the given point.
     79     * Determine whether to abort or retry X times if pathing fails.
     80     * Generally safer to let it abort and inform us.
    8181     */
    82     virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) = 0;
     82    virtual void SetAbortIfStuck(u8 shouldAbort) = 0;
     83
     84    /**
     85     * Stop moving immediately, don't send messages.
     86     * This should be used if you are going to ask for a new path,
     87     * in the same function, for example.
     88     * In doubt, UnitAI should probably call this.
     89     * Use with caution.
     90     */
     91    virtual void DiscardMove() = 0;
     92
     93    /**
     94     * Stop moving immediately, send messages.
     95     * In doubt, components that are not UnitIA should probably call this.
     96     */
     97    virtual void CompleteMove() = 0;
     98
     99    /**
     100     * Get the movement speed from last turn to this turn
     101     * This is effectively historical data
     102     * And not a good indicator of whether we are actually moving,
     103     * Prefer IsActuallyMoving
     104     */
     105    virtual fixed GetActualSpeed() = 0;
    83106
    84107    /**
    85      * Stop moving immediately.
     108     * Get how much faster/slower we are at than normal.
    86109     */
    87     virtual void StopMoving() = 0;
     110    virtual fixed GetSpeedRatio() = 0;
    88111
    89112    /**
    90      * Get the current movement speed.
     113     * Get how much faster than our regular speed we can go.
    91114     */
    92     virtual fixed GetCurrentSpeed() = 0;
     115    virtual fixed GetTopSpeedRatio() = 0;
    93116
    94117    /**
    95118     * Set the current movement speed.
     119     * 'speed' in % of top speed (ie 3.0 will be 3 times top speed).
    96120     */
    97121    virtual void SetSpeed(fixed speed) = 0;
    98122
    99123    /**
    100      * Get whether the unit is moving.
     124     * Get whether the unit is actually moving on the map this turn.
     125     */
     126    virtual bool IsActuallyMoving() = 0;
     127
     128    /**
     129     * Get whether a unit is trying to go somewhere
     130     * NB: this does not mean its position is actually changing right now.
    101131     */
    102     virtual bool IsMoving() = 0;
     132    virtual bool IsTryingToMove() = 0;
    103133
    104134    /**
    105      * Get the default speed that this unit will have when walking, in metres per second.
     135     * Get the unit theoretical speed in metres per second.
     136     * GetActualSpeed will return historical speed
     137     * This is affected by SetSpeed.
    106138     */
    107     virtual fixed GetWalkSpeed() = 0;
     139    virtual fixed GetSpeed() = 0;
    108140
    109141    /**
    110      * Get the default speed that this unit will have when running, in metres per second.
     142     * Get the unit template speed in metres per second.
     143     * This is NOT affected by SetSpeed.
    111144     */
    112     virtual fixed GetRunSpeed() = 0;
     145    virtual fixed GetTemplateSpeed() = 0;
    113146
    114147    /**
    115148     * Set whether the unit will turn to face the target point after finishing moving.
  • source/simulation2/components/ICmpVisual.cpp

    diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp
    index e39942d..9305c5f 100644
    a b  
    2424BEGIN_INTERFACE_WRAPPER(Visual)
    2525DEFINE_INTERFACE_METHOD_2("SetVariant", void, ICmpVisual, SetVariant, CStr, CStr)
    2626DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring)
    27 DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed)
    28 DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)
    29 DEFINE_INTERFACE_METHOD_2("ReplaceMoveAnimation", void, ICmpVisual, ReplaceMoveAnimation, std::string, std::string)
    3027DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimationSyncRepeat, fixed)
    3128DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed)
    3229DEFINE_INTERFACE_METHOD_4("SetShadingColor", void, ICmpVisual, SetShadingColor, fixed, fixed, fixed, fixed)
  • source/simulation2/components/ICmpVisual.h

    diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
    index 6b11fdc..c31d2a0 100644
    a b public:  
    9999    virtual void SelectAnimation(const std::string& name, bool once, fixed speed, const std::wstring& soundgroup) = 0;
    100100
    101101    /**
    102      * Replaces a specified animation with another. Only affects the special speed-based
    103      * animation determination behaviour.
    104      * @param name Animation to match.
    105      * @param replace Animation that should replace the matched animation.
     102     * if moving is true, start playing the walk/run animations, scaled to the unit's movement speed.
    106103     */
    107     virtual void ReplaceMoveAnimation(const std::string& name, const std::string& replace) = 0;
    108 
    109     /**
    110      * Ensures that the given animation will be used when it normally would be,
    111      * removing reference to any animation that might replace it.
    112      * @param name Animation name to remove from the replacement map.
    113      */
    114     virtual void ResetMoveAnimation(const std::string& name) = 0;
    115 
    116     /**
    117      * Start playing the walk/run animations, scaled to the unit's movement speed.
    118      * @param runThreshold movement speed at which to switch to the run animation
    119      */
    120     virtual void SelectMovementAnimation(fixed runThreshold) = 0;
     104    virtual void SetMoving(bool moving) = 0;
    121105
    122106    /**
    123107     * Adjust the speed of the current animation, so it can match simulation events.
  • source/simulation2/scripting/MessageTypeConversions.cpp

    diff --git a/source/simulation2/scripting/MessageTypeConversions.cpp b/source/simulation2/scripting/MessageTypeConversions.cpp
    index cb55a17..addc730 100644
    a b CMessage* CMessageTerritoryPositionChanged::FromJSVal(ScriptInterface& scriptInt  
    267267
    268268////////////////////////////////
    269269
    270 JS::Value CMessageMotionChanged::ToJSVal(ScriptInterface& scriptInterface) const
     270JS::Value CMessageBeginMove::ToJSVal(ScriptInterface& scriptInterface) const
    271271{
    272272    TOJSVAL_SETUP();
    273     SET_MSG_PROPERTY(starting);
    274     SET_MSG_PROPERTY(error);
    275273    return JS::ObjectValue(*obj);
    276274}
    277275
    278 CMessage* CMessageMotionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
     276CMessage* CMessageBeginMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
    279277{
    280278    FROMJSVAL_SETUP();
    281     GET_MSG_PROPERTY(bool, starting);
    282     GET_MSG_PROPERTY(bool, error);
    283     return new CMessageMotionChanged(starting, error);
     279    return new CMessageBeginMove();
     280}
     281
     282////////////////////////////////
     283
     284JS::Value CMessagePausedMove::ToJSVal(ScriptInterface& scriptInterface) const
     285{
     286    TOJSVAL_SETUP();
     287    return JS::ObjectValue(*obj);
     288}
     289
     290CMessage* CMessagePausedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
     291{
     292    FROMJSVAL_SETUP();
     293    return new CMessagePausedMove();
     294}
     295
     296////////////////////////////////
     297
     298JS::Value CMessageFinishedMove::ToJSVal(ScriptInterface& scriptInterface) const
     299{
     300    TOJSVAL_SETUP();
     301    SET_MSG_PROPERTY(failed);
     302    return JS::ObjectValue(*obj);
     303}
     304
     305CMessage* CMessageFinishedMove::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
     306{
     307    FROMJSVAL_SETUP();
     308    GET_MSG_PROPERTY(bool, failed);
     309    return new CMessageFinishedMove(failed);
    284310}
    285311
    286312////////////////////////////////
  • source/tools/atlas/GameInterface/ActorViewer.cpp

    diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp
    index f09da1a..812a3cd 100644
    a b void ActorViewer::SetActor(const CStrW& name, const CStrW& animation, player_id_  
    375375        {
    376376            CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
    377377            if (cmpUnitMotion)
    378                 speed = cmpUnitMotion->GetWalkSpeed().ToFloat();
     378                speed = cmpUnitMotion->GetTemplateSpeed().ToFloat();
    379379            else
    380380                speed = 7.f; // typical unit speed
    381381
    void ActorViewer::SetActor(const CStrW& name, const CStrW& animation, player_id_  
    385385        {
    386386            CmpPtr<ICmpUnitMotion> cmpUnitMotion(m.Simulation2, m.Entity);
    387387            if (cmpUnitMotion)
    388                 speed = cmpUnitMotion->GetRunSpeed().ToFloat();
     388                speed = cmpUnitMotion->GetTemplateSpeed().ToFloat();
    389389            else
    390390                speed = 12.f; // typical unit speed
    391391