1
0
forked from 0ad/0ad

Gates. Adds UI buttons, replaces wall section with a gate, defines and create obstruction shapes, detects friendly units and eventually disable door's "block movement" flag, allows us to lock / unlock the door by disabling "block pathfinding" flag. Needs icons, play sound, animations. Fixes #1385, refs #619.

This was SVN commit r12081.
This commit is contained in:
Badmadblacksad 2012-07-08 16:25:33 +00:00
parent 9a3a6b9c2f
commit 2c5933912c
23 changed files with 703 additions and 50 deletions

View File

@ -1626,6 +1626,27 @@ function performStance(entity, stanceName)
}
}
// Lock / Unlock the gate
function lockGate(lock)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "lock-gate",
"entities": selection,
"lock": lock,
});
}
// Transform a wall to a gate
function transformWallToGate()
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "wall-to-gate",
"entities": selection,
});
}
// Set the camera to follow the given unit
function setCameraFollow(entity)
{

View File

@ -859,6 +859,19 @@
</object>
</object>
<object name="unitGatePanel"
size="10 12 100% 100%"
>
<object size="0 0 100% 100%">
<repeat count="1">
<object name="unitGateButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
<object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
</object>
</repeat>
</object>
</object>
</object> <!-- END OF UNIT COMMANDS -->
</object><!-- END OF BOTTOM PANEL -->

View File

@ -8,6 +8,7 @@ const RESEARCH = "Research";
const CONSTRUCTION = "Construction";
const COMMAND = "Command";
const STANCE = "Stance";
const GATE = "Gate";
// Constants
const COMMANDS_PANEL_WIDTH = 228;
@ -24,10 +25,10 @@ const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
const BARTER_ACTIONS = ["Sell", "Buy"];
// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0};
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0};
// Unit panels are panels with row(s) of buttons
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command"];
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command", "Gate"];
// Indexes of resources to sell and buy on barter panel
var g_barterSell = 0;
@ -203,6 +204,11 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
numberOfItems = 6;
break;
case GATE:
if(numberOfItems > 1)
numberOfItems = 1;
break;
default:
break;
}
@ -319,6 +325,7 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
case STANCE:
case FORMATION:
case GATE:
var tooltip = toTitleCase(item);
break;
@ -492,12 +499,16 @@ function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)
icon.sprite = "stretched:session/icons/" + item.icon;
}
else if (guiName == GATE)
{
icon.sprite = "stretched:session/icons/production.png";
}
else if (template.icon)
{
var grayscale = "";
button.enabled = true;
if (guiName != SELECTION && template.requiredTechnology && !Engine.GuiInterfaceCall("IsTechnologyResearched", template.requiredTechnology))
if (0 && guiName != SELECTION && template.requiredTechnology && !Engine.GuiInterfaceCall("IsTechnologyResearched", template.requiredTechnology))
{
button.enabled = false;
var techName = getEntityName(GetTechnologyData(template.requiredTechnology));
@ -793,6 +804,26 @@ function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s
setupUnitTradingPanel(entState, selection);
}
if(!entState.foundation && (entState.gate || hasClass(entState, "StoneWall") && !hasClass(entState, "Tower")))
{
if (entState.gate)
{
var action = entState.gate.locked ? "Unlock gate": "Lock gate";
setupUnitPanel(GATE, usedPanels, entState, [action],
function (item) { lockGate(!entState.gate.locked); } );
}
else // Wall
{
var templateData = GetTemplateData(entState.template);
// Only allow long walls section to be transformed to gates
if (templateData.wallPiece.length > 20) //TODO : increase
{
setupUnitPanel(GATE, usedPanels, entState, ["Create a gate"],
function (item) { transformWallToGate(); } );
}
}
}
supplementalDetailsPanel.hidden = false;
commandsPanel.hidden = false;
}

View File

@ -137,7 +137,7 @@ Foundation.prototype.Build = function(builderEnt, work)
// but we've temporarily allowed units to walk all over it
// (via CCmpTemplateManager). Now we need to remove that temporary
// blocker-disabling, so that we'll perform standard unit blocking instead.
cmpObstruction.SetDisableBlockMovementPathfinding(false);
cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
}
this.committed = true;

View File

@ -0,0 +1,176 @@
function Gate() {}
Gate.prototype.Schema =
"<element name='Radius'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>";
/**
* Initialize Gate Component
*/
Gate.prototype.Init = function()
{
this.allyUnits = [];
};
Gate.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != -1)
{
this.SetupRangeQuery(msg.to);
this.UnlockGate();
this.CloseGate();
}
};
/**
* Cleanup on destroy
*/
Gate.prototype.OnDestroy = function()
{
// Clean up range query
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (this.allyUnitsQuery)
cmpRangeManager.DestroyActiveQuery(this.allyUnitsQuery);
};
/**
* Setup the Range Query to detect units coming in & out of range
*/
Gate.prototype.SetupRangeQuery = function(owner)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (this.allyUnitsQuery)
cmpRangeManager.DestroyActiveQuery(this.allyUnitsQuery);
var allyPlayers = []
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player);
var numPlayers = cmpPlayerManager.GetNumPlayers();
for (var i = 1; i < numPlayers; ++i)
{ // Exclude gaia, allies, and self
// TODO: How to handle neutral players - Special query to attack military only?
if (cmpPlayer.IsAlly(i))
allyPlayers.push(i);
}
if (this.GetRadius() > 0)
{
var range = this.GetRadius();
this.allyUnitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, 0, range, allyPlayers, 0, cmpRangeManager.GetEntityFlagMask("normal"));
cmpRangeManager.EnableActiveQuery(this.allyUnitsQuery);
}
};
/**
* Called when units enter or leave range
*/
Gate.prototype.OnRangeUpdate = function(msg)
{
if (msg.tag != this.allyUnitsQuery)
return;
if (msg.added.length > 0)
{
for each (var entity in msg.added)
{
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
if(cmpIdentity)
{
var classes = cmpIdentity.GetClassesList();
if(classes.indexOf("Unit") != -1)
this.allyUnits.push(entity);
}
}
}
if (msg.removed.length > 0)
{
for each (var entity in msg.removed)
{
this.allyUnits.splice(this.allyUnits.indexOf(entity), 1);
}
}
this.ManeuverGate();
};
/**
* Get the range query radius
*/
Gate.prototype.GetRadius = function()
{
return +this.template.Radius;
};
/**
* Open or close the gate
*/
Gate.prototype.ManeuverGate = function()
{
if (this.opened == true )
{
if (this.allyUnits.length == 0)
{
this.CloseGate();
}
}
else
{
if (this.allyUnits.length > 0)
{
this.OpenGate();
}
}
};
Gate.prototype.IsLocked = function()
{
return this.locked;
};
Gate.prototype.LockGate = function()
{
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return;
cmpObstruction.SetDisableBlockMovementPathfinding(false, false, 0);
this.locked = true;
this.opened = false;
};
Gate.prototype.UnlockGate = function()
{
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return;
cmpObstruction.SetDisableBlockMovementPathfinding(false, true, 0);
this.locked = false;
this.opened = false;
if (this.allyUnits.length > 0)
this.OpenGate();
};
Gate.prototype.OpenGate = function()
{
if (this.locked)
return;
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return;
cmpObstruction.SetDisableBlockMovementPathfinding(true, true, 0);
this.opened = true;
};
Gate.prototype.CloseGate = function()
{
if (this.locked)
return;
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return;
cmpObstruction.SetDisableBlockMovementPathfinding(false, true, 0);
this.opened = false;
};
Engine.RegisterComponentType(IID_Gate, "Gate", Gate);

View File

@ -289,6 +289,14 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
"orders": cmpUnitAI.GetOrders(),
};
}
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
ret.gate = {
"locked": cmpGate.IsLocked(),
};
}
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
{
@ -408,11 +416,15 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
{
ret.obstruction.shape.type = "cluster";
}
}
if (template.Health)

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Gate");

View File

@ -351,6 +351,29 @@ function ProcessCommand(player, cmd)
}
break;
case "wall-to-gate":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
TryTransformWallToGate(ent, cmpPlayer);
}
break;
case "lock-gate":
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
for each (var ent in entities)
{
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
if (cmd.lock)
cmpGate.LockGate();
else
cmpGate.UnlockGate();
}
}
break;
case "setup-trade-route":
for each (var ent in cmd.entities)
{
@ -1071,6 +1094,67 @@ function FilterEntityList(entities, player, controlAll)
return entities.filter(function(ent) { return CanControlUnit(ent, player, controlAll);} );
}
/**
* Try to transform a wall to a gate
*/
function TryTransformWallToGate(ent, cmpPlayer)
{
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (!cmpIdentity)
return;
var civ = cmpIdentity.GetCiv();
var template = "structures/" + civ + "_wall_gate";
var gate = Engine.AddEntity(template);
var cmpCost = Engine.QueryInterface(gate, IID_Cost);
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
{
if (g_DebugCommands)
{
warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd));
}
Engine.DestroyEntity(gate);
return;
}
ReplaceBuildingWith(ent, gate);
}
/**
* Unconditionally replace a building with another one
*/
function ReplaceBuildingWith(ent, building)
{
// Move the building to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
var pos = cmpPosition.GetPosition2D();
cmpBuildingPosition.JumpTo(pos.x, pos.y);
var rot = cmpPosition.GetRotation();
cmpBuildingPosition.SetYRotation(rot.y);
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
// Copy ownership
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
// Copy control groups
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
PlaySound("constructed", building);
Engine.PostMessage(ent, MT_ConstructionFinished,
{ "entity": ent, "newentity": building });
Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
Engine.DestroyEntity(ent);
}
Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
Engine.RegisterGlobal("ProcessCommand", ProcessCommand);

View File

@ -13,7 +13,20 @@
<History>The Athenian city wall was pierced by numerous gates and posterns of various sizes and importance. The "Sacred Gate" was the gate on the road to Eleusis. Another gate was the Dipylon Gate, whose name literally means "Double Gate."</History>
</Identity>
<Obstruction>
<Static width="38.0" depth="6.5"/>
<Cluster>
<Right width="12" depth="6.5">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="12" depth="6.5">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="14.0" depth="6.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/hellenes/wall_gate.xml</Actor>

View File

@ -20,7 +20,20 @@
<History>The Romans called this wall 'Murus Gallicus'. Translated, it means 'Gaulish wall'. It was extremely resistant to assault by battering ram. Julius Caesar described a type of wood and stone wall, known as a Murus Gallicus, in his account of the Gallic Wars. These walls were made of a stone wall filled with rubble, with wooden logs inside for stability. Caesar noted how the flexibility of the wood added to the strength of the fort in case of battering ram attack.</History>
</Identity>
<Obstruction>
<Static width="25.0" depth="8.0"/>
<Cluster>
<Right width="6" depth="8">
<PosX>9.5</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="6" depth="8">
<PosX>-9.5</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="13.0" depth="8">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/celts/wall_gate.xml</Actor>

View File

@ -20,7 +20,20 @@
<History>The Romans called this wall 'Murus Gallicus'. Translated, it means 'Gaulish wall'. It was extremely resistant to assault by battering ram. Julius Caesar described a type of wood and stone wall, known as a Murus Gallicus, in his account of the Gallic Wars. These walls were made of a stone wall filled with rubble, with wooden logs inside for stability. Caesar noted how the flexibility of the wood added to the strength of the fort in case of battering ram attack.</History>
</Identity>
<Obstruction>
<Static width="25.0" depth="8.0"/>
<Cluster>
<Right width="5" depth="8">
<PosX>10</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="5" depth="8">
<PosX>-10</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="15.0" depth="8">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/celts/wall_gate.xml</Actor>

View File

@ -20,7 +20,20 @@
<History>The Romans called this wall 'Murus Gallicus'. Translated, it means 'Gaulish wall'. It was extremely resistant to assault by battering ram. Julius Caesar described a type of wood and stone wall, known as a Murus Gallicus, in his account of the Gallic Wars. These walls were made of a stone wall filled with rubble, with wooden logs inside for stability. Caesar noted how the flexibility of the wood added to the strength of the fort in case of battering ram attack.</History>
</Identity>
<Obstruction>
<Static width="25.0" depth="8.0"/>
<Cluster>
<Right width="7" depth="8">
<PosX>9</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="7" depth="8">
<PosX>-9</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="11.0" depth="8">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/celts/wall_gate.xml</Actor>

View File

@ -13,7 +13,20 @@
<History>(Insert history)</History>
</Identity>
<Obstruction>
<Static width="38.0" depth="6.5"/>
<Cluster>
<Right width="12" depth="6.5">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="12" depth="6.5">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="14.0" depth="6.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/hellenes/wall_gate.xml</Actor>

View File

@ -10,7 +10,20 @@
<History>One of the central attributes of the Iberians civ is that it was a highly defensive one that constantly gave the Carthaginians trouble in their bid to conquer the peninsula (which they never really did) and took the Romans another 200 years to subdue, along with incredibly large cumulative loss of Roman soldier's lives. This doubled gate has been found incorporated into walls surrounding Iberian villages, Oppidum, and fortresses, Castros. It presents rather formidable aspects with its 4 towers, 2 gates, and a courtyard-like interior wherein enemy forces could become entrapped between the two gates, combined with a monolithically strong stone structure. The concept comes from archeologist and paleontologist descriptions of the remains of such gates at various locations scattered about the Iberian Peninsula.</History>
</Identity>
<Obstruction>
<Static width="36.0" depth="8.5"/>
<Cluster>
<Right width="10" depth="8.5">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="10" depth="8.5">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="16.0" depth="8.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/iberians/wall_gate.xml</Actor>

View File

@ -13,7 +13,20 @@
<History>(Insert history)</History>
</Identity>
<Obstruction>
<Static width="38.0" depth="6.5"/>
<Cluster>
<Right width="12" depth="6.5">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="12" depth="6.5">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="14.0" depth="6.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/hellenes/wall_gate.xml</Actor>

View File

@ -16,7 +16,20 @@
<History>Persepolis, the Persian royal capital, was constructed on an immense man-made terrace with strong defensive walls.</History>
</Identity>
<Obstruction>
<Static width="37.0" depth="7.0"/>
<Cluster>
<Right width="11" depth="7.0">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="11" depth="7.0">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="15.0" depth="7.0">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/persians/wall_gate.xml</Actor>

View File

@ -28,7 +28,20 @@
<History>(Insert History Here)</History>
</Identity>
<Obstruction>
<Static width="37.0" depth="5.0"/>
<Cluster>
<Right width="12" depth="5">
<PosX>12.5</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="12" depth="5">
<PosX>-12.5</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="13.0" depth="5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<TerritoryDecay>
<HealthDecayRate>1</HealthDecayRate>

View File

@ -10,7 +10,20 @@
<History>Rome had a number of gates piercing its city walls. One of the most famous of these was the Appian Gate.</History>
</Identity>
<Obstruction>
<Static width="37.0" depth="7.0"/>
<Cluster>
<Right width="13" depth="7">
<PosX>12</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="13" depth="7">
<PosX>-12</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="11.0" depth="7">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/romans/wall_gate.xml</Actor>

View File

@ -10,7 +10,20 @@
<History>(Insert history)</History>
</Identity>
<Obstruction>
<Static width="38.0" depth="6.5"/>
<Cluster>
<Right width="12" depth="6.5">
<PosX>13</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="12" depth="6.5">
<PosX>-13</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="14.0" depth="6.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<VisualActor>
<Actor>structures/hellenes/wall_gate.xml</Actor>

View File

@ -5,6 +5,9 @@
<Pierce>40.0</Pierce>
<Crush>10.0</Crush>
</Armour>
<Gate>
<Radius>20</Radius>
</Gate>
<BuildRestrictions>
<Category>Wall</Category>
</BuildRestrictions>
@ -36,7 +39,20 @@
<metal>0</metal>
</Loot>
<Obstruction>
<Static width="6.0" depth="6.0"/>
<Cluster>
<Right width="5" depth="5">
<PosX>10</PosX>
<PosZ>0</PosZ>
</Right>
<Left width="5" depth="5">
<PosX>-10</PosX>
<PosZ>0</PosZ>
</Left>
<Door width="20.0" depth="6.5">
<PosX>0</PosX>
<PosZ>0</PosZ>
</Door>
</Cluster>
</Obstruction>
<RallyPoint disable=""/>
<Sound>

View File

@ -24,6 +24,10 @@
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/serialization/SerializeTemplates.h"
#define MAX(x,y) x>y ? x : y
#define MIN(x,y) x>y ? y : x
/**
* Obstruction implementation. This keeps the ICmpPathfinder's model of the world updated when the
@ -47,13 +51,23 @@ public:
enum {
STATIC,
UNIT
UNIT,
CLUSTER
} m_Type;
entity_pos_t m_Size0; // radius or width
entity_pos_t m_Size1; // radius or depth
flags_t m_TemplateFlags;
typedef struct {
entity_pos_t dx, dz;
entity_angle_t da;
entity_pos_t size0, size1;
flags_t flags;
} Shape;
std::vector<Shape> m_Shapes;
// Dynamic state:
/// Whether the obstruction is actively obstructing or just an inactive placeholder.
@ -82,6 +96,7 @@ public:
/// Identifier of this entity's obstruction shape, as registered in the obstruction manager. Contains
/// structure, but should be treated as opaque here.
tag_t m_Tag;
std::vector<tag_t> m_ClusterTags;
/// Set of flags affecting the behaviour of this entity's obstruction shape.
flags_t m_Flags;
@ -105,6 +120,25 @@ public:
"<ref name='positiveDecimal'/>"
"</attribute>"
"</element>"
"<element name='Cluster'>"
"<zeroOrMore>"
"<element>"
"<anyName/>"
"<element name='PosX'>"
"<data type='decimal'/>"
"</element>"
"<element name='PosZ'>"
"<data type='decimal'/>"
"</element>"
"<attribute name='width'>"
"<ref name='positiveDecimal'/>"
"</attribute>"
"<attribute name='depth'>"
"<ref name='positiveDecimal'/>"
"</attribute>"
"</element>"
"</zeroOrMore>"
"</element>"
"</choice>"
"<element name='Active' a:help='If false, this entity will be ignored in collision tests by other units but can still perform its own collision tests'>"
"<data type='boolean'/>"
@ -131,18 +165,6 @@ public:
virtual void Init(const CParamNode& paramNode)
{
if (paramNode.GetChild("Unit").IsOk())
{
m_Type = UNIT;
m_Size0 = m_Size1 = paramNode.GetChild("Unit").GetChild("@radius").ToFixed();
}
else
{
m_Type = STATIC;
m_Size0 = paramNode.GetChild("Static").GetChild("@width").ToFixed();
m_Size1 = paramNode.GetChild("Static").GetChild("@depth").ToFixed();
}
m_TemplateFlags = 0;
if (paramNode.GetChild("BlockMovement").ToBool())
m_TemplateFlags |= ICmpObstructionManager::FLAG_BLOCK_MOVEMENT;
@ -159,9 +181,47 @@ public:
if (paramNode.GetChild("DisableBlockPathfinding").ToBool())
m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
if (paramNode.GetChild("Unit").IsOk())
{
m_Type = UNIT;
m_Size0 = m_Size1 = paramNode.GetChild("Unit").GetChild("@radius").ToFixed();
}
else if (paramNode.GetChild("Static").IsOk())
{
m_Type = STATIC;
m_Size0 = paramNode.GetChild("Static").GetChild("@width").ToFixed();
m_Size1 = paramNode.GetChild("Static").GetChild("@depth").ToFixed();
}
else
{
m_Type = CLUSTER;
CFixedVector2D max = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
CFixedVector2D min = CFixedVector2D(fixed::FromInt(0), fixed::FromInt(0));
const CParamNode::ChildrenMap& clusterMap = paramNode.GetChild("Cluster").GetChildren();
for(CParamNode::ChildrenMap::const_iterator it = clusterMap.begin(); it != clusterMap.end(); ++it)
{
Shape b;
b.size0 = it->second.GetChild("@width").ToFixed();
b.size1 = it->second.GetChild("@depth").ToFixed();
b.dx = it->second.GetChild("PosX").ToFixed();
b.dz = it->second.GetChild("PosZ").ToFixed();
b.da = entity_angle_t::FromInt(0);
b.flags = m_Flags;
m_Shapes.push_back(b);
max.X = MAX(max.X, b.dx + b.size0/2);
max.Y = MAX(max.Y, b.dz + b.size1/2);
min.X = MIN(min.X, b.dx - b.size0/2);
min.Y = MIN(min.Y, b.dz - b.size1/2);
}
m_Size0 = fixed::FromInt(2).Multiply(MAX(max.X, -min.X));
m_Size1 = fixed::FromInt(2).Multiply(MAX(max.Y, -min.Y));
}
m_Active = paramNode.GetChild("Active").ToBool();
m_Tag = tag_t();
if (m_Type == CLUSTER)
m_ClusterTags.clear();
m_Moving = false;
m_ControlGroup = GetEntityId();
m_ControlGroup2 = INVALID_ENTITY;
@ -171,6 +231,15 @@ public:
{
}
struct SerializeTag
{
template<typename S>
void operator()(S& serialize, const char* UNUSED(name), tag_t& value)
{
serialize.NumberU32_Unbounded("tag", value.n);
}
};
template<typename S>
void SerializeCommon(S& serialize)
{
@ -180,6 +249,8 @@ public:
serialize.NumberU32_Unbounded("control group 2", m_ControlGroup2);
serialize.NumberU32_Unbounded("tag", m_Tag.n);
serialize.NumberU8_Unbounded("flags", m_Flags);
if (m_Type == CLUSTER)
SerializeVector<SerializeTag>()(serialize, "cluster tags", m_ClusterTags);
}
virtual void Serialize(ISerializer& serialize)
@ -215,6 +286,17 @@ public:
if (data.inWorld && m_Tag.valid())
{
cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a);
if(m_Type == CLUSTER)
{
for (size_t i = 0; i < m_Shapes.size(); ++i)
{
Shape& b = m_Shapes[i];
fixed s, c;
sincos_approx(data.a, s, c);
cmpObstructionManager->MoveShape(m_ClusterTags[i], data.x + b.dx.Multiply(c) + b.dz.Multiply(s), data.z + b.dz.Multiply(c) - b.dx.Multiply(s), data.a + b.da);
}
}
}
else if (data.inWorld && !m_Tag.valid())
{
@ -222,14 +304,18 @@ public:
if (m_Type == STATIC)
m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
else
else if (m_Type == UNIT)
m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
data.x, data.z, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
else
AddClusterShapes(data.x, data.x, data.a);
}
else if (!data.inWorld && m_Tag.valid())
{
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = tag_t();
if(m_Type == CLUSTER)
RemoveClusterShapes();
}
break;
}
@ -243,6 +329,8 @@ public:
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = tag_t();
if(m_Type == CLUSTER)
RemoveClusterShapes();
}
break;
}
@ -273,9 +361,11 @@ public:
if (m_Type == STATIC)
m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2);
else
else if (m_Type == UNIT)
m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(),
pos.X, pos.Y, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup);
else
AddClusterShapes(pos.X, pos.Y, cmpPosition->GetRotation().Y);
}
else if (!active && m_Active)
{
@ -292,25 +382,33 @@ public:
cmpObstructionManager->RemoveShape(m_Tag);
m_Tag = tag_t();
if (m_Type == CLUSTER)
RemoveClusterShapes();
}
}
// else we didn't change the active status
}
virtual void SetDisableBlockMovementPathfinding(bool disabled)
virtual void SetDisableBlockMovementPathfinding(bool movementDisabled, bool pathfindingDisabled, int32_t shape)
{
if (disabled)
{
// Remove the blocking flags
m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
m_Flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
}
flags_t *flags = NULL;
if (shape == -1)
flags = &m_Flags;
else if (m_Type == CLUSTER && shape < (int32_t)m_Shapes.size())
flags = &m_Shapes[shape].flags;
else
{
// Add the blocking flags if the template had enabled them
m_Flags = (flags_t)(m_Flags | (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT));
m_Flags = (flags_t)(m_Flags | (m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING));
}
return; // error
// Remove the blocking / pathfinding flags or
// Add the blocking / pathfinding flags if the template had enabled them
if (movementDisabled)
*flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
else
*flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
if (pathfindingDisabled)
*flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
else
*flags |= (flags_t)(m_TemplateFlags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
// Reset the shape with the new flags (kind of inefficiently - we
// should have a ICmpObstructionManager::SetFlags function or something)
@ -347,8 +445,10 @@ public:
CFixedVector2D pos = cmpPosition->GetPosition2D();
if (m_Type == STATIC)
out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
else
else if (m_Type == UNIT)
out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0);
else
out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1);
return true;
}
@ -393,8 +493,10 @@ public:
if (m_Type == STATIC)
return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
else
else if (m_Type == UNIT)
return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
else
return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
}
virtual std::vector<entity_id_t> GetConstructionCollisions()
@ -429,9 +531,11 @@ public:
if (m_Type == STATIC)
cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
else
else if (m_Type == UNIT)
cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret);
else
cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
return ret;
}
@ -484,10 +588,62 @@ public:
{
cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
}
else
{
cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2);
for (size_t i = 0; i < m_ClusterTags.size(); ++i)
{
cmpObstructionManager->SetStaticControlGroup(m_ClusterTags[i], m_ControlGroup, m_ControlGroup2);
}
}
}
}
}
protected:
inline void AddClusterShapes(entity_pos_t x, entity_pos_t z, entity_angle_t a)
{
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpObstructionManager)
return; // error
flags_t flags = m_Flags;
// Disable block movement and block pathfinding for the obstruction shape
flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
flags &= (flags_t)(~ICmpObstructionManager::FLAG_BLOCK_PATHFINDING);
m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
x, z, a, m_Size0, m_Size1, flags, m_ControlGroup, m_ControlGroup2);
fixed s, c;
sincos_approx(a, s, c);
for (size_t i = 0; i < m_Shapes.size(); ++i)
{
Shape& b = m_Shapes[i];
tag_t tag = cmpObstructionManager->AddStaticShape(GetEntityId(),
x + b.dx.Multiply(c) + b.dz.Multiply(s), z + b.dz.Multiply(c) - b.dx.Multiply(s), a + b.da, b.size0, b.size1, b.flags, m_ControlGroup, m_ControlGroup2);
m_ClusterTags.push_back(tag);
}
}
inline void RemoveClusterShapes()
{
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpObstructionManager)
return; // error
for (size_t i = 0; i < m_ClusterTags.size(); ++i)
{
if (m_ClusterTags[i].valid())
{
cmpObstructionManager->RemoveShape(m_ClusterTags[i]);
}
}
m_ClusterTags.clear();
}
};
REGISTER_COMPONENT_TYPE(Obstruction)

View File

@ -26,7 +26,7 @@ DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUni
DEFINE_INTERFACE_METHOD_1("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string)
DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector<entity_id_t>, ICmpObstruction, GetConstructionCollisions)
DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)
DEFINE_INTERFACE_METHOD_3("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool, bool, int32_t)
DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag)
DEFINE_INTERFACE_METHOD_1("SetControlGroup", void, ICmpObstruction, SetControlGroup, entity_id_t)
DEFINE_INTERFACE_METHOD_0("GetControlGroup", entity_id_t, ICmpObstruction, GetControlGroup)

View File

@ -60,7 +60,7 @@ public:
virtual void SetMovingFlag(bool enabled) = 0;
virtual void SetDisableBlockMovementPathfinding(bool disabled) = 0;
virtual void SetDisableBlockMovementPathfinding(bool movementDisabled, bool pathfindingDisabled, int32_t shape) = 0;
virtual bool GetBlockMovementFlag() = 0;