Ticket #1537: chasing.5.diff

File chasing.5.diff, 31.5 KB (added by sanderd17, 10 years ago)
  • binaries/data/mods/public/simulation/components/Timer.js

     
    1616Timer.prototype.GetTime = function()
    1717{
    1818    return this.time;
    19 }
     19};
    2020
    2121/**
     22 * Returns the turn length of the latest turn in millisecons
     23 */
     24Timer.prototype.GetTurnLength = function()
     25{
     26    return this.turnLength;
     27};
     28
     29/**
    2230 * Create a new timer, which will call the 'funcname' method with arguments (data, lateness)
    2331 * on the 'iid' component of the 'ent' entity, after at least 'time' milliseconds.
    2432 * 'lateness' is how late the timer is executed after the specified time (in milliseconds).
     
    6068
    6169Timer.prototype.OnUpdate = function(msg)
    6270{
    63     var dt = Math.round(msg.turnLength * 1000);
    64     this.time += dt;
     71    this.turnLength = Math.round(msg.turnLength * 1000);
     72    this.time += this.turnLength;
    6573
    6674    // Collect the timers that need to run
    6775    // (We do this in two stages to avoid deleting from the timer list while
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    171171        // ignore
    172172    },
    173173
     174    "Update": function(msg) {
     175        // ignore
     176    },
     177
    174178    // Formation handlers:
    175179
    176180    "FormationLeave": function(msg) {
     
    378382        // We use the distance between the entities to account for ranged attacks
    379383        var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance);
    380384        var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    381         if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1))
     385        if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1, 0))
    382386        {
    383387            // We've started fleeing from the given target
    384388            if (this.IsAnimal())
     
    413417        this.order.data.attackType = type;
    414418
    415419        // If we are already at the target, try attacking it from here
    416         if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
     420        if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
    417421        {
    418422            this.StopMoving();
    419423            // For packable units within attack range:
     
    485489        }
    486490
    487491        // Try to move within attack range
    488         if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
     492        if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
    489493        {
    490494            // We've started walking to the given point
    491495            if (this.IsAnimal())
     
    15901594
    15911595                "MoveCompleted": function() {
    15921596
    1593                     if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack , this.order.data.attackType))
     1597                    if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
    15941598                    {
    15951599                        // If the unit needs to unpack, do so
    15961600                        if (this.CanUnpack())
     
    16001604                    }
    16011605                    else
    16021606                    {
    1603                         if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
     1607                        if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
    16041608                        {
    16051609                            this.SetNextState("APPROACHING");
    16061610                        }
     
    16251629            "UNPACKING": {
    16261630                "enter": function() {
    16271631                    // If we're not in range yet (maybe we stopped moving), move to target again
    1628                     if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
     1632                    if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType))
    16291633                    {
    1630                         if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
     1634                        if (this.MoveToTargetAttackRange(this.order.data.target, this.order.data.attackType))
    16311635                            this.SetNextState("APPROACHING");
    16321636                        else
    16331637                        {
     
    16901694                "Timer": function(msg) {
    16911695                    var target = this.order.data.target;
    16921696                    // Check the target is still alive and attackable
    1693                     if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null))
     1697                    if
     1698                    (
     1699                        !this.TargetIsAlive(target) ||
     1700                        !this.CanAttack(target, this.order.data.forceResponse || null) ||
     1701                        !this.CheckTargetAttackRange(target, this.order.data.attackType)
     1702                    )
     1703                        return;
     1704
     1705                    // If we are hunting, first update the target position of the gather order so we know where will be the killed animal
     1706                    if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos)
    16941707                    {
    1695                         // Check we can still reach the target
    1696                         if (this.CheckTargetAttackRange(target, IID_Attack, this.order.data.attackType))
     1708                        var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position);
     1709                        if (cmpPosition && cmpPosition.IsInWorld())
    16971710                        {
    1698                             // If we are hunting, first update the target position of the gather order so we know where will be the killed animal
    1699                             if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos)
    1700                             {
    1701                                 var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position);
    1702                                 if (cmpPosition && cmpPosition.IsInWorld())
    1703                                 {
    1704                                     // Store the initial position, so that we can find the rest of the herd later
    1705                                     if (!this.orderQueue[1].data.initPos)
    1706                                         this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos;
    1707                                     this.orderQueue[1].data.lastPos = cmpPosition.GetPosition();
    1708                                     // We still know where the animal is, so we shouldn't give up before going there
    1709                                     this.orderQueue[1].data.secondTry = undefined;
    1710                                 }
    1711                             }
     1711                            // Store the initial position, so that we can find the rest of the herd later
     1712                            if (!this.orderQueue[1].data.initPos)
     1713                                this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos;
     1714                            this.orderQueue[1].data.lastPos = cmpPosition.GetPosition();
     1715                            // We still know where the animal is, so we shouldn't give up before going there
     1716                            this.orderQueue[1].data.secondTry = undefined;
     1717                        }
     1718                    }
    17121719
    1713                             var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    1714                             this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
     1720                    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     1721                    this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
    17151722
    1716                             this.FaceTowardsTarget(target);
    1717                             var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    1718                             cmpAttack.PerformAttack(this.order.data.attackType, target);
     1723                    this.FaceTowardsTarget(target);
     1724                    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     1725                    cmpAttack.PerformAttack(this.order.data.attackType, target);
    17191726
    1720                             if (this.resyncAnimation)
    1721                             {
    1722                                 this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat);
    1723                                 this.resyncAnimation = false;
    1724                             }
    1725                             return;
    1726                         }
     1727                    if (this.resyncAnimation)
     1728                    {
     1729                        this.SetAnimationSync(this.attackTimers.repeat, this.attackTimers.repeat);
     1730                        this.resyncAnimation = false;
     1731                    }
     1732                },
    17271733
    1728                         // Can't reach it - try to chase after it
    1729                         if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
    1730                         {
    1731                             if (this.MoveToTargetRange(target, IID_Attack, this.order.data.attackType))
    1732                             {
    1733                                 this.SetNextState("COMBAT.CHASING");
    1734                                 return;
    1735                             }
    1736                         }
     1734                // respond to target position changes and target deaths immediately
     1735                "Update": function(msg) {
     1736                    var target = this.order.data.target;
     1737                    var attackType = this.order.data.attackType;
     1738                    if
     1739                    (
     1740                        this.TargetIsAlive(target) &&
     1741                        this.CanAttack(target, this.order.data.forceResponse || null)
     1742                    )
     1743                    {
     1744                        if
     1745                        (
     1746                            !this.CheckTargetAttackRange(target, attackType) &&
     1747                            this.ShouldChaseTargetedEntity(target, this.order.data.force) &&
     1748                            this.MoveToTargetAttackRange(target, attackType)
     1749                        )
     1750                            this.SetNextState("COMBAT.CHASING");
     1751                        return;
    17371752                    }
    17381753
    1739                     this.oldAttackType = this.order.data.attackType;
    17401754                    // Can't reach it, no longer owned by enemy, or it doesn't exist any more - give up
    17411755                    // Except if in WalkAndFight mode where we look for more ennemies around before moving again
     1756                    this.oldAttackType = attackType;
    17421757                    if (this.FinishOrder())
    17431758                    {
    17441759                        if (this.orderQueue.length > 0 && this.orderQueue[0].type == "WalkAndFight")
     
    17481763
    17491764                    // See if we can switch to a new nearby enemy
    17501765                    if (this.FindNewTargets())
    1751                     {
    1752                         // Attempt to immediately re-enter the timer function, to avoid wasting the attack.
    1753                         if (this.orderQueue.length > 0 && this.orderQueue[0].data.attackType == this.oldAttackType)
    1754                             this.TimerHandler(msg.data, msg.lateness);
    17551766                        return;
    1756                     }
    17571767
    17581768                    // Return to our original position
    17591769                    if (this.GetStance().respondHoldGround)
     
    17601770                        this.WalkToHeldPosition();
    17611771                },
    17621772
    1763                 // TODO: respond to target deaths immediately, rather than waiting
    1764                 // until the next Timer event
    1765 
    17661773                "Attacked": function(msg) {
    17671774                    if (this.order.data.target != msg.data.attacker)
    17681775                    {
     
    35873594    UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed});
    35883595};
    35893596
     3597UnitAI.prototype.OnUpdate = function(msg)
     3598{
     3599    UnitFsm.ProcessMessage(this, {"type": "Update", "data": msg});
     3600}
     3601
    35903602//// Helper functions to be called by the FSM ////
    35913603
    35923604UnitAI.prototype.GetWalkSpeed = function()
     
    38563868        return false;
    38573869
    38583870    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    3859     return cmpUnitMotion.MoveToTargetRange(target, 0, 0);
     3871    return cmpUnitMotion.MoveToTargetRange(target, 0, 0, 0);
    38603872};
    38613873
    3862 UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
     3874UnitAI.prototype.MoveToTargetRange = function(target, iid, type, time)
    38633875{
    38643876    if (!this.CheckTargetVisible(target))
    38653877        return false;
     
    38683880    if (!cmpRanged)
    38693881        return false;
    38703882    var range = cmpRanged.GetRange(type);
     3883    if (!time)
     3884        time = 0;
    38713885
    38723886    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    3873     return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
     3887    return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, time);
    38743888};
    38753889
    38763890/**
     
    38783892 * for melee attacks, this goes straight to the default range checks
    38793893 * for ranged attacks, the parabolic range is used
    38803894 */
    3881 UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type)
     3895UnitAI.prototype.MoveToTargetAttackRange = function(target, type)
    38823896{
     3897    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     3898    var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     3899    var timers = cmpAttack.GetTimers(type);
     3900    // add one turnlength, as even without prepare time, there's one turn between stopping
     3901    // to move and starting to attack
     3902    var time = timers.prepare/1000 + cmpTimer.GetTurnLength() / 1000;
    38833903
    38843904    if(type!= "Ranged")
    3885         return this.MoveToTargetRange(target, iid, type);
     3905        return this.MoveToTargetRange(target, IID_Attack, type, time);
    38863906   
    38873907    if (!this.CheckTargetVisible(target))
    38883908        return false;
    38893909   
    3890     var cmpRanged = Engine.QueryInterface(this.entity, iid);
    3891     var range = cmpRanged.GetRange(type);
     3910    var range = cmpAttack.GetRange(type);
    38923911
    38933912    var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    38943913    var s = thisCmpPosition.GetPosition();
     
    39133932    var guessedMaxRange = (range.max + parabolicMaxRange)/2;
    39143933
    39153934    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    3916     if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange))
     3935    if (cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange, time))
    39173936        return true;
    39183937
    39193938    // if that failed, try closer
    3920     return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange));
     3939    return cmpUnitMotion.MoveToTargetRange(target, range.min, Math.min(range.max, parabolicMaxRange), time);
    39213940};
    39223941
    3923 UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
     3942UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max, time)
    39243943{
    39253944    if (!this.CheckTargetVisible(target))
    39263945        return false;
    39273946
     3947    if (!time)
     3948        time = 0;
     3949
    39283950    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    3929     return cmpUnitMotion.MoveToTargetRange(target, min, max);
     3951    return cmpUnitMotion.MoveToTargetRange(target, min, max, time);
    39303952};
    39313953
    39323954UnitAI.prototype.MoveToGarrisonRange = function(target)
     
    39403962    var range = cmpGarrisonHolder.GetLoadingRange();
    39413963
    39423964    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
    3943     return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
     3965    return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max, 0);
    39443966};
    39453967
    39463968UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max)
     
    39663988 * For ranged attacks, the parabolic formula is used to accout for bigger ranges
    39673989 * when the target is lower, and smaller ranges when the target is higher
    39683990 */
    3969 UnitAI.prototype.CheckTargetAttackRange = function(target, iid, type)
     3991UnitAI.prototype.CheckTargetAttackRange = function(target, type)
    39703992{
    39713993
    39723994    if (type != "Ranged")
    3973         return this.CheckTargetRange(target,iid,type);
     3995        return this.CheckTargetRange(target, IID_Attack, type);
    39743996   
    39753997    var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
    39763998    if (!targetCmpPosition || !targetCmpPosition.IsInWorld())
    39773999        return false;
    39784000
    3979     var cmpRanged = Engine.QueryInterface(this.entity, iid);
     4001    var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
    39804002    var range = cmpRanged.GetRange(type);
    39814003
    39824004    var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     
    42204242        if (cmpUnitAI && cmpAttack)
    42214243        {
    42224244            for each (var type in cmpAttack.GetAttackTypes())
    4223                 if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type))
     4245                if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, type))
    42244246                    return false;
    42254247        }
    42264248    }
     
    42674289        if (cmpUnitAI && cmpAttack)
    42684290        {
    42694291            for each (var type in cmpAttack.GetAttackTypes())
    4270                 if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type))
     4292                if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, type))
    42714293                    return true;
    42724294        }
    42734295    }
  • binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml

     
    1111      <Pierce>10.0</Pierce>
    1212      <Crush>0.0</Crush>
    1313      <MaxRange>16.0</MaxRange>
    14       <MinRange>0.0</MinRange>
     14      <MinRange>10.0</MinRange>
    1515      <ProjectileSpeed>25.0</ProjectileSpeed>
    1616      <PrepareTime>900</PrepareTime>
    1717      <RepeatTime>1500</RepeatTime>
  • binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_archer.xml

     
    66      <Pierce>20.0</Pierce>
    77      <Crush>0.0</Crush>
    88      <MaxRange>56.0</MaxRange>
    9       <MinRange>0.0</MinRange>
    109      <ProjectileSpeed>75.0</ProjectileSpeed>
    1110      <PrepareTime>1200</PrepareTime>
    1211      <RepeatTime>2000</RepeatTime>
  • binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml

     
    66      <Pierce>25.0</Pierce>
    77      <Crush>0.0</Crush>
    88      <MaxRange>44</MaxRange>
    9       <MinRange>0.0</MinRange>
    109      <ProjectileSpeed>50.0</ProjectileSpeed>
    1110      <PrepareTime>1200</PrepareTime>
    1211      <RepeatTime>2000</RepeatTime>
  • source/simulation2/components/CCmpObstruction.cpp

     
    442442        return m_Tag;
    443443    }
    444444
     445    virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out)
     446    {
     447        return GetObstructionSquare(out, true);
     448    }
     449
    445450    virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out)
    446451    {
     452        return GetObstructionSquare(out, false);
     453    }
     454
     455    virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out, bool previousPosition)
     456    {
    447457        CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
    448458        if (!cmpPosition)
    449459            return false; // error
     
    455465        if (!cmpPosition->IsInWorld())
    456466            return false; // no obstruction square
    457467
    458         CFixedVector2D pos = cmpPosition->GetPosition2D();
     468        CFixedVector2D pos;
     469        if (previousPosition)
     470            pos = cmpPosition->GetPreviousPosition2D();
     471        else
     472            pos = cmpPosition->GetPosition2D();
    459473        if (m_Type == STATIC)
    460474            out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
    461475        else if (m_Type == UNIT)
  • source/simulation2/components/CCmpUnitMotion.cpp

     
    107107static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1);
    108108static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1);
    109109
    110 static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/4; // for extending the goal outwards/inwards a little bit
     110static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/2; // for extending the goal outwards/inwards a little bit
    111111
    112112class CCmpUnitMotion : public ICmpUnitMotion
    113113{
     
    132132    bool m_FormationController;
    133133    fixed m_WalkSpeed, m_OriginalWalkSpeed; // in metres per second
    134134    fixed m_RunSpeed, m_OriginalRunSpeed;
     135    fixed m_TimeToStayInRange;
    135136    ICmpPathfinder::pass_class_t m_PassClass;
    136137    ICmpPathfinder::cost_class_t m_CostClass;
    137138
     
    297298        m_WalkSpeed = m_OriginalWalkSpeed = paramNode.GetChild("WalkSpeed").ToFixed();
    298299        m_Speed = m_WalkSpeed;
    299300        m_CurSpeed = fixed::Zero();
     301        m_TimeToStayInRange = fixed::Zero();
    300302
    301303        if (paramNode.GetChild("Run").IsOk())
    302304            m_RunSpeed = m_OriginalRunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed();
     
    350352
    351353        serialize.NumberFixed_Unbounded("speed", m_Speed);
    352354
     355        serialize.NumberFixed_Unbounded("timeToStayInRange", m_TimeToStayInRange);
     356
    353357        serialize.Bool("moving", m_Moving);
    354358        serialize.Bool("facePointAfterMove", m_FacePointAfterMove);
    355359
     
    470474
    471475    virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
    472476    virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange);
    473     virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
     477    virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange);
    474478    virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange);
    475479    virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z);
    476480
     
    995999                        }
    9961000                        else
    9971001                        {
     1002                            // check if target was reached in case of a moving target
     1003                            CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), m_TargetEntity);
     1004                            if
     1005                            (
     1006                                cmpUnitMotion &&
     1007                                cmpUnitMotion->IsMoving() &&
     1008                                MoveToTargetRange(m_TargetEntity, m_TargetMinRange, m_TargetMaxRange, m_TimeToStayInRange)
     1009                            )
     1010                                return;
     1011
    9981012                            // Not in formation, so just finish moving
    999 
    10001013                            StopMoving();
     1014                            m_State = STATE_IDLE;
     1015                            MoveSucceeded();
    10011016
    1002 
    10031017                            if (m_FacePointAfterMove)
    10041018                                FaceTowardsPointFromPos(pos, m_FinalGoal.x, m_FinalGoal.z);
    10051019                            // TODO: if the goal was a square building, we ought to point towards the
     
    13051319
    13061320bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange, entity_id_t target)
    13071321{
     1322    // TODO also take moving targets into account, like in MoveToTargetRange
    13081323    PROFILE("MoveToPointRange");
    13091324
    13101325    CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     
    13831398    m_TargetOffset = CFixedVector2D();
    13841399    m_TargetMinRange = minRange;
    13851400    m_TargetMaxRange = maxRange;
     1401    m_TimeToStayInRange = fixed::Zero();
    13861402    m_FinalGoal = goal;
    13871403
    13881404    BeginPathing(pos, goal);
     
    14541470    return (errCircle < errSquare);
    14551471}
    14561472
    1457 bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
     1473bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
    14581474{
    14591475    PROFILE("MoveToTargetRange");
    14601476
     
    14741490    if (cmpObstruction)
    14751491        hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
    14761492
     1493    CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
     1494    if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
     1495        return false;
     1496
    14771497    /*
    14781498     * If we're starting outside the maxRange, we need to move closer in.
    14791499     * If we're starting inside the minRange, we need to move further out.
     
    15011521
    15021522    if (hasObstruction)
    15031523    {
     1524        // get the rotation difference, and bring it to the (-pi,pi] interval
     1525        fixed rotationDifference = cmpPosition->GetRotation().Y - cmpTargetPosition->GetRotation().Y;
     1526        fixed Pi = fixed::Pi();
     1527        while (rotationDifference > Pi)
     1528            rotationDifference -= Pi * 2;
     1529        while (rotationDifference <= -Pi)
     1530            rotationDifference += Pi * 2;
     1531
     1532        // if the target is moving in the same direction, take an overshoot into account
     1533        entity_pos_t overShoot = fixed::Zero();
     1534        CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), target);
     1535        if (cmpUnitMotion && rotationDifference < Pi / 2 && rotationDifference > -Pi / 2)
     1536            overShoot = cmpUnitMotion->GetCurrentSpeed().Multiply(timeToStayInRange);
     1537
     1538        entity_pos_t newMaxRange = maxRange - overShoot;
     1539        entity_pos_t newMinRange = minRange + overShoot;
     1540
     1541        // make sure the ranges are possible, and have the same behaviour
     1542        if (newMaxRange <= minRange && maxRange >= minRange)
     1543            newMaxRange = minRange;
     1544        if (maxRange > fixed::Zero() && newMinRange > newMaxRange)
     1545            newMinRange = newMaxRange;
     1546
    15041547        CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
    15051548        ICmpPathfinder::Goal goal;
    15061549        goal.x = obstruction.x;
     
    15081551
    15091552        entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
    15101553
    1511         if (distance < minRange)
     1554        // compare with previous obstruction
     1555        ICmpObstructionManager::ObstructionSquare previousObstruction;
     1556        cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
     1557        entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize);
     1558
     1559        if (distance < newMinRange && previousDistance < newMinRange)
    15121560        {
    15131561            // Too close to the square - need to move away
    15141562
    15151563            // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
    15161564
    1517             entity_pos_t goalDistance = minRange + g_GoalDelta;
     1565            entity_pos_t goalDistance = newMinRange + g_GoalDelta;
    15181566
    15191567            goal.type = ICmpPathfinder::Goal::SQUARE;
    15201568            goal.u = obstruction.u;
     
    15231571            goal.hw = obstruction.hw + delta;
    15241572            goal.hh = obstruction.hh + delta;
    15251573        }
    1526         else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
     1574        else if (newMaxRange < entity_pos_t::Zero() || distance < newMaxRange || previousDistance < newMaxRange)
    15271575        {
    15281576            // We're already in range - no need to move anywhere
    15291577            if (m_FacePointAfterMove)
     
    15371585            // Circumscribe the square
    15381586            entity_pos_t circleRadius = halfSize.Length();
    15391587
    1540             if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
     1588            if (ShouldTreatTargetAsCircle(newMaxRange, obstruction.hw, obstruction.hh, circleRadius))
    15411589            {
    15421590                // The target is small relative to our range, so pretend it's a circle
    15431591
     
    15461594                // check is still valid (though not sufficient)
    15471595                entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
    15481596
    1549                 if (circleDistance < maxRange)
     1597                if (circleDistance < newMaxRange)
    15501598                {
    15511599                    // We're already in range - no need to move anywhere
    15521600                    if (m_FacePointAfterMove)
     
    15541602                    return false;
    15551603                }
    15561604
    1557                 entity_pos_t goalDistance = maxRange - g_GoalDelta;
     1605                entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
    15581606
     1607                if (previousCircleDistance < newMaxRange)
     1608                {
     1609                    // We're already in range - no need to move anywhere
     1610                    if (m_FacePointAfterMove)
     1611                        FaceTowardsPointFromPos(pos, goal.x, goal.z);
     1612                    return false;
     1613                }
     1614
     1615
     1616                entity_pos_t goalDistance = newMaxRange - g_GoalDelta;
     1617
    15591618                goal.type = ICmpPathfinder::Goal::CIRCLE;
    15601619                goal.hw = circleRadius + goalDistance;
    15611620            }
     
    15641623                // The target is large relative to our range, so treat it as a square and
    15651624                // get close enough that the diagonals come within range
    15661625
    1567                 entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
     1626                entity_pos_t goalDistance = (newMaxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
    15681627
    15691628                goal.type = ICmpPathfinder::Goal::SQUARE;
    15701629                goal.u = obstruction.u;
     
    15801639        m_TargetOffset = CFixedVector2D();
    15811640        m_TargetMinRange = minRange;
    15821641        m_TargetMaxRange = maxRange;
     1642        m_TimeToStayInRange = timeToStayInRange;
    15831643        m_FinalGoal = goal;
    15841644
    15851645        BeginPathing(pos, goal);
     
    15901650    {
    15911651        // The target didn't have an obstruction or obstruction shape, so treat it as a point instead
    15921652
    1593         CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
    1594         if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
    1595             return false;
    1596 
    15971653        CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
    15981654
    15991655        return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange, target);
     
    16261682        CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
    16271683        entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
    16281684
     1685        // compare with previous obstruction
     1686        ICmpObstructionManager::ObstructionSquare previousObstruction;
     1687        cmpObstruction->GetPreviousObstructionSquare(previousObstruction);
     1688        entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize);
     1689       
    16291690        // See if we're too close to the target square
    1630         if (distance < minRange)
     1691        if (distance < minRange && previousDistance < minRange)
    16311692            return false;
    16321693
    16331694        // See if we're close enough to the target square
    1634         if (maxRange < entity_pos_t::Zero() || distance <= maxRange)
     1695        if (maxRange < entity_pos_t::Zero() || distance <= maxRange || previousDistance <= maxRange)
    16351696            return true;
    16361697
    16371698        entity_pos_t circleRadius = halfSize.Length();
     
    16451706
    16461707            if (circleDistance <= maxRange)
    16471708                return true;
     1709            // also check circle around previous position
     1710            circleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius;
     1711
     1712            if (circleDistance <= maxRange)
     1713                return true;
    16481714        }
    16491715
    16501716        return false;
     
    16551721        if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
    16561722            return false;
    16571723
    1658         CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
     1724        CFixedVector2D targetPos = cmpTargetPosition->GetPreviousPosition2D();
    16591725
    16601726        entity_pos_t distance = (pos - targetPos).Length();
    16611727
    1662         if (minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange))
    1663             return true;
    1664 
    1665         return false;
     1728        return minRange <= distance &&
     1729            (maxRange < entity_pos_t::Zero() || distance <= maxRange);
    16661730    }
    16671731}
    16681732
  • source/simulation2/components/ICmpObstruction.h

     
    4747     */
    4848    virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) = 0;
    4949
     50    /**
     51     * Same as the method above, but returns an obstruction shape for the previous turn
     52     */
     53    virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) = 0;
     54
    5055    virtual entity_pos_t GetUnitRadius() = 0;
    5156
    5257    virtual bool IsControlPersistent() = 0;
  • source/simulation2/components/ICmpUnitMotion.cpp

     
    2626DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t)
    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)
    29 DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t)
     29DEFINE_INTERFACE_METHOD_4("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t)
    3030DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t)
    3131DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t)
    3232DEFINE_INTERFACE_METHOD_0("StopMoving", void, ICmpUnitMotion, StopMoving)
     
    6060        return m_Script.Call<bool>("IsInTargetRange", target, minRange, maxRange);
    6161    }
    6262
    63     virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
     63    virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange)
    6464    {
    65         return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange);
     65        return m_Script.Call<bool>("MoveToTargetRange", target, minRange, maxRange, timeToStayInRange);
    6666    }
    6767
    6868    virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z)
  • source/simulation2/components/ICmpUnitMotion.h

     
    6666     * and sends another MotionChanged after finishing moving.
    6767     * If maxRange is negative, then the maximum range is treated as infinity.
    6868     */
    69     virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) = 0;
     69    virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t timeToStayInRange) = 0;
    7070
    7171    /**
    7272     * Join a formation, and move towards a given offset relative to the formation controller entity.