forked from mirrors/0ad
Cheer after combat when no enemy units are in range
Cheering as is done now is highly unfair and illogical. Cheering units are immune to all damage for 2.8 seconds. That means unit A attacking cheering unit B is wasting its attack and doing 0 damage while taking damage from anyone attacking it (unit A). That can save cheering unit with 1 HP from being killed what is not fair to the attacker. Changes: removing immunity to damage while cheering do not forcing cheering order so unit can respond to attacks removing cheering from promotion cheering after unit cannot find more targets(units) while in combat and has nothing else to do tell units around to cheer as well Differential Revision: D1977 Reviewed by: Freagarach Accepted by: goldie, borg- Comments by: Stan, wraitii This was SVN commit r24034.
This commit is contained in:
@@ -46,12 +46,6 @@ Promotion.prototype.Promote = function(promotedTemplateName)
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -146,6 +146,8 @@ var g_OrdersCancelUnpacking = new Set([
|
||||
// When leaving a foundation, we want to be clear of it by this distance.
|
||||
var g_LeaveFoundationRange = 4;
|
||||
|
||||
UnitAI.prototype.notifyToCheerInRange = 30;
|
||||
|
||||
// See ../helpers/FSM.js for some documentation of this FSM specification syntax
|
||||
UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
@@ -662,11 +664,8 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
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) {
|
||||
@@ -1488,6 +1487,10 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"IDLE": {
|
||||
"Order.Cheer": function() {
|
||||
this.SetNextState("CHEERING");
|
||||
},
|
||||
|
||||
"enter": function() {
|
||||
// Switch back to idle animation to guarantee we won't
|
||||
// get stuck with an incorrect animation
|
||||
@@ -1938,6 +1941,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
this.order.data.target = target;
|
||||
}
|
||||
|
||||
this.shouldCheer = false;
|
||||
if (!this.CanAttack(target))
|
||||
{
|
||||
this.SetNextState("COMBAT.FINDINGNEWTARGET");
|
||||
@@ -1988,7 +1992,14 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
let cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI);
|
||||
if (cmpBuildingAI)
|
||||
{
|
||||
cmpBuildingAI.SetUnitAITarget(this.order.data.target);
|
||||
return false;
|
||||
}
|
||||
|
||||
let cmpUnitAI = Engine.QueryInterface(this.order.data.target, IID_UnitAI);
|
||||
this.shouldCheer = cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal());
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -2072,6 +2083,10 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"FINDINGNEWTARGET": {
|
||||
"Order.Cheer": function() {
|
||||
this.SetNextState("CHEERING");
|
||||
},
|
||||
|
||||
"enter": function() {
|
||||
// Try to find the formation the target was a part of.
|
||||
let cmpFormation = Engine.QueryInterface(this.order.data.target, IID_Formation);
|
||||
@@ -2106,6 +2121,12 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
if (this.GetStance().respondHoldGround)
|
||||
this.WalkToHeldPosition();
|
||||
|
||||
if (this.shouldCheer)
|
||||
{
|
||||
this.Cheer();
|
||||
this.CallPlayerOwnedEntitiesFunctionInRange("Cheer", [], this.notifyToCheerInRange);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
@@ -3131,23 +3152,29 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
"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;
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
this.StopTimer();
|
||||
this.ResetAnimation();
|
||||
if (this.formationAnimationVariant)
|
||||
this.SetAnimationVariant(this.formationAnimationVariant)
|
||||
else
|
||||
this.SetDefaultAnimationVariant();
|
||||
var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
|
||||
cmpResistance.SetInvulnerability(false);
|
||||
},
|
||||
|
||||
"LosRangeUpdate": function(msg) {
|
||||
if (msg && msg.data && msg.data.added && msg.data.added.length)
|
||||
this.RespondToSightedEntities(msg.data.added);
|
||||
},
|
||||
|
||||
"LosHealRangeUpdate": function(msg) {
|
||||
if (msg && msg.data && msg.data.added && msg.data.added.length)
|
||||
this.RespondToHealableEntities(msg.data.added);
|
||||
},
|
||||
|
||||
"LosAttackRangeUpdate": function(msg) {
|
||||
if (msg && msg.data && msg.data.added && msg.data.added.length && this.GetStance().targetVisibleEnemies)
|
||||
this.AttackEntitiesByPreference(msg.data.added);
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
@@ -3359,6 +3386,33 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
"WALKING": "INDIVIDUAL.WALKING", // reuse the same walking behaviour for animals
|
||||
// only used for domestic animals
|
||||
|
||||
"CHEERING": {
|
||||
"enter": function() {
|
||||
this.SelectAnimation("promotion");
|
||||
this.StartTimer(2800);
|
||||
return false;
|
||||
},
|
||||
|
||||
"leave": function() {
|
||||
this.StopTimer();
|
||||
this.ResetAnimation();
|
||||
},
|
||||
|
||||
"LosRangeUpdate": function(msg) {
|
||||
if (msg && msg.data && msg.data.added && msg.data.added.length)
|
||||
this.RespondToSightedEntities(msg.data.added);
|
||||
},
|
||||
|
||||
"LosAttackRangeUpdate": function(msg) {
|
||||
if (this.template.NaturalBehaviour == "violent" && msg && msg.data && msg.data.added && msg.data.added.length)
|
||||
this.AttackVisibleEntity(msg.data.added);
|
||||
},
|
||||
|
||||
"Timer": function(msg) {
|
||||
this.FinishOrder();
|
||||
},
|
||||
},
|
||||
|
||||
// Reuse the same garrison behaviour for animals.
|
||||
"GARRISON": "INDIVIDUAL.GARRISON",
|
||||
},
|
||||
@@ -3537,8 +3591,8 @@ UnitAI.prototype.OnOwnershipChanged = function(msg)
|
||||
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() });
|
||||
@@ -3855,14 +3909,8 @@ UnitAI.prototype.PushOrder = function(type, data)
|
||||
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);
|
||||
@@ -3984,17 +4032,9 @@ UnitAI.prototype.ReplaceOrder = function(type, data)
|
||||
|
||||
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();
|
||||
@@ -4103,13 +4143,7 @@ UnitAI.prototype.BackToWork = function()
|
||||
}
|
||||
|
||||
// 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() });
|
||||
@@ -5780,12 +5814,9 @@ UnitAI.prototype.Flee = function(target, queued)
|
||||
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)
|
||||
@@ -6465,6 +6496,26 @@ UnitAI.prototype.CallMemberFunction = function(funcname, args, resetWaitingEntit
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Call obj.funcname(args) on UnitAI components owned by player in given range.
|
||||
*/
|
||||
UnitAI.prototype.CallPlayerOwnedEntitiesFunctionInRange = function(funcname, args, range)
|
||||
{
|
||||
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership)
|
||||
return;
|
||||
let owner = cmpOwnership.GetOwner();
|
||||
if (owner == INVALID_PLAYER)
|
||||
return;
|
||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
let nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, [owner], 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.
|
||||
|
||||
Reference in New Issue
Block a user