Index: binaries/data/mods/public/simulation/components/Buildable.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/Buildable.js
@@ -0,0 +1,47 @@
+class Buildable
+{
+ Init()
+ {
+ }
+
+ get Schema()
+ {
+ return "Defines whether the entity can be built." +
+ "" +
+ "" +
+ "1.0" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+ }
+
+ /**
+ * @return {boolean} - Whether the building builds itself.
+ */
+ IsAutoBuildable()
+ {
+ return this.template && this.template.AutoBuild && +this.template.AutoBuild.Rate != 0;
+ }
+
+ /**
+ * @return {number} - The rate with technologies and aura modification applied.
+ */
+ GetAutoBuildRate()
+ {
+ if (!this.IsAutoBuildable())
+ return 0;
+
+ return ApplyValueModificationsToEntity("Foundation/AutoBuild/Rate", +this.template.AutoBuild.Rate, this.entity);
+ }
+}
+
+// We have no dynamic state to save.
+Buildable.prototype.Serialize = null;
+
+Engine.RegisterComponentType(IID_Buildable, "Buildable", Buildable);
Index: binaries/data/mods/public/simulation/components/Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/Foundation.js
+++ binaries/data/mods/public/simulation/components/Foundation.js
@@ -16,8 +16,15 @@
this.totalBuilderRate = 0; // Total amount of work the builders do each second
this.buildMultiplier = 1; // Multiplier for the amount of work builders do
this.buildTimePenalty = 0.7; // Penalty for having multiple builders
-
+ this.isAutoBuildable = false;
this.previewEntity = INVALID_ENTITY;
+
+ let cmpBuildable = Engine.QueryInterface(this.entity, IID_Buildable);
+ if (cmpBuildable && cmpBuildable.IsAutoBuildable())
+ {
+ this.isAutoBuildable = true;
+ this.StartTimer();
+ }
};
Foundation.prototype.InitialiseConstruction = function(owner, template)
@@ -124,7 +131,20 @@
if (this.builders.has(builderEnt))
return;
- this.builders.set(builderEnt, Engine.QueryInterface(builderEnt, IID_Builder).GetRate());
+ let rate = 0;
+ let cmpBuilder = Engine.QueryInterface(builderEnt, IID_Builder);
+ if (cmpBuilder)
+ rate = cmpBuilder.GetRate();
+ else if (this.isAutoBuildable)
+ {
+ let cmpBuildable = Engine.QueryInterface(this.entity, IID_Buildable);
+ if (cmpBuildable)
+ rate = cmpBuildable.GetAutoBuildRate();
+ }
+ else
+ return;
+
+ this.builders.set(builderEnt, rate);
this.totalBuilderRate += this.builders.get(builderEnt);
this.SetBuildMultiplier();
@@ -261,8 +281,8 @@
}
var cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position);
- var pos = cmpFoundationPosition.GetPosition2D();
- var rot = cmpFoundationPosition.GetRotation();
+ let pos = cmpFoundationPosition.GetPosition2D();
+ let rot = cmpFoundationPosition.GetRotation();
cmpPreviewPosition.SetYRotation(rot.y);
cmpPreviewPosition.SetXZRotation(rot.x, rot.z);
cmpPreviewPosition.JumpTo(pos.x, pos.y);
@@ -319,9 +339,9 @@
Engine.DestroyEntity(building);
return;
}
- var pos = cmpPosition.GetPosition2D();
+ let pos = cmpPosition.GetPosition2D();
cmpBuildingPosition.JumpTo(pos.x, pos.y);
- var rot = cmpPosition.GetRotation();
+ let rot = cmpPosition.GetRotation();
cmpBuildingPosition.SetYRotation(rot.y);
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
// TODO: should add a ICmpPosition::CopyFrom() instead of all this
@@ -421,5 +441,37 @@
return cmpHealth.GetMaxHitpoints() / cmpCost.GetBuildTime();
};
+Foundation.prototype.StartTimer = function()
+{
+ if (this.timer)
+ return;
+
+ this.AddBuilder(this.entity);
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ this.timer = cmpTimer.SetInterval(this.entity, IID_Foundation, "AutoBuild", 0, 1000, undefined);
+};
+
+Foundation.prototype.CancelTimer = function()
+{
+ if (!this.timer)
+ return;
+
+ this.RemoveBuilder(this.entity);
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.CancelTimer(this.timer);
+ delete this.timer;
+};
+
+
+Foundation.prototype.AutoBuild = function()
+{
+ let cmpBuildable = Engine.QueryInterface(this.entity, IID_Buildable);
+ if (!cmpBuildable)
+ return;
+ let rate = cmpBuildable.GetAutoBuildRate();
+ if (rate != 0)
+ this.Build(this.entity, rate);
+};
+
Engine.RegisterComponentType(IID_Foundation, "Foundation", Foundation);
Index: binaries/data/mods/public/simulation/components/interfaces/Buildable.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/Buildable.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("Buildable");
Index: binaries/data/mods/public/simulation/components/tests/test_Buildable.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/tests/test_Buildable.js
@@ -0,0 +1,24 @@
+Engine.LoadComponentScript("interfaces/Buildable.js");
+Engine.LoadComponentScript("Buildable.js");
+
+const cmpBuildableAuto = ConstructComponent(10, "Buildable", {
+ "AutoBuild": {
+ "Rate": "1.0",
+ },
+});
+
+TS_ASSERT_EQUALS(cmpBuildableAuto.GetAutoBuildRate(), 1);
+TS_ASSERT_EQUALS(cmpBuildableAuto.IsAutoBuildable(), true);
+
+const cmpBuildable = ConstructComponent(11, "Buildable", {});
+
+TS_ASSERT_EQUALS(cmpBuildable.GetAutoBuildRate(), 0);
+TS_ASSERT_EQUALS(cmpBuildable.IsAutoBuildable(), false);
+
+const cmpBuildableNoRate = ConstructComponent(12, "Buildable", {
+ "AutoBuild": {
+ "Rate": "0",
+ },
+});
+TS_ASSERT_EQUALS(cmpBuildableNoRate.GetAutoBuildRate(), 0);
+TS_ASSERT_EQUALS(cmpBuildableNoRate.IsAutoBuildable(), false);
Index: binaries/data/mods/public/simulation/components/tests/test_Foundation.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Foundation.js
+++ binaries/data/mods/public/simulation/components/tests/test_Foundation.js
@@ -1,24 +1,28 @@
Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("ValueModification.js");
+Engine.LoadComponentScript("interfaces/Buildable.js");
Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/Cost.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/Health.js");
+Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
+Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Foundation.js");
-
+Engine.LoadComponentScript("Timer.js");
let player = 1;
let playerEnt = 3;
let foundationEnt = 20;
let previewEnt = 21;
let newEnt = 22;
+let finalTemplate = "structures/athen_civil_centre.xml";
function testFoundation(...mocks)
{
ResetState();
- let finalTemplate = "structures/athen_civil_centre.xml";
let foundationHP = 1;
let maxHP = 100;
let rot = new Vector3D(1, 2, 3);
@@ -41,6 +45,10 @@
},
});
+ AddMock(foundationEnt, IID_Buildable, {
+ "IsAutoBuildable": () => false,
+ });
+
Engine.RegisterGlobal("PlaySound", (name, source) => {
TS_ASSERT_EQUALS(name, "constructed");
TS_ASSERT_EQUALS(source, newEnt);
@@ -209,3 +217,51 @@
},
}]);
+// Test autobuild feature.
+const foundationEnt2 = 42;
+let turnLength = 0.2;
+let currentFoundationHP = 1;
+let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
+
+AddMock(foundationEnt2, IID_Cost, {
+ "GetBuildTime": () => 50,
+ "GetResourceCosts": () => ({ "wood": 100 }),
+});
+
+AddMock(foundationEnt2, IID_Buildable, {
+ "IsAutoBuildable": () => true,
+ "GetAutoBuildRate": () => 1,
+});
+
+const cmpAutoBuildingFoundation = ConstructComponent(foundationEnt2, "Foundation", {});
+AddMock(foundationEnt2, IID_Health, {
+ "GetHitpoints": () => currentFoundationHP,
+ "GetMaxHitpoints": () => 100,
+ "Increase": hp => {
+ currentFoundationHP = Math.min(currentFoundationHP + hp, 100);
+ cmpAutoBuildingFoundation.OnHealthChanged();
+ },
+});
+cmpAutoBuildingFoundation.InitialiseConstruction(player, finalTemplate);
+
+// We start at 3 cause there is no delay on the first run.
+cmpTimer.OnUpdate({ "turnLength": turnLength });
+
+for (let i = 0; i < 10; ++i)
+{
+ if (i == 8)
+ {
+ cmpAutoBuildingFoundation.CancelTimer();
+ TS_ASSERT_EQUALS(cmpAutoBuildingFoundation.GetNumBuilders(), 0);
+ }
+
+ let currentPercentage = cmpAutoBuildingFoundation.GetBuildPercentage();
+ cmpTimer.OnUpdate({ "turnLength": turnLength * 5 });
+ let newPercentage = cmpAutoBuildingFoundation.GetBuildPercentage();
+
+ if (i >= 8)
+ TS_ASSERT_EQUALS(currentPercentage, newPercentage);
+ else
+ // Rate * Max Health / Cost.
+ TS_ASSERT_EQUALS(currentPercentage + 2, newPercentage);
+}
Index: binaries/data/mods/public/simulation/templates/special/filter/foundation.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/special/filter/foundation.xml
+++ binaries/data/mods/public/simulation/templates/special/filter/foundation.xml
@@ -2,6 +2,7 @@
+
Index: binaries/data/mods/public/simulation/templates/structures/athen_house.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/structures/athen_house.xml
+++ binaries/data/mods/public/simulation/templates/structures/athen_house.xml
@@ -4,6 +4,11 @@
athen
Oikos
+
+
+ 1
+
+
structures/hellenes/house.xml
Index: binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure.xml
+++ binaries/data/mods/public/simulation/templates/template_structure.xml
@@ -44,6 +44,7 @@
9.8
+