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,10 +131,19 @@
{
if (placementSupport.template && placementSupport.position)
{
- var result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
+ // Fetch an updated list of snapping candidate entities
+ placementSupport.socketSnapEntities = Engine.PickSimilarEntities(
+ "template_socket",
+ true, // Include offscreen.
+ true, // require exact template match
+ true // include foundations
+ );
+
+ let result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z,
+ "snapEntities": placementSupport.socketSnapEntities,
"angle": placementSupport.angle,
"actorSeed": placementSupport.actorSeed
});
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,7 @@
this.template = null;
this.tooltipMessage = ""; // tooltip text to show while the user is placing a structure
this.tooltipError = false;
+ 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,7 @@
var ret = cmpObstruction.CheckFoundation(passClassName, false);
}
- if (ret != "success")
+ if (ret != "success" && this.template.PlacementType != "socket")
{
switch (ret)
{
@@ -229,6 +242,14 @@
return result; // Fail
}
}
+ if (this.template.PlacementType == "socket")
+ {
+ if (!this.CheckSocketPlacement(pos))
+ {
+ result.message = markForTranslation("%(name)s must be built on a valid socket.");
+ return result; // Fail
+ }
+ }
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
@@ -298,6 +319,21 @@
return result;
};
+BuildRestrictions.prototype.CheckSocketPlacement = function(pos)
+{
+ 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,77 @@
* 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
+{
+ get Schema()
+ {
+ return "Specifies this is a building slot, an entity where a structure can be placed upon." +
+ "" +
+ "" +
+ "true" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+ }
+
+ Init()
+ {
+ this.owner = INVALID_PLAYER;
+ };
+
+ /**
+ * Initialises construction, thus rendering this socket useless.
+ *
+ * @param {number} player - The player requesting the initialisation.
+ *
+ * @return {boolean} Whether the initialisation was successful.
+ */
+ InitConstruction(player)
+ {
+ this.owner = player;
+
+ 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 owner to -1 and moving back to its former position.
+ */
+ Reset()
+ {
+ this.owner = INVALID_PLAYER;
+
+ 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 owner.
+ *
+ * @return {number} The current owner of this build slot.
+ */
+ GetOwner()
+ {
+ return this.owner;
+ }
+}
+
+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
@@ -1040,6 +1040,28 @@
"translateMessage": false,
"translateParameters": [],
};
+ if (cmd.snapEntities)
+ {
+ let snapRadius = 10;
+ let snapData = this.GetFoundationSnapData(player, {
+ "x": cmd.x,
+ "z": cmd.z,
+ "template": "template_socket",
+ "snapEntities": cmd.snapEntities,
+ "snapRadius": snapRadius,
+ });
+
+ if (snapData)
+ {
+ cmd.x = snapData.x;
+ cmd.z = snapData.z;
+ cmd.angle = snapData.angle;
+ cmd.snapped = true;
+
+ if (snapData.ent)
+ cmd.snappedEnt = snapData.ent;
+ }
+ }
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
@@ -1678,7 +1700,8 @@
return position;
}
- if (template.BuildRestrictions.PlacementType == "shore")
+ if (template.BuildRestrictions &&
+ template.BuildRestrictions.PlacementType == "shore")
{
let angle = GetDockAngle(template, data.x, data.z);
if (angle !== undefined)
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 playerId = 2;
+
+let cmpBuildSlot = ConstructComponent(buildSlotId, "BuildSlot", {
+ "HideUponUse": false
+});
+
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.GetOwner(), INVALID_PLAYER);
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.InitConstruction(playerId), true);
+TS_ASSERT_UNEVAL_EQUALS(cmpBuildSlot.GetOwner(), playerId);
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
+
+
+ false
+
+
+ 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}_house
structures/{civ}_storehouse
structures/{civ}_farmstead
Index: source/simulation2/scripting/JSInterface_Simulation.h
===================================================================
--- source/simulation2/scripting/JSInterface_Simulation.h
+++ source/simulation2/scripting/JSInterface_Simulation.h
@@ -34,6 +34,7 @@
std::vector GetEntitiesWithStaticObstructionOnScreen(ScriptInterface::CxPrivate* pCxPrivate);
JS::Value GetEdgesOfStaticObstructionsOnScreenNearTo(ScriptInterface::CxPrivate* pCxPrivate, entity_pos_t x, entity_pos_t z);
std::vector PickSimilarPlayerEntities(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations);
+ std::vector PickSimilarEntities(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations);
JS::Value GetAIs(ScriptInterface::CxPrivate* pCxPrivate);
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* pCxPrivate, bool enabled);
Index: source/simulation2/scripting/JSInterface_Simulation.cpp
===================================================================
--- source/simulation2/scripting/JSInterface_Simulation.cpp
+++ source/simulation2/scripting/JSInterface_Simulation.cpp
@@ -195,6 +195,11 @@
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetViewedPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
}
+std::vector JSI_Simulation::PickSimilarEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
+{
+ return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, INVALID_PLAYER, includeOffScreen, matchRank, false, allowFoundations);
+}
+
JS::Value JSI_Simulation::GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
@@ -218,5 +223,6 @@
scriptInterface.RegisterFunction, &GetEntitiesWithStaticObstructionOnScreen>("GetEntitiesWithStaticObstructionOnScreen");
scriptInterface.RegisterFunction("GetEdgesOfStaticObstructionsOnScreenNearTo");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities");
+ scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarEntities>("PickSimilarEntities");
scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay");
}