Index: binaries/data/mods/public/simulation/components/AutoBuildable.js =================================================================== --- binaries/data/mods/public/simulation/components/AutoBuildable.js +++ binaries/data/mods/public/simulation/components/AutoBuildable.js @@ -0,0 +1,37 @@ +class AutoBuildable +{ + Init() + { + this.rate = +this.template.Rate; + } + + get Schema() + { + return "Defines whether the entity can be built by itself." + + "" + + "1.0" + + "" + + "" + + "" + + ""; + } + + /** + * @return {number} - The rate with technologies and aura modification applied. + */ + GetRate() + { + return this.rate; + } +} + +AutoBuildable.prototype.OnValueModification = function(msg) +{ + if (msg.component != "AutoBuildable") + return; + + this.rate = ApplyValueModificationsToEntity("AutoBuildable/Rate", +this.template.Rate , this.entity); + Engine.PostMessage(this.entity, MT_AutoBuildRateChanged, undefined); +}; + +Engine.RegisterComponentType(IID_AutoBuildable, "AutoBuildable", AutoBuildable); 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,11 @@ 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.previewEntity = INVALID_ENTITY; + + let cmpAutoBuildable = Engine.QueryInterface(this.entity, IID_AutoBuildable); + if (cmpAutoBuildable) + this.StartTimer(); }; Foundation.prototype.InitialiseConstruction = function(owner, template) @@ -152,7 +155,12 @@ if (this.builders.has(builderEnt)) return false; - let buildRate = Engine.QueryInterface(builderEnt, IID_Builder).GetRate(); + let cmpBuilder = Engine.QueryInterface(builderEnt, IID_Builder) || + Engine.QueryInterface(this.entity, IID_AutoBuildable); + if (!cmpBuilder) + return false; + + let buildRate = cmpBuilder.GetRate(); this.builders.set(builderEnt, buildRate); this.totalBuilderRate += buildRate; @@ -304,8 +312,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); @@ -362,9 +370,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 @@ -464,5 +472,50 @@ 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 cmpAutoBuildable = Engine.QueryInterface(this.entity, IID_AutoBuildable); + if (!cmpAutoBuildable) + { + this.CancelTimer(); + return; + } + let rate = cmpAutoBuildable.GetRate(); + if (rate != 0) + this.Build(this.entity, rate); +}; + + +Foundation.prototype.OnAutoBuildRateChanged = function() +{ + if (this.timer) + return; + + let cmpAutoBuildable = Engine.QueryInterface(this.entity, IID_AutoBuildable); + if (cmpAutoBuildable && cmpAutoBuildable.GetRate() != 0) + this.StartTimer(); +}; + Engine.RegisterComponentType(IID_Foundation, "Foundation", Foundation); Index: binaries/data/mods/public/simulation/components/interfaces/AutoBuildable.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/AutoBuildable.js +++ binaries/data/mods/public/simulation/components/interfaces/AutoBuildable.js @@ -0,0 +1,7 @@ +Engine.RegisterInterface("AutoBuildable"); + +/** + * Message of the form { "from": number, "to": number } + * sent from AutoBuildable component whenever its rate changes. + */ +Engine.RegisterMessageType("AutoBuildRateChanged"); Index: binaries/data/mods/public/simulation/components/tests/test_AutoBuildable.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_AutoBuildable.js +++ binaries/data/mods/public/simulation/components/tests/test_AutoBuildable.js @@ -0,0 +1,15 @@ +Engine.LoadHelperScript("ValueModification.js"); +Engine.LoadComponentScript("interfaces/AutoBuildable.js"); +Engine.LoadComponentScript("interfaces/ModifiersManager.js"); +Engine.LoadComponentScript("AutoBuildable.js"); + +const cmpBuildableAuto = ConstructComponent(10, "AutoBuildable", { + "Rate": "1.0" +}); + +TS_ASSERT_EQUALS(cmpBuildableAuto.GetRate(), 1); + +const cmpBuildableNoRate = ConstructComponent(12, "AutoBuildable", { + "Rate": "0" +}); +TS_ASSERT_EQUALS(cmpBuildableNoRate.GetRate(), 0); 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/AutoBuildable.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); @@ -209,3 +213,50 @@ }, }]); +// 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_AutoBuildable, { + "GetRate": () => 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,9 @@ athen Oikos + + 1 + structures/hellenes/house.xml