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 @@ -46,12 +46,6 @@ // Save the entity id. this.promotedUnitEntity = ChangeEntityTemplate(this.entity, promotedTemplateName); - - let cmpPosition = Engine.QueryInterface(this.promotedUnitEntity, IID_Position); - let cmpUnitAI = Engine.QueryInterface(this.promotedUnitEntity, IID_UnitAI); - - if (cmpPosition && cmpPosition.IsInWorld() && cmpUnitAI) - cmpUnitAI.Cheer(); }; Promotion.prototype.IncreaseXp = function(amount) 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 @@ -657,11 +657,8 @@ this.isGarrisoned = false; }, - "Order.Cheering": function(msg) { - if (this.IsFormationMember()) - this.SetNextState("FORMATIONMEMBER.CHEERING"); - else - this.SetNextState("INDIVIDUAL.CHEERING"); + "Order.Cheer": function(msg) { + return { "discardOrder": true }; }, "Order.Pack": function(msg) { @@ -1477,6 +1474,11 @@ }, "IDLE": { + "Order.Cheer": function() { + if (this.isIdle) + this.SetNextState("CHEERING"); + }, + "enter": function() { // Switch back to idle animation to guarantee we won't // get stuck with an incorrect animation @@ -1922,6 +1924,7 @@ this.order.data.target = target; } + this.shouldCheer = false; if (!this.CanAttack(target)) { this.SetNextState("COMBAT.FINDINGNEWTARGET"); @@ -1973,6 +1976,11 @@ let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); if (cmpBuildingAI) cmpBuildingAI.SetUnitAITarget(this.order.data.target); + else + { + let cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI); + this.shouldCheer = cmpUnitAI && !cmpUnitAI.IsAnimal(); + } return false; }, @@ -2083,6 +2091,12 @@ if (this.GetStance().respondHoldGround) this.WalkToHeldPosition(); + if (this.shouldCheer) + { + this.Cheer(); + this.CallFrienldyUnitsFunctionInRange("Cheer", [], 30); + } + return true; }, }, @@ -3129,11 +3143,8 @@ "CHEERING": { "enter": function() { - // Unit is invulnerable while cheering - var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance); - cmpResistance.SetInvulnerability(true); this.SelectAnimation("promotion"); - this.StartTimer(2800, 2800); + this.StartTimer(2800); return false; }, @@ -3144,8 +3155,15 @@ this.SetAnimationVariant(this.formationAnimationVariant) else this.SetDefaultAnimationVariant(); - var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance); - cmpResistance.SetInvulnerability(false); + }, + + "LosRangeUpdate": function(msg) { + if (this.GetStance().targetVisibleEnemies) + this.AttackEntitiesByPreference(msg.data.added); + }, + + "LosHealRangeUpdate": function(msg) { + this.RespondToHealableEntities(msg.data.added); }, "Timer": function(msg) { @@ -3535,8 +3553,8 @@ if (msg.to != INVALID_PLAYER && msg.from != INVALID_PLAYER) { // Switch to a virgin state to let states execute their leave handlers. - // except if garrisoned or cheering or (un)packing, in which case we only clear the order queue - if (this.isGarrisoned || this.IsPacking() || this.orderQueue[0] && this.orderQueue[0].type == "Cheering") + // Except if garrisoned or (un)packing, in which case we only clear the order queue. + if (this.isGarrisoned || this.IsPacking()) { this.orderQueue.length = Math.min(this.orderQueue.length, 1); Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); @@ -3808,14 +3826,8 @@ UnitAI.prototype.PushOrderFront = function(type, data, ignorePacking = false) { var order = { "type": type, "data": data }; - // If current order is cheering then add new order after it - // same thing if current order if packing/unpacking - if (this.order && this.order.type == "Cheering") - { - var cheeringOrder = this.orderQueue.shift(); - this.orderQueue.unshift(cheeringOrder, order); - } - else if (!ignorePacking && this.order && this.IsPacking()) + // If current order is packing/unpacking then add new order after it. + if (!ignorePacking && this.order && this.IsPacking()) { var packingOrder = this.orderQueue.shift(); this.orderQueue.unshift(packingOrder, order); @@ -3938,17 +3950,9 @@ let garrisonHolder = this.IsGarrisoned() && type != "Ungarrison" ? this.GetGarrisonHolder() : null; - // Special cases of orders that shouldn't be replaced: - // 1. Cheering - we're invulnerable, add order after we finish - // 2. Packing/unpacking - we're immobile, add order after we finish (unless it's cancel) + // Do not replace Packing/unpacking unless it is cancel order // TODO: maybe a better way of doing this would be to use priority levels - if (this.order && this.order.type == "Cheering") - { - var order = { "type": type, "data": data }; - var cheeringOrder = this.orderQueue.shift(); - this.orderQueue = [cheeringOrder, order]; - } - else if (this.IsPacking() && type != "CancelPack" && type != "CancelUnpack") + if (this.IsPacking() && type != "CancelPack" && type != "CancelUnpack") { var order = { "type": type, "data": data }; var packingOrder = this.orderQueue.shift(); @@ -4057,13 +4061,7 @@ } // Clear the order queue considering special orders not to avoid - if (this.order && this.order.type == "Cheering") - { - var cheeringOrder = this.orderQueue.shift(); - this.orderQueue = [cheeringOrder]; - } - else - this.orderQueue = []; + this.orderQueue = []; this.AddOrders(this.workOrders); Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() }); @@ -5703,12 +5701,9 @@ this.AddOrder("Flee", { "target": target, "force": false }, queued); }; -/** - * Pushes a cheer order to the front of the queue. Forced so it won't be interrupted by attacks. - */ UnitAI.prototype.Cheer = function() { - this.PushOrderFront("Cheering", { "force": true }); + this.PushOrderFront("Cheer", { "force": false }); }; UnitAI.prototype.Pack = function(queued) @@ -6331,6 +6326,22 @@ }; /** + * Call obj.funcname(args) on UnitAI components owned by player in given range. + */ +UnitAI.prototype.CallFrienldyUnitsFunctionInRange = function(funcname, args, range) +{ + let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + let nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, [cmpOwnership.GetOwner()], IID_UnitAI); + for (let i = 0; i < nearby.length; ++i) { + let cmpUnitAI = Engine.QueryInterface(nearby[i], IID_UnitAI); + cmpUnitAI[funcname].apply(cmpUnitAI, args); + } +}; + +/** * Call obj.functname(args) on UnitAI components of all formation members, * and return true if all calls return true. */