1
0
forked from 0ad/0ad

Move ship to shoreline when tasking units to garrison inside, fixes #1391

This was SVN commit r14115.
This commit is contained in:
mimo 2013-11-08 23:22:59 +00:00
parent 5dfc3746e3
commit 3a278df3f7
8 changed files with 291 additions and 24 deletions

View File

@ -21,7 +21,12 @@ GarrisonHolder.prototype.Schema =
"</element>" +
"<element name='LoadingRange' a:help='The maximum distance from this holder at which entities are allowed to garrison. Should be about 2.0 for land entities and preferably greater for ships'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>";
"</element>" +
"<optional>" +
"<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" +
"<data type='boolean'/>" +
"</element>" +
"</optional>";
/**
* Initialize GarrisonHolder Component
@ -43,6 +48,21 @@ GarrisonHolder.prototype.GetLoadingRange = function()
return { "max": max, "min": 0 };
};
/**
* Return true if this garrisonHolder can pickup ent
*/
GarrisonHolder.prototype.CanPickup = function(ent)
{
if (!this.template.Pickup || this.IsFull())
return false;
var cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwner)
return false;
var player = cmpOwner.GetOwner();
return IsOwnedByPlayer(player, ent);
};
/**
* Return the list of entities garrisoned inside
*/
@ -69,6 +89,14 @@ GarrisonHolder.prototype.GetCapacity = function()
return ApplyValueModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity);
};
/**
* Return true if this garrisonHolder is full
*/
GarrisonHolder.prototype.IsFull = function()
{
return this.GetGarrisonedEntitiesCount() >= this.GetCapacity();
};
/**
* Get the heal rate with which garrisoned units will be healed
*/

View File

@ -160,6 +160,10 @@ var UnitFsmSpec = {
// ignore
},
"PickupCanceled": function(msg) {
// ignore
},
// Formation handlers:
"FormationLeave": function(msg) {
@ -328,6 +332,31 @@ var UnitFsmSpec = {
}
},
"Order.PickupUnit": function(msg) {
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull())
{
this.FinishOrder();
return;
}
// TODO improve these movements in case of ship: the MoveToTarget of the units brings
// them to the nearest point-on-land from the ship, while the MoveToPoint of the ship
// brings it to the nearest point-on-water from the units, and these can be quite
// different in some cases leading to weird movements. MoveToTarget should update its path
// according to the target movement more often.
if (this.MoveToTarget(this.order.data.target))
{
this.SetNextState("INDIVIDUAL.PICKUP.APPROACHING");
}
else
{
// We are already at the target, or can't move at all
this.StopMoving();
this.SetNextState("INDIVIDUAL.PICKUP.LOADING");
}
},
"Order.Flee": function(msg) {
// We use the distance between the enities to account for ranged attacks
var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance);
@ -622,7 +651,7 @@ var UnitFsmSpec = {
return;
}
if (this.MoveToTarget(this.order.data.target))
if (this.MoveToGarrisonRange(this.order.data.target))
{
this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING");
}
@ -741,26 +770,31 @@ var UnitFsmSpec = {
},
"Order.Garrison": function(msg) {
// TODO: on what should we base this range?
// Check if we are already in range, otherwise walk there
if (!this.CheckTargetRangeExplicit(msg.data.target, 0, 10))
if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder))
{
if (!this.TargetIsAlive(msg.data.target) || !this.CheckTargetVisible(msg.data.target))
// The target was destroyed
this.FinishOrder();
else
// Out of range; move there in formation
this.PushOrderFront("WalkToTargetRange", { "target": msg.data.target, "min": 0, "max": 10 });
this.FinishOrder();
return;
}
// Check if we are already in range, otherwise walk there
if (!this.CheckGarrisonRange(msg.data.target))
{
if (!this.CheckTargetVisible(msg.data.target))
{
this.FinishOrder();
return;
}
else
{
// Out of range; move there in formation
if (this.MoveToGarrisonRange(msg.data.target))
{
this.SetNextState("GARRISON.APPROACHING");
return;
}
}
}
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
// We don't want to rearrange the formation if the individual units are carrying
// out a task and one of the members dies/leaves the formation.
cmpFormation.SetRearrange(false);
cmpFormation.CallMemberFunction("Garrison", [msg.data.target, false]);
this.SetNextStateAlwaysEntering("MEMBER");
this.SetNextState("GARRISON.GARRISONING");
},
"Order.Gather": function(msg) {
@ -991,6 +1025,57 @@ var UnitFsmSpec = {
},
},
"GARRISON":{
"enter": function() {
// If the garrisonholder should pickup, warn it so it can take needed action
var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
{
this.pickup = this.order.data.target; // temporary, deleted in "leave"
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
}
},
"leave": function() {
// If a pickup has been requested and not yet canceled, cancel it
if (this.pickup)
{
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
delete this.pickup;
}
},
"APPROACHING": {
"MoveStarted": function(msg) {
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
cmpFormation.SetRearrange(true);
cmpFormation.MoveMembersIntoFormation(true, true);
},
"MoveCompleted": function(msg) {
this.SetNextState("GARRISONING");
},
},
"GARRISONING": {
"enter": function() {
// If a pickup has been requested, cancel it as it will be requested by members
if (this.pickup)
{
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
delete this.pickup;
}
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
// We don't want to rearrange the formation if the individual units are carrying
// out a task and one of the members dies/leaves the formation.
cmpFormation.SetRearrange(false);
cmpFormation.CallMemberFunction("Garrison", [this.order.data.target, false]);
this.SetNextStateAlwaysEntering("MEMBER");
},
},
},
"FORMING": {
"MoveStarted": function(msg) {
var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
@ -2271,6 +2356,26 @@ var UnitFsmSpec = {
},
"GARRISON": {
"enter": function() {
// If the garrisonholder should pickup, warn it so it can take needed action
var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder);
if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity))
{
this.pickup = this.order.data.target; // temporary, deleted in "leave"
Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity });
}
},
"leave": function() {
// If a pickup has been requested and not yet canceled, cancel it
if (this.pickup)
{
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
delete this.pickup;
}
},
"APPROACHING": {
"enter": function() {
this.SelectAnimation("move");
@ -2279,10 +2384,6 @@ var UnitFsmSpec = {
"MoveCompleted": function() {
this.SetNextState("GARRISONED");
},
"leave": function() {
this.StopTimer();
}
},
"GARRISONED": {
@ -2314,14 +2415,31 @@ var UnitFsmSpec = {
this.SetGathererAnimationOverride();
}
}
// If a pickup has been requested, remove it
if (this.pickup)
{
Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity });
delete this.pickup;
}
return false;
}
}
else
{
// Unable to reach the target, try again
// (or follow if it's a moving target)
// Unable to reach the target, try again (or follow if it is a moving target)
// except if the does not exits anymore or its orders have changed
if (this.pickup)
{
var cmpUnitAI = Engine.QueryInterface(this.pickup, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.HasPickupOrder(this.entity))
{
this.FinishOrder();
return true;
}
}
if (this.MoveToTarget(target))
{
this.SetNextState("APPROACHING");
@ -2401,6 +2519,40 @@ var UnitFsmSpec = {
// Ignore attacks while unpacking
},
},
"PICKUP": {
"APPROACHING": {
"enter": function() {
this.SelectAnimation("move");
},
"MoveCompleted": function() {
this.SetNextState("LOADING");
},
"PickupCanceled": function() {
this.StopMoving();
this.FinishOrder();
},
},
"LOADING": {
"enter": function() {
this.SelectAnimation("idle");
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (!cmpGarrisonHolder || cmpGarrisonHolder.IsFull())
{
this.FinishOrder();
return true;
}
return false;
},
"PickupCanceled": function() {
this.FinishOrder();
},
},
},
},
"ANIMAL": {
@ -2676,6 +2828,39 @@ UnitAI.prototype.OnVisionRangeChanged = function(msg)
this.SetupRangeQueries();
};
UnitAI.prototype.HasPickupOrder = function(entity)
{
for each (var order in this.orderQueue)
if (order.type == "PickupUnit" && order.data.target == entity)
return true;
return false;
};
UnitAI.prototype.OnPickupRequested = function(msg)
{
// First check if we already have such a request
if (this.HasPickupOrder(msg.entity))
return;
// Otherwise, insert the PickUp order after the last forced order
this.PushOrderAfterForced("PickupUnit", { "target": msg.entity });
};
UnitAI.prototype.OnPickupCanceled = function(msg)
{
var cmpUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI);
for (var i = 0; i < this.orderQueue.length; ++i)
{
if (this.orderQueue[i].type == "PickupUnit" && this.orderQueue[i].data.target == msg.entity)
{
if (i == 0)
UnitFsm.ProcessMessage(this, {"type": "PickupCanceled", "data": msg});
else
this.orderQueue.splice(i, 1);
break;
}
}
};
// Wrapper function that sets up the normal, healer, and Gaia range queries.
UnitAI.prototype.SetupRangeQueries = function()
{
@ -2942,6 +3127,31 @@ UnitAI.prototype.PushOrderFront = function(type, data)
}
};
/**
* Insert an order after the last forced order onto the queue
* and after the other orders of the same type
*/
UnitAI.prototype.PushOrderAfterForced = function(type, data)
{
if (!this.order || ((!this.order.data || !this.order.data.force) && this.order.type != type))
{
this.PushOrderFront(type, data);
}
else
{
for (var i = 1; i < this.orderQueue.length; ++i)
{
if (this.orderQueue[i].data && this.orderQueue[i].data.force)
continue;
if (this.orderQueue[i].type == type)
continue;
this.orderQueue.splice(i, 0, {"type": type, "data": data});
return;
}
this.PushOrder(type, data);
}
};
UnitAI.prototype.ReplaceOrder = function(type, data)
{
// Remember the previous work orders to be able to go back to them later if required
@ -3528,6 +3738,20 @@ UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
return cmpUnitMotion.MoveToTargetRange(target, min, max);
};
UnitAI.prototype.MoveToGarrisonRange = function(target)
{
if (!this.CheckTargetVisible(target))
return false;
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
var range = cmpGarrisonHolder.GetLoadingRange();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
};
UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max)
{
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
@ -3589,6 +3813,8 @@ UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max)
UnitAI.prototype.CheckGarrisonRange = function(target)
{
var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
return false;
var range = cmpGarrisonHolder.GetLoadingRange();
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);

View File

@ -11,3 +11,11 @@ Engine.RegisterMessageType("UnitAIStateChanged");
// Message of the form { "to": orderData }.
// sent whenever the unit changes state
Engine.RegisterMessageType("UnitAIOrderDataChanged");
// Message of the form { "entity": entity },
// sent whenever a pickup is requested
Engine.RegisterMessageType("PickupRequested");
// Message of the form { "entity": entity },
// sent whenever a pickup is no more needed
Engine.RegisterMessageType("PickupCanceled");

View File

@ -37,6 +37,7 @@
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>
<Pickup>true</Pickup>
</GarrisonHolder>
<Health>
<Max>800</Max>

View File

@ -26,6 +26,7 @@
<List datatype="tokens">Support Infantry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>
<Pickup>true</Pickup>
</GarrisonHolder>
<Identity>
<GenericName>Fishing Boat</GenericName>

View File

@ -18,6 +18,7 @@
<List datatype="tokens">Support Infantry Cavalry</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>
<Pickup>true</Pickup>
</GarrisonHolder>
<Health>
<Max>400</Max>

View File

@ -45,6 +45,7 @@
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>
<Pickup>true</Pickup>
</GarrisonHolder>
<Health>
<Max>2000</Max>

View File

@ -37,6 +37,7 @@
<List datatype="tokens">Support Infantry Cavalry Siege</List>
<BuffHeal>1</BuffHeal>
<LoadingRange>10</LoadingRange>
<Pickup>true</Pickup>
</GarrisonHolder>
<Health>
<Max>1400</Max>