Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -204,11 +204,18 @@ } } - if (template.BuildingAI) - ret.buildingAI = { - "defaultArrowCount": Math.round(getEntityValue("BuildingAI/DefaultArrowCount")), - "garrisonArrowMultiplier": getEntityValue("BuildingAI/GarrisonArrowMultiplier"), - "maxArrowCount": Math.round(getEntityValue("BuildingAI/MaxArrowCount")) + if (template.ManpowerHolderBuilding) + ret.manpowerHolder = { + "defaultArrowCount": Math.round(getEntityValue("ManpowerHolderBuilding/DefaultArrowCount", "ManpowerHolder/DefaultArrowCount")), + "garrisonArrowMultiplier": getEntityValue("ManpowerHolderBuilding/GarrisonArrowMultiplier", "ManpowerHolder/GarrisonArrowMultiplier"), + "maxArrowCount": Math.round(getEntityValue("ManpowerHolderBuilding/MaxArrowCount", "ManpowerHolder/MaxArrowCount")) + }; + + else if (template.ManpowerHolderUnit) + ret.manpowerHolder = { + "defaultArrowCount": Math.round(getEntityValue("ManpowerHolderUnit/DefaultArrowCount", "ManpowerHolder/DefaultArrowCount")), + "garrisonArrowMultiplier": getEntityValue("ManpowerHolderUnit/GarrisonArrowMultiplier", "ManpowerHolder/GarrisonArrowMultiplier"), + "maxArrowCount": Math.round(getEntityValue("ManpowerHolderUnit/MaxArrowCount", "ManpowerHolder/MaxArrowCount")) }; if (template.BuildRestrictions) Index: binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- binaries/data/mods/public/gui/common/tooltips.js +++ binaries/data/mods/public/gui/common/tooltips.js @@ -146,12 +146,12 @@ // Either one arrow shot by UnitAI, let timeString = getSecondsString(template.attack[type].repeatTime / 1000); - // or multiple arrows shot by BuildingAI - if (!template.buildingAI || type != "Ranged") + // or multiple arrows shot by ManpowerHolder + if (!template.manpowerHolder || type != "Ranged") return timeString; // Show either current rate from simulation or default count if the sim is not running - let arrows = template.buildingAI.arrowCount || template.buildingAI.defaultArrowCount; + let arrows = template.manpowerHolder.arrowCount || template.manpowerHolder.defaultArrowCount; let arrowString = sprintf(translatePlural("%(arrowcount)s %(arrows)s", "%(arrowcount)s %(arrows)s", arrows), { "arrowcount": arrows, "arrows": unitFont(translatePlural("arrow", "arrows", arrows)) @@ -222,7 +222,7 @@ let rate = sprintf(translate("%(label)s %(details)s"), { "label": headerFont( - template.buildingAI && type == "Ranged" ? + template.manpowerHolder && type == "Ranged" ? translate("Interval:") : translate("Rate:")), "details": attackRateDetails(template, type) @@ -321,13 +321,13 @@ function getProjectilesTooltip(template) { - if (!template.garrisonHolder || !template.buildingAI) + if (!template.garrisonHolder || !template.manpowerHolder) return ""; let limit = Math.min( - template.buildingAI.maxArrowCount || Infinity, - template.buildingAI.defaultArrowCount + - Math.round(template.buildingAI.garrisonArrowMultiplier * + template.manpowerHolder.maxArrowCount || Infinity, + template.manpowerHolder.defaultArrowCount + + Math.round(template.manpowerHolder.garrisonArrowMultiplier * template.garrisonHolder.capacity) ); @@ -342,12 +342,12 @@ sprintf(translate("%(label)s: %(value)s"), { "label": headerFont(translateWithContext("projectiles", "Default")), - "value": template.buildingAI.defaultArrowCount + "value": template.manpowerHolder.defaultArrowCount }), sprintf(translate("%(label)s: %(value)s"), { "label": headerFont(translateWithContext("projectiles", "Per Unit")), - "value": +template.buildingAI.garrisonArrowMultiplier.toFixed(2) + "value": +template.manpowerHolder.garrisonArrowMultiplier.toFixed(2) }) ].join(commaFont(translate(", "))); } Index: binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- binaries/data/mods/public/simulation/ai/common-api/entity.js +++ binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -428,17 +428,17 @@ }, getDefaultArrow: function() { - return +this.get("BuildingAI/DefaultArrowCount"); + return +this.get("ManpowerHolder/DefaultArrowCount"); }, getArrowMultiplier: function() { - return +this.get("BuildingAI/GarrisonArrowMultiplier"); + return +this.get("ManpowerHolder/GarrisonArrowMultiplier"); }, getGarrisonArrowClasses: function() { - if (!this.get("BuildingAI")) + if (!this.get("ManpowerHolder")) return undefined; - return this.get("BuildingAI/GarrisonArrowClasses").split(/\s+/); + return this.get("ManpowerHolder/GarrisonArrowClasses").split(/\s+/); }, buffHeal: function() { Index: binaries/data/mods/public/simulation/ai/petra/researchManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/researchManager.js +++ binaries/data/mods/public/simulation/ai/petra/researchManager.js @@ -143,7 +143,7 @@ return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 }; else if (template.modifications[i].value === "ResourceGatherer/Rates/metal.ore") return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 }; - else if (template.modifications[i].value === "BuildingAI/DefaultArrowCount") + else if (template.modifications[i].value === "ManpowerHolder/DefaultArrowCount") return { "name": tech[0], "increasePriority": this.CostSum(template.cost) < 400 }; else if (template.modifications[i].value === "Health/RegenRate") return { "name": tech[0], "increasePriority": false }; Index: binaries/data/mods/public/simulation/components/BuildingAI.js =================================================================== --- binaries/data/mods/public/simulation/components/BuildingAI.js +++ /dev/null @@ -1,379 +0,0 @@ -//Number of rounds of firing per 2 seconds -const roundCount = 10; -const attackType = "Ranged"; - -function BuildingAI() {} - -BuildingAI.prototype.Schema = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; - -BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; - -BuildingAI.prototype.Init = function() -{ - this.currentRound = 0; - this.archersGarrisoned = 0; - this.arrowsLeft = 0; - this.targetUnits = []; -}; - -BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) -{ - let classes = this.template.GarrisonArrowClasses; - - for (let ent of msg.added) - { - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (!cmpIdentity) - continue; - if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) - ++this.archersGarrisoned; - } - - for (let ent of msg.removed) - { - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (!cmpIdentity) - continue; - if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) - --this.archersGarrisoned; - } -}; - -BuildingAI.prototype.OnOwnershipChanged = function(msg) -{ - this.targetUnits = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); -}; - -BuildingAI.prototype.OnDiplomacyChanged = function(msg) -{ - if (!IsOwnedByPlayer(msg.player, this.entity)) - return; - - // Remove maybe now allied/neutral units - this.targetUnits = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); -}; - -BuildingAI.prototype.OnDestroy = function() -{ - if (this.timer) - { - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.timer); - this.timer = undefined; - } - - // Clean up range queries - let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.enemyUnitsQuery) - cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); - if (this.gaiaUnitsQuery) - cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); -}; - -/** - * React on Attack value modifications, as it might influence the range - */ -BuildingAI.prototype.OnValueModification = function(msg) -{ - if (msg.component != "Attack") - return; - - this.targetUnits = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); -}; - -/** - * Setup the Range Query to detect units coming in & out of range - */ -BuildingAI.prototype.SetupRangeQuery = function() -{ - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; - - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.enemyUnitsQuery) - { - cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); - this.enemyUnitsQuery = undefined; - } - - var cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return; - - var enemies = cmpPlayer.GetEnemies(); - if (enemies.length && enemies[0] == 0) - enemies.shift(); // remove gaia - - if (!enemies.length) - return; - - var range = cmpAttack.GetRange(attackType); - this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, range.elevationBonus, - enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); - - cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); -}; - -// Set up a range query for Gaia units within LOS range which can be attacked. -// This should be called whenever our ownership changes. -BuildingAI.prototype.SetupGaiaRangeQuery = function() -{ - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; - - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.gaiaUnitsQuery) - { - cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); - this.gaiaUnitsQuery = undefined; - } - - var cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) - return; - - var range = cmpAttack.GetRange(attackType); - - // This query is only interested in Gaia entities that can attack. - this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, range.elevationBonus, - [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); - - cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); -}; - -/** - * Called when units enter or leave range - */ -BuildingAI.prototype.OnRangeUpdate = function(msg) -{ - - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; - - // Target enemy units except non-dangerous animals - if (msg.tag == this.gaiaUnitsQuery) - { - msg.added = msg.added.filter(e => { - let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); - return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); - }); - } - else if (msg.tag != this.enemyUnitsQuery) - return; - - // Add new targets - for (let entity of msg.added) - if (cmpAttack.CanAttack(entity)) - this.targetUnits.push(entity); - - // Remove targets outside of vision-range - for (let entity of msg.removed) - { - let index = this.targetUnits.indexOf(entity); - if (index > -1) - this.targetUnits.splice(index, 1); - } - - if (this.targetUnits.length) - this.StartTimer(); -}; - -BuildingAI.prototype.StartTimer = function() -{ - if (this.timer) - return; - - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; - - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - var attackTimers = cmpAttack.GetTimers(attackType); - - this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", - attackTimers.prepare, attackTimers.repeat / roundCount, null); -}; - -BuildingAI.prototype.GetDefaultArrowCount = function() -{ - var arrowCount = +this.template.DefaultArrowCount; - return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity)); -}; - -BuildingAI.prototype.GetMaxArrowCount = function() -{ - if (!this.template.MaxArrowCount) - return Infinity; - - let maxArrowCount = +this.template.MaxArrowCount; - return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity)); -}; - -BuildingAI.prototype.GetGarrisonArrowMultiplier = function() -{ - var arrowMult = +this.template.GarrisonArrowMultiplier; - return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); -}; - -BuildingAI.prototype.GetGarrisonArrowClasses = function() -{ - var string = this.template.GarrisonArrowClasses; - if (string) - return string.split(/\s+/); - return []; -}; - -/** - * Returns the number of arrows which needs to be fired. - * DefaultArrowCount + Garrisoned Archers(ie., any unit capable - * of shooting arrows from inside buildings) - */ -BuildingAI.prototype.GetArrowCount = function() -{ - let count = this.GetDefaultArrowCount() + - Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); - - return Math.min(count, this.GetMaxArrowCount()); -}; - -BuildingAI.prototype.SetUnitAITarget = function(ent) -{ - this.unitAITarget = ent; - if (ent) - this.StartTimer(); -}; - -/** - * Fire arrows with random temporal distribution on prefered targets. - * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. - */ -BuildingAI.prototype.FireArrows = function() -{ - if (!this.targetUnits.length && !this.unitAITarget) - { - if (!this.timer) - return; - - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.timer); - this.timer = undefined; - return; - } - - let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; - - if (this.currentRound > roundCount - 1) - this.currentRound = 0; - - if (this.currentRound == 0) - this.arrowsLeft = this.GetArrowCount(); - - let arrowsToFire = 0; - if (this.currentRound == roundCount - 1) - arrowsToFire = this.arrowsLeft; - else - arrowsToFire = Math.min( - randIntInclusive(0, 2 * this.GetArrowCount() / roundCount), - this.arrowsLeft - ); - - if (arrowsToFire <= 0) - { - ++this.currentRound; - return; - } - - // Add targets to a weighted list, to allow preferences - let targets = new WeightedList(); - let maxPreference = this.MAX_PREFERENCE_BONUS; - let addTarget = function(target) - { - let preference = cmpAttack.GetPreference(target); - let weight = 1; - - if (preference !== null && preference !== undefined) - weight += maxPreference / (1 + preference); - - targets.push(target, weight); - }; - - // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ - if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) - addTarget(this.unitAITarget); - for (let target of this.targetUnits) - addTarget(target); - - for (let i = 0; i < arrowsToFire; ++i) - { - let selectedIndex = targets.randomIndex(); - let selectedTarget = targets.itemAt(selectedIndex); - - if (selectedTarget && this.CheckTargetVisible(selectedTarget)) - { - cmpAttack.PerformAttack(attackType, selectedTarget); - PlaySound("attack", this.entity); - continue; - } - - // Could not attack target, retry - targets.removeAt(selectedIndex); - --i; - - if (!targets.length()) - { - this.arrowsLeft += arrowsToFire; - break; - } - } - - this.arrowsLeft -= arrowsToFire; - this.currentRound++; -}; - -/** - * Returns true if the target entity is visible through the FoW/SoD. - */ -BuildingAI.prototype.CheckTargetVisible = function(target) -{ - var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (!cmpOwnership) - return false; - - // Entities that are hidden and miraged are considered visible - var cmpFogging = Engine.QueryInterface(target, IID_Fogging); - if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) - return true; - - // Either visible directly, or visible in fog - let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; -}; - -Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); 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 @@ -433,7 +433,7 @@ let ret = { "armour": null, "attack": null, - "buildingAI": null, + "manpowerHolder": null, "deathDamage": null, "heal": null, "isBarterMarket": null, @@ -513,14 +513,14 @@ if (cmpAuras) ret.auras = cmpAuras.GetDescriptions(); - let cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI); - if (cmpBuildingAI) - ret.buildingAI = { - "defaultArrowCount": cmpBuildingAI.GetDefaultArrowCount(), - "maxArrowCount": cmpBuildingAI.GetMaxArrowCount(), - "garrisonArrowMultiplier": cmpBuildingAI.GetGarrisonArrowMultiplier(), - "garrisonArrowClasses": cmpBuildingAI.GetGarrisonArrowClasses(), - "arrowCount": cmpBuildingAI.GetArrowCount() + let cmpManpowerHolder = Engine.QueryInterface(ent, IID_ManpowerHolder); + if (cmpManpowerHolder) + ret.manpowerHolder = { + "defaultArrowCount": cmpManpowerHolder.GetDefaultArrowCount(), + "maxArrowCount": cmpManpowerHolder.GetMaxArrowCount(), + "garrisonArrowMultiplier": cmpManpowerHolder.GetGarrisonArrowMultiplier(), + "garrisonArrowClasses": cmpManpowerHolder.GetGarrisonArrowClasses(), + "arrowCount": cmpManpowerHolder.GetArrowCount() }; let cmpDeathDamage = Engine.QueryInterface(ent, IID_DeathDamage); Index: binaries/data/mods/public/simulation/components/ManpowerHolderBuilding.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/ManpowerHolderBuilding.js @@ -0,0 +1,369 @@ +//Number of rounds of firing per 2 seconds +const roundCount = 10; +const attackType = "Ranged"; + +function ManpowerHolderBuilding() {} + +ManpowerHolderBuilding.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +ManpowerHolderBuilding.prototype.MAX_PREFERENCE_BONUS = 2; + +ManpowerHolderBuilding.prototype.Init = function() +{ + this.currentRound = 0; + this.archersGarrisoned = 0; + this.arrowsLeft = 0; + this.targetUnits = []; +}; + +ManpowerHolderBuilding.prototype.OnGarrisonedUnitsChanged = function(msg) +{ + let classes = this.template.GarrisonArrowClasses; + + for (let ent of msg.added) + { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (!cmpIdentity) + continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) + ++this.archersGarrisoned; + } + + for (let ent of msg.removed) + { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (!cmpIdentity) + continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) + --this.archersGarrisoned; + } +}; + +ManpowerHolderBuilding.prototype.OnOwnershipChanged = function(msg) +{ + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +ManpowerHolderBuilding.prototype.OnDiplomacyChanged = function(msg) +{ + if (!IsOwnedByPlayer(msg.player, this.entity)) + return; + + // Remove maybe now allied/neutral units + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +ManpowerHolderBuilding.prototype.OnDestroy = function() +{ + if (this.timer) + { + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } + + // Clean up range queries + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + if (this.gaiaUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * React on Attack value modifications, as it might influence the range + */ +ManpowerHolderBuilding.prototype.OnValueModification = function(msg) +{ + if (msg.component != "Attack") + return; + + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +/** + * Setup the Range Query to detect units coming in & out of range + */ +ManpowerHolderBuilding.prototype.SetupRangeQuery = function() +{ + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + this.enemyUnitsQuery = undefined; + } + + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return; + + var enemies = cmpPlayer.GetEnemies(); + if (enemies.length && enemies[0] == 0) + enemies.shift(); // remove gaia + + if (!enemies.length) + return; + + var range = cmpAttack.GetRange(attackType); + this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, range.elevationBonus, + enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); + + cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); +}; + +// Set up a range query for Gaia units within LOS range which can be attacked. +// This should be called whenever our ownership changes. +ManpowerHolderBuilding.prototype.SetupGaiaRangeQuery = function() +{ + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.gaiaUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); + this.gaiaUnitsQuery = undefined; + } + + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) + return; + + var range = cmpAttack.GetRange(attackType); + + // This query is only interested in Gaia entities that can attack. + this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, range.elevationBonus, + [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); + + cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * Called when units enter or leave range + */ +ManpowerHolderBuilding.prototype.OnRangeUpdate = function(msg) +{ + + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + // Target enemy units except non-dangerous animals + if (msg.tag == this.gaiaUnitsQuery) + { + msg.added = msg.added.filter(e => { + let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); + return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); + }); + } + else if (msg.tag != this.enemyUnitsQuery) + return; + + // Add new targets + for (let entity of msg.added) + if (cmpAttack.CanAttack(entity)) + this.targetUnits.push(entity); + + // Remove targets outside of vision-range + for (let entity of msg.removed) + { + let index = this.targetUnits.indexOf(entity); + if (index > -1) + this.targetUnits.splice(index, 1); + } + + if (this.targetUnits.length) + this.StartTimer(); +}; + +ManpowerHolderBuilding.prototype.StartTimer = function() +{ + if (this.timer) + return; + + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + var attackTimers = cmpAttack.GetTimers(attackType); + + this.timer = cmpTimer.SetInterval(this.entity, IID_ManpowerHolder, "FireArrows", + attackTimers.prepare, attackTimers.repeat / roundCount, null); +}; + +ManpowerHolderBuilding.prototype.GetDefaultArrowCount = function() +{ + var arrowCount = +this.template.DefaultArrowCount; + return Math.round(ApplyValueModificationsToEntity("ManpowerHolderBuilding/DefaultArrowCount", arrowCount, this.entity)); +}; + +ManpowerHolderBuilding.prototype.GetMaxArrowCount = function() +{ + if (!this.template.MaxArrowCount) + return Infinity; + + let maxArrowCount = +this.template.MaxArrowCount; + return Math.round(ApplyValueModificationsToEntity("ManpowerHolderBuilding/MaxArrowCount", maxArrowCount, this.entity)); +}; + +ManpowerHolderBuilding.prototype.GetGarrisonArrowMultiplier = function() +{ + var arrowMult = +this.template.GarrisonArrowMultiplier; + return ApplyValueModificationsToEntity("ManpowerHolderBuilding/GarrisonArrowMultiplier", arrowMult, this.entity); +}; + +ManpowerHolderBuilding.prototype.GetGarrisonArrowClasses = function() +{ + var string = this.template.GarrisonArrowClasses; + if (string) + return string.split(/\s+/); + return []; +}; + +/** + * Returns the number of arrows which needs to be fired. + * DefaultArrowCount + Garrisoned Archers(ie., any unit capable + * of shooting arrows from inside buildings) + */ +ManpowerHolderBuilding.prototype.GetArrowCount = function() +{ + let count = this.GetDefaultArrowCount() + + Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); + + return Math.min(count, this.GetMaxArrowCount()); +}; + +/** + * Fire arrows with random temporal distribution on prefered targets. + * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. + */ +ManpowerHolderBuilding.prototype.FireArrows = function() +{ + if (!this.targetUnits.length) + { + if (!this.timer) + return; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + return; + } + + let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + if (this.currentRound > roundCount - 1) + this.currentRound = 0; + + if (this.currentRound == 0) + this.arrowsLeft = this.GetArrowCount(); + + let arrowsToFire = 0; + if (this.currentRound == roundCount - 1) + arrowsToFire = this.arrowsLeft; + else + arrowsToFire = Math.min( + randIntInclusive(0, 2 * this.GetArrowCount() / roundCount), + this.arrowsLeft + ); + + if (arrowsToFire <= 0) + { + ++this.currentRound; + return; + } + + // Add targets to a weighted list, to allow preferences + let targets = new WeightedList(); + let maxPreference = this.MAX_PREFERENCE_BONUS; + let addTarget = function(target) + { + let preference = cmpAttack.GetPreference(target); + let weight = 1; + + if (preference !== null && preference !== undefined) + weight += maxPreference / (1 + preference); + + targets.push(target, weight); + }; + + for (let target of this.targetUnits) + addTarget(target); + + for (let i = 0; i < arrowsToFire; ++i) + { + let selectedIndex = targets.randomIndex(); + let selectedTarget = targets.itemAt(selectedIndex); + + if (selectedTarget && this.CheckTargetVisible(selectedTarget)) + { + cmpAttack.PerformAttack(attackType, selectedTarget); + PlaySound("attack", this.entity); + continue; + } + + // Could not attack target, retry + targets.removeAt(selectedIndex); + --i; + + if (!targets.length()) + { + this.arrowsLeft += arrowsToFire; + break; + } + } + + this.arrowsLeft -= arrowsToFire; + this.currentRound++; +}; + +/** + * Returns true if the target entity is visible through the FoW/SoD. + */ +ManpowerHolderBuilding.prototype.CheckTargetVisible = function(target) +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return false; + + // Entities that are hidden and miraged are considered visible + var cmpFogging = Engine.QueryInterface(target, IID_Fogging); + if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) + return true; + + // Either visible directly, or visible in fog + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; +}; + +Engine.RegisterComponentType(IID_ManpowerHolder, "ManpowerHolderBuilding", ManpowerHolderBuilding); Index: binaries/data/mods/public/simulation/components/ManpowerHolderUnit.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/ManpowerHolderUnit.js @@ -0,0 +1,379 @@ +//Number of rounds of firing per 2 seconds +const roundCount = 10; +const attackType = "Ranged"; + +function ManpowerHolderUnit() {} + +ManpowerHolderUnit.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +ManpowerHolderUnit.prototype.MAX_PREFERENCE_BONUS = 2; + +ManpowerHolderUnit.prototype.Init = function() +{ + this.currentRound = 0; + this.archersGarrisoned = 0; + this.arrowsLeft = 0; + this.targetUnits = []; +}; + +ManpowerHolderUnit.prototype.OnGarrisonedUnitsChanged = function(msg) +{ + let classes = this.template.GarrisonArrowClasses; + + for (let ent of msg.added) + { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (!cmpIdentity) + continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) + ++this.archersGarrisoned; + } + + for (let ent of msg.removed) + { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (!cmpIdentity) + continue; + if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) + --this.archersGarrisoned; + } +}; + +ManpowerHolderUnit.prototype.OnOwnershipChanged = function(msg) +{ + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +ManpowerHolderUnit.prototype.OnDiplomacyChanged = function(msg) +{ + if (!IsOwnedByPlayer(msg.player, this.entity)) + return; + + // Remove maybe now allied/neutral units + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +ManpowerHolderUnit.prototype.OnDestroy = function() +{ + if (this.timer) + { + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } + + // Clean up range queries + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + if (this.gaiaUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * React on Attack value modifications, as it might influence the range + */ +ManpowerHolderUnit.prototype.OnValueModification = function(msg) +{ + if (msg.component != "Attack") + return; + + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); +}; + +/** + * Setup the Range Query to detect units coming in & out of range + */ +ManpowerHolderUnit.prototype.SetupRangeQuery = function() +{ + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + this.enemyUnitsQuery = undefined; + } + + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return; + + var enemies = cmpPlayer.GetEnemies(); + if (enemies.length && enemies[0] == 0) + enemies.shift(); // remove gaia + + if (!enemies.length) + return; + + var range = cmpAttack.GetRange(attackType); + this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, range.elevationBonus, + enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); + + cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); +}; + +// Set up a range query for Gaia units within LOS range which can be attacked. +// This should be called whenever our ownership changes. +ManpowerHolderUnit.prototype.SetupGaiaRangeQuery = function() +{ + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.gaiaUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); + this.gaiaUnitsQuery = undefined; + } + + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) + return; + + var range = cmpAttack.GetRange(attackType); + + // This query is only interested in Gaia entities that can attack. + this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, range.elevationBonus, + [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); + + cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * Called when units enter or leave range + */ +ManpowerHolderUnit.prototype.OnRangeUpdate = function(msg) +{ + + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + // Target enemy units except non-dangerous animals + if (msg.tag == this.gaiaUnitsQuery) + { + msg.added = msg.added.filter(e => { + let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); + return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); + }); + } + else if (msg.tag != this.enemyUnitsQuery) + return; + + // Add new targets + for (let entity of msg.added) + if (cmpAttack.CanAttack(entity)) + this.targetUnits.push(entity); + + // Remove targets outside of vision-range + for (let entity of msg.removed) + { + let index = this.targetUnits.indexOf(entity); + if (index > -1) + this.targetUnits.splice(index, 1); + } + + if (this.targetUnits.length) + this.StartTimer(); +}; + +ManpowerHolderUnit.prototype.StartTimer = function() +{ + if (this.timer) + return; + + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + var attackTimers = cmpAttack.GetTimers(attackType); + + this.timer = cmpTimer.SetInterval(this.entity, IID_ManpowerHolder, "FireArrows", + attackTimers.prepare, attackTimers.repeat / roundCount, null); +}; + +ManpowerHolderUnit.prototype.GetDefaultArrowCount = function() +{ + var arrowCount = +this.template.DefaultArrowCount; + return Math.round(ApplyValueModificationsToEntity("ManpowerHolderUnit/DefaultArrowCount", arrowCount, this.entity)); +}; + +ManpowerHolderUnit.prototype.GetMaxArrowCount = function() +{ + if (!this.template.MaxArrowCount) + return Infinity; + + let maxArrowCount = +this.template.MaxArrowCount; + return Math.round(ApplyValueModificationsToEntity("ManpowerHolderUnit/MaxArrowCount", maxArrowCount, this.entity)); +}; + +ManpowerHolderUnit.prototype.GetGarrisonArrowMultiplier = function() +{ + var arrowMult = +this.template.GarrisonArrowMultiplier; + return ApplyValueModificationsToEntity("ManpowerHolderUnit/GarrisonArrowMultiplier", arrowMult, this.entity); +}; + +ManpowerHolderUnit.prototype.GetGarrisonArrowClasses = function() +{ + var string = this.template.GarrisonArrowClasses; + if (string) + return string.split(/\s+/); + return []; +}; + +/** + * Returns the number of arrows which needs to be fired. + * DefaultArrowCount + Garrisoned Archers(ie., any unit capable + * of shooting arrows from inside buildings) + */ +ManpowerHolderUnit.prototype.GetArrowCount = function() +{ + let count = this.GetDefaultArrowCount() + + Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); + + return Math.min(count, this.GetMaxArrowCount()); +}; + +ManpowerHolderUnit.prototype.SetUnitAITarget = function(ent) +{ + this.unitAITarget = ent; + if (ent) + this.StartTimer(); +}; + +/** + * Fire arrows with random temporal distribution on prefered targets. + * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. + */ +ManpowerHolderUnit.prototype.FireArrows = function() +{ + if (!this.targetUnits.length && !this.unitAITarget) + { + if (!this.timer) + return; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + return; + } + + let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + + if (this.currentRound > roundCount - 1) + this.currentRound = 0; + + if (this.currentRound == 0) + this.arrowsLeft = this.GetArrowCount(); + + let arrowsToFire = 0; + if (this.currentRound == roundCount - 1) + arrowsToFire = this.arrowsLeft; + else + arrowsToFire = Math.min( + randIntInclusive(0, 2 * this.GetArrowCount() / roundCount), + this.arrowsLeft + ); + + if (arrowsToFire <= 0) + { + ++this.currentRound; + return; + } + + // Add targets to a weighted list, to allow preferences + let targets = new WeightedList(); + let maxPreference = this.MAX_PREFERENCE_BONUS; + let addTarget = function(target) + { + let preference = cmpAttack.GetPreference(target); + let weight = 1; + + if (preference !== null && preference !== undefined) + weight += maxPreference / (1 + preference); + + targets.push(target, weight); + }; + + // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ + if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) + addTarget(this.unitAITarget); + for (let target of this.targetUnits) + addTarget(target); + + for (let i = 0; i < arrowsToFire; ++i) + { + let selectedIndex = targets.randomIndex(); + let selectedTarget = targets.itemAt(selectedIndex); + + if (selectedTarget && this.CheckTargetVisible(selectedTarget)) + { + cmpAttack.PerformAttack(attackType, selectedTarget); + PlaySound("attack", this.entity); + continue; + } + + // Could not attack target, retry + targets.removeAt(selectedIndex); + --i; + + if (!targets.length()) + { + this.arrowsLeft += arrowsToFire; + break; + } + } + + this.arrowsLeft -= arrowsToFire; + this.currentRound++; +}; + +/** + * Returns true if the target entity is visible through the FoW/SoD. + */ +ManpowerHolderUnit.prototype.CheckTargetVisible = function(target) +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return false; + + // Entities that are hidden and miraged are considered visible + var cmpFogging = Engine.QueryInterface(target, IID_Fogging); + if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) + return true; + + // Either visible directly, or visible in fog + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; +}; + +Engine.RegisterComponentType(IID_ManpowerHolder, "ManpowerHolderUnit", ManpowerHolderUnit); Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1481,8 +1481,10 @@ } // if the attacker is a building and we can repair the guarded, repair it rather than attacking - var cmpBuildingAI = Engine.QueryInterface(msg.data.attacker, IID_BuildingAI); - if (cmpBuildingAI && this.CanRepair(this.isGuardOf)) + // TODO: what's exactly the goal of that (should that apply when the attacker of the guarded unit is + // a structure or also a ship or a siege tower?). + var cmpManpowerHolder = Engine.QueryInterface(msg.data.attacker, IID_ManpowerHolder); + if (cmpManpowerHolder && this.CanRepair(this.isGuardOf)) { this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); return; @@ -1962,15 +1964,15 @@ this.FaceTowardsTarget(this.order.data.target); - var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); - if (cmpBuildingAI) - cmpBuildingAI.SetUnitAITarget(this.order.data.target); + var cmpManpowerHolder = Engine.QueryInterface(this.entity, IID_ManpowerHolder); + if (cmpManpowerHolder) + cmpManpowerHolder.SetUnitAITarget(this.order.data.target); }, "leave": function() { - var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); - if (cmpBuildingAI) - cmpBuildingAI.SetUnitAITarget(0); + var cmpManpowerHolder = Engine.QueryInterface(this.entity, IID_ManpowerHolder); + if (cmpManpowerHolder) + cmpManpowerHolder.SetUnitAITarget(0); this.StopTimer(); }, @@ -2011,9 +2013,10 @@ this.FaceTowardsTarget(target); - // BuildingAI has it's own attack-routine - var cmpBuildingAI = Engine.QueryInterface(this.entity, IID_BuildingAI); - if (!cmpBuildingAI) + // Let ManpowerHolderUnit handle ranged attack + // TODO: Check what should be done here + var cmpManpowerHolder = Engine.QueryInterface(this.entity, IID_ManpowerHolder); + if (!cmpManpowerHolder || this.order.data.attackType != "Ranged") { let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); cmpAttack.PerformAttack(this.order.data.attackType, target); Index: binaries/data/mods/public/simulation/components/interfaces/BuildingAI.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/BuildingAI.js +++ /dev/null @@ -1 +0,0 @@ -Engine.RegisterInterface("BuildingAI"); Index: binaries/data/mods/public/simulation/components/interfaces/ManpowerHolder.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/components/interfaces/ManpowerHolder.js @@ -0,0 +1 @@ +Engine.RegisterInterface("ManpowerHolder"); Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -18,6 +18,7 @@ Engine.LoadComponentScript("interfaces/Heal.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/Loot.js"); +Engine.LoadComponentScript("interfaces/ManpowerHolder.js"); Engine.LoadComponentScript("interfaces/Market.js"); Engine.LoadComponentScript("interfaces/Pack.js"); Engine.LoadComponentScript("interfaces/ProductionQueue.js"); @@ -34,7 +35,6 @@ Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("interfaces/Upgrade.js"); -Engine.LoadComponentScript("interfaces/BuildingAI.js"); Engine.LoadComponentScript("GuiInterface.js"); Resources = { @@ -644,7 +644,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedEntityState(-1, 10), { armour: null, attack: null, - buildingAI: null, + manpowerHolder: null, deathDamage:null, heal: null, isBarterMarket: true, Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -3,7 +3,7 @@ Engine.LoadHelperScript("Player.js"); Engine.LoadComponentScript("interfaces/Attack.js"); Engine.LoadComponentScript("interfaces/Auras.js"); -Engine.LoadComponentScript("interfaces/BuildingAI.js"); +Engine.LoadComponentScript("interfaces/ManpowerHolder.js"); Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/DamageReceiver.js"); Engine.LoadComponentScript("interfaces/Formation.js"); Index: binaries/data/mods/public/simulation/data/auras/units/heroes/iber_hero_caros_1.json =================================================================== --- binaries/data/mods/public/simulation/data/auras/units/heroes/iber_hero_caros_1.json +++ binaries/data/mods/public/simulation/data/auras/units/heroes/iber_hero_caros_1.json @@ -1,10 +1,10 @@ { "type": "garrison", - "affects": ["Structure", "SiegeTower"], + "affects": ["Structure"], "modifications": [ - { "value": "BuildingAI/GarrisonArrowMultiplier", "multiply": 1.75 }, - { "value": "BuildingAI/MaxArrowCount", "multiply": 1.75 } + { "value": "ManpowerHolder/GarrisonArrowMultiplier", "multiply": 1.75 }, + { "value": "ManpowerHolder/MaxArrowCount", "multiply": 1.75 } ], - "auraDescription": "75% more arrows per soldier for the building or siege tower he is garrisoned in.", + "auraDescription": "75% more arrows per soldier for the building he is garrisoned in.", "auraName": "Valiant Defender" } Index: binaries/data/mods/public/simulation/data/technologies/attack_tower_crenellations.json =================================================================== --- binaries/data/mods/public/simulation/data/technologies/attack_tower_crenellations.json +++ binaries/data/mods/public/simulation/data/technologies/attack_tower_crenellations.json @@ -7,7 +7,7 @@ "icon": "crenelations.png", "researchTime": 40, "tooltip": "Install crenellations and murder holes to have 40% more arrows fired per garrisoned soldier.", - "modifications": [{"value": "BuildingAI/GarrisonArrowMultiplier", "multiply": 1.4}], + "modifications": [{"value": "ManpowerHolder/GarrisonArrowMultiplier", "multiply": 1.4}], "affects": ["DefenseTower"], "soundComplete": "interface/alarm/alarm_upgradearmory.xml" } Index: binaries/data/mods/public/simulation/data/technologies/attack_tower_watch.json =================================================================== --- binaries/data/mods/public/simulation/data/technologies/attack_tower_watch.json +++ binaries/data/mods/public/simulation/data/technologies/attack_tower_watch.json @@ -15,7 +15,7 @@ "icon": "helmet_corinthian_bronze.png", "researchTime": 40, "tooltip": "Post sentries to add one arrow to towers.", - "modifications": [{"value": "BuildingAI/DefaultArrowCount", "add": 1.0}], + "modifications": [{"value": "ManpowerHolder/DefaultArrowCount", "add": 1.0}], "affects": ["DefenseTower"], "soundComplete": "interface/alarm/alarm_upgradearmory.xml" } Index: binaries/data/mods/public/simulation/data/technologies/successors/special_hellenistic_metropolis.json =================================================================== --- binaries/data/mods/public/simulation/data/technologies/successors/special_hellenistic_metropolis.json +++ binaries/data/mods/public/simulation/data/technologies/successors/special_hellenistic_metropolis.json @@ -8,7 +8,7 @@ "researchTime": 60, "tooltip": "Civic centers +100% health and capture points, double default arrows.", "modifications": [ - {"value": "BuildingAI/DefaultArrowCount", "multiply": 2.0}, + {"value": "ManpowerHolder/DefaultArrowCount", "multiply": 2.0}, {"value": "Capturable/CapturePoints", "multiply": 2.0}, {"value": "Health/Max", "multiply": 2.0} ], Index: binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml =================================================================== --- binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml +++ binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml @@ -12,10 +12,10 @@ 2000 - + 3 0.5 - + 8.0 Index: binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml =================================================================== --- binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml +++ binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml @@ -12,10 +12,10 @@ 2000 - + 5 0.5 - + 8.0 Index: binaries/data/mods/public/simulation/templates/other/plane.xml =================================================================== --- binaries/data/mods/public/simulation/templates/other/plane.xml +++ binaries/data/mods/public/simulation/templates/other/plane.xml @@ -15,11 +15,11 @@ 0 - + 3 1 Infantry - + true 0.0 Index: binaries/data/mods/public/simulation/templates/structures/iber_defense_tower.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/iber_defense_tower.xml +++ binaries/data/mods/public/simulation/templates/structures/iber_defense_tower.xml @@ -5,9 +5,9 @@ 12.0 - + 2 - + 5 200 Index: binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml +++ binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml @@ -25,11 +25,11 @@ 0 - + 1 15 1 - + neutral enemy ArmyCamp 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 @@ -10,11 +10,11 @@ 1 - + 0 0 Infantry Cavalry - + land own Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -29,10 +29,10 @@ Human - + 3 1 - + own neutral CivilCentre Index: binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml +++ binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre_military_colony.xml @@ -1,10 +1,10 @@ - + 1 15 1 - + own neutral Colony Index: binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml @@ -17,11 +17,11 @@ Human - + 1 1 Infantry - + DefenseTower Index: binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml @@ -26,9 +26,9 @@ Human - + 1 - + Outpost Index: binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml +++ binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml @@ -16,11 +16,11 @@ Human - + 0 1 Infantry - + land-shore Wall Index: binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml +++ binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml @@ -21,10 +21,10 @@ Human - + 3 1 - + Fortress Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml +++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml @@ -16,12 +16,12 @@ Ship Human - + 2 10 1 Infantry Cavalry - + 2 20 Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml +++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml @@ -24,12 +24,12 @@ Ship Structure - + 1 10 1 Catapult - + 3 30 Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml +++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml @@ -16,12 +16,12 @@ Ship Human - + 3 13 1 Infantry Cavalry - + 3 25 Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml +++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml @@ -1,6 +1,11 @@ + + 10 + 8 + 1000 + 0.0 12.0 @@ -17,12 +22,12 @@ Human - + 0 1 10 Infantry - + 60