Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -273,6 +273,9 @@
if (template.BuildRestrictions.Distance.MaxDistance)
ret.buildRestrictions.distance.max = getEntityValue("BuildRestrictions/Distance/MaxDistance");
}
+
+ if (template.BuildRestrictions.Sockets)
+ ret.buildRestrictions.sockets = template.BuildRestrictions.Sockets._string.split(/\s+/);
}
if (template.TrainingRestrictions)
Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js
+++ binaries/data/mods/public/gui/session/input.js
@@ -131,11 +131,15 @@
{
if (placementSupport.template && placementSupport.position)
{
- var result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
+ let result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementSupport.template,
- "x": placementSupport.position.x,
- "z": placementSupport.position.z,
+ "pos": placementSupport.position,
"angle": placementSupport.angle,
+ "snapClasses": placementSupport.socketSnapClasses,
+ // Ideally we'd only get entities with certain class.
+ "snapEntities": placementSupport.socketSnapClasses &&
+ placementSupport.socketSnapClasses.length &&
+ Engine.GetEntitiesWithStaticObstructionOnScreen(),
"actorSeed": placementSupport.actorSeed
});
@@ -176,7 +180,7 @@
placementSupport.tooltipMessage = sprintf(translatePlural("Basic range: %(range)s meter", "Basic range: %(range)s meters", range), { "range": range }) + "\n" +
sprintf(translatePlural("Average bonus range: %(range)s meter", "Average bonus range: %(range)s meters", averageRange), { "range": averageRange });
}
- return true;
+ return result;
}
}
else if (placementSupport.mode === "wall")
@@ -296,7 +300,8 @@
return false;
}
- if (!updateBuildingPlacementPreview())
+ let buildingPlacementInfo = updateBuildingPlacementPreview();
+ if (!buildingPlacementInfo)
{
// invalid location - don't build it
// TODO: play a sound?
@@ -308,14 +313,15 @@
Engine.PostNetworkCommand({
"type": "construct",
"template": placementSupport.template,
- "x": placementSupport.position.x,
- "z": placementSupport.position.z,
+ "x": buildingPlacementInfo.x,
+ "z": buildingPlacementInfo.z,
"angle": placementSupport.angle,
"actorSeed": placementSupport.actorSeed,
"entities": selection,
"autorepair": true,
"autocontinue": true,
- "queued": queued
+ "queued": queued,
+ "snapEntity": buildingPlacementInfo.snappedEnt
});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_build", "entity": selection[0] });
@@ -1086,21 +1092,19 @@
{
placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
- if (isSnapToEdgesEnabled())
+ let snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
+ "template": placementSupport.template,
+ "x": placementSupport.position.x,
+ "z": placementSupport.position.z,
+ "snapEntities": placementSupport.socketSnapEntities,
+ "snapToEdges": isSnapToEdgesEnabled() && Engine.GetEdgesOfStaticObstructionsOnScreenNearTo(
+ placementSupport.position.x, placementSupport.position.z)
+ });
+ if (snapData)
{
- let snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
- "template": placementSupport.template,
- "x": placementSupport.position.x,
- "z": placementSupport.position.z,
- "snapToEdges": Engine.GetEdgesOfStaticObstructionsOnScreenNearTo(
- placementSupport.position.x, placementSupport.position.z)
- });
- if (snapData)
- {
- placementSupport.angle = snapData.angle;
- placementSupport.position.x = snapData.x;
- placementSupport.position.z = snapData.z;
- }
+ placementSupport.angle = snapData.angle;
+ placementSupport.position.x = snapData.x;
+ placementSupport.position.z = snapData.z;
}
g_DragStart = new Vector2D(ev.x, ev.y);
@@ -1311,6 +1315,7 @@
placementSupport.mode = "building";
placementSupport.template = buildTemplate;
inputState = INPUT_BUILDING_PLACEMENT;
+ placementSupport.socketSnapClasses = templateData.buildRestrictions.sockets;
}
if (templateData.attack &&
Index: binaries/data/mods/public/gui/session/placement.js
===================================================================
--- binaries/data/mods/public/gui/session/placement.js
+++ binaries/data/mods/public/gui/session/placement.js
@@ -15,6 +15,8 @@
this.template = null;
this.tooltipMessage = ""; // tooltip text to show while the user is placing a structure
this.tooltipError = false;
+ this.socketClasses = null;
+ this.socketSnapEntities = null;
this.wallSet = null; // maps types of wall pieces ("tower", "long", "short", ...) to template names
this.wallSnapEntities = null; // list of candidate entities to snap the starting and (!) ending positions to when building walls
this.wallEndPosition = null;
Index: binaries/data/mods/public/simulation/components/BuildRestrictions.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildRestrictions.js
+++ binaries/data/mods/public/simulation/components/BuildRestrictions.js
@@ -18,6 +18,7 @@
"land" +
"shore" +
"land-shore"+
+ "socket"+
"" +
"" +
"" +
@@ -45,11 +46,23 @@
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "tokens" +
+ "" +
+ "" +
+ "" +
"";
BuildRestrictions.prototype.Init = function()
{
this.territories = this.template.Territory.split(/\s+/);
+ if (this.template.Sockets)
+ this.sockets = this.template.Sockets._string.split(/\s+/);
+ if (this.template.PlacementType == "socket" && !this.sockets)
+ warn("Placement type 'Socket' without a socket specified, this building (" + this.entity + ") can never be built.");
};
/**
@@ -144,7 +157,8 @@
var ret = cmpObstruction.CheckFoundation(passClassName, false);
}
- if (ret != "success")
+ // When a socket is needed obstruction ought to be ignored.
+ if (ret != "success" && this.template.PlacementType != "socket")
{
switch (ret)
{
@@ -230,6 +244,12 @@
}
}
+ if (this.template.PlacementType == "socket" && !this.CheckSocketPlacement(pos))
+ {
+ result.message = markForTranslation("%(name)s must be built on a valid socket.");
+ return result;
+ }
+
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
@@ -298,6 +318,24 @@
return result;
};
+/**
+ * Whether this entity is placed on the correct socket.
+ */
+BuildRestrictions.prototype.CheckSocketPlacement = function()
+{
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let classes = this.sockets;
+
+ let filter = function(id)
+ {
+ let cmpIdentity = Engine.QueryInterface(id, IID_Identity);
+ return cmpIdentity && MatchesClassList(classes, cmpIdentity.GetClassesList());
+ };
+ let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+
+ return cmpRangeManager.ExecuteQuery(this.entity, 0, 0, cmpPlayerManager.GetAllPlayers(), IID_BuildSlot).some(filter);
+};
+
BuildRestrictions.prototype.GetCategory = function()
{
return this.template.Category;
Index: binaries/data/mods/public/simulation/components/BuildSlot.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildSlot.js
+++ binaries/data/mods/public/simulation/components/BuildSlot.js
@@ -1,10 +1,3 @@
-function Settlement() {}
-
-Settlement.prototype.Schema =
- "";
-
-Engine.RegisterComponentType(IID_Settlement, "Settlement", Settlement);
-
/*
* TODO: the vague plan is that this should keep track of who currently owns the settlement,
* and some other code can detect this (or get notified of changes) when it needs to.
@@ -13,3 +6,98 @@
* tell us that its player owns us, and move us back into our original position when the building
* is destroyed. Don't know if that's a sensible plan, though.
*/
+class BuildSlot
+{
+ Init()
+ {
+ this.occupant = INVALID_ENTITY;
+ }
+
+ /**
+ * Initialises construction, thus rendering this socket useless.
+ *
+ * @param {number} player - The player requesting the initialisation.
+ * @param {number} entity - The entity being built on/in this slot.
+ *
+ * @return {boolean} Whether the initialisation was successful.
+ */
+ InitConstruction(entity)
+ {
+ this.occupant = entity;
+
+ if (this.template.HideUponUse != "true")
+ return true;
+
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition)
+ return false;
+
+ this.previousPosition = cmpPosition.GetPosition();
+ cmpPosition.MoveOutOfWorld();
+
+ return true;
+ }
+
+ /**
+ * Resets this socket by setting the occupant to 0 and moving back to its former position.
+ */
+ Reset()
+ {
+ this.occupant = INVALID_ENTITY;
+
+ if (!this.previousPosition)
+ return;
+
+ let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
+ if (!cmpPosition)
+ return;
+
+ cmpPosition.JumpTo(this.previousPosition.x, this.previousPosition.z);
+ delete this.previousPosition;
+ }
+
+ /**
+ * Get the current occupant, i.e. the building placed upon this socket.
+ *
+ * @return {number} The current occupant of this build slot.
+ */
+ GetOccupant()
+ {
+ return this.occupant;
+ }
+}
+
+BuildSlot.prototype.Schema =
+ "Specifies this is a building slot, an entity where a structure can be placed upon." +
+ "" +
+ "" +
+ "true" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+BuildSlot.prototype.OnGlobalEntityRenamed = function(msg)
+{
+ if (msg.entity != this.occupant)
+ return;
+
+ // Our occupant died, reset our state.
+ if (msg.newentity == INVALID_ENTITY)
+ this.Reset();
+ else
+ this.occupant = msg.newentity;
+};
+
+BuildSlot.prototype.OnGlobalOwnershipChanged = function(msg)
+{
+ if (msg.entity != this.occupant)
+ return;
+
+ // Our occupant died, reset our state.
+ if (msg.to == INVALID_PLAYER)
+ this.Reset();
+};
+
+Engine.RegisterComponentType(IID_BuildSlot, "BuildSlot", BuildSlot);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -1041,6 +1041,20 @@
"translateParameters": [],
};
+ let snapData;
+ if (cmd.snapEntities)
+ {
+ snapData = this.GetFoundationSnapData(player, {
+ "x": cmd.pos.x,
+ "z": cmd.pos.z,
+ "template": cmd.template,
+ "snapEntities": cmd.snapEntities
+ });
+
+ if (snapData)
+ this.SetSnapData(snapData, cmd, true);
+ }
+
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
@@ -1063,7 +1077,7 @@
let pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
{
- pos.JumpTo(cmd.x, cmd.z);
+ pos.JumpTo(cmd.pos.x, cmd.pos.z);
pos.SetYRotation(cmd.angle);
}
@@ -1077,6 +1091,16 @@
else
result = cmpBuildRestrictions.CheckPlacement();
+ result.x = cmd.pos.x;
+ result.z = cmd.pos.z;
+ if (snapData)
+ {
+ result.x = snapData.x;
+ result.z = snapData.z;
+ if (snapData.ent)
+ result.snappedEnt = snapData.ent
+ }
+
let cmpRangeOverlayManager = Engine.QueryInterface(ent, IID_RangeOverlayManager);
if (cmpRangeOverlayManager)
cmpRangeOverlayManager.SetEnabled(true, this.enabledVisualRangeOverlayTypes);
@@ -1255,15 +1279,7 @@
});
if (startSnapData)
- {
- start.pos.x = startSnapData.x;
- start.pos.z = startSnapData.z;
- start.angle = startSnapData.angle;
- start.snapped = true;
-
- if (startSnapData.ent)
- start.snappedEnt = startSnapData.ent;
- }
+ this.SetSnapData(startSnapData, start);
if (end.pos)
{
@@ -1276,15 +1292,7 @@
});
if (endSnapData)
- {
- end.pos.x = endSnapData.x;
- end.pos.z = endSnapData.z;
- end.angle = endSnapData.angle;
- end.snapped = true;
-
- if (endSnapData.ent)
- end.snappedEnt = endSnapData.ent;
- }
+ this.SetSnapData(endSnapData, end);
}
}
@@ -1635,6 +1643,11 @@
return false;
}
+ if (data.snapEntities && !data.snapRadius)
+ data.snapRadius = Math.max(
+ template.Obstruction.Static["@depth"] / 2,
+ template.Obstruction.Static["@width"] / 2);
+
if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
{
// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
@@ -1692,6 +1705,23 @@
return false;
};
+/**
+ * @param {Object} snapData - The snapped object's data.
+ * @param {Object} target - The snapped object's target object.
+ * @param {boolean} isSnappedSlot - Whether the object is a slot.
+ */
+GuiInterface.prototype.SetSnapData = function(snapData, target, isSnappedSlot)
+{
+ target.pos.x = snapData.x;
+ target.pos.z = snapData.z;
+ target.angle = snapData.angle;
+ target.snapped = true;
+ target.snappedSlot = isSnappedSlot;
+
+ if (snapData.ent)
+ target.snappedEnt = snapData.ent;
+};
+
GuiInterface.prototype.PlaySound = function(player, data)
{
if (!data.entity)
Index: binaries/data/mods/public/simulation/components/Settlement.js
===================================================================
--- binaries/data/mods/public/simulation/components/Settlement.js
+++ binaries/data/mods/public/simulation/components/Settlement.js
@@ -1,15 +0,0 @@
-function Settlement() {}
-
-Settlement.prototype.Schema =
- "";
-
-Engine.RegisterComponentType(IID_Settlement, "Settlement", Settlement);
-
-/*
- * TODO: the vague plan is that this should keep track of who currently owns the settlement,
- * and some other code can detect this (or get notified of changes) when it needs to.
- * A civcenter's BuildRestrictions component will see that it's being built on this settlement,
- * call MoveOutOfWorld on us (so we're invisible and only the building is visible/selectable),
- * tell us that its player owns us, and move us back into our original position when the building
- * is destroyed. Don't know if that's a sensible plan, though.
- */
Index: binaries/data/mods/public/simulation/components/interfaces/BuildSlot.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/BuildSlot.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("BuildSlot");
Index: binaries/data/mods/public/simulation/components/tests/test_BuildSlot.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_BuildSlot.js
@@ -0,0 +1,13 @@
+Engine.LoadComponentScript("interfaces/BuildSlot.js");
+Engine.LoadComponentScript("BuildSlot.js");
+
+const buildSlotId = 1;
+const buildingId = 2;
+
+let cmpBuildSlot = ConstructComponent(buildSlotId, "BuildSlot", {
+ "HideUponUse": "false"
+});
+
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.GetOccupant(), INVALID_ENTITY);
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.InitConstruction(buildingId), true);
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.GetOccupant(), buildingId);
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -1136,6 +1136,13 @@
if (cmpVisual && cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
+ if (cmd.snapEntity)
+ {
+ let cmpBuildSlot = Engine.QueryInterface(cmd.snapEntity, IID_BuildSlot);
+ if (cmpBuildSlot)
+ cmpBuildSlot.InitConstruction(ent);
+ }
+
// Initialise the foundation
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
cmpFoundation.InitialiseConstruction(player, cmd.template);
Index: binaries/data/mods/public/simulation/templates/template_socket.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/templates/template_socket.xml
@@ -0,0 +1,93 @@
+
+
+
+
+ land
+ own
+ special
+
+
+ true
+
+
+ 500
+ 0.5
+ 5.0
+
+
+ 0
+ 0
+ 1
+
+ 0
+ 0
+ 0
+ 0
+
+
+
+ 10
+
+ 0.85
+ 0.65
+ 0.35
+
+ corpse
+ 0
+ 0
+ true
+
+
+ gaia
+ Socket
+ socket_house
+ true
+
+
+ true
+ true
+ true
+ true
+ false
+ false
+ false
+ false
+
+
+
+
+
+ 0
+ upright
+ false
+ 0.0
+ 6.0
+
+
+
+
+ outline_border.png
+ outline_border_mask.png
+ 0.4
+
+
+
+
+ 6.0
+ 0.6
+ 12.0
+
+
+ true
+ false
+ false
+ false
+
+
+
+ false
+ true
+ false
+ structures/fndn_3x3.xml
+
+
Index: binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
@@ -1,7 +1,9 @@
+ socket
House
+ socket_house
300
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
@@ -24,6 +24,7 @@
1.0
+ template_socket
structures/{civ}_civil_centre
structures/{civ}_crannog
structures/{civ}_military_colony