Index: binaries/data/mods/public/simulation/components/Promotion.js =================================================================== --- binaries/data/mods/public/simulation/components/Promotion.js +++ binaries/data/mods/public/simulation/components/Promotion.js @@ -31,101 +31,12 @@ Promotion.prototype.Promote = function(promotedTemplateName) { // If the unit is dead, don't promote it - var cmpCurrentUnitHealth = Engine.QueryInterface(this.entity, IID_Health); + let cmpCurrentUnitHealth = Engine.QueryInterface(this.entity, IID_Health); if (cmpCurrentUnitHealth.GetHitpoints() == 0) return; - // Create promoted unit entity - var promotedUnitEntity = Engine.AddEntity(promotedTemplateName); + ChangeEntityTemplate(this.entity, promotedTemplateName); - // Copy parameters from current entity to promoted one - var cmpCurrentUnitPosition = Engine.QueryInterface(this.entity, IID_Position); - var cmpPromotedUnitPosition = Engine.QueryInterface(promotedUnitEntity, IID_Position); - if (cmpCurrentUnitPosition.IsInWorld()) - { - var pos = cmpCurrentUnitPosition.GetPosition2D(); - cmpPromotedUnitPosition.JumpTo(pos.x, pos.y); - } - var rot = cmpCurrentUnitPosition.GetRotation(); - cmpPromotedUnitPosition.SetYRotation(rot.y); - cmpPromotedUnitPosition.SetXZRotation(rot.x, rot.z); - var heightOffset = cmpCurrentUnitPosition.GetHeightOffset(); - cmpPromotedUnitPosition.SetHeightOffset(heightOffset); - - var cmpCurrentUnitOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - var cmpPromotedUnitOwnership = Engine.QueryInterface(promotedUnitEntity, IID_Ownership); - cmpPromotedUnitOwnership.SetOwner(cmpCurrentUnitOwnership.GetOwner()); - - // change promoted unit health to the same percent of hitpoints as unit had before promotion - var cmpPromotedUnitHealth = Engine.QueryInterface(promotedUnitEntity, IID_Health); - var healthFraction = Math.max(0, Math.min(1, cmpCurrentUnitHealth.GetHitpoints() / cmpCurrentUnitHealth.GetMaxHitpoints())); - var promotedUnitHitpoints = cmpPromotedUnitHealth.GetMaxHitpoints() * healthFraction; - cmpPromotedUnitHealth.SetHitpoints(promotedUnitHitpoints); - - var cmpPromotedUnitPromotion = Engine.QueryInterface(promotedUnitEntity, IID_Promotion); - if (cmpPromotedUnitPromotion) - cmpPromotedUnitPromotion.IncreaseXp(this.currentXp); - - var cmpCurrentUnitResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); - var cmpPromotedUnitResourceGatherer = Engine.QueryInterface(promotedUnitEntity, IID_ResourceGatherer); - if (cmpCurrentUnitResourceGatherer && cmpPromotedUnitResourceGatherer) - { - var carriedResorces = cmpCurrentUnitResourceGatherer.GetCarryingStatus(); - cmpPromotedUnitResourceGatherer.GiveResources(carriedResorces); - } - - var cmpCurrentUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); - var cmpPromotedUnitAI = Engine.QueryInterface(promotedUnitEntity, IID_UnitAI); - var heldPos = cmpCurrentUnitAI.GetHeldPosition(); - if (heldPos) - cmpPromotedUnitAI.SetHeldPosition(heldPos.x, heldPos.z); - if (cmpCurrentUnitAI.GetStanceName()) - cmpPromotedUnitAI.SwitchToStance(cmpCurrentUnitAI.GetStanceName()); - - var orders = cmpCurrentUnitAI.GetOrders(); - if (cmpCurrentUnitPosition.IsInWorld()) // do not cheer if not visibly garrisoned - cmpPromotedUnitAI.Cheer(); - if (cmpCurrentUnitAI.IsGarrisoned()) - cmpPromotedUnitAI.SetGarrisoned(); - cmpPromotedUnitAI.AddOrders(orders); - - var workOrders = cmpCurrentUnitAI.GetWorkOrders(); - cmpPromotedUnitAI.SetWorkOrders(workOrders); - - if (cmpCurrentUnitAI.IsGuardOf()) - { - let guarded = cmpCurrentUnitAI.IsGuardOf(); - let cmpGuard = Engine.QueryInterface(guarded, IID_Guard); - if (cmpGuard) - { - cmpGuard.RenameGuard(this.entity, promotedUnitEntity); - cmpPromotedUnitAI.SetGuardOf(guarded); - } - } - - let cmpCurrentUnitGuard = Engine.QueryInterface(this.entity, IID_Guard); - let cmpPromotedUnitGuard = Engine.QueryInterface(promotedUnitEntity, IID_Guard); - if (cmpCurrentUnitGuard && cmpPromotedUnitGuard) - { - let entities = cmpCurrentUnitGuard.GetEntities(); - if (entities.length) - { - cmpPromotedUnitGuard.SetEntities(entities); - for (let ent of entities) - { - let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if (cmpUnitAI) - cmpUnitAI.SetGuardOf(promotedUnitEntity); - } - } - } - - Engine.PostMessage(this.entity, MT_EntityRenamed, { "entity": this.entity, "newentity": promotedUnitEntity }); - - // Destroy current entity - if (cmpCurrentUnitPosition && cmpCurrentUnitPosition.IsInWorld()) - cmpCurrentUnitPosition.MoveOutOfWorld(); - Engine.DestroyEntity(this.entity); // save the entity id this.promotedUnitEntity = promotedUnitEntity; }; Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -270,7 +270,7 @@ return; } - this.SetHeldPosition(this.order.data.x, this.order.data.z); + this.SetRememberedPosition(this.order.data.x, this.order.data.z); if (this.IsAnimal()) this.SetNextState("ANIMAL.WALKING"); else @@ -294,7 +294,7 @@ return; } - this.SetHeldPosition(this.order.data.x, this.order.data.z); + this.SetRememberedPosition(this.order.data.x, this.order.data.z); if (this.IsAnimal()) this.SetNextState("ANIMAL.WALKING"); // WalkAndFight not applicable for animals else @@ -402,6 +402,10 @@ } this.order.data.attackType = type; + // Remember the position of our target, if any, in case it disappears + // later and we want to head to its last known position + this.SetRememberedPositionOnEntity(this.order.data.target); + // If we are already at the target, try attacking it from here if (this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) { @@ -512,7 +516,7 @@ if (this.order.data.secondTry === undefined) { this.order.data.secondTry = true; - this.PushOrderFront("Walk", this.order.data.lastPos); + this.PushOrderFront("Walk", this.GetRememberedPosition()); } else { @@ -526,6 +530,10 @@ return; } + // Remember the position of our target, if any, in case it disappears + // later and we want to head to its last known position + this.SetRememberedPositionOnEntity(this.order.data.target); + if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); else @@ -642,17 +650,17 @@ "FORMATIONCONTROLLER": { "Order.Walk": function(msg) { - this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + this.CallMemberFunction("SetRememberedPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKING"); }, "Order.WalkAndFight": function(msg) { - this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + this.CallMemberFunction("SetRememberedPosition", [msg.data.x, msg.data.z]); this.SetNextState("WALKINGANDFIGHTING"); }, "Order.MoveIntoFormation": function(msg) { - this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + this.CallMemberFunction("SetRememberedPosition", [msg.data.x, msg.data.z]); this.SetNextState("FORMING"); }, @@ -679,7 +687,7 @@ }, "Order.Patrol": function(msg) { - this.CallMemberFunction("SetHeldPosition", [msg.data.x, msg.data.z]); + this.CallMemberFunction("SetRememberedPosition", [msg.data.x, msg.data.z]); this.SetNextState("PATROL"); }, @@ -756,7 +764,7 @@ if (msg.data.secondTry === undefined) { msg.data.secondTry = true; - this.PushOrderFront("Walk", msg.data.lastPos); + this.PushOrderFront("Walk", this.GetRememberedPosition()); } else { @@ -1583,7 +1591,7 @@ this.SetAnimationVariant("combat"); this.StartTimer(0, 1000); - this.SetHeldPositionOnEntity(this.isGuardOf); + this.SetRememberedPositionOnEntity(this.isGuardOf); return false; }, @@ -1608,7 +1616,7 @@ } } - this.SetHeldPositionOnEntity(this.isGuardOf); + this.SetRememberedPositionOnEntity(this.isGuardOf); }, "leave": function(msg) { @@ -1628,7 +1636,7 @@ "enter": function() { this.StopMoving(); this.StartTimer(1000, 1000); - this.SetHeldPositionOnEntity(this.entity); + this.SetRememberedPositionOnEntity(this.entity); this.SetAnimationVariant("combat"); return false; }, @@ -1751,13 +1759,37 @@ // Return to our original position if (this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); + this.MaybeWalkToRememberedPosition(); } + else + this.SetRememberedPositionOnEntity(this.order.data.target); }, - "MovementUpdate": function() { + "MovementUpdate": function(msg) { + if (msg.error) + { + if (this.orderQueue.length > 1) + { + this.FinishOrder(); + return; + } + else if (!this.order.data.force) + { + // Move to Attacking and let the timer handle things. + this.SetNextState("ATTACKING"); + return; + } + // Go to the last known position and try to find enemies there. + let lastPos = this.GetRememberedPosition(); + warn("here"); + this.PushOrder("Walk", { "x": lastPos.x, "z": lastPos.z, "force": false }); + this.PushOrder("WalkAndFight", { "x": lastPos.x, "z": lastPos.z, "force": false }); + return; + } + if (!this.CheckTargetAttackRange(this.order.data.target, this.order.data.attackType)) return; + // If the unit needs to unpack, do so if (this.CanUnpack()) { @@ -1820,20 +1852,25 @@ if (cmpFormation) animationName = cmpFormation.GetFormationAnimation(this.entity, animationName); } + this.SetAnimationVariant("combat"); - this.SelectAnimation(animationName); - this.SetAnimationSync(prepare, this.attackTimers.repeat); this.StartTimer(prepare, this.attackTimers.repeat); - // TODO: we should probably only bother syncing projectile attacks, not melee - // If using a non-default prepare time, re-sync the animation when the timer runs. - this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false; - - this.FaceTowardsTarget(this.order.data.target); - - var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); + let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); if (cmpBuildingAI) cmpBuildingAI.SetUnitAITarget(this.order.data.target); + + if (this.CheckTargetAttackRange(target, this.order.data.attackType)) + { + this.SelectAnimation(animationName); + this.SetAnimationSync(prepare, this.attackTimers.repeat); + // TODO: we should probably only bother syncing projectile attacks, not melee + + // If using a non-default prepare time, re-sync the animation when the timer runs. + this.resyncAnimation = (prepare != this.attackTimers.prepare) ? true : false; + + this.FaceTowardsTarget(this.order.data.target); + } }, "leave": function() { @@ -1846,13 +1883,13 @@ }, "Timer": function(msg) { - var target = this.order.data.target; - var cmpFormation = Engine.QueryInterface(target, IID_Formation); + let target = this.order.data.target; + let cmpFormation = Engine.QueryInterface(target, IID_Formation); // if the target is a formation, save the attacking formation, and pick a member if (cmpFormation) { - var thisObject = this; - var filter = function(t) { + let thisObject = this; + let filter = function(t) { return thisObject.CanAttack(t); }; this.order.data.formationTarget = target; @@ -1862,28 +1899,15 @@ // Check the target is still alive and attackable if (this.CanAttack(target)) { - // If we are hunting, first update the target position of the gather order so we know where will be the killed animal - if (this.order.data.hunting && this.orderQueue[1] && this.orderQueue[1].data.lastPos) - { - var cmpPosition = Engine.QueryInterface(this.order.data.target, IID_Position); - if (cmpPosition && cmpPosition.IsInWorld()) - { - // Store the initial position, so that we can find the rest of the herd later - if (!this.orderQueue[1].data.initPos) - this.orderQueue[1].data.initPos = this.orderQueue[1].data.lastPos; - this.orderQueue[1].data.lastPos = cmpPosition.GetPosition(); - // We still know where the animal is, so we shouldn't give up before going there - this.orderQueue[1].data.secondTry = undefined; - } - } + this.SetRememberedPositionOnEntity(this.order.data.target); - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.lastAttacked = cmpTimer.GetTime() - msg.lateness; this.FaceTowardsTarget(target); // BuildingAI has it's own attack-routine - var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); + let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); if (!cmpBuildingAI) { let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); @@ -1915,7 +1939,7 @@ } // if we're targetting a formation, find a new member of that formation - var cmpTargetFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); + let cmpTargetFormation = Engine.QueryInterface(this.order.data.formationTarget || INVALID_ENTITY, IID_Formation); // if there is no target, it means previously searching for the target inside the target formation failed, so don't repeat the search if (target && cmpTargetFormation) { @@ -1946,7 +1970,7 @@ // Return to our original position if (this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); + this.MaybeWalkToRememberedPosition(); }, // TODO: respond to target deaths immediately, rather than waiting @@ -1997,7 +2021,7 @@ // Return to our original position if (this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); + this.MaybeWalkToRememberedPosition(); } }, @@ -2207,11 +2231,11 @@ // Our target is no longer visible - go to its last known position first // and then hopefully it will become visible. - if (!this.CheckTargetVisible(target) && this.order.data.lastPos) + if (!this.CheckTargetVisible(target) && this.GetRememberedPosition()) { this.PushOrderFront("Walk", { - "x": this.order.data.lastPos.x, - "z": this.order.data.lastPos.z, + "x": this.GetRememberedPosition().x, + "z": this.GetRememberedPosition().z, "force": this.order.data.force }); return; @@ -2308,11 +2332,8 @@ "Timer": function(msg) { if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force, IID_Heal, null)) - { - // Return to our original position unless we have a better order. - if (!this.FinishOrder() && this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); - } + if (this.GetStance().respondHoldGround) + this.MaybeWalkToRememberedPosition(); }, "MovementUpdate": function(msg) { @@ -2395,7 +2416,7 @@ // Return to our original position if (this.GetStance().respondHoldGround) - this.WalkToHeldPosition(); + this.MaybeWalkToRememberedPosition(); }, }, }, @@ -3073,7 +3094,7 @@ this.isIdle = false; this.finishedOrder = false; // used to find if all formation members finished the order - this.heldPosition = undefined; + this.rememberedPosition = undefined; // Queue of remembered works this.workOrders = []; @@ -4383,7 +4404,7 @@ } }; -UnitAI.prototype.CheckTargetDistanceFromHeldPosition = function(target, iid, type) +UnitAI.prototype.CheckTargetDistanceFromrememberedPosition = function(target, iid, type) { var cmpRanged = Engine.QueryInterface(this.entity, iid); var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetRange(type); @@ -4398,11 +4419,11 @@ var halfvision = cmpVision.GetRange() / 2; var pos = cmpPosition.GetPosition(); - var heldPosition = this.heldPosition; - if (heldPosition === undefined) - heldPosition = { "x": pos.x, "z": pos.z }; + var rememberedPosition = this.rememberedPosition; + if (rememberedPosition === undefined) + rememberedPosition = { "x": pos.x, "z": pos.z }; - return Math.euclidDistance2D(pos.x, pos.z, heldPosition.x, heldPosition.z) < halfvision + range.max; + return Math.euclidDistance2D(pos.x, pos.z, rememberedPosition.x, rememberedPosition.z) < halfvision + range.max; }; UnitAI.prototype.CheckTargetIsInVisionRange = function(target) @@ -4449,7 +4470,7 @@ { var target = ents.find(target => this.CanAttack(target) - && this.CheckTargetDistanceFromHeldPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true)) + && this.CheckTargetDistanceFromrememberedPosition(target, IID_Attack, this.GetBestAttackAgainst(target, true)) && (this.GetStance().respondChaseBeyondVision || this.CheckTargetIsInVisionRange(target)) ); if (!target) @@ -4523,7 +4544,7 @@ // Stop if we're in hold-ground mode and it's too far from the holding point if (this.GetStance().respondHoldGround) { - if (!this.CheckTargetDistanceFromHeldPosition(target, iid, type)) + if (!this.CheckTargetDistanceFromrememberedPosition(target, iid, type)) return true; } @@ -4895,7 +4916,12 @@ this.WalkToTarget(target, queued); return; } - this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture}, queued); + + // Remember the position of our target, if any, in case it disappears + // later and we want to head to its last known position + this.SetRememberedPositionOnEntity(target); + + this.AddOrder("Attack", { "target": target, "force": true, "allowCapture": allowCapture }, queued); }; /** @@ -4972,12 +4998,9 @@ // Remember the position of our target, if any, in case it disappears // later and we want to head to its last known position - var lastPos = undefined; - var cmpPosition = Engine.QueryInterface(target, IID_Position); - if (cmpPosition && cmpPosition.IsInWorld()) - lastPos = cmpPosition.GetPosition(); + this.SetRememberedPositionOnEntity(target); - this.AddOrder("Gather", { "target": target, "type": type, "template": template, "lastPos": lastPos, "force": force }, queued); + this.AddOrder("Gather", { "target": target, "type": type, "template": template, "force": force }, queued); }; /** @@ -5255,7 +5278,7 @@ if (!cmpPosition || !cmpPosition.IsInWorld()) return; var pos = cmpPosition.GetPosition(); - this.SetHeldPosition(pos.x, pos.z); + this.SetRememberedPosition(pos.x, pos.z); this.SetStance(stance); // Stop moving if switching to stand ground @@ -5495,33 +5518,32 @@ cmpUnitMotion.SetSpeedMultiplier(speed); }; -UnitAI.prototype.SetHeldPosition = function(x, z) +UnitAI.prototype.SetRememberedPosition = function(x, z) { - this.heldPosition = {"x": x, "z": z}; + this.rememberedPosition = { "x": x, "z": z }; }; -UnitAI.prototype.SetHeldPositionOnEntity = function(entity) +UnitAI.prototype.SetRememberedPositionOnEntity = function(entity) { - var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!cmpPosition || !cmpPosition.IsInWorld()) + let cmpPosition = Engine.QueryInterface(entity, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld() || !this.CheckTargetVisible(entity)) return; - var pos = cmpPosition.GetPosition(); - this.SetHeldPosition(pos.x, pos.z); + let pos = cmpPosition.GetPosition(); + this.SetRememberedPosition(pos.x, pos.z); }; -UnitAI.prototype.GetHeldPosition = function() +UnitAI.prototype.GetRememberedPosition = function() { - return this.heldPosition; + return this.rememberedPosition; }; -UnitAI.prototype.WalkToHeldPosition = function() +/** + * Walk to remembered position unless we have another order in the queue. + */ +UnitAI.prototype.MaybeWalkToRememberedPosition = function() { - if (this.heldPosition) - { - this.AddOrder("Walk", { "x": this.heldPosition.x, "z": this.heldPosition.z, "force": false }, false); - return true; - } - return false; + if (!this.FinishOrder() && this.rememberedPosition) + this.AddOrder("Walk", { "x": this.rememberedPosition.x, "z": this.rememberedPosition.z, "force": false }, false); }; //// Helper functions //// Index: binaries/data/mods/public/simulation/components/tests/test_Promotion.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Promotion.js +++ binaries/data/mods/public/simulation/components/tests/test_Promotion.js @@ -32,7 +32,7 @@ }); AddMock(60, IID_UnitAI, { - "GetHeldPosition": () => {}, + "GetRememberedPosition": () => {}, "GetStanceName": () => {}, "GetOrders": () => {}, "IsGarrisoned": () => {}, Index: binaries/data/mods/public/simulation/helpers/Transform.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Transform.js +++ binaries/data/mods/public/simulation/helpers/Transform.js @@ -57,9 +57,9 @@ var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI); if (cmpUnitAI && cmpNewUnitAI) { - let pos = cmpUnitAI.GetHeldPosition(); + let pos = cmpUnitAI.GetRememberedPosition(); if (pos) - cmpNewUnitAI.SetHeldPosition(pos.x, pos.z); + cmpNewUnitAI.SetRememberedPosition(pos.x, pos.z); if (cmpUnitAI.GetStanceName()) cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName()); cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());