Index: ps/trunk/binaries/data/mods/public/gui/session/input.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/input.js
+++ ps/trunk/binaries/data/mods/public/gui/session/input.js
@@ -346,6 +346,7 @@
"autocontinue": true,
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "x": placementSupport.position.x, "z": placementSupport.position.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
if (!queued)
Index: ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js
+++ ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js
@@ -45,6 +45,13 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "x": target.x,
+ "z": target.z,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_walk",
"entity": selection[0]
@@ -85,6 +92,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_walk",
"entity": selection[0]
@@ -123,6 +136,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_attack",
"entity": selection[0]
@@ -169,6 +188,13 @@
"allowCapture": false
});
+ Engine.GuiInterfaceCall("DisplayWayPoint",
+ {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_attack",
"entity": selection[0]
@@ -229,6 +255,13 @@
"queued": queued,
"allowCapture": false
});
+
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", { "name": "order_patrol", "entity": selection[0] });
return true;
},
@@ -275,6 +308,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_heal",
"entity": selection[0]
@@ -326,6 +365,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_repair",
"entity": selection[0]
@@ -367,6 +412,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_repair",
"entity": selection[0]
@@ -438,6 +489,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_gather",
"entity": selection[0]
@@ -486,6 +543,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_gather",
"entity": selection[0]
@@ -547,6 +610,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_trade",
"entity": selection[0]
@@ -638,6 +707,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_garrison",
"entity": selection[0]
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -32,6 +32,7 @@
this.timeNotificationID = 1;
this.timeNotifications = [];
this.entsRallyPointsDisplayed = [];
+ this.entsWayPointsDisplayed = [];
this.entsWithAuraAndStatusBars = new Set();
this.enabledVisualRangeOverlayTypes = {};
};
@@ -1035,6 +1036,67 @@
};
/**
+ * Displays the way points of a given list of entities (carried in cmd.entities).
+ *
+ * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the way point should
+ * be rendered or a target to retrieve that location, in order to support instantaneously rendering a way point
+ * marker at a specified location instead of incurring a delay before PostNetworkCommand is processed.
+ * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
+ * WayPoint component.
+ */
+GuiInterface.prototype.DisplayWayPoint = function(player, cmd)
+{
+ let cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ let cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
+ // If there are some way points already displayed, first hide them
+ for (let ent of this.entsWayPointsDisplayed)
+ {
+ let cmpWayPointRenderer = Engine.QueryInterface(ent, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.SetDisplayed(false);
+ }
+ this.entsWayPointsDisplayed = [];
+ // Show the way points for the passed entities
+ for (let ent of cmd.entities)
+ {
+ let cmpWayPointRenderer = Engine.QueryInterface(ent, IID_WayPointRenderer);
+ if (!cmpWayPointRenderer)
+ continue;
+ // entity must have a way point component to display a way point marker
+ // (regardless of whether cmd specifies a custom location)
+ let cmpWayPoint = Engine.QueryInterface(ent, IID_WayPoint);
+ if (!cmpWayPoint)
+ continue;
+ // Verify the owner
+ let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
+ if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
+ if (!cmpOwnership || cmpOwnership.GetOwner() != player)
+ continue;
+ // If the command was passed an explicit position or a target, use that and
+ // override the real way point position; otherwise use the real position
+ let pos;
+ if (cmd.x && cmd.z)
+ pos = cmd;
+ else if (cmd.target)
+ pos = Engine.QueryInterface(cmd.target, IID_Position).GetPosition();
+ else
+ pos = cmpWayPoint.GetPositions()[0]; // may return undefined if no way point is set
+ if (pos)
+ {
+ // Only update the position if we changed it (cmd.queued is set)
+ if (cmd.queued == true)
+ cmpWayPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ else if (cmd.queued == false)
+ cmpWayPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ cmpWayPointRenderer.SetDisplayed(true);
+
+ // remember which entities have their way points displayed so we can hide them again
+ this.entsWayPointsDisplayed.push(ent);
+ }
+ }
+};
+
+/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
@@ -1986,6 +2048,7 @@
"GetPlayerEntities": 1,
"GetNonGaiaEntities": 1,
"DisplayRallyPoint": 1,
+ "DisplayWayPoint": 1,
"SetBuildingPlacementPreview": 1,
"SetWallPlacementPreview": 1,
"GetFoundationSnapData": 1,
Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js
@@ -3687,6 +3687,16 @@
this.orderQueue.shift();
this.order = this.orderQueue[0];
+ // Remove current waypoint
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ cmpWayPoint.Shift();
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.Shift();
+ }
+ // TODO: Waypoints for formations
if (this.orderQueue.length)
{
@@ -3738,6 +3748,9 @@
var order = { "type": type, "data": data };
this.orderQueue.push(order);
+ if (!data.force)
+ this.AddWayPoint(data);
+
// If we didn't already have an order, then process this new one
if (this.orderQueue.length == 1)
{
@@ -3768,6 +3781,7 @@
{
var cheeringOrder = this.orderQueue.shift();
this.orderQueue.unshift(cheeringOrder, order);
+ // TODO: AddWayPoint
}
else if (this.order && this.IsPacking())
{
@@ -3778,6 +3792,9 @@
{
this.orderQueue.unshift(order);
this.order = order;
+
+ this.AddWayPointFront(data);
+
let ret = this.UnitFsm.ProcessMessage(this,
{"type": "Order."+this.order.type, "data": this.order.data}
);
@@ -3791,6 +3808,8 @@
this.orderQueue.shift();
this.order = this.orderQueue[0];
}
+
+ // TODO: WayPoints
}
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
@@ -3849,15 +3868,88 @@
var order = { "type": type, "data": data };
var packingOrder = this.orderQueue.shift();
this.orderQueue = [packingOrder, order];
+
+ // TODO Waypoints
}
else
{
this.orderQueue = [];
this.PushOrder(type, data);
+
+ this.ReplaceWayPoint(data);
}
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
};
+UnitAI.prototype.AddWayPointFront = function(data)
+{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+
+ if(!cmpWayPoint)
+ return;
+
+ let pos;
+ let cmpPosition = Engine.QueryInterface(data.target, IID_Position);
+
+ if (data.target && cmpPosition)
+ pos = cmpPosition.GetPosition();
+ else
+ pos = {'x': data.x, 'z': data.z };
+
+ cmpWayPoint.AddPositionFront(pos.x, pos.z);
+
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+
+ // AddPositionFront takes a CFixedVector2D which has X/Y components, not X/Z
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.AddPositionFront({'x': pos.x, 'y': pos.z});
+};
+
+UnitAI.prototype.AddWayPoint = function(data)
+{
+ var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ var pos;
+ var cmpPosition;
+ if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
+ pos = cmpPosition.GetPosition();
+ else if (data.x && data.z)
+ pos = {'x': data.x, 'z': data.z};
+ else
+ return;
+ cmpWayPoint.AddPosition(pos.x, pos.z);
+ var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ }
+};
+
+UnitAI.prototype.ReplaceWayPoint = function(data)
+{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (!cmpWayPoint)
+ return;
+
+ let pos;
+ let cmpPosition;
+
+ cmpWayPoint.Unset();
+
+ if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
+ pos = cmpPosition.GetPosition();
+ else if (data.x && data.z)
+ pos = {'x': data.x, 'z': data.z};
+ else
+ return;
+
+ cmpWayPoint.AddPosition(pos.x, pos.z);
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+
+ // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); }
+
UnitAI.prototype.GetOrders = function()
{
return this.orderQueue.slice();
@@ -5292,6 +5384,10 @@
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
if (cmpTrader.HasBothMarkets())
{
+
+ this.AddWayPoint({"target": cmpTrader.GetFirstMarket()});
+ this.AddWayPoint({"target": cmpTrader.GetSecondMarket()});
+
let data = {
"target": cmpTrader.GetFirstMarket(),
"route": route,
@@ -5346,6 +5442,14 @@
UnitAI.prototype.MoveToMarket = function(targetMarket)
{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ cmpWayPoint.Shift();
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.Shift();
+ }
if (this.waypoints && this.waypoints.length > 1)
{
let point = this.waypoints.pop();
@@ -5361,11 +5465,13 @@
if (!this.CanTrade(currentMarket))
{
this.StopTrading();
+ // TODO clear waypoints
return;
}
if (!this.CheckTargetRange(currentMarket, IID_Trader))
{
+ this.AddWayPoint({"target": currentMarket});
if (!this.MoveToMarket(currentMarket)) // If the current market is not reached try again
this.StopTrading();
return;
Index: ps/trunk/binaries/data/mods/public/simulation/components/WayPoint.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/WayPoint.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/WayPoint.js
@@ -0,0 +1,41 @@
+function WayPoint() {}
+
+WayPoint.prototype.Schema = "";
+
+WayPoint.prototype.Init = function()
+{
+ this.pos = [];
+};
+
+WayPoint.prototype.AddPosition = function(x, z)
+{
+ this.pos.push({
+ 'x': x,
+ 'z': z
+ });
+};
+
+WayPoint.prototype.AddPositionFront = function(x, z)
+{
+ this.pos.push({
+ 'x': x,
+ 'z': z
+ });
+};
+
+WayPoint.prototype.GetPositions = function()
+{
+ return this.pos;
+};
+
+WayPoint.prototype.Unset = function()
+{
+ this.pos = [];
+};
+
+WayPoint.prototype.Shift = function()
+{
+ this.pos.shift();
+};
+
+Engine.RegisterComponentType(IID_WayPoint, "WayPoint", WayPoint);
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("WayPoint");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
@@ -13,6 +13,7 @@
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
+Engine.LoadComponentScript("interfaces/WayPoint.js");
Engine.LoadComponentScript("Formation.js");
Engine.LoadComponentScript("UnitAI.js");
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
@@ -0,0 +1,14 @@
+Engine.LoadComponentScript("interfaces/WayPoint.js");
+Engine.LoadComponentScript("WayPoint.js");
+
+let cmpWayPoint = ConstructComponent(ent, "Pack", {});
+
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions, []);
+cmpWayPoint.AddPosition(10, 5);
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions, [{ 'x': 10, 'z': 5 }]);
+cmpWayPoint.AddPosition(6, 8);
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions, [{ 'x': 10, 'z': 5 }, { 'x': 6, 'z': 8 }]);
+cmpWayPoint.Shift();
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions, [{ 'x': 6, 'z': 8 }]);
+cmpWayPoint.Unset();
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions, []);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js
@@ -2,8 +2,7 @@
// are likely to fail, which may be useful for debugging AIs
var g_DebugCommands = false;
-function ProcessCommand(player, cmd)
-{
+function ProcessCommand(player, cmd) {
let data = {
"cmpPlayerManager": Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager)
};
@@ -40,30 +39,26 @@
// moves the entities closer to the target before giving up.)
// Now handle various commands
- if (g_Commands[cmd.type])
- {
+ if (g_Commands[cmd.type]) {
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.CallEvent("PlayerCommand", { "player": player, "cmd": cmd });
g_Commands[cmd.type](player, cmd, data);
}
else
- error("Invalid command: unknown command type: "+uneval(cmd));
+ error("Invalid command: unknown command type: " + uneval(cmd));
}
var g_Commands = {
- "debug-print": function(player, cmd, data)
- {
+ "debug-print": function (player, cmd, data) {
print(cmd.message);
},
- "chat": function(player, cmd, data)
- {
+ "chat": function (player, cmd, data) {
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({ "type": cmd.type, "players": [player], "message": cmd.message });
},
- "aichat": function(player, cmd, data)
- {
+ "aichat": function (player, cmd, data) {
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
var notification = { "players": [player] };
for (var key in cmd)
@@ -71,31 +66,28 @@
cmpGuiInterface.PushNotification(notification);
},
- "cheat": function(player, cmd, data)
- {
+ "cheat": function (player, cmd, data) {
Cheat(cmd);
},
- "diplomacy": function(player, cmd, data)
- {
+ "diplomacy": function (player, cmd, data) {
let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);
if (data.cmpPlayer.GetLockTeams() ||
- cmpCeasefireManager && cmpCeasefireManager.IsCeasefireActive())
+ cmpCeasefireManager && cmpCeasefireManager.IsCeasefireActive())
return;
- switch(cmd.to)
- {
- case "ally":
- data.cmpPlayer.SetAlly(cmd.player);
- break;
- case "neutral":
- data.cmpPlayer.SetNeutral(cmd.player);
- break;
- case "enemy":
- data.cmpPlayer.SetEnemy(cmd.player);
- break;
- default:
- warn("Invalid command: Could not set "+player+" diplomacy status of player "+cmd.player+" to "+cmd.to);
+ switch (cmd.to) {
+ case "ally":
+ data.cmpPlayer.SetAlly(cmd.player);
+ break;
+ case "neutral":
+ data.cmpPlayer.SetNeutral(cmd.player);
+ break;
+ case "enemy":
+ data.cmpPlayer.SetEnemy(cmd.player);
+ break;
+ default:
+ warn("Invalid command: Could not set " + player + " diplomacy status of player " + cmd.player + " to " + cmd.to);
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
@@ -107,13 +99,11 @@
});
},
- "tribute": function(player, cmd, data)
- {
+ "tribute": function (player, cmd, data) {
data.cmpPlayer.TributeResource(cmd.player, cmd.amounts);
},
- "control-all": function(player, cmd, data)
- {
+ "control-all": function (player, cmd, data) {
if (!data.cmpPlayer.GetCheatsEnabled())
return;
@@ -127,8 +117,7 @@
data.cmpPlayer.SetControlAllUnits(cmd.flag);
},
- "reveal-map": function(player, cmd, data)
- {
+ "reveal-map": function (player, cmd, data) {
if (!data.cmpPlayer.GetCheatsEnabled())
return;
@@ -145,35 +134,30 @@
cmpRangeManager.SetLosRevealAll(-1, cmd.enable);
},
- "walk": function(player, cmd, data)
- {
+ "walk": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued);
});
},
- "walk-to-range": function(player, cmd, data)
- {
+ "walk-to-range": function (player, cmd, data) {
// Only used by the AI
- for (let ent of data.entities)
- {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.WalkToPointRange(cmd.x, cmd.z, cmd.min, cmd.max, cmd.queued);
}
},
- "attack-walk": function(player, cmd, data)
- {
+ "attack-walk": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, cmd.queued);
});
},
- "attack": function(player, cmd, data)
- {
+ "attack": function (player, cmd, data) {
if (g_DebugCommands && !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target)))
- warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd));
+ warn("Invalid command: attack target is not owned by enemy of player " + player + ": " + uneval(cmd));
let allowCapture = cmd.allowCapture || cmd.allowCapture == null;
@@ -182,83 +166,72 @@
});
},
- "patrol": function(player, cmd, data)
- {
+ "patrol": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI =>
cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, cmd.queued)
);
},
- "heal": function(player, cmd, data)
- {
+ "heal": function (player, cmd, data) {
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target)))
- warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd));
+ warn("Invalid command: heal target is not owned by player " + player + " or their ally: " + uneval(cmd));
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.Heal(cmd.target, cmd.queued);
});
},
- "repair": function(player, cmd, data)
- {
+ "repair": function (player, cmd, data) {
// This covers both repairing damaged buildings, and constructing unfinished foundations
if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target))
- warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd));
+ warn("Invalid command: repair target is not owned by ally of player " + player + ": " + uneval(cmd));
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued);
});
},
- "gather": function(player, cmd, data)
- {
+ "gather": function (player, cmd, data) {
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByGaia(cmd.target)))
- warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd));
+ warn("Invalid command: resource is not owned by gaia or player " + player + ": " + uneval(cmd));
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.Gather(cmd.target, cmd.queued);
});
},
- "gather-near-position": function(player, cmd, data)
- {
+ "gather-near-position": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued);
});
},
- "returnresource": function(player, cmd, data)
- {
+ "returnresource": function (player, cmd, data) {
if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target))
- warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd));
+ warn("Invalid command: dropsite is not owned by player " + player + ": " + uneval(cmd));
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.ReturnResource(cmd.target, cmd.queued);
});
},
- "back-to-work": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "back-to-work": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
- if(!cmpUnitAI || !cmpUnitAI.BackToWork())
+ if (!cmpUnitAI || !cmpUnitAI.BackToWork())
notifyBackToWorkFailure(player);
}
},
- "remove-guard": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "remove-guard": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.RemoveGuard();
}
},
- "train": function(player, cmd, data)
- {
+ "train": function (player, cmd, data) {
// Check entity limits
var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(cmd.template);
var unitCategory = null;
@@ -266,20 +239,16 @@
unitCategory = template.TrainingRestrictions.Category;
// Verify that the building(s) can be controlled by the player
- if (data.entities.length <= 0)
- {
+ if (data.entities.length <= 0) {
if (g_DebugCommands)
- warn("Invalid command: training building(s) cannot be controlled by player "+player+": "+uneval(cmd));
+ warn("Invalid command: training building(s) cannot be controlled by player " + player + ": " + uneval(cmd));
return;
}
- for (let ent of data.entities)
- {
- if (unitCategory)
- {
+ for (let ent of data.entities) {
+ if (unitCategory) {
var cmpPlayerEntityLimits = QueryOwnerInterface(ent, IID_EntityLimits);
- if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count))
- {
+ if (!cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count)) {
if (g_DebugCommands)
warn(unitCategory + " train limit is reached: " + uneval(cmd));
continue;
@@ -287,8 +256,7 @@
}
var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
- if (!cmpTechnologyManager.CanProduce(cmd.template))
- {
+ if (!cmpTechnologyManager.CanProduce(cmd.template)) {
if (g_DebugCommands)
warn("Invalid command: training requires unresearched technology: " + uneval(cmd));
continue;
@@ -298,13 +266,10 @@
// Check if the building can train the unit
// TODO: the AI API does not take promotion technologies into account for the list
// of trainable units (taken directly from the unit template). Here is a temporary fix.
- if (queue && data.cmpPlayer.IsAI())
- {
+ if (queue && data.cmpPlayer.IsAI()) {
var list = queue.GetEntitiesList();
- if (list.indexOf(cmd.template) === -1 && cmd.promoted)
- {
- for (var promoted of cmd.promoted)
- {
+ if (list.indexOf(cmd.template) === -1 && cmd.promoted) {
+ for (var promoted of cmd.promoted) {
if (list.indexOf(promoted) === -1)
continue;
cmd.template = promoted;
@@ -320,18 +285,15 @@
}
},
- "research": function(player, cmd, data)
- {
- if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
- {
+ "research": function (player, cmd, data) {
+ if (!CanControlUnit(cmd.entity, player, data.controlAllUnits)) {
if (g_DebugCommands)
- warn("Invalid command: research building cannot be controlled by player "+player+": "+uneval(cmd));
+ warn("Invalid command: research building cannot be controlled by player " + player + ": " + uneval(cmd));
return;
}
var cmpTechnologyManager = QueryOwnerInterface(cmd.entity, IID_TechnologyManager);
- if (!cmpTechnologyManager.CanResearch(cmd.template))
- {
+ if (!cmpTechnologyManager.CanResearch(cmd.template)) {
if (g_DebugCommands)
warn("Invalid command: Requirements to research technology are not met: " + uneval(cmd));
return;
@@ -342,12 +304,10 @@
queue.AddBatch(cmd.template, "technology");
},
- "stop-production": function(player, cmd, data)
- {
- if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
- {
+ "stop-production": function (player, cmd, data) {
+ if (!CanControlUnit(cmd.entity, player, data.controlAllUnits)) {
if (g_DebugCommands)
- warn("Invalid command: production building cannot be controlled by player "+player+": "+uneval(cmd));
+ warn("Invalid command: production building cannot be controlled by player " + player + ": " + uneval(cmd));
return;
}
@@ -356,29 +316,24 @@
queue.RemoveBatch(cmd.id);
},
- "construct": function(player, cmd, data)
- {
+ "construct": function (player, cmd, data) {
TryConstructBuilding(player, data.cmpPlayer, data.controlAllUnits, cmd);
},
- "construct-wall": function(player, cmd, data)
- {
+ "construct-wall": function (player, cmd, data) {
TryConstructWall(player, data.cmpPlayer, data.controlAllUnits, cmd);
},
- "delete-entities": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "delete-entities": function (player, cmd, data) {
+ for (let ent of data.entities) {
let cmpHealth = QueryMiragedInterface(ent, IID_Health);
- if (!data.controlAllUnits)
- {
+ if (!data.controlAllUnits) {
if (cmpHealth && cmpHealth.IsUndeletable())
continue;
let cmpCapturable = QueryMiragedInterface(ent, IID_Capturable);
if (cmpCapturable &&
- cmpCapturable.GetCapturePoints()[player] < cmpCapturable.GetMaxCapturePoints() / 2)
+ cmpCapturable.GetCapturePoints()[player] < cmpCapturable.GetMaxCapturePoints() / 2)
continue;
let cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply);
@@ -387,8 +342,7 @@
}
let cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
- if (cmpMirage)
- {
+ if (cmpMirage) {
let cmpMiragedHealth = Engine.QueryInterface(cmpMirage.parent, IID_Health);
if (cmpMiragedHealth)
cmpMiragedHealth.Kill();
@@ -404,13 +358,10 @@
}
},
- "set-rallypoint": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "set-rallypoint": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
- if (cmpRallyPoint)
- {
+ if (cmpRallyPoint) {
if (!cmd.queued)
cmpRallyPoint.Unset();
@@ -420,30 +371,25 @@
}
},
- "unset-rallypoint": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "unset-rallypoint": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
cmpRallyPoint.Reset();
}
},
- "defeat-player": function(player, cmd, data)
- {
+ "defeat-player": function (player, cmd, data) {
let cmpPlayer = QueryPlayerIDInterface(player);
if (cmpPlayer)
cmpPlayer.SetState("defeated", !!cmd.resign);
},
- "garrison": function(player, cmd, data)
- {
+ "garrison": function (player, cmd, data) {
// Verify that the building can be controlled by the player or is mutualAlly
- if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits))
- {
+ if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) {
if (g_DebugCommands)
- warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd));
+ warn("Invalid command: garrison target cannot be controlled by player " + player + " (or ally): " + uneval(cmd));
return;
}
@@ -452,13 +398,11 @@
});
},
- "guard": function(player, cmd, data)
- {
+ "guard": function (player, cmd, data) {
// Verify that the target can be controlled by the player or is mutualAlly
- if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits))
- {
+ if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) {
if (g_DebugCommands)
- warn("Invalid command: guard/escort target cannot be controlled by player "+player+": "+uneval(cmd));
+ warn("Invalid command: guard/escort target cannot be controlled by player " + player + ": " + uneval(cmd));
return;
}
@@ -467,20 +411,17 @@
});
},
- "stop": function(player, cmd, data)
- {
+ "stop": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.Stop(cmd.queued);
});
},
- "unload": function(player, cmd, data)
- {
+ "unload": function (player, cmd, data) {
// Verify that the building can be controlled by the player or is mutualAlly
- if (!CanControlUnitOrIsAlly(cmd.garrisonHolder, player, data.controlAllUnits))
- {
+ if (!CanControlUnitOrIsAlly(cmd.garrisonHolder, player, data.controlAllUnits)) {
if (g_DebugCommands)
- warn("Invalid command: unload target cannot be controlled by player "+player+" (or ally): "+uneval(cmd));
+ warn("Invalid command: unload target cannot be controlled by player " + player + " (or ally): " + uneval(cmd));
return;
}
@@ -499,18 +440,15 @@
notifyUnloadFailure(player, cmd.garrisonHolder);
},
- "unload-template": function(player, cmd, data)
- {
+ "unload-template": function (player, cmd, data) {
var entities = FilterEntityListWithAllies(cmd.garrisonHolders, player, data.controlAllUnits);
- for (let garrisonHolder of entities)
- {
+ for (let garrisonHolder of entities) {
var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder);
- if (cmpGarrisonHolder)
- {
+ if (cmpGarrisonHolder) {
// Only the owner of the garrisonHolder may unload entities from any owners
if (!IsOwnedByPlayer(player, garrisonHolder) && !data.controlAllUnits
- && player != +cmd.owner)
- continue;
+ && player != +cmd.owner)
+ continue;
if (!cmpGarrisonHolder.UnloadTemplate(cmd.template, cmd.owner, cmd.all))
notifyUnloadFailure(player, garrisonHolder);
@@ -518,57 +456,47 @@
}
},
- "unload-all-by-owner": function(player, cmd, data)
- {
+ "unload-all-by-owner": function (player, cmd, data) {
var entities = FilterEntityListWithAllies(cmd.garrisonHolders, player, data.controlAllUnits);
- for (let garrisonHolder of entities)
- {
+ for (let garrisonHolder of entities) {
var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder);
if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadAllByOwner(player))
notifyUnloadFailure(player, garrisonHolder);
}
},
- "unload-all": function(player, cmd, data)
- {
+ "unload-all": function (player, cmd, data) {
var entities = FilterEntityList(cmd.garrisonHolders, player, data.controlAllUnits);
- for (let garrisonHolder of entities)
- {
+ for (let garrisonHolder of entities) {
var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder);
if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadAll())
notifyUnloadFailure(player, garrisonHolder);
}
},
- "increase-alert-level": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "increase-alert-level": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser);
if (!cmpAlertRaiser || !cmpAlertRaiser.IncreaseAlertLevel())
notifyAlertFailure(player);
}
},
- "alert-end": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "alert-end": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser);
if (cmpAlertRaiser)
cmpAlertRaiser.EndOfAlert();
}
},
- "formation": function(player, cmd, data)
- {
+ "formation": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player, cmd.name).forEach(cmpUnitAI => {
cmpUnitAI.MoveIntoFormation(cmd);
});
},
- "promote": function(player, cmd, data)
- {
+ "promote": function (player, cmd, data) {
if (!data.cmpPlayer.GetCheatsEnabled())
return;
@@ -580,28 +508,23 @@
"translateMessage": true
});
- for (let ent of cmd.entities)
- {
+ for (let ent of cmd.entities) {
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
if (cmpPromotion)
cmpPromotion.IncreaseXp(cmpPromotion.GetRequiredXp() - cmpPromotion.GetCurrentXp());
}
},
- "stance": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "stance": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI && !cmpUnitAI.IsTurret())
cmpUnitAI.SwitchToStance(cmd.name);
}
},
- "lock-gate": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "lock-gate": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (!cmpGate)
continue;
@@ -613,43 +536,59 @@
}
},
- "setup-trade-route": function(player, cmd, data)
- {
+ "setup-trade-route": function (player, cmd, data) {
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => {
cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source, cmd.route, cmd.queued);
});
+
+ for (var ent in cmd.entities) {
+ var cmpWayPoint = Engine.QueryInterface(ent, IID_WayPoint);
+ if (cmpWayPoint) {
+ if (!cmd.queued)
+ cmpWayPoint.Unset();
+ if (cmd.x && cmd.z)
+ cmpWayPoint.AddPosition(cmd.x, cmd.z);
+ else if (cmd.target) {
+ var cmpPosition = Engine.QueryInterface(cmd.target, IID_Position);
+ if (cmpPosition) {
+ cmpWayPoint.AddPosition(cmpPosition.GetPosition().x, cmpPosition.GetPosition().z);
+ // For setup-trade-route call from RallyPointCommands
+ if (cmd.source) {
+ warn("cmd.source " + uneval(cmd));
+ var cmpSourcePosition = Engine.QueryInterface(cmd.source, IID_Position);
+ if (cmpSourcePosition)
+ cmpWayPoint.AddPosition(cmpSourcePosition.GetPosition().x, cmpSourcePosition.GetPosition().z);
+ }
+ }
+ }
+ }
+ }
},
- "set-trading-goods": function(player, cmd, data)
- {
+ "set-trading-goods": function (player, cmd, data) {
data.cmpPlayer.SetTradingGoods(cmd.tradingGoods);
},
- "barter": function(player, cmd, data)
- {
+ "barter": function (player, cmd, data) {
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
cmpBarter.ExchangeResources(data.playerEnt, cmd.sell, cmd.buy, cmd.amount);
},
- "set-shading-color": function(player, cmd, data)
- {
+ "set-shading-color": function (player, cmd, data) {
// Prevent multiplayer abuse
if (!data.cmpPlayer.IsAI())
return;
// Debug command to make an entity brightly colored
- for (let ent of cmd.entities)
- {
+ for (let ent of cmd.entities) {
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
cmpVisual.SetShadingColor(cmd.rgb[0], cmd.rgb[1], cmd.rgb[2], 0); // alpha isn't used so just send 0
}
},
- "pack": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "pack": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI)
continue;
@@ -661,10 +600,8 @@
}
},
- "cancel-pack": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "cancel-pack": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI)
continue;
@@ -676,17 +613,14 @@
}
},
- "upgrade": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "upgrade": function (player, cmd, data) {
+ for (let ent of data.entities) {
var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template))
continue;
- if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template))
- {
+ if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template)) {
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [data.cmpPlayer.GetPlayerID()],
@@ -695,8 +629,7 @@
continue;
}
- if (!CanGarrisonedChangeTemplate(ent, cmd.template))
- {
+ if (!CanGarrisonedChangeTemplate(ent, cmd.template)) {
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [data.cmpPlayer.GetPlayerID()],
@@ -710,16 +643,14 @@
var template = cmpTemplateManager.GetTemplate(cmd.template);
var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
if (template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1) ||
- template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category))
- {
+ template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category)) {
if (g_DebugCommands)
warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
continue;
}
var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
- if (cmpUpgrade.GetRequiredTechnology(cmd.template) && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology(cmd.template)))
- {
+ if (cmpUpgrade.GetRequiredTechnology(cmd.template) && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology(cmd.template))) {
if (g_DebugCommands)
warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
continue;
@@ -729,18 +660,15 @@
}
},
- "cancel-upgrade": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "cancel-upgrade": function (player, cmd, data) {
+ for (let ent of data.entities) {
let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
if (cmpUpgrade)
cmpUpgrade.CancelUpgrade(data.cmpPlayer.playerID);
}
},
- "attack-request": function(player, cmd, data)
- {
+ "attack-request": function (player, cmd, data) {
// Send a chat message to human players
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
@@ -757,8 +685,7 @@
cmpAIInterface.PushEvent("AttackRequest", cmd);
},
- "spy-request": function(player, cmd, data)
- {
+ "spy-request": function (player, cmd, data) {
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
let ent = pickRandom(cmpRangeManager.GetEntitiesByPlayer(cmd.player).filter(ent => {
let cmpVisionSharing = Engine.QueryInterface(ent, IID_VisionSharing);
@@ -766,8 +693,7 @@
}));
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
- if (ent)
- {
+ if (ent) {
Engine.QueryInterface(ent, IID_VisionSharing).AddSpy(cmd.source);
cmpGUIInterface.PushNotification({
"type": "spy-response",
@@ -804,10 +730,8 @@
// message to any component you like.
},
- "set-dropsite-sharing": function(player, cmd, data)
- {
- for (let ent of data.entities)
- {
+ "set-dropsite-sharing": function (player, cmd, data) {
+ for (let ent of data.entities) {
let cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (cmpResourceDropsite && cmpResourceDropsite.IsSharable())
cmpResourceDropsite.SetSharing(cmd.shared);
@@ -818,8 +742,7 @@
/**
* Sends a GUI notification about unit(s) that failed to ungarrison.
*/
-function notifyUnloadFailure(player, garrisonHolder)
-{
+function notifyUnloadFailure(player, garrisonHolder) {
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "text",
@@ -832,8 +755,7 @@
/**
* Sends a GUI notification about worker(s) that failed to go back to work.
*/
-function notifyBackToWorkFailure(player)
-{
+function notifyBackToWorkFailure(player) {
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "text",
@@ -846,8 +768,7 @@
/**
* Sends a GUI notification about Alerts that failed to be raised
*/
-function notifyAlertFailure(player)
-{
+function notifyAlertFailure(player) {
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": "text",
@@ -861,16 +782,13 @@
* Get some information about the formations used by entities.
* The entities must have a UnitAI component.
*/
-function ExtractFormations(ents)
-{
+function ExtractFormations(ents) {
var entities = []; // subset of ents that have UnitAI
var members = {}; // { formationentity: [ent, ent, ...], ... }
- for (let ent of ents)
- {
+ for (let ent of ents) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var fid = cmpUnitAI.GetFormationController();
- if (fid != INVALID_ENTITY)
- {
+ if (fid != INVALID_ENTITY) {
if (!members[fid])
members[fid] = [];
members[fid].push(ent);
@@ -878,7 +796,7 @@
entities.push(ent);
}
- var ids = [ id for (id in members) ];
+ var ids = [id for (id in members) ];
return { "entities": entities, "members": members, "ids": ids };
}
@@ -887,8 +805,7 @@
* Tries to find the best angle to put a dock at a given position
* Taken from GuiInterface.js
*/
-function GetDockAngle(template, x, z)
-{
+function GetDockAngle(template, x, z) {
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
@@ -897,7 +814,7 @@
// Get footprint size
var halfSize = 0;
if (template.Footprint.Square)
- halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2;
+ halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"]) / 2;
else if (template.Footprint.Circle)
halfSize = template.Footprint.Circle["@radius"];
@@ -911,15 +828,13 @@
* 6. Calculate angle using average of sequence
*/
const numPoints = 16;
- for (var dist = 0; dist < 4; ++dist)
- {
+ for (var dist = 0; dist < 4; ++dist) {
var waterPoints = [];
- for (var i = 0; i < numPoints; ++i)
- {
- var angle = (i/numPoints)*2*Math.PI;
- var d = halfSize*(dist+1);
- var nx = x - d*Math.sin(angle);
- var nz = z + d*Math.cos(angle);
+ for (var i = 0; i < numPoints; ++i) {
+ var angle = (i / numPoints) * 2 * Math.PI;
+ var d = halfSize * (dist + 1);
+ var nx = x - d * Math.sin(angle);
+ var nz = z + d * Math.cos(angle);
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
waterPoints.push(i);
@@ -928,11 +843,9 @@
var length = waterPoints.length;
if (!length)
continue;
- for (var i = 0; i < length; ++i)
- {
+ for (var i = 0; i < length; ++i) {
var count = 0;
- for (let j = 0; j < length - 1; ++j)
- {
+ for (let j = 0; j < length - 1; ++j) {
if ((waterPoints[(i + j) % length] + 1) % numPoints == waterPoints[(i + j + 1) % length])
++count;
else
@@ -942,18 +855,16 @@
}
var start = 0;
var count = 0;
- for (var c in consec)
- {
- if (consec[c] > count)
- {
+ for (var c in consec) {
+ if (consec[c] > count) {
start = c;
count = consec[c];
}
}
// If we've found a shoreline, stop searching
- if (count != numPoints-1)
- return -((waterPoints[start] + consec[start]/2) % numPoints) / numPoints * 2 * Math.PI;
+ if (count != numPoints - 1)
+ return -((waterPoints[start] + consec[start] / 2) % numPoints) / numPoints * 2 * Math.PI;
}
return undefined;
}
@@ -962,8 +873,7 @@
* Attempts to construct a building using the specified parameters.
* Returns true on success, false on failure.
*/
-function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
-{
+function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) {
// Message structure:
// {
// "type": "construct",
@@ -1001,8 +911,7 @@
// Tentatively create the foundation (we might find later that it's a invalid build command)
var ent = Engine.AddEntity(foundationTemplate);
- if (ent == INVALID_ENTITY)
- {
+ if (ent == INVALID_ENTITY) {
// Error (e.g. invalid template names)
error("Error creating foundation entity for '" + cmd.template + "'");
return false;
@@ -1011,8 +920,7 @@
// If it's a dock, get the right angle.
var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(cmd.template);
var angle = cmd.angle;
- if (template.BuildRestrictions.Category === "Dock")
- {
+ if (template.BuildRestrictions.Category === "Dock") {
let angleDock = GetDockAngle(template, cmd.x, cmd.z);
if (angleDock !== undefined)
angle = angleDock;
@@ -1024,13 +932,11 @@
cmpPosition.SetYRotation(angle);
// Set the obstruction control group if needed
- if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2)
- {
+ if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2) {
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
// primary control group must always be valid
- if (cmd.obstructionControlGroup)
- {
+ if (cmd.obstructionControlGroup) {
if (cmd.obstructionControlGroup <= 0)
warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0");
@@ -1047,13 +953,11 @@
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
- if (cmpBuildRestrictions)
- {
+ if (cmpBuildRestrictions) {
var ret = cmpBuildRestrictions.CheckPlacement();
- if (!ret.success)
- {
+ if (!ret.success) {
if (g_DebugCommands)
- warn("Invalid command: build restrictions check failed with '"+ret.message+"' for player "+player+": "+uneval(cmd));
+ warn("Invalid command: build restrictions check failed with '" + ret.message + "' for player " + player + ": " + uneval(cmd));
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
ret.players = [player];
@@ -1071,10 +975,9 @@
// Check entity limits
var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
- if (cmpEntityLimits && !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
- {
+ if (cmpEntityLimits && !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) {
if (g_DebugCommands)
- warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd));
+ warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
// Remove the foundation because the construction was aborted
cmpPosition.MoveOutOfWorld();
@@ -1083,10 +986,9 @@
}
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
- if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template))
- {
+ if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template)) {
if (g_DebugCommands)
- warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd));
+ warn("Invalid command: required technology check failed for player " + player + ": " + uneval(cmd));
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
@@ -1106,10 +1008,9 @@
let cmpCost = Engine.QueryInterface(ent, IID_Cost);
let costs = cmpCost.GetResourceCosts(player);
- if (!cmpPlayer.TrySubtractResources(costs))
- {
+ if (!cmpPlayer.TrySubtractResources(costs)) {
if (g_DebugCommands)
- warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd));
+ warn("Invalid command: building cost check failed for player " + player + ": " + uneval(cmd));
Engine.DestroyEntity(ent);
cmpPosition.MoveOutOfWorld();
@@ -1126,11 +1027,10 @@
// send Metadata info if any
if (cmd.metadata)
- Engine.PostMessage(ent, MT_AIMetadata, { "id": ent, "metadata" : cmd.metadata, "owner" : player } );
+ Engine.PostMessage(ent, MT_AIMetadata, { "id": ent, "metadata": cmd.metadata, "owner": player });
// Tell the units to start building this new entity
- if (cmd.autorepair)
- {
+ if (cmd.autorepair) {
ProcessCommand(player, {
"type": "repair",
"entities": entities,
@@ -1143,8 +1043,7 @@
return ent;
}
-function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd)
-{
+function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) {
// 'cmd' message structure:
// {
// "type": "construct-wall",
@@ -1177,14 +1076,12 @@
if (cmd.pieces.length <= 0)
return;
- if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower)
- {
+ if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower) {
error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the starting side");
return;
}
- if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower)
- {
+ if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower) {
error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the ending side");
return;
}
@@ -1224,11 +1121,9 @@
// If we're snapping to an existing entity at the starting end, set lastTowerControlGroup to its control group ID so that
// the first wall piece can be built while overlapping it.
- if (cmd.startSnappedEntity)
- {
+ if (cmd.startSnappedEntity) {
var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction);
- if (!cmpSnappedStartObstruction)
- {
+ if (!cmpSnappedStartObstruction) {
error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component");
return;
}
@@ -1240,8 +1135,7 @@
var i = 0;
var queued = cmd.queued;
var pieces = clone(cmd.pieces);
- for (; i < pieces.length; ++i)
- {
+ for (; i < pieces.length; ++i) {
var piece = pieces[i];
// All wall pieces after the first must be queued.
@@ -1250,12 +1144,10 @@
// 'lastTowerControlGroup' must always be defined and valid here, except if we're at the first piece and we didn't do
// start position snapping (implying that the first entity we build must be a tower)
- if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY)
- {
- if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity))
- {
- error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")");
- break;
+ if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) {
+ if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity)) {
+ error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")");
+ break;
}
}
@@ -1277,29 +1169,30 @@
// If we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's
// control group directly at construction time (instead of setting it in the second pass) to allow it to be built
// while overlapping the snapped entity.
- if (i == pieces.length - 1 && cmd.endSnappedEntity)
- {
+ if (i == pieces.length - 1 && cmd.endSnappedEntity) {
var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
if (cmpEndSnappedObstruction)
constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup();
}
+ // Waypoints
+ let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ if (cmpGuiInterface)
+ cmpGuiInterface.DisplayWayPoint(player, constructPieceCmd);
+
var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd);
- if (pieceEntityId)
- {
+ if (pieceEntityId) {
// wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later
piece.ent = pieceEntityId;
// if we built a tower, do the control group dance (see outline above) and update lastTowerControlGroup and lastTowerIndex
- if (piece.template == cmd.wallSet.templates.tower)
- {
+ if (piece.template == cmd.wallSet.templates.tower) {
var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction);
var newTowerControlGroup = pieceEntityId;
- if (i > 0)
- {
+ if (i > 0) {
//warn(" updating previous wall piece's secondary control group to " + newTowerControlGroup);
- var cmpPreviousObstruction = Engine.QueryInterface(pieces[i-1].ent, IID_Obstruction);
+ var cmpPreviousObstruction = Engine.QueryInterface(pieces[i - 1].ent, IID_Obstruction);
// TODO: ensure that cmpPreviousObstruction exists
// TODO: ensure that the previous obstruction does not yet have a secondary control group set
cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup);
@@ -1326,11 +1219,9 @@
lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces
// only start off with the ending side's snapped tower's control group if we were able to build the entire wall
- if (cmd.endSnappedEntity && wallComplete)
- {
+ if (cmd.endSnappedEntity && wallComplete) {
var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction);
- if (!cmpSnappedEndObstruction)
- {
+ if (!cmpSnappedEndObstruction) {
error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component");
return;
}
@@ -1338,44 +1229,36 @@
lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup();
}
- for (var j = lastBuiltPieceIndex; j >= 0; --j)
- {
+ for (var j = lastBuiltPieceIndex; j >= 0; --j) {
var piece = pieces[j];
- if (!piece.ent)
- {
+ if (!piece.ent) {
error("[TryConstructWall] No entity ID set for constructed entity of template '" + piece.template + "'");
continue;
}
var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction);
- if (!cmpPieceObstruction)
- {
+ if (!cmpPieceObstruction) {
error("[TryConstructWall] Wall piece of template '" + piece.template + "' has no Obstruction component");
continue;
}
- if (piece.template == cmd.wallSet.templates.tower)
- {
+ if (piece.template == cmd.wallSet.templates.tower) {
// encountered a tower entity, update the last tower control group
lastTowerControlGroup = cmpPieceObstruction.GetControlGroup();
}
- else
- {
+ else {
// Encountered a non-tower entity, update its secondary control group to 'lastTowerControlGroup'.
// Note that the wall piece may already have its secondary control group set to the tower's entity ID from a control group
// dance during the first pass, in which case we should validate it against 'lastTowerControlGroup'.
var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2();
- if (existingSecondaryControlGroup == INVALID_ENTITY)
- {
- if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY)
- {
+ if (existingSecondaryControlGroup == INVALID_ENTITY) {
+ if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY) {
cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup);
}
}
- else if (existingSecondaryControlGroup != lastTowerControlGroup)
- {
+ else if (existingSecondaryControlGroup != lastTowerControlGroup) {
error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass, iteration " + j + ")");
break;
}
@@ -1386,11 +1269,9 @@
/**
* Remove the given list of entities from their current formations.
*/
-function RemoveFromFormation(ents)
-{
+function RemoveFromFormation(ents) {
var formation = ExtractFormations(ents);
- for (var fid in formation.members)
- {
+ for (var fid in formation.members) {
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
@@ -1401,12 +1282,10 @@
* Returns a list of UnitAI components, each belonging either to a
* selected unit or to a formation entity for groups of the selected units.
*/
-function GetFormationUnitAIs(ents, player, formationTemplate)
-{
+function GetFormationUnitAIs(ents, player, formationTemplate) {
// If an individual was selected, remove it from any formation
// and command it individually
- if (ents.length == 1)
- {
+ if (ents.length == 1) {
// Skip unit if it has no UnitAI
var cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI);
if (!cmpUnitAI)
@@ -1414,14 +1293,13 @@
RemoveFromFormation(ents);
- return [ cmpUnitAI ];
+ return [cmpUnitAI];
}
// Separate out the units that don't support the chosen formation
var formedEnts = [];
var nonformedUnitAIs = [];
- for (let ent of ents)
- {
+ for (let ent of ents) {
// Skip units with no UnitAI or no position
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
@@ -1435,16 +1313,14 @@
var nullFormation = (formationTemplate || cmpUnitAI.GetLastFormationTemplate()) == "formations/null";
if (!nullFormation && cmpIdentity && cmpIdentity.CanUseFormation(formationTemplate || "formations/null"))
formedEnts.push(ent);
- else
- {
+ else {
if (nullFormation)
cmpUnitAI.SetLastFormationTemplate("formations/null");
nonformedUnitAIs.push(cmpUnitAI);
}
}
- if (formedEnts.length == 0)
- {
+ if (formedEnts.length == 0) {
// No units support the foundation - return all the others
return nonformedUnitAIs;
}
@@ -1453,15 +1329,13 @@
var formation = ExtractFormations(formedEnts);
var formationUnitAIs = [];
- if (formation.ids.length == 1)
- {
+ if (formation.ids.length == 1) {
// Selected units either belong to this formation or have no formation
// Check that all its members are selected
var fid = formation.ids[0];
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation && cmpFormation.GetMemberCount() == formation.members[fid].length
- && cmpFormation.GetMemberCount() == formation.entities.length)
- {
+ && cmpFormation.GetMemberCount() == formation.entities.length) {
cmpFormation.DeleteTwinFormations();
// The whole formation was selected, so reuse its controller for this command
formationUnitAIs = [Engine.QueryInterface(+fid, IID_UnitAI)];
@@ -1470,13 +1344,11 @@
}
}
- if (!formationUnitAIs.length)
- {
+ if (!formationUnitAIs.length) {
// We need to give the selected units a new formation controller
// Remove selected units from their current formation
- for (var fid in formation.members)
- {
+ for (var fid in formation.members) {
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation);
if (cmpFormation)
cmpFormation.RemoveMembers(formation.members[fid]);
@@ -1486,24 +1358,18 @@
var formationSeparation = 60;
var clusters = ClusterEntities(formation.entities, formationSeparation);
var formationEnts = [];
- for (let cluster of clusters)
- {
- if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate))
- {
+ for (let cluster of clusters) {
+ if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate)) {
// get the most recently used formation, or default to line closed
var lastFormationTemplate = undefined;
- for (let ent of cluster)
- {
+ for (let ent of cluster) {
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
- if (cmpUnitAI)
- {
+ if (cmpUnitAI) {
var template = cmpUnitAI.GetLastFormationTemplate();
- if (lastFormationTemplate === undefined)
- {
+ if (lastFormationTemplate === undefined) {
lastFormationTemplate = template;
}
- else if (lastFormationTemplate != template)
- {
+ else if (lastFormationTemplate != template) {
lastFormationTemplate = undefined;
break;
}
@@ -1537,8 +1403,7 @@
/**
* Group a list of entities in clusters via single-links
*/
-function ClusterEntities(ents, separationDistance)
-{
+function ClusterEntities(ents, separationDistance) {
var clusters = [];
if (!ents.length)
return clusters;
@@ -1548,8 +1413,7 @@
// triangular matrix with the (squared) distances between the different clusters
// the other half is not initialised
var matrix = [];
- for (let i = 0; i < ents.length; ++i)
- {
+ for (let i = 0; i < ents.length; ++i) {
matrix[i] = [];
clusters.push([ents[i]]);
var cmpPosition = Engine.QueryInterface(ents[i], IID_Position);
@@ -1557,15 +1421,14 @@
for (let j = 0; j < i; ++j)
matrix[i][j] = positions[i].distanceToSquared(positions[j]);
}
- while (clusters.length > 1)
- {
+ while (clusters.length > 1) {
// search two clusters that are closer than the required distance
var closeClusters = undefined;
for (var i = matrix.length - 1; i >= 0 && !closeClusters; --i)
for (var j = i - 1; j >= 0 && !closeClusters; --j)
if (matrix[i][j] < distSq)
- closeClusters = [i,j];
+ closeClusters = [i, j];
// if no more close clusters found, just return all found clusters so far
if (!closeClusters)
@@ -1577,8 +1440,7 @@
// calculate the minimum distance between the new cluster and all other remaining
// clusters by taking the minimum of the two distances.
var distances = [];
- for (let i = 0; i < clusters.length; ++i)
- {
+ for (let i = 0; i < clusters.length; ++i) {
if (i == closeClusters[1] || i == closeClusters[0])
continue;
var dist1 = matrix[closeClusters[1]][i] || matrix[i][closeClusters[1]];
@@ -1587,16 +1449,15 @@
}
// remove the rows and columns in the matrix for the merged clusters,
// and the clusters themselves from the cluster list
- clusters.splice(closeClusters[0],1);
- clusters.splice(closeClusters[1],1);
- matrix.splice(closeClusters[0],1);
- matrix.splice(closeClusters[1],1);
- for (let i = 0; i < matrix.length; ++i)
- {
+ clusters.splice(closeClusters[0], 1);
+ clusters.splice(closeClusters[1], 1);
+ matrix.splice(closeClusters[0], 1);
+ matrix.splice(closeClusters[1], 1);
+ for (let i = 0; i < matrix.length; ++i) {
if (matrix[i].length > closeClusters[0])
- matrix[i].splice(closeClusters[0],1);
+ matrix[i].splice(closeClusters[0], 1);
if (matrix[i].length > closeClusters[1])
- matrix[i].splice(closeClusters[1],1);
+ matrix[i].splice(closeClusters[1], 1);
}
// add a new row of distances to the matrix and the new cluster
clusters.push(newCluster);
@@ -1605,8 +1466,7 @@
return clusters;
}
-function GetFormationRequirements(formationTemplate)
-{
+function GetFormationRequirements(formationTemplate) {
var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(formationTemplate);
if (!template.Formation)
return false;
@@ -1615,8 +1475,7 @@
}
-function CanMoveEntsIntoFormation(ents, formationTemplate)
-{
+function CanMoveEntsIntoFormation(ents, formationTemplate) {
// TODO: should check the player's civ is allowed to use this formation
// See simulation/components/Player.js GetFormations() for a list of all allowed formations
@@ -1625,8 +1484,7 @@
return false;
var count = 0;
- for (let ent of ents)
- {
+ for (let ent of ents) {
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (!cmpIdentity || !cmpIdentity.CanUseFormation(formationTemplate))
continue;
@@ -1642,8 +1500,7 @@
* returns: true if the entity is valid and owned by the player
* or control all units is activated, else false
*/
-function CanControlUnit(entity, player, controlAll)
-{
+function CanControlUnit(entity, player, controlAll) {
return IsOwnedByPlayer(player, entity) || controlAll;
}
@@ -1653,24 +1510,21 @@
* or the entity is owned by an mutualAlly
* or control all units is activated, else false
*/
-function CanControlUnitOrIsAlly(entity, player, controlAll)
-{
+function CanControlUnitOrIsAlly(entity, player, controlAll) {
return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity) || controlAll;
}
/**
* Filter entities which the player can control
*/
-function FilterEntityList(entities, player, controlAll)
-{
+function FilterEntityList(entities, player, controlAll) {
return entities.filter(ent => CanControlUnit(ent, player, controlAll));
}
/**
* Filter entities which the player can control or are mutualAlly
*/
-function FilterEntityListWithAllies(entities, player, controlAll)
-{
+function FilterEntityListWithAllies(entities, player, controlAll) {
return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
}
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -135,4 +135,17 @@
false
false
+
+
+ special/rallypoint
+ art/textures/misc/rallypoint_line.png
+ art/textures/misc/rallypoint_line_mask.png
+ 0.2
+
+
+ square
+ round
+ default
+ default
+
Index: ps/trunk/source/simulation2/TypeList.h
===================================================================
--- ps/trunk/source/simulation2/TypeList.h
+++ ps/trunk/source/simulation2/TypeList.h
@@ -193,3 +193,6 @@
INTERFACE(WaterManager)
COMPONENT(WaterManager)
+
+INTERFACE(WayPointRenderer)
+COMPONENT(WayPointRenderer)
Index: ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
+++ ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
@@ -91,16 +91,16 @@
/// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
/// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
/// network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
- std::vector m_RallyPoints;
+ std::deque m_RallyPoints;
/// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
- std::vector > m_Path;
+ std::deque > m_Path;
/// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
std::deque > m_VisibilitySegments;
bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
bool m_SmoothPath; ///< Smooth the path before rendering?
- std::vector m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
+ std::deque m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
size_t m_LastMarkerCount;
player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
std::wstring m_MarkerTemplate; ///< Template name of the rally point markers.
@@ -120,11 +120,11 @@
/// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
/// dashes for segments that are inside the SoD.
- std::vector > m_TexturedOverlayLines;
+ std::list > m_TexturedOverlayLines;
/// Draw little overlay circles to indicate where the exact path points are?
bool m_EnableDebugNodeOverlay;
- std::vector > m_DebugNodeOverlays;
+ std::deque > m_DebugNodeOverlays;
public:
@@ -238,7 +238,7 @@
break;
case MT_Destroy:
{
- for (std::vector::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
+ for (std::deque::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
{
if (*it != INVALID_ENTITY)
{
@@ -742,11 +742,16 @@
// pass (which is only sensible).
while (index >= m_TexturedOverlayLines.size())
{
- std::vector tmp;
+ std::list tmp;
m_TexturedOverlayLines.push_back(tmp);
}
- m_TexturedOverlayLines[index].clear();
+ std::list >::iterator iter = m_TexturedOverlayLines.begin();
+ size_t count = index;
+ while(count--)
+ iter++;
+ (*iter).clear();
+
if (m_Path[index].size() < 2)
return;
@@ -785,7 +790,7 @@
overlayLine.m_Coords.push_back(m_Path[index][j].Y);
}
- m_TexturedOverlayLines[index].push_back(overlayLine);
+ (*iter).push_back(overlayLine);
}
else
{
@@ -857,7 +862,7 @@
dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
}
- m_TexturedOverlayLines[index].push_back(dashOverlay);
+ (*iter).push_back(dashOverlay);
}
}
@@ -868,7 +873,7 @@
{
while (index >= m_DebugNodeOverlays.size())
{
- std::vector tmp;
+ std::deque tmp;
m_DebugNodeOverlays.push_back(tmp);
}
for (size_t j = 0; j < m_Path[index].size(); ++j)
@@ -1258,12 +1263,13 @@
void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector)
{
// we only get here if the rally point is set and should be displayed
- for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
+ for (std::list >::iterator it = m_TexturedOverlayLines.begin();
+ it != m_TexturedOverlayLines.end(); ++it)
{
- for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j)
+ for (std::list::iterator iter = (*it).begin(); iter != (*it).end(); ++iter)
{
- if (!m_TexturedOverlayLines[i][j].m_Coords.empty())
- collector.Submit(&m_TexturedOverlayLines[i][j]);
+ if (!(*iter).m_Coords.empty())
+ collector.Submit(&(*iter));
}
}
Index: ps/trunk/source/simulation2/components/CCmpWayPointRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpWayPointRenderer.cpp
+++ ps/trunk/source/simulation2/components/CCmpWayPointRenderer.cpp
@@ -0,0 +1,903 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+#include "ICmpWayPointRenderer.h"
+
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
+#include "simulation2/helpers/Geometry.h"
+#include "simulation2/system/Component.h"
+#include "ps/CLogger.h"
+#include "graphics/Overlay.h"
+#include "graphics/TextureManager.h"
+#include "renderer/Renderer.h"
+// TODO refactor
+// Maybe create a common ancestor for this and RallyPointRenderer FlagPointRenderer
+struct SVisibilitySegment
+{
+ bool m_Visible;
+ size_t m_StartIndex;
+ size_t m_EndIndex; // inclusive
+ SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
+ : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
+ {}
+ bool operator==(const SVisibilitySegment& other) const
+ {
+ return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
+ }
+ bool operator!=(const SVisibilitySegment& other) const
+ {
+ return !(*this == other);
+ }
+ bool IsSinglePoint()
+ {
+ return (m_StartIndex == m_EndIndex);
+ }
+};
+class CCmpWayPointRenderer : public ICmpWayPointRenderer
+{
+ // import some types for less verbosity
+ typedef ICmpRangeManager::CLosQuerier CLosQuerier;
+ typedef SOverlayTexturedLine::LineCapType LineCapType;
+public:
+ static void ClassInit(CComponentManager& componentManager)
+ {
+ componentManager.SubscribeToMessageType(MT_RenderSubmit);
+ componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_TurnStart);
+ componentManager.SubscribeToMessageType(MT_Destroy);
+ componentManager.SubscribeToMessageType(MT_PositionChanged);
+ }
+
+ DEFAULT_COMPONENT_ALLOCATOR(WayPointRenderer)
+
+protected:
+ /// Display position of the way points. Note that this are merely the display positions; they not necessarily the same as the
+ /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
+ /// instantaneously rendering the way point markers/lines when the user sets one in-game (instead of waiting until the
+ /// network-synchronization code sets it on the WayPoint component, which might take up to half a second).
+ std::deque m_WayPoints;
+ // TODO are we using the pathfinder? / should we be using it that much? TODO wait for the pathfinder rewrite and use hopefully exported data
+ // from that to make a curved path that uses the same data as the moving unit.
+ /// Full path to the way points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
+ std::deque > m_Path;
+ /// Visibility segments of the way point paths; splits the path into SoD/non-SoD segments.
+ std::deque > m_VisibilitySegments;
+ bool m_Displayed; ///< Should we render the way points and the path lines? (set from JS when e.g. the unit is selected/deselected)
+ std::deque m_MarkerEntityIds; ///< Entity IDs of the way point markers.
+ size_t m_LastMarkerCount;
+ player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
+ std::wstring m_MarkerTemplate; ///< Template name of the way point markers.
+ /// Marker connector line settings (loaded from XML)
+ float m_LineThickness;
+ CColor m_LineColor;
+ CColor m_LineDashColor;
+ LineCapType m_LineStartCapType;
+ LineCapType m_LineEndCapType;
+ std::wstring m_LineTexturePath;
+ std::wstring m_LineTextureMaskPath;
+ std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
+ std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path.
+ CTexturePtr m_Texture;
+ CTexturePtr m_TextureMask;
+ /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
+ /// dashes for segments that are inside the SoD.
+ std::list > m_TexturedOverlayLines;
+public:
+ static std::string GetSchema()
+ {
+ return
+ "Displays a way point marker where units will go to when tasked to move"
+ ""
+ "special/WayPoint"
+ "0.75"
+ "round"
+ "square"
+ ""
+ ""
+ "default"
+ "default"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
+ }
+
+ virtual void Init(const CParamNode& paramNode);
+
+ virtual void Deinit()
+ {
+ }
+
+ virtual void Serialize(ISerializer& UNUSED(serialize))
+ {
+ // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
+ }
+
+ virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
+ {
+ Init(paramNode);
+ }
+
+ virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
+ {
+ switch (msg.GetType())
+ {
+ case MT_RenderSubmit:
+ {
+ if (m_Displayed && IsSet())
+ {
+ const CMessageRenderSubmit& msgData = static_cast (msg);
+ RenderSubmit(msgData.collector);
+ }
+ }
+ break;
+ case MT_OwnershipChanged:
+ {
+ UpdateMarkers(); // update marker variation to new player's civilization
+ }
+ break;
+ case MT_TurnStart:
+ {
+ UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
+ }
+ break;
+ case MT_Destroy:
+ {
+ for (std::deque::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
+ {
+ if (*it != INVALID_ENTITY)
+ {
+ GetSimContext().GetComponentManager().DestroyComponentsSoon(*it);
+ *it = INVALID_ENTITY;
+ }
+ }
+ }
+ break;
+ case MT_PositionChanged:
+ {
+ RecomputeWayPointPath_wrapper(0);
+ }
+ break;
+ }
+ }
+
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos)
+ {
+ AddPosition(pos, false);
+ }
+ // Rename to Unshift???
+ virtual void AddPositionFront(const CFixedVector2D& pos)
+ {
+ m_WayPoints.push_front(pos);
+ UpdateMarkers();
+ // TODO don't recompute everything (maybe just shift everything one back? and recompute first only
+ RecomputeAllWayPointPaths();
+ }
+ virtual void SetPosition(const CFixedVector2D& pos)
+ {
+ if (!(m_WayPoints.size() == 1 && m_WayPoints.front() == pos))
+ {
+ m_WayPoints.clear();
+ AddPosition(pos, true);
+ }
+ }
+ virtual void SetDisplayed(bool displayed)
+ {
+ if (m_Displayed != displayed)
+ {
+ m_Displayed = displayed;
+ // move the markers out of oblivion and back into the real world, or vice-versa
+ UpdateMarkers();
+
+ // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
+ // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
+ // while this way point was not being displayed.
+ UpdateOverlayLines();
+ }
+ }
+ virtual void Shift()
+ {
+ LOGERROR("m_WayPoints %d \tm_Path %d \tm_VisibilitySegments %d \tm_TexturedOverlayLines %d\n",m_WayPoints.size(),m_Path.size(),m_VisibilitySegments.size(),m_TexturedOverlayLines.size());
+ // Still not sure why size is sometimes <= 0
+ if (m_WayPoints.size() > 0)
+ {
+ m_WayPoints.pop_front();
+ m_Path.pop_front();
+ m_VisibilitySegments.pop_front();
+ m_TexturedOverlayLines.pop_front();
+ // TODO should we skip this as this is already done on MT_PositionChanged?
+ RecomputeWayPointPath_wrapper(0);
+ UpdateMarkers();
+ }
+ }
+private:
+ /**
+ * Helper function for AddPosition_wrapper and SetPosition.
+ */
+ void AddPosition(CFixedVector2D pos, bool recompute)
+ {
+ m_WayPoints.push_back(pos);
+ UpdateMarkers();
+ if (recompute)
+ RecomputeAllWayPointPaths();
+ else
+ RecomputeWayPointPath_wrapper(m_WayPoints.size()-1);
+ }
+ /**
+ * Returns true iff at least one display way point is set; i.e., if we have a point to render our marker/line at.
+ */
+ bool IsSet()
+ {
+ return !m_WayPoints.empty();
+ }
+ /**
+ * Repositions the way point markers; moves them outside of the world (ie. hides them), or positions them at the currently
+ * set way points. Also updates the actor's variation according to the entity's current owning player's civilization.
+ *
+ * Should be called whenever either the position of a way point changes (including whether it is set or not), or the display
+ * flag changes, or the ownership of the entity changes.
+ */
+ void UpdateMarkers();
+ /**
+ * Recomputes all the full paths from this entity to the way point and from the way point to the next, and does all the necessary
+ * post-processing to make them prettier.
+ *
+ * Should be called whenever all way points' position changes.
+ */
+ void RecomputeAllWayPointPaths();
+ /**
+ * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
+ *
+ * Should be called whenever either the starting position or the way point's position changes.
+ */
+ void RecomputeWayPointPath_wrapper(size_t index);
+ /**
+ * Recomputes the full path from this entity/the previous way point to the next way point, and does all the necessary
+ * post-processing to make it prettier. This doesn't check if we have a valid position or if a way point is set.
+ *
+ * You shouldn't need to call this method directly.
+ */
+ void RecomputeWayPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
+ /**
+ * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
+ * match if necessary. Does nothing if the way point lines are not currently set to be displayed, or if no way point is set.
+ */
+ void UpdateOverlayLines();
+ /**
+ * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
+ * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
+ * does nothing.
+ */
+ void ConstructAllOverlayLines();
+ /**
+ * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
+ * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
+ */
+ void ConstructOverlayLines(size_t index);
+ /**
+ * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
+ * building/previous way point to way point. Used to construct the overlay line segments and track changes to the SoD.
+ */
+ void GetVisibilitySegments(std::deque& out, size_t index);
+ /**
+ * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
+ * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
+ * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a way point path to curve and bend when it
+ * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
+ *
+ * @p coords array of path coordinates to simplify
+ * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
+ * single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that
+ * at most 3 consecutive node links will be joined into a single link.
+ * @p floating whether to consider nodes who are under the water level as floating on top of the water
+ */
+ void ReduceSegmentsByVisibility(std::deque& coords, unsigned maxSegmentLinks = 0, bool floating = true);
+ /**
+ * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
+ * segments. You should not have to call this method directly.
+ */
+ static void MergeVisibilitySegments(std::deque& segments);
+ void RenderSubmit(SceneCollector& collector);
+};
+
+REGISTER_COMPONENT_TYPE(WayPointRenderer)
+
+void CCmpWayPointRenderer::Init(const CParamNode& paramNode)
+{
+ m_Displayed = false;
+ m_LastOwner = INVALID_PLAYER;
+ m_LastMarkerCount = 0;
+ // ---------------------------------------------------------------------------------------------
+ // load some XML configuration data (schema guarantees that all these nodes are valid)
+ m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
+ const CParamNode& lineColor = paramNode.GetChild("LineColour");
+ m_LineColor = CColor(
+ lineColor.GetChild("@r").ToInt()/255.f,
+ lineColor.GetChild("@g").ToInt()/255.f,
+ lineColor.GetChild("@b").ToInt()/255.f,
+ 1.f
+ );
+ const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour");
+ m_LineDashColor = CColor(
+ lineDashColor.GetChild("@r").ToInt()/255.f,
+ lineDashColor.GetChild("@g").ToInt()/255.f,
+ lineDashColor.GetChild("@b").ToInt()/255.f,
+ 1.f
+ );
+ m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
+ m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
+ m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
+ m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
+ m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
+ m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();
+ m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
+ // ---------------------------------------------------------------------------------------------
+ // load some textures
+ if (CRenderer::IsInitialised())
+ {
+ CTextureProperties texturePropsBase(m_LineTexturePath);
+ texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
+ texturePropsBase.SetMaxAnisotropy(4.f);
+ m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
+ CTextureProperties texturePropsMask(m_LineTextureMaskPath);
+ texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
+ texturePropsMask.SetMaxAnisotropy(4.f);
+ m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
+ }
+}
+
+void CCmpWayPointRenderer::UpdateMarkers()
+{
+ player_id_t previousOwner = m_LastOwner;
+ for (size_t i = 0; i < m_WayPoints.size(); ++i)
+ {
+ if (i >= m_MarkerEntityIds.size())
+ m_MarkerEntityIds.push_back(INVALID_ENTITY);
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
+ {
+ // no marker exists yet, create one first
+ CComponentManager& componentMgr = GetSimContext().GetComponentManager();
+ // allocate a new entity for the marker
+ if (!m_MarkerTemplate.empty())
+ {
+ m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
+ if (m_MarkerEntityIds[i] != INVALID_ENTITY)
+ m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
+ }
+ }
+ // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
+ LOGERROR("Failed to create way point marker entity");
+ CmpPtr cmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
+ if (cmpPosition)
+ {
+ if (m_Displayed && IsSet())
+ {
+ cmpPosition->JumpTo(m_WayPoints[i].X, m_WayPoints[i].Y);
+ }
+ else
+ {
+ cmpPosition->MoveOutOfWorld(); // hide it
+ }
+ }
+ // set way point flag selection based on player civilization
+ CmpPtr cmpOwnership(GetSimContext(), GetEntityId());
+ if (cmpOwnership)
+ {
+ player_id_t ownerId = cmpOwnership->GetOwner();
+ if (ownerId != INVALID_PLAYER && (ownerId != previousOwner || m_LastMarkerCount <= i))
+ {
+ m_LastOwner = ownerId;
+ CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
+ // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
+ // (we can't rely on component initialization order in Init())
+ if (cmpPlayerManager)
+ {
+ CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
+ if (cmpPlayer)
+ {
+ CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
+ if (cmpVisualActor)
+ {
+ cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8());
+ }
+ }
+ }
+ }
+ }
+ }
+ // Hide currently unused markers
+ for (size_t i = m_WayPoints.size(); i < m_LastMarkerCount; ++i)
+ {
+ CmpPtr cmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
+ if (cmpPosition)
+ cmpPosition->MoveOutOfWorld();
+ }
+ m_LastMarkerCount = m_WayPoints.size();
+}
+
+void CCmpWayPointRenderer::RecomputeAllWayPointPaths()
+{
+ m_Path.clear();
+ m_VisibilitySegments.clear();
+ m_TexturedOverlayLines.clear();
+ if (!IsSet())
+ return; // no use computing a path if the way point isn't set
+ CmpPtr cmpPosition(GetSimContext(), GetEntityId());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return; // no point going on if this entity doesn't have a position or is outside of the world
+ // Not used
+ CmpPtr cmpFootprint(GetSimContext(), GetEntityId());
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+ for (size_t i = 0; i < m_WayPoints.size(); ++i)
+ {
+ RecomputeWayPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
+ }
+}
+
+void CCmpWayPointRenderer::RecomputeWayPointPath_wrapper(size_t index)
+{
+ if (!IsSet())
+ return; // no use computing a path if the wayw point isn't set
+ CmpPtr cmpPosition(GetSimContext(), GetEntityId());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return; // no point going on if this entity doesn't have a position or is outside of the world
+ // Not used
+ CmpPtr cmpFootprint(GetSimContext(), GetEntityId());
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+ RecomputeWayPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
+}
+
+void CCmpWayPointRenderer::RecomputeWayPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& UNUSED(cmpFootprint), CmpPtr UNUSED(cmpPathfinder))
+{
+ while (index >= m_Path.size())
+ {
+ std::deque tmp;
+ m_Path.push_back(tmp);
+ }
+ m_Path[index].clear();
+ while (index >= m_VisibilitySegments.size())
+ {
+ std::deque tmp;
+ m_VisibilitySegments.push_back(tmp);
+ }
+ m_VisibilitySegments[index].clear();
+ entity_pos_t pathStartX;
+ entity_pos_t pathStartY;
+ if (index == 0)
+ {
+ pathStartX = cmpPosition->GetPosition2D().X;
+ pathStartY = cmpPosition->GetPosition2D().Y;
+ }
+ else
+ {
+ pathStartX = m_WayPoints[index-1].X;
+ pathStartY = m_WayPoints[index-1].Y;
+ }
+ // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
+ // list of waypoints (i.e. a Path) from the building/previous way point to the goal, where each
+ // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
+ // TODO update comment
+ m_Path[index].push_back(CVector2D(m_WayPoints[index].X.ToFloat(), m_WayPoints[index].Y.ToFloat()));
+ m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat()));
+ // find which point is the last visible point before going into the SoD, so we have a point to compare to on the next turn
+ GetVisibilitySegments(m_VisibilitySegments[index], index);
+ // build overlay lines for the new path
+ ConstructOverlayLines(index);
+}
+
+void CCmpWayPointRenderer::ConstructAllOverlayLines()
+{
+ m_TexturedOverlayLines.clear();
+ for (size_t i = 0; i < m_Path.size(); ++i)
+ ConstructOverlayLines(i);
+}
+
+void CCmpWayPointRenderer::ConstructOverlayLines(size_t index)
+{
+ // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
+ // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every
+ // pass (which is only sensible).
+ while (index >= m_TexturedOverlayLines.size())
+ {
+ std::list tmp;
+ m_TexturedOverlayLines.push_back(tmp);
+ }
+ std::list >::iterator iter = m_TexturedOverlayLines.begin();
+ size_t count = index;
+ while(count--)
+ iter++;
+ (*iter).clear();
+ if (m_Path[index].size() < 2)
+ return;
+ CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+ LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
+ for (std::deque::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
+ {
+ const SVisibilitySegment& segment = (*it);
+ if (segment.m_Visible)
+ {
+ // does this segment border on the building or way point flag on either side?
+ bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
+ bool bordersFlag = (segment.m_StartIndex == 0);
+ // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
+ SOverlayTexturedLine overlayLine;
+ overlayLine.m_Thickness = m_LineThickness;
+ overlayLine.m_SimContext = &GetSimContext();
+ overlayLine.m_TextureBase = m_Texture;
+ overlayLine.m_TextureMask = m_TextureMask;
+ overlayLine.m_Color = m_LineColor;
+ overlayLine.m_Closed = false;
+ // we should take care to only use m_LineXCap for the actual end points at the building and the way point; any intermediate
+ // end points (i.e., that border a dashed segment) should have the dashed cap
+ // the path line is actually in reverse order as well, so let's swap out the start and end caps
+ overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
+ overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
+ overlayLine.m_AlwaysVisible = true;
+ // push overlay line coordinates
+ ENSURE(segment.m_EndIndex > segment.m_StartIndex);
+ for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
+ {
+ overlayLine.m_Coords.push_back(m_Path[index][j].X);
+ overlayLine.m_Coords.push_back(m_Path[index][j].Y);
+ }
+ (*iter).push_back(overlayLine);
+ }
+ else
+ {
+ // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
+ std::vector straightLine;
+ straightLine.push_back(m_Path[index][segment.m_StartIndex]);
+ straightLine.push_back(m_Path[index][segment.m_EndIndex]);
+ // We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed
+ // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
+ // to fit exactly.
+ float maxDashSize = 3.f;
+ float maxClearSize = 3.f;
+
+ float dashSize = maxDashSize;
+ float clearSize = maxClearSize;
+ float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
+ float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points
+ // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
+ // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
+ int numFitUnmodified = floor(distance/(dashSize + clearSize));
+ float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
+ // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
+ // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
+ // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
+ // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
+ // length and the clear's length.
+ // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
+ numFitUnmodified = std::max(1, numFitUnmodified);
+ float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
+ dashSize += pairDashRatio * pairwiseLengthDifference;
+ clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
+ // ------------------------------------------------------------------------------------------------
+ SDashedLine dashedLine;
+ SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
+ // build overlay lines for dashes
+ size_t numDashes = dashedLine.m_StartIndices.size();
+ for (size_t i=0; i < numDashes; i++)
+ {
+ SOverlayTexturedLine dashOverlay;
+ dashOverlay.m_Thickness = m_LineThickness;
+ dashOverlay.m_SimContext = &GetSimContext();
+ dashOverlay.m_TextureBase = m_Texture;
+ dashOverlay.m_TextureMask = m_TextureMask;
+ dashOverlay.m_Color = m_LineDashColor;
+ dashOverlay.m_Closed = false;
+ dashOverlay.m_StartCapType = dashesLineCapType;
+ dashOverlay.m_EndCapType = dashesLineCapType;
+ dashOverlay.m_AlwaysVisible = true;
+ // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
+ size_t dashStartIndex = dashedLine.m_StartIndices[i];
+ size_t dashEndIndex = dashedLine.GetEndIndex(i);
+ ENSURE(dashEndIndex > dashStartIndex);
+ for (size_t n = dashStartIndex; n < dashEndIndex; n++)
+ {
+ dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X);
+ dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
+ }
+ (*iter).push_back(dashOverlay);
+ }
+
+ }
+ }
+}
+
+void CCmpWayPointRenderer::UpdateOverlayLines()
+{
+ // We should only do this if the way point is currently being displayed and set inside the world, otherwise it's a massive
+ // waste of time to calculate all this stuff (this method is called every turn)
+ if (!m_Displayed || !IsSet())
+ return;
+ // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
+ std::deque > newVisibilitySegments;
+ for (size_t i = 0; i < m_Path.size(); ++i)
+ {
+ std::deque tmp;
+ newVisibilitySegments.push_back(tmp);
+ GetVisibilitySegments(newVisibilitySegments[i], i);
+ }
+ // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
+ if (m_VisibilitySegments.size() != newVisibilitySegments.size())
+ {
+ m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
+ ConstructAllOverlayLines();
+ }
+ else
+ {
+ for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
+ {
+ if (m_VisibilitySegments[i] != newVisibilitySegments[i])
+ {
+ // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
+ // change, only the overlay lines we construct from it.
+ m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
+ ConstructOverlayLines(i);
+ }
+ }
+ }
+}
+// TODO remove pathfinder?; or remove this as a whole?
+void CCmpWayPointRenderer::ReduceSegmentsByVisibility(std::deque& coords, unsigned maxSegmentLinks, bool floating)
+{
+ CmpPtr cmpPathFinder(GetSimContext(), SYSTEM_ENTITY);
+ CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+ CmpPtr cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
+ ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager);
+ if (coords.size() < 3)
+ return;
+ // The basic idea is this: starting from a base node, keep checking each individual point along the path to see if there's a visible
+ // line between it and the base point. If so, keep going, otherwise, make the last visible point the new base node and start the same
+ // process from there on until the entire line is checked. The output is the array of base nodes.
+ std::deque newCoords;
+ StationaryOnlyObstructionFilter obstructionFilter;
+ entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
+ pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
+ newCoords.push_back(coords[0]); // save the first base node
+ size_t baseNodeIdx = 0;
+ size_t curNodeIdx = 1;
+
+ float baseNodeY;
+ entity_pos_t baseNodeX;
+ entity_pos_t baseNodeZ;
+ // set initial base node coords
+ baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
+ baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
+ baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
+ if (floating)
+ baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
+ while (curNodeIdx < coords.size())
+ {
+ ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
+ entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
+ entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
+ float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
+ if (floating)
+ curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
+ // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
+ bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
+ // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
+ // roughly the same terrain elevation
+ curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning
+ if (maxSegmentLinks > 0)
+ // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
+ curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
+ if (!curNodeVisible)
+ {
+ // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
+ // hence become the new base node for further iterations.
+ // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
+ // we should take care not to stay stuck at the current base node
+ if (curNodeIdx > baseNodeIdx + 1)
+ {
+ baseNodeIdx = curNodeIdx - 1;
+ }
+ else
+ {
+ // curNodeIdx == baseNodeIdx + 1
+ baseNodeIdx = curNodeIdx;
+ curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
+ }
+ newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
+ // update base node coordinates
+ baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
+ baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
+ baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
+ if (floating)
+ baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
+ }
+ curNodeIdx++;
+ }
+ // we always need to add the last point back to the array; if e.g. all the points up to the last one are all visible from the current
+ // base node, then the loop above just ends and no endpoint is ever added to the list.
+ ENSURE(curNodeIdx == coords.size());
+ newCoords.push_back(coords[coords.size() - 1]);
+ coords.swap(newCoords);
+}
+
+void CCmpWayPointRenderer::GetVisibilitySegments(std::deque& out, size_t index)
+{
+ out.clear();
+ if (m_Path[index].size() < 2)
+ return;
+ CmpPtr cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY);
+ player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
+ CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
+ // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
+ // a new one at the next point.
+ bool lastVisible = losQuerier.IsExplored(
+ (fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
+ (fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
+ );
+ size_t curSegmentStartIndex = 0; // starting node index of the current segment
+ for (size_t k = 1; k < m_Path[index].size(); ++k)
+ {
+ // grab tile indices for this coord
+ int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ bool nodeVisible = losQuerier.IsExplored(i, j);
+ if (nodeVisible != lastVisible)
+ {
+ // visibility changed; write out the segment that was just completed and get ready for the new one
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
+ //curSegmentStartIndex = k; // new segment starts here
+ curSegmentStartIndex = k - 1;
+ lastVisible = nodeVisible;
+ }
+ }
+ // terminate the last segment
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
+ MergeVisibilitySegments(out);
+}
+
+void CCmpWayPointRenderer::MergeVisibilitySegments(std::deque& segments)
+{
+ // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
+ // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
+ // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
+ // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
+ size_t numSegments = segments.size();
+ // WARNING: FOR LOOP TRICKERY AHEAD!
+ for (size_t i = 1; i < numSegments - 1;)
+ {
+ SVisibilitySegment& segment = segments[i];
+ if (segment.IsSinglePoint())
+ {
+ // since the segments' visibility alternates, the surrounding ones should have the same visibility
+ ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
+ segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
+ segments.erase(segments.begin() + i); // erase this segment ...
+ segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
+ numSegments -= 2; // we removed 2 segments, so update the loop condition
+ // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
+ // at position i; so don't increment i here
+ }
+ else
+ {
+ ++i;
+ }
+ }
+ ENSURE(numSegments == segments.size());
+ // check to see if the first segment needs to be merged with its neighbour
+ if (segments.size() >= 2 && segments[0].IsSinglePoint())
+ {
+ int firstSegmentStartIndex = segments.front().m_StartIndex;
+ ENSURE(firstSegmentStartIndex == 0);
+ ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
+
+ segments.erase(segments.begin());
+ segments.front().m_StartIndex = firstSegmentStartIndex;
+ }
+ // check to see if the last segment needs to be merged with its neighbour
+ if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
+ {
+ int lastSegmentEndIndex = segments.back().m_EndIndex;
+ ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
+ segments.erase(segments.end());
+ segments.back().m_EndIndex = lastSegmentEndIndex;
+ }
+ // --------------------------------------------------------------------------------------------------------
+ // at this point, every segment should have at least 2 points
+ for (size_t i = 0; i < segments.size(); ++i)
+ {
+ ENSURE(!segments[i].IsSinglePoint());
+ ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
+ }
+}
+
+void CCmpWayPointRenderer::RenderSubmit(SceneCollector& collector)
+{
+ // we only get here if the way point is set and should be displayed
+ for (std::list >::iterator it = m_TexturedOverlayLines.begin();
+ it != m_TexturedOverlayLines.end(); ++it)
+ {
+ for (std::list::iterator iter = (*it).begin(); iter != (*it).end(); ++iter)
+ {
+ if (!(*iter).m_Coords.empty())
+ collector.Submit(&(*iter));
+ }
+ }
+}
Index: ps/trunk/source/simulation2/components/ICmpRallyPointRenderer.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpRallyPointRenderer.h
+++ ps/trunk/source/simulation2/components/ICmpRallyPointRenderer.h
@@ -24,7 +24,7 @@
/**
* Rally Point.
- * Holds the position of a unit's rally points, and renders them to screen.
+ * Holds the position(s) of a unit's rally points, and renders them to screen.
*/
class ICmpRallyPointRenderer : public IComponent
{
Index: ps/trunk/source/simulation2/components/ICmpWayPointRenderer.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpWayPointRenderer.h
+++ ps/trunk/source/simulation2/components/ICmpWayPointRenderer.h
@@ -0,0 +1,48 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPWAYPOINT
+#define INCLUDED_ICMPWAYPOINT
+
+#include "maths/FixedVector2D.h"
+#include "simulation2/helpers/Position.h"
+#include "simulation2/system/Interface.h"
+
+/**
+ * Way Point.
+ * Holds the position(s) of a unit's way points, and renders them to screen.
+ */
+class ICmpWayPointRenderer : public IComponent
+{
+public:
+ /// Sets whether the way point marker and line should be displayed.
+ virtual void SetDisplayed(bool displayed) = 0;
+ /// Sets the position at which the way point marker should be displayed.
+ /// Discards all previous positions
+ virtual void SetPosition(const CFixedVector2D& position) = 0;
+ /// Add another position at which a marker should be displayed, connected
+ /// to the previous one.
+ virtual void AddPosition_wrapper(const CFixedVector2D& position) = 0;
+ /// At a position at which a marker should be displayed, connected to
+ /// the previous first.
+ virtual void AddPositionFront(const CFixedVector2D& position) = 0;
+ /// Remove the first way point (as we have reached it)
+ virtual void Shift() = 0;
+
+ DECLARE_INTERFACE_TYPE(WayPointRenderer)
+};
+#endif // INCLUDED_ICMPWAYPOINT
Index: ps/trunk/source/simulation2/components/ICmpWayPointRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpWayPointRenderer.cpp
+++ ps/trunk/source/simulation2/components/ICmpWayPointRenderer.cpp
@@ -0,0 +1,31 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpWayPointRenderer.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+class CFixedVector2D;
+
+BEGIN_INTERFACE_WRAPPER(WayPointRenderer)
+DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpWayPointRenderer, SetDisplayed, bool)
+DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpWayPointRenderer, SetPosition, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_1("AddPosition", void, ICmpWayPointRenderer, AddPosition_wrapper, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_1("AddPositionFront", void, ICmpWayPointRenderer, AddPositionFront, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_0("Shift", void, ICmpWayPointRenderer, Shift)
+END_INTERFACE_WRAPPER(WayPointRenderer)