1
1
forked from 0ad/0ad

Add the Engine code for turrets + use them to get units on walls. The scripted TurretHolder isn't included due to lack of usability for now. Refs #2577

This was SVN commit r15246.
This commit is contained in:
sanderd17 2014-05-30 14:46:06 +00:00
parent d05bd656e7
commit cfec28e553
30 changed files with 627 additions and 17 deletions

View File

@ -227,6 +227,16 @@ Attack.prototype.CanAttack = function(target)
if (cmpFormation)
return true;
var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
return false;
// Check if the relative height difference is larger than the attack range
// If the relative height is bigger, it means they will never be able to
// reach each other, no matter how close they come.
var heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return undefined;
@ -235,6 +245,9 @@ Attack.prototype.CanAttack = function(target)
for each (var type in this.GetAttackTypes())
{
if (heightDiff > this.GetRange(type).max)
continue;
var canAttack = true;
var restrictedClasses = this.GetRestrictedClasses(type);

View File

@ -29,6 +29,26 @@ GarrisonHolder.prototype.Schema =
"<element name='Pickup' a:help='This garrisonHolder will move to pick up units to be garrisoned'>" +
"<data type='boolean'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='VisibleGarrisonPoints' a:help='Points that will be used to visibly garrison a unit'>" +
"<zeroOrMore>" +
"<element a:help='Element containing the offset coordinates'>" +
"<anyName/>" +
"<interleave>" +
"<element name='X'>" +
"<data type='decimal'/>" +
"</element>" +
"<element name='Y'>" +
"<data type='decimal'/>" +
"</element>" +
"<element name='Z'>" +
"<data type='decimal'/>" +
"</element>" +
"</interleave>" +
"</element>" +
"</zeroOrMore>" +
"</element>" +
"</optional>";
/**
@ -40,6 +60,18 @@ GarrisonHolder.prototype.Init = function()
this.entities = [];
this.timer = undefined;
this.allowGarrisoning = {};
this.visibleGarrisonPoints = [];
if (this.template.VisibleGarrisonPoints)
{
for each (var offset in this.template.VisibleGarrisonPoints)
{
var o = {};
o.x = +offset.X;
o.y = +offset.Y;
o.z = +offset.Z;
this.visibleGarrisonPoints.push({"offset":o, "entity": null});
}
}
};
/**
@ -206,7 +238,21 @@ GarrisonHolder.prototype.Garrison = function(entity)
if (!this.PerformGarrison(entity))
return false;
cmpPosition.MoveOutOfWorld();
var visiblyGarrisoned = false;
for (var vgp of this.visibleGarrisonPoints)
{
if (vgp.entity)
continue;
vgp.entity = entity;
cmpPosition.SetTurretParent(this.entity, vgp.offset);
var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI)
if (cmpUnitAI)
cmpUnitAI.SetTurret(true);
visiblyGarrisoned = true;
break;
}
if (!visiblyGarrisoned)
cmpPosition.MoveOutOfWorld();
return true;
};
@ -291,8 +337,21 @@ GarrisonHolder.prototype.Eject = function(entity, forced)
}
}
var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
this.entities.splice(entityIndex, 1);
for (var vgp of this.visibleGarrisonPoints)
{
if (vgp.entity != entity)
continue;
cmpNewPosition.SetTurretParent(INVALID_ENTITY, new Vector3D());
vgp.entity = null;
var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI)
if (cmpUnitAI)
cmpUnitAI.SetTurret(false);
break;
}
var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Ungarrison();
@ -306,8 +365,8 @@ GarrisonHolder.prototype.Eject = function(entity, forced)
cmpAura.RemoveGarrisonBonus(this.entity);
var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
cmpNewPosition.SetHeightOffset(0);
// TODO: what direction should they face in?
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [], "removed": [entity] });
@ -579,8 +638,8 @@ GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg)
var entityIndex = this.entities.indexOf(msg.entity);
if (entityIndex != -1)
{
this.entities[entityIndex] = msg.newentity;
Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, { "added" : [msg.newentity], "removed": [msg.entity] });
this.Eject(msg.entity);
this.Garrison(msg.newentity);
}
};

View File

@ -179,7 +179,7 @@ UnitAI.prototype.UnitFsmSpec = {
// Called when being told to walk as part of a formation
"Order.FormationWalk": function(msg) {
// Let players move captured domestic animals around
if (this.IsAnimal() && !this.IsDomestic())
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
{
this.FinishOrder();
return;
@ -207,7 +207,7 @@ UnitAI.prototype.UnitFsmSpec = {
"Order.LeaveFoundation": function(msg) {
// If foundation is not ally of entity, or if entity is unpacked siege,
// ignore the order
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack())
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack() || this.IsTurret())
{
this.FinishOrder();
return;
@ -252,7 +252,7 @@ UnitAI.prototype.UnitFsmSpec = {
"Order.Walk": function(msg) {
// Let players move captured domestic animals around
if (this.IsAnimal() && !this.IsDomestic())
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
{
this.FinishOrder();
return;
@ -281,7 +281,7 @@ UnitAI.prototype.UnitFsmSpec = {
"Order.WalkAndFight": function(msg) {
// Let players move captured domestic animals around
if (this.IsAnimal() && !this.IsDomestic())
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
{
this.FinishOrder();
return;
@ -308,7 +308,7 @@ UnitAI.prototype.UnitFsmSpec = {
"Order.WalkToTarget": function(msg) {
// Let players move captured domestic animals around
if (this.IsAnimal() && !this.IsDomestic())
if (this.IsAnimal() && !this.IsDomestic() || this.IsTurret())
{
this.FinishOrder();
return;
@ -489,7 +489,7 @@ UnitAI.prototype.UnitFsmSpec = {
// If we can't reach the target, but are standing ground, then abandon this attack order.
// Unless we're hunting, that's a special case where we should continue attacking our target.
if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting)
if (this.GetStance().respondStandGround && !this.order.data.force && !this.order.data.hunting || this.IsTurret())
{
this.FinishOrder();
return;
@ -688,6 +688,11 @@ UnitAI.prototype.UnitFsmSpec = {
},
"Order.Garrison": function(msg) {
if (this.IsTurret())
{
this.FinishOrder();
return;
}
// For packable units:
// 1. If packed, we can move to the garrison target.
// 2. If unpacked, we first need to pack, then follow case 1.
@ -1245,7 +1250,7 @@ UnitAI.prototype.UnitFsmSpec = {
"Order.LeaveFoundation": function(msg) {
// If foundation is not ally of entity, or if entity is unpacked siege,
// ignore the order
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack())
if (!IsOwnedByAllyOfEntity(this.entity, msg.data.target) || this.IsPacking() || this.CanPack() || this.IsTurret())
{
this.FinishOrder();
return;
@ -2836,6 +2841,9 @@ UnitAI.prototype.UnitFsmSpec = {
delete this.pickup;
}
if (this.IsTurret())
this.SetNextState("IDLE");
return false;
}
}
@ -3135,6 +3143,28 @@ UnitAI.prototype.Init = function()
this.lastHealed = undefined;
this.SetStance(this.template.DefaultStance);
this.SetTurret(false);
};
/**
* Set the flag to true to use this unit as a turret
* This means no moving is allowed, only turning
*/
UnitAI.prototype.SetTurret = function(flag)
{
this.isTurret = flag;
if (flag == false && this.oldStance)
this.SetStance(this.oldStance);
else if (flag == true)
{
this.OldStance = this.GetStance();
this.SetStance("standground");
}
};
UnitAI.prototype.IsTurret = function()
{
return this.isTurret;
};
UnitAI.prototype.ReactsToAlert = function(level)
@ -4168,7 +4198,7 @@ UnitAI.prototype.MoveToTarget = function(target)
UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
{
if (!this.CheckTargetVisible(target))
if (!this.CheckTargetVisible(target) || this.IsTurret())
return false;
var cmpRanged = Engine.QueryInterface(this.entity, iid);
@ -4599,6 +4629,9 @@ UnitAI.prototype.ShouldAbandonChase = function(target, force, iid, type)
*/
UnitAI.prototype.ShouldChaseTargetedEntity = function(target, force)
{
if (this.IsTurret())
return false;
// TODO: use special stances instead?
var cmpPack = Engine.QueryInterface(this.entity, IID_Pack);
if (cmpPack)
@ -5530,6 +5563,8 @@ UnitAI.prototype.CanGarrison = function(target)
UnitAI.prototype.CanGather = function(target)
{
if (this.IsTurret())
return false;
// The target must be a valid resource supply.
var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
if (!cmpResourceSupply)
@ -5602,6 +5637,8 @@ UnitAI.prototype.CanHeal = function(target)
UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
{
if (this.IsTurret())
return false;
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
@ -5636,6 +5673,8 @@ UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
UnitAI.prototype.CanTrade = function(target)
{
if (this.IsTurret())
return false;
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())
@ -5651,6 +5690,8 @@ UnitAI.prototype.CanTrade = function(target)
UnitAI.prototype.CanRepair = function(target)
{
if (this.IsTurret())
return false;
// Formation controllers should always respond to commands
// (then the individual units can make up their own minds)
if (this.IsFormationController())

View File

@ -4,6 +4,25 @@
<Square width="38.5" depth="8.5"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.3</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>brit</Civ>
<SelectionGroupName>structures/celt_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,19 @@
<Square width="26.5" depth="8.5"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Health>
<Max>3000</Max>
</Health>

View File

@ -4,6 +4,25 @@
<Square width="45.0" depth="11.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>12.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>12.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>12.5</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>12.5</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>12.5</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>cart</Civ>
<SelectionGroupName>structures/cart_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,19 @@
<Square width="31.0" depth="11.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>12.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>12.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>12.5</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>cart</Civ>
<SelectionGroupName>structures/cart_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="38.5" depth="8.5"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.3</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>celt</Civ>
<SelectionGroupName>structures/celt_wallset_stone</SelectionGroupName>

View File

@ -7,6 +7,19 @@
<Health>
<Max>3000</Max>
</Health>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>celt</Civ>
<SelectionGroupName>structures/celt_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="38.5" depth="8.5"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.3</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>gaul</Civ>
<SelectionGroupName>structures/celt_wallset_stone</SelectionGroupName>

View File

@ -7,6 +7,19 @@
<Health>
<Max>3000</Max>
</Health>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.3</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.3</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.3</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>gaul</Civ>
<SelectionGroupName>structures/celt_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="37" depth="9"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.0</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.0</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.0</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.0</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.0</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>iber</Civ>
<SelectionGroupName>structures/iber_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,19 @@
<Square width="25" depth="9"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.0</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.0</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.0</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Health>
<Max>3000</Max>
</Health>

View File

@ -10,6 +10,25 @@
<Square width="36.5" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.5</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.5</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.5</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>maur</Civ>
<SelectionGroupName>structures/maur_wallset_stone</SelectionGroupName>

View File

@ -10,6 +10,19 @@
<Square width="26.5" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.5</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>maur</Civ>
<SelectionGroupName>structures/maur_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="38.5" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>10.6</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>10.6</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>10.6</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>10.6</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>10.6</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>pers</Civ>
<SelectionGroupName>structures/pers_wallset_stone</SelectionGroupName>
@ -19,4 +38,4 @@
<WallPiece>
<Length>37.0</Length>
</WallPiece>
</Entity>
</Entity>

View File

@ -4,6 +4,19 @@
<Square width="26.0" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>10.6</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>10.6</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>10.6</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>pers</Civ>
<SelectionGroupName>structures/pers_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="40" depth="10.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.7</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>9.7</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>9.7</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>9.7</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>9.7</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>ptol</Civ>
<SelectionGroupName>structures/ptol_wallset_stone</SelectionGroupName>
@ -19,4 +38,4 @@
<WallPiece>
<Length>38.0</Length>
</WallPiece>
</Entity>
</Entity>

View File

@ -4,6 +4,19 @@
<Square width="28.0" depth="10.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>9.7</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>9.7</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>9.7</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>ptol</Civ>
<SelectionGroupName>structures/mace_wallset_stone</SelectionGroupName>

View File

@ -23,6 +23,25 @@
<Square width="40.0" depth="8.0"/>
<Height>7.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>5.7</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>5.7</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>5.7</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>5.7</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>5.7</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Health>
<Max>2000</Max>
</Health>

View File

@ -26,6 +26,19 @@
<Health>
<Max>1500</Max>
</Health>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>5.7</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>5.7</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>5.7</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>rome</Civ>
<SelectionGroupName>structures/rome_wallset_siege</SelectionGroupName>

View File

@ -4,6 +4,25 @@
<Square width="38.5" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>8.9</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>8.9</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>8.9</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>8.9</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>8.9</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Civ>rome</Civ>
<SelectionGroupName>structures/rome_wallset_stone</SelectionGroupName>

View File

@ -4,6 +4,19 @@
<Square width="26.5" depth="9.0"/>
<Height>9.0</Height>
</Footprint>
<GarrisonHolder>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>8.9</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>8.9</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>8.9</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Health>
<Max>3000</Max>
</Health>

View File

@ -9,6 +9,31 @@
<Health>
<SpawnEntityOnDeath>rubble/rubble_stone_wall_long</SpawnEntityOnDeath>
</Health>
<GarrisonHolder>
<Max>5</Max>
<List datatype="tokens">Infantry</List>
<EjectHealth>0.1</EjectHealth>
<EjectClassesOnDestroy datatype="tokens">Infantry</EjectClassesOnDestroy>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>11.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>8</X><Y>11.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-8</X><Y>11.5</Y><Z>0</Z>
</Archer3>
<Archer4>
<X>4</X><Y>11.5</Y><Z>0</Z>
</Archer4>
<Archer5>
<X>-4</X><Y>11.5</Y><Z>0</Z>
</Archer5>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Identity>
<Classes datatype="tokens">LongWall</Classes>
<Tooltip>Long wall segments can be converted to gates.</Tooltip>

View File

@ -6,6 +6,25 @@
<stone>20</stone>
</Resources>
</Cost>
<GarrisonHolder>
<Max>3</Max>
<List datatype="tokens">Infantry</List>
<EjectHealth>0.1</EjectHealth>
<EjectClassesOnDestroy datatype="tokens">Infantry</EjectClassesOnDestroy>
<BuffHeal>0</BuffHeal>
<LoadingRange>2</LoadingRange>
<VisibleGarrisonPoints>
<Archer1>
<X>0</X><Y>11.5</Y><Z>0</Z>
</Archer1>
<Archer2>
<X>4</X><Y>11.5</Y><Z>0</Z>
</Archer2>
<Archer3>
<X>-4</X><Y>11.5</Y><Z>0</Z>
</Archer3>
</VisibleGarrisonPoints>
</GarrisonHolder>
<Health>
<Max>3000</Max>
<SpawnEntityOnDeath>rubble/rubble_stone_wall_medium</SpawnEntityOnDeath>

View File

@ -40,7 +40,7 @@
<Height>8.0</Height>
</Footprint>
<GarrisonHolder>
<Max>5</Max>
<Max>2</Max>
<EjectHealth>0.1</EjectHealth>
<EjectClassesOnDestroy datatype="tokens">Unit</EjectClassesOnDestroy>
<List datatype="tokens">Support Infantry</List>

View File

@ -76,10 +76,16 @@ public:
entity_pos_t m_Y, m_LastYDifference; // either the relative or the absolute Y coordinate
bool m_RelativeToGround; // whether m_Y is relative to terrain/water plane, or an absolute height
// when the entity is a turret, only m_RotY is used, and this is the rotation
// relative to the parent entity
entity_angle_t m_RotX, m_RotY, m_RotZ;
player_id_t m_Territory;
entity_id_t m_TurretParent;
CFixedVector3D m_TurretPosition;
std::set<entity_id_t> m_Turrets;
// not serialized:
float m_InterpolatedRotX, m_InterpolatedRotY, m_InterpolatedRotZ;
float m_LastInterpolatedRotX, m_LastInterpolatedRotZ; // not serialized
@ -142,6 +148,8 @@ public:
m_Territory = INVALID_PLAYER;
m_NeedInitialXZRotation = false;
m_TurretParent = INVALID_ENTITY;
m_TurretPosition = CFixedVector3D();
}
virtual void Deinit()
@ -223,6 +231,60 @@ public:
UpdateXZRotation();
}
virtual void UpdateTurretPosition()
{
if (m_TurretParent == INVALID_ENTITY)
return;
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (!cmpPosition)
{
LOGERROR(L"Turret with parent without position component");
return;
}
if (!cmpPosition->IsInWorld())
MoveOutOfWorld();
else
{
CFixedVector2D rotatedPosition = CFixedVector2D(m_TurretPosition.X, m_TurretPosition.Z);
rotatedPosition = rotatedPosition.Rotate(cmpPosition->GetRotation().Y);
CFixedVector2D rootPosition = cmpPosition->GetPosition2D();
entity_pos_t x = rootPosition.X + rotatedPosition.X;
entity_pos_t z = rootPosition.Y + rotatedPosition.Y;
if (!m_InWorld || m_X != x || m_Z != z)
MoveTo(x, z);
entity_pos_t y = cmpPosition->GetHeightOffset() + m_TurretPosition.Y;
if (!m_InWorld || GetHeightOffset() != y)
SetHeightOffset(y);
m_InWorld = true;
}
}
virtual std::set<entity_id_t>* GetTurrets()
{
return &m_Turrets;
}
virtual void SetTurretParent(entity_id_t id, CFixedVector3D offset)
{
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (cmpPosition)
cmpPosition->GetTurrets()->erase(GetEntityId());
}
m_TurretParent = id;
m_TurretPosition = offset;
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (cmpPosition)
cmpPosition->GetTurrets()->insert(GetEntityId());
}
UpdateTurretPosition();
}
virtual bool IsInWorld()
{
return m_InWorld;
@ -255,7 +317,6 @@ public:
{
m_X = x;
m_Z = z;
m_RotY = ry;
if (!m_InWorld)
{
@ -265,7 +326,8 @@ public:
m_LastYDifference = entity_pos_t::Zero();
}
AdvertisePositionChanges();
// TurnTo will advertise the position changes
TurnTo(ry);
}
virtual void JumpTo(entity_pos_t x, entity_pos_t z)
@ -405,6 +467,12 @@ public:
virtual void TurnTo(entity_angle_t y)
{
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (cmpPosition)
y -= cmpPosition->GetRotation().Y;
}
m_RotY = y;
AdvertisePositionChanges();
@ -412,6 +480,12 @@ public:
virtual void SetYRotation(entity_angle_t y)
{
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (cmpPosition)
y -= cmpPosition->GetRotation().Y;
}
m_RotY = y;
m_InterpolatedRotY = m_RotY.ToFloat();
@ -444,6 +518,13 @@ public:
virtual CFixedVector3D GetRotation()
{
entity_angle_t y = m_RotY;
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (cmpPosition)
y += cmpPosition->GetRotation().Y;
}
return CFixedVector3D(m_RotX, m_RotY, m_RotZ);
}
@ -474,6 +555,32 @@ public:
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating)
{
if (m_TurretParent != INVALID_ENTITY)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_TurretParent);
if (!cmpPosition)
{
LOGERROR(L"Turret with parent without position component");
CMatrix3D m;
m.SetIdentity();
return m;
}
if (!cmpPosition->IsInWorld())
{
LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on turret entity when IsInWorld is false");
CMatrix3D m;
m.SetIdentity();
return m;
}
else
{
CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset, forceFloating);
CMatrix3D ownTransformation = CMatrix3D();
ownTransformation.SetYRotation(m_InterpolatedRotY);
ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat());
return parentTransformMatrix * ownTransformation;
}
}
if (!m_InWorld)
{
LOGERROR(L"CCmpPosition::GetInterpolatedTransform called on entity when IsInWorld is false");
@ -562,6 +669,7 @@ public:
}
case MT_TurnStart:
{
m_LastInterpolatedRotX = m_InterpolatedRotX;
m_LastInterpolatedRotZ = m_InterpolatedRotZ;
@ -606,6 +714,12 @@ public:
private:
void AdvertisePositionChanges()
{
for (std::set<entity_id_t>::const_iterator it = m_Turrets.begin(); it != m_Turrets.end(); ++it)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
if (cmpPosition)
cmpPosition->UpdateTurretPosition();
}
if (m_InWorld)
{
CMessagePositionChanged msg(GetEntityId(), true, m_X, m_Z, m_RotY);

View File

@ -22,6 +22,7 @@
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Position)
DEFINE_INTERFACE_METHOD_2("SetTurretParent", void, ICmpPosition, SetTurretParent, entity_id_t, CFixedVector3D)
DEFINE_INTERFACE_METHOD_0("IsInWorld", bool, ICmpPosition, IsInWorld)
DEFINE_INTERFACE_METHOD_0("MoveOutOfWorld", void, ICmpPosition, MoveOutOfWorld)
DEFINE_INTERFACE_METHOD_2("MoveTo", void, ICmpPosition, MoveTo, entity_pos_t, entity_pos_t)

View File

@ -58,6 +58,21 @@ class CMatrix3D;
class ICmpPosition : public IComponent
{
public:
/**
* Set this as a turret of an other entity
*/
virtual void SetTurretParent(entity_id_t parent, CFixedVector3D offset) = 0;
/**
* Has to be called to update the simulation position of the turret
*/
virtual void UpdateTurretPosition() = 0;
/**
* Get the list of turrets to read or edit
*/
virtual std::set<entity_id_t>* GetTurrets() = 0;
/**
* Returns true if the entity currently exists at a defined position in the world.
*/

View File

@ -40,6 +40,9 @@ class MockPosition : public ICmpPosition
public:
DEFAULT_MOCK_COMPONENT()
virtual void SetTurretParent(entity_id_t UNUSED(id), CFixedVector3D UNUSED(pos)) {}
virtual void UpdateTurretPosition() {}
virtual std::set<entity_id_t>* GetTurrets() { return NULL; }
virtual bool IsInWorld() { return true; }
virtual void MoveOutOfWorld() { }
virtual void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { }