diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 8ff3ddde05..b49da8636c 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -309,11 +309,13 @@ kill = Delete, Backspace ; Destroy selected units stop = "H" ; Stop the current action backtowork = "Y" ; The unit will go back to work unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected +unloadturrets = "U" ; Unload turreted units. move = "" ; Modifier to move to a point instead of another action (e.g. gather) attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point garrison = Ctrl ; Modifier to garrison when clicking on building +occupyturret = Ctrl ; Modifier to occupy a turret when clicking on a turret holder. autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building patrol = "P" ; Modifier to patrol a unit diff --git a/binaries/data/mods/public/globalscripts/Templates.js b/binaries/data/mods/public/globalscripts/Templates.js index dfd68f603c..295b7e963e 100644 --- a/binaries/data/mods/public/globalscripts/Templates.js +++ b/binaries/data/mods/public/globalscripts/Templates.js @@ -491,6 +491,11 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {}) ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource); } + if (template.TurretHolder) + ret.turretHolder = { + "turretPoints": template.TurretHolder.TurretPoints + }; + if (template.WallSet) { ret.wallSet = { diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js index f8ca1845b1..c072d6a00b 100644 --- a/binaries/data/mods/public/gui/common/tooltips.js +++ b/binaries/data/mods/public/gui/common/tooltips.js @@ -588,6 +588,16 @@ function getGarrisonTooltip(template) return tooltips.join("\n"); } +function getTurretsTooltip(template) +{ + if (!template.turretHolder) + return ""; + return sprintf(translate("%(label)s: %(turretsLimit)s"), { + "label": headerFont(translate("Turret Positions")), + "turretsLimit": Object.keys(template.turretHolder.turretPoints).length + }); +} + function getProjectilesTooltip(template) { if (!template.garrisonHolder || !template.buildingAI) diff --git a/binaries/data/mods/public/gui/hotkeys/spec/ingame.json b/binaries/data/mods/public/gui/hotkeys/spec/ingame.json index 928e97b729..2eb149b1a6 100644 --- a/binaries/data/mods/public/gui/hotkeys/spec/ingame.json +++ b/binaries/data/mods/public/gui/hotkeys/spec/ingame.json @@ -27,6 +27,10 @@ "name": "Unload", "desc": "Unload garrisoned units when a building/mechanical unit is selected." }, + "session.unloadturrets": { + "name": "Unload Turrets", + "desc": "Unload turreted units." + }, "session.unloadtype": { "name": "Unload unit type", "desc": "Modifier to unload all units of type." @@ -51,6 +55,10 @@ "name": "Garrison", "desc": "Modifier to garrison when clicking on building." }, + "session.occupyturret": { + "name": "Occupy Turret Point", + "desc": "Modifier to occupy a turret when clicking on a turret holder." + }, "session.autorallypoint": { "name": "Auto-rally point", "desc": "Modifier to set the rally point on the building itself." diff --git a/binaries/data/mods/public/gui/reference/common/ReferencePage.js b/binaries/data/mods/public/gui/reference/common/ReferencePage.js index f7b56a9d11..02a9d6ee35 100644 --- a/binaries/data/mods/public/gui/reference/common/ReferencePage.js +++ b/binaries/data/mods/public/gui/reference/common/ReferencePage.js @@ -57,6 +57,7 @@ ReferencePage.prototype.StatsFunctions = [ getHealerTooltip, getResistanceTooltip, getGarrisonTooltip, + getTurretsTooltip, getProjectilesTooltip, getSpeedTooltip, getGatherTooltip, diff --git a/binaries/data/mods/public/gui/session/hotkeys/misc.xml b/binaries/data/mods/public/gui/session/hotkeys/misc.xml index bc82520047..bf50ccaf92 100644 --- a/binaries/data/mods/public/gui/session/hotkeys/misc.xml +++ b/binaries/data/mods/public/gui/session/hotkeys/misc.xml @@ -32,6 +32,10 @@ unloadAll(); + + unloadAllTurrets(); + + stopUnits(g_Selection.toList()); diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js index 8c04425f42..83fcc96663 100644 --- a/binaries/data/mods/public/gui/session/selection_details.js +++ b/binaries/data/mods/public/gui/session/selection_details.js @@ -325,6 +325,7 @@ function displaySingle(entState) getGatherTooltip, getSpeedTooltip, getGarrisonTooltip, + getTurretsTooltip, getPopulationBonusTooltip, getProjectilesTooltip, getResourceTrickleTooltip, diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js index a96886340a..273e3c24e1 100644 --- a/binaries/data/mods/public/gui/session/selection_panels.js +++ b/binaries/data/mods/public/gui/session/selection_panels.js @@ -193,6 +193,7 @@ g_SelectionPanels.Construction = { getEntityCostTooltip(template, data.player), getResourceDropsiteTooltip(template), getGarrisonTooltip(template), + getTurretsTooltip(template), getPopulationBonusTooltip(template), showTemplateViewerOnRightClickTooltip(template) ); @@ -975,6 +976,7 @@ g_SelectionPanels.Training = { getHealerTooltip, getResistanceTooltip, getGarrisonTooltip, + getTurretsTooltip, getProjectilesTooltip, getSpeedTooltip, getResourceDropsiteTooltip diff --git a/binaries/data/mods/public/gui/session/selection_panels_helpers.js b/binaries/data/mods/public/gui/session/selection_panels_helpers.js index f141bd515b..a82541ddca 100644 --- a/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ b/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -415,31 +415,6 @@ function unloadTemplate(template, owner) }); } -function unloadSelection() -{ - let parent = 0; - let ents = []; - for (let ent in g_Selection.selected) - { - let state = GetEntityState(+ent); - if (!state || !state.turretParent) - continue; - if (!parent) - { - parent = state.turretParent; - ents.push(+ent); - } - else if (state.turretParent == parent) - ents.push(+ent); - } - if (parent) - Engine.PostNetworkCommand({ - "type": "unload", - "entities": ents, - "garrisonHolder": parent - }); -} - function unloadAll() { let garrisonHolders = g_Selection.toList().filter(e => { @@ -474,6 +449,44 @@ function unloadAll() }); } +function unloadAllTurrets() +{ + let turretHolders = g_Selection.toList().filter(e => { + let state = GetEntityState(e); + return state && !!state.turretHolder; + }); + + if (!turretHolders.length) + return; + + let ownedHolders = []; + let ejectables = []; + for (let ent of turretHolders) + { + let turretHolderState = GetEntityState(ent); + if (controlsPlayer(turretHolderState.player)) + ownedHolders.push(ent); + else + { + for (let turret of turretHolderState.turretHolder.turretPoints.map(tp => tp.entity)) + if (turret && controlsPlayer(GetEntityState(turret).player)) + ejectables.push(turret); + } + } + + if (ejectables.length) + Engine.PostNetworkCommand({ + "type": "leave-turret", + "entities": ejectables + }); + + if (ownedHolders.length) + Engine.PostNetworkCommand({ + "type": "unload-turrets", + "entities": ownedHolders + }); +} + function backToWork() { Engine.PostNetworkCommand({ diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js index 200862918d..21bc8859bd 100644 --- a/binaries/data/mods/public/gui/session/unit_actions.js +++ b/binaries/data/mods/public/gui/session/unit_actions.js @@ -684,6 +684,77 @@ var g_UnitActions = "specificness": 0, }, + "occupy-turret": + { + "execute": function(target, action, selection, queued, pushFront) + { + Engine.PostNetworkCommand({ + "type": "occupy-turret", + "entities": selection, + "target": action.target, + "queued": queued, + "pushFront": pushFront, + "formation": g_AutoFormation.getNull() + }); + + Engine.GuiInterfaceCall("PlaySound", { + "name": "order_garrison", + "entity": action.firstAbleEntity + }); + + return true; + }, + "getActionInfo": function(entState, targetState) + { + if (!entState.turretable || !targetState || !targetState.turretHolder || + !playerCheck(entState, targetState, ["Player", "MutualAlly"])) + return false; + + if (!targetState.turretHolder.turretPoints.find(point => + !point.allowedClasses || MatchesClassList(entState.identity.classes, point.allowedClasses))) + return false; + + let occupiedTurrets = targetState.turretHolder.turretPoints.filter(point => point.entity != null); + let tooltip = sprintf(translate("Current turrets: %(occupied)s/%(capacity)s"), { + "occupied": occupiedTurrets.length, + "capacity": targetState.turretHolder.turretPoints.length + }); + + if (occupiedTurrets.length == targetState.turretHolder.turretPoints.length) + tooltip = coloredText(tooltip, "orange"); + + return { + "possible": true, + "tooltip": tooltip + }; + }, + "preSelectedActionCheck": function(target, selection) + { + return preSelectedAction == ACTION_GARRISON && + (this.actionCheck(target, selection) || { + "type": "none", + "cursor": "action-garrison-disabled", + "target": null + }); + }, + "hotkeyActionCheck": function(target, selection) + { + return Engine.HotkeyIsPressed("session.occupyturret") && + this.actionCheck(target, selection); + }, + "actionCheck": function(target, selection) + { + let actionInfo = getActionInfo("occupy-turret", target, selection); + return actionInfo.possible && { + "type": "occupy-turret", + "cursor": "action-garrison", + "tooltip": actionInfo.tooltip, + "target": target + }; + }, + "specificness": 21, + }, + "garrison": { "execute": function(target, action, selection, queued, pushFront) @@ -984,6 +1055,22 @@ var g_UnitActions = targetState.garrisonHolder.capacity) tooltip = coloredText(tooltip, "orange"); } + else if (targetState && targetState.turretHolder && + playerCheck(entState, targetState, ["Player", "MutualAlly"])) + { + data.command = "occupy-turret"; + data.target = targetState.id; + cursor = "action-garrison"; + + let occupiedTurrets = targetState.turretHolder.turretPoints.filter(point => point.entity != null); + tooltip = sprintf(translate("Current turrets: %(occupied)s/%(capacity)s"), { + "occupied": occupiedTurrets.length, + "capacity": targetState.turretHolder.turretPoints.length + }); + + if (occupiedTurrets.length >= targetState.turretHolder.turretPoints.length) + tooltip = coloredText(tooltip, "orange"); + } else if (targetState && targetState.resourceSupply) { let resourceType = targetState.resourceSupply.type; @@ -1224,6 +1311,41 @@ var g_EntityCommands = "allowedPlayers": ["Player", "Ally"] }, + "unload-all-turrets": { + "getInfo": function(entStates) + { + let count = 0; + for (let entState of entStates) + { + if (!entState.turretHolder) + continue; + + if (allowedPlayersCheck([entState], ["Player"])) + count += entState.turretHolder.turretPoints.filter(turretPoint => turretPoint.entity).length; + else + for (let turretPoint of entState.turretHolder.turretPoints) + if (turretPoint.entity && allowedPlayersCheck([GetEntityState(turretPoint.entity)], ["Player"])) + ++count; + } + + if (!count) + return false; + + return { + "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unloadturrets") + + translate("Unload Turrets."), + "icon": "garrison-out.png", + "count": count, + "enabled": true + }; + }, + "execute": function() + { + unloadAllTurrets(); + }, + "allowedPlayers": ["Player", "Ally"] + }, + "delete": { "getInfo": function(entStates) { @@ -1317,15 +1439,11 @@ var g_EntityCommands = "allowedPlayers": ["Player"] }, - "unload": { + "leave-turret": { "getInfo": function(entStates) { - if (entStates.every(entState => { - if (!entState.unitAI || !entState.turretParent) - return true; - let parent = GetEntityState(entState.turretParent); - return !parent || !parent.garrisonHolder || parent.garrisonHolder.entities.indexOf(entState.id) == -1; - })) + if (entStates.every(entState => !entState.turretable || + entState.turretable.holder == INVALID_ENTITY)) return false; return { @@ -1334,9 +1452,16 @@ var g_EntityCommands = "enabled": true }; }, - "execute": function() + "execute": function(entStates) { - unloadSelection(); + if (!entStates.length) + return; + + Engine.PostNetworkCommand({ + "type": "leave-turret", + "entities": entStates.filter(entState => entState.turretable && + entState.turretable.holder != INVALID_ENTITY).map(entState => entState.id) + }); }, "allowedPlayers": ["Player"] }, diff --git a/binaries/data/mods/public/simulation/ai/common-api/entity.js b/binaries/data/mods/public/simulation/ai/common-api/entity.js index 22e8032317..2037ad0d9c 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -543,6 +543,8 @@ m.Template = m.Class({ "isGarrisonHolder": function() { return this.get("GarrisonHolder") !== undefined; }, + "isTurretHolder": function() { return this.get("TurretHolder") !== undefined; }, + /** * returns true if the tempalte can capture the given target entity * if no target is given, returns true if the template has the Capture attack @@ -565,6 +567,8 @@ m.Template = m.Class({ "canGarrison": function() { return "Garrisonable" in this._template; }, + "canOccupyTurret": function() { return "Turretable" in this._template; }, + "isTreasureCollecter": function() { return this.get("TreasureCollecter") !== undefined; }, }); @@ -840,23 +844,29 @@ m.Entity = m.Class({ return this; }, - "garrison": function(target, queued = false) { - Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued }); + "garrison": function(target, queued = false, pushFront = false) { + Engine.PostCommand(PlayerID, { "type": "garrison", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront }); return this; }, - "attack": function(unitId, allowCapture = true, queued = false) { - Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued }); + "occupy-turret": function(target, queued = false, pushFront = false) { + Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": [this.id()], "target": target.id(), "queued": queued, "pushFront": pushFront }); return this; }, - "collectTreasure": function(target, autocontinue = false, queued = false) { + "attack": function(unitId, allowCapture = true, queued = false, pushFront = false) { + Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued, "pushFront": pushFront }); + return this; + }, + + "collectTreasure": function(target, autocontinue = false, queued = false, pushFront = false) { Engine.PostCommand(PlayerID, { "type": "collect-treasure", "entities": [this.id()], "target": target.id(), "autocontinue": autocontinue, - "queued": queued + "queued": queued, + "pushFront": pushFront }); return this; }, diff --git a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js index 12f11e6fe7..e4251579c1 100644 --- a/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js +++ b/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js @@ -175,6 +175,12 @@ m.EntityCollection.prototype.garrison = function(target, queued = false) return this; }; +m.EntityCollection.prototype.occupyTurret = function(target, queued = false) +{ + Engine.PostCommand(PlayerID, { "type": "occupy-turret", "entities": this.toIdArray(), "target": target.id(), "queued": queued }); + return this; +}; + m.EntityCollection.prototype.destroy = function() { Engine.PostCommand(PlayerID, { "type": "delete-entities", "entities": this.toIdArray() }); diff --git a/binaries/data/mods/public/simulation/components/Auras.js b/binaries/data/mods/public/simulation/components/Auras.js index 4804b9ba58..3611357102 100644 --- a/binaries/data/mods/public/simulation/components/Auras.js +++ b/binaries/data/mods/public/simulation/components/Auras.js @@ -180,6 +180,11 @@ Auras.prototype.IsGarrisonedUnitsAura = function(name) return this.GetType(name) == "garrisonedUnits"; }; +Auras.prototype.IsTurretedUnitsAura = function(name) +{ + return this.GetType(name) == "turretedUnits"; +}; + Auras.prototype.IsRangeAura = function(name) { return this.GetType(name) == "range"; @@ -321,6 +326,15 @@ Auras.prototype.OnGarrisonedUnitsChanged = function(msg) } }; +Auras.prototype.OnTurretsChanged = function(msg) +{ + for (let name of this.GetAuraNames().filter(n => this.IsTurretedUnitsAura(n))) + { + this.ApplyAura(name, msg.added); + this.RemoveAura(name, msg.removed); + } +}; + Auras.prototype.ApplyFormationAura = function(memberList) { for (let name of this.GetAuraNames().filter(n => this.IsFormationAura(n))) @@ -512,7 +526,7 @@ Auras.prototype.OnGarrisonedStateChanged = function(msg) if (msg.holderID != INVALID_ENTITY) this.ApplyGarrisonAura(msg.holderID); - if (msg.olderHolder != INVALID_ENTITY) + if (msg.oldHolder != INVALID_ENTITY) this.RemoveGarrisonAura(msg.oldHolder); }; diff --git a/binaries/data/mods/public/simulation/components/BuildingAI.js b/binaries/data/mods/public/simulation/components/BuildingAI.js index 908dda58c0..e41d00d282 100644 --- a/binaries/data/mods/public/simulation/components/BuildingAI.js +++ b/binaries/data/mods/public/simulation/components/BuildingAI.js @@ -30,36 +30,21 @@ BuildingAI.prototype.Init = function() this.targetUnits = []; }; -BuildingAI.prototype.OnGarrisonedUnitsChanged = function() +BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) { - this.RecalculateProjectileCount(); -}; - -BuildingAI.prototype.OnTurretsChanged = function() -{ - this.RecalculateProjectileCount(); -}; - -BuildingAI.prototype.RecalculateProjectileCount = function() -{ - this.archersGarrisoned = 0; let classes = this.template.GarrisonArrowClasses; - - let cmpTurretHolder = Engine.QueryInterface(this.entity, IID_TurretHolder); - let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); - for (let ent of cmpGarrisonHolder.GetEntities()) + for (let ent of msg.added) { - // Only count non-visible garrisoned entities towards extra arrows. - if (cmpTurretHolder && cmpTurretHolder.OccupiesTurret(ent)) - continue; - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (!cmpIdentity) - continue; - - if (MatchesClassList(cmpIdentity.GetClassesList(), classes)) + if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes)) ++this.archersGarrisoned; } + for (let ent of msg.removed) + { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), classes)) + --this.archersGarrisoned; + } }; BuildingAI.prototype.OnOwnershipChanged = function(msg) diff --git a/binaries/data/mods/public/simulation/components/GarrisonHolder.js b/binaries/data/mods/public/simulation/components/GarrisonHolder.js index 2e6ed9b9a4..d9c0c47819 100644 --- a/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ b/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -199,38 +199,6 @@ GarrisonHolder.prototype.Garrison = function(entity) return true; }; -/** - * @param {number} entity - EntityID to find the spawn position for. - * @param {boolean} forced - Optionally whether the spawning is forced. - * @return {Vector3D} - An appropriate spawning position. - */ -GarrisonHolder.prototype.GetSpawnPosition = function(entity, forced) -{ - let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); - let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); - let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); - - // If the garrisonHolder is a sinking ship, restrict the location to the intersection of both passabilities - // TODO: should use passability classes to be more generic - let pos; - if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship")) - pos = cmpFootprint.PickSpawnPointBothPass(entity); - else - pos = cmpFootprint.PickSpawnPoint(entity); - - if (pos.y < 0) - { - // Error: couldn't find space satisfying the unit's passability criteria - if (!forced) - return null; - - // If ejection is forced, we need to continue, so use center of the building - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - pos = cmpPosition.GetPosition(); - } - return pos; -}; - /** * @param {number} entity - The entity ID of the entity to eject. * @param {boolean} forced - Whether eject is forced (e.g. if building is destroyed). @@ -256,32 +224,6 @@ GarrisonHolder.prototype.Eject = function(entity, forced) return true; }; -/** - * @param {number} entity - The entity ID of the entity to order to the rally point. - */ -GarrisonHolder.prototype.OrderToRallyPoint = function(entity) -{ - let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); - if (!cmpRallyPoint || !cmpRallyPoint.GetPositions()[0]) - return; - - let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (!cmpOwnership) - return; - - let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership); - if (!cmpEntOwnership || cmpOwnership.GetOwner() != cmpEntOwnership.GetOwner()) - return; - - let commands = GetRallyPointCommands(cmpRallyPoint, [entity]); - // Ignore the rally point if it is autogarrison - if (commands[0].type == "garrison" && commands[0].target == this.entity) - return; - - for (let command of commands) - ProcessCommand(cmpOwnership.GetOwner(), command); -}; - /** * Tell unit to unload from this entity. * @param {number} entity - The entity to unload. diff --git a/binaries/data/mods/public/simulation/components/Garrisonable.js b/binaries/data/mods/public/simulation/components/Garrisonable.js index 150685cb31..fc8778b3ea 100644 --- a/binaries/data/mods/public/simulation/components/Garrisonable.js +++ b/binaries/data/mods/public/simulation/components/Garrisonable.js @@ -69,7 +69,7 @@ Garrisonable.prototype.CanGarrison = function(target) * @param {number} target - The entity ID of the entity this entity is being garrisoned in. * @return {boolean} - Whether garrisoning succeeded. */ -Garrisonable.prototype.Garrison = function(target, renamed = false) +Garrisonable.prototype.Garrison = function(target) { if (!this.CanGarrison(target)) return false; @@ -93,35 +93,24 @@ Garrisonable.prototype.Garrison = function(target, renamed = false) "holderID": target }); - if (renamed) - return true; - - let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder); - if (cmpTurretHolder) - cmpTurretHolder.OccupyTurret(this.entity); - return true; }; /** * @param {boolean} forced - Optionally whether the spawning is forced. - * @param {boolean} renamed - Optionally whether the ungarrisoning is due to renaming. * @return {boolean} - Whether the ungarrisoning succeeded. */ -Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false) +Garrisonable.prototype.UnGarrison = function(forced = false) { if (!this.holder) return true; - let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); - if (!cmpGarrisonHolder) - return false; - - let pos = cmpGarrisonHolder.GetSpawnPosition(this.entity, forced); + let pos = PositionHelper.GetSpawnPosition(this.holder, this.entity, forced); if (!pos) return false; - if (!cmpGarrisonHolder.Eject(this.entity, forced)) + let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); + if (!cmpGarrisonHolder || !cmpGarrisonHolder.Eject(this.entity, forced)) return false; let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); @@ -147,14 +136,9 @@ Garrisonable.prototype.UnGarrison = function(forced = false, renamed = false) "holderID": INVALID_ENTITY }); - if (renamed) - return true; - - let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); - if (cmpTurretHolder) - cmpTurretHolder.LeaveTurret(this.entity); - - cmpGarrisonHolder.OrderToRallyPoint(this.entity); + let cmpRallyPoint = Engine.QueryInterface(this.holder, IID_RallyPoint); + if (cmpRallyPoint) + cmpRallyPoint.OrderToRallyPoint(this.entity, ["garrison"]); delete this.holder; return true; @@ -165,22 +149,11 @@ Garrisonable.prototype.OnEntityRenamed = function(msg) if (!this.holder) return; - let cmpGarrisonHolder = Engine.QueryInterface(this.holder, IID_GarrisonHolder); - if (cmpGarrisonHolder) - { - this.UnGarrison(true, true); - let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable); - if (cmpGarrisonable) - cmpGarrisonable.Garrison(this.holder, true); - } - - // We process EntityRenamed of turrets seperately since we - // want to occupy the same position after being renamed. - let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); - if (cmpTurretHolder) - cmpTurretHolder.SwapEntities(msg.entity, msg.newentity); - - delete this.holder; + let holder = this.holder; + this.UnGarrison(true, true); + let cmpGarrisonable = Engine.QueryInterface(msg.newentity, IID_Garrisonable); + if (cmpGarrisonable) + cmpGarrisonable.Garrison(holder, true); }; Engine.RegisterComponentType(IID_Garrisonable, "Garrisonable", Garrisonable); diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 5f77031bf0..479be904f1 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -388,6 +388,12 @@ GuiInterface.prototype.GetEntityState = function(player, ent) "turretPoints": cmpTurretHolder.GetTurretPoints() }; + let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable); + if (cmpTurretable) + ret.turretable = { + "holder": cmpTurretable.HolderID() + }; + let cmpGarrisonable = Engine.QueryInterface(ent, IID_Garrisonable); if (cmpGarrisonable) ret.garrisonable = { diff --git a/binaries/data/mods/public/simulation/components/RallyPoint.js b/binaries/data/mods/public/simulation/components/RallyPoint.js index e672f11b50..7238be687e 100644 --- a/binaries/data/mods/public/simulation/components/RallyPoint.js +++ b/binaries/data/mods/public/simulation/components/RallyPoint.js @@ -100,6 +100,31 @@ RallyPoint.prototype.Reset = function() cmpRallyPointRenderer.Reset(); }; +/** + * @param {number} entity - The entity ID of the entity to order to the rally point. + * @param {string[]} ignore - The commands to ignore when performed on this.entity. + * E.g. "garrison" when unloading. + */ +RallyPoint.prototype.OrderToRallyPoint = function(entity, ignore = []) +{ + let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return; + let owner = cmpOwnership.GetOwner(); + + let cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership); + if (!cmpEntOwnership || cmpEntOwnership.GetOwner() != owner) + return; + + let commands = GetRallyPointCommands(this, [entity]); + if (!commands.length || + commands[0].target == this.entity && ignore.includes(commands[0].type)) + return; + + for (let command of commands) + ProcessCommand(owner, command); +}; + RallyPoint.prototype.OnGlobalEntityRenamed = function(msg) { for (var data of this.data) diff --git a/binaries/data/mods/public/simulation/components/TurretHolder.js b/binaries/data/mods/public/simulation/components/TurretHolder.js index 9a65039f4a..dd56f35a56 100644 --- a/binaries/data/mods/public/simulation/components/TurretHolder.js +++ b/binaries/data/mods/public/simulation/components/TurretHolder.js @@ -1,8 +1,6 @@ /** * This class holds the functions regarding entities being visible on * another entity, but tied to their parents location. - * Currently renaming and changing ownership are still managed by GarrisonHolder.js, - * but in the future these components should be independent. */ class TurretHolder { @@ -54,6 +52,15 @@ class TurretHolder return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses._string); } + /** + * @param {number} entity - The entity to check for. + * @return {boolean} - Whether the entity is allowed to occupy any turret point. + */ + CanOccupy(entity) + { + return !!this.turretPoints.find(turretPoint => this.AllowedToOccupyTurret(entity, turretPoint)); + } + /** * Occupy a turret point with the given entity. * @param {number} entity - The entity to use. @@ -87,6 +94,7 @@ class TurretHolder return false; turretPoint.entity = entity; + // Angle of turrets: // Renamed entities (turretPoint != undefined) should keep their angle. // Otherwise if an angle is given in the turretPoint, use it. @@ -100,19 +108,6 @@ class TurretHolder cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset); - let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion); - if (cmpUnitMotion) - cmpUnitMotion.SetFacePointAfterMove(false); - - let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); - if (cmpUnitAI) - cmpUnitAI.SetTurretStance(); - - // Remove the unit's obstruction to avoid interfering with pathing. - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - if (cmpObstruction) - cmpObstruction.SetActive(false); - Engine.PostMessage(this.entity, MT_TurretsChanged, { "added": [entity], "removed": [] @@ -152,23 +147,11 @@ class TurretHolder if (!turretPoint) return false; - let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position); - cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D()); - - let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion); - if (cmpUnitMotionEntity) - cmpUnitMotionEntity.SetFacePointAfterMove(true); - - let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI); - if (cmpUnitAIEntity) - cmpUnitAIEntity.ResetTurretStance(); - turretPoint.entity = null; - // Reset the obstruction flags to template defaults. - let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction); - if (cmpObstruction) - cmpObstruction.SetActive(true); + let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position); + if (cmpPositionEntity) + cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D()); Engine.PostMessage(this.entity, MT_TurretsChanged, { "added": [], @@ -205,7 +188,8 @@ class TurretHolder */ GetOccupiedTurretName(entity) { - return this.GetOccupiedTurret(entity).name || ""; + let turret = this.GetOccupiedTurret(entity); + return turret ? turret.name : ""; } /** @@ -220,6 +204,60 @@ class TurretHolder return entities; } + /** + * @return {boolean} - Whether all the turret points are occupied. + */ + IsFull() + { + return !!this.turretPoints.find(turretPoint => turretPoint.entity == null); + } + + /** + * @return {Object} - Max and min ranges at which entities can occupy any turret. + */ + GetLoadingRange() + { + return { "min": 0, "max": +(this.template.LoadingRange || 2) }; + } + + /** + * @param {number} ent - The entity ID of the turret to be potentially picked up. + * @return {boolean} - Whether this entity can pick the specified entity up. + */ + CanPickup(ent) + { + if (!this.template.Pickup || this.IsFull()) + return false; + let cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership); + return !!cmpOwner && IsOwnedByPlayer(cmpOwner.GetOwner(), ent); + } + + /** + * @param {number[]} entities - The entities to ask to leave or to kill. + */ + EjectOrKill(entities) + { + let removedEntities = []; + for (let entity of entities) + { + let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable); + if (!cmpTurretable || !cmpTurretable.LeaveTurret(true)) + { + let cmpHealth = Engine.QueryInterface(entity, IID_Health); + if (cmpHealth) + cmpHealth.Kill(); + else + Engine.DestroyEntity(entity); + removedEntities.push(entity); + } + } + if (removedEntities.length) + Engine.PostMessage(this.entity, MT_TurretsChanged, { + "added": [], + "removed": removedEntities + }); + } + /** * Sets an init turret, present from game start. (E.g. set in Atlas.) * @param {String} turretName - The name of the turret point to be used. @@ -237,20 +275,6 @@ class TurretHolder this.initTurrets.set(turretName, entity); } - /** - * @param {number} from - The entity to substitute. - * @param {number} to - The entity to subtitute with. - */ - SwapEntities(from, to) - { - let turretPoint = this.GetOccupiedTurret(from); - if (turretPoint) - { - this.LeaveTurret(from, turretPoint); - this.OccupyTurret(to, turretPoint); - } - } - /** * Update list of turreted entities when a game inits. */ @@ -274,10 +298,7 @@ class TurretHolder } /** - * Initialise the turreted units. - * Really ugly, but because GarrisonHolder is processed earlier, and also turrets - * entities on init, we can find an entity that already is present. - * In that case we reject and occupy. + * Initialise turreted units. */ OnGlobalInitGame(msg) { @@ -285,16 +306,48 @@ class TurretHolder return; for (let [turretPointName, entity] of this.initTurrets) - { - if (this.OccupiesTurret(entity)) - this.LeaveTurret(entity); if (!this.OccupyNamedTurret(entity, turretPointName)) warn("Entity " + entity + " could not occupy the turret point " + turretPointName + " of turret holder " + this.entity + "."); - } delete this.initTurrets; } + + /** + * @param {Object} msg - { "entity": number, "newentity": number }. + */ + OnEntityRenamed(msg) + { + for (let entity of this.GetEntities()) + { + let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable); + if (!cmpTurretable) + continue; + let currentPoint = this.GetOccupiedTurretName(entity); + cmpTurretable.LeaveTurret(true); + cmpTurretable.OccupyTurret(msg.newentity, currentPoint); + } + } + + /** + * @param {Object} msg - { "entity": number, "from": number, "to": number }. + */ + OnOwnershipChanged(msg) + { + let entities = this.GetEntities(); + if (!entities.length) + return; + + if (msg.to == INVALID_PLAYER) + this.EjectOrKill(entities); + else + for (let entity of entities.filter(entity => !IsOwnedByMutualAllyOfEntity(entity, this.entity))) + { + let cmpTurretable = Engine.QueryInterface(entity, IID_Turretable); + if (cmpTurretable) + cmpTurretable.LeaveTurret(); + } + } } TurretHolder.prototype.Schema = @@ -328,6 +381,16 @@ TurretHolder.prototype.Schema = "" + "" + "" + - ""; + "" + + "" + + "" + + "" + + "" + + "" + "" + + "" + + "" + + "" + + ""; Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); diff --git a/binaries/data/mods/public/simulation/components/Turretable.js b/binaries/data/mods/public/simulation/components/Turretable.js new file mode 100644 index 0000000000..af53fa6b1b --- /dev/null +++ b/binaries/data/mods/public/simulation/components/Turretable.js @@ -0,0 +1,165 @@ +function Turretable() {} + +Turretable.prototype.Schema = + ""; + +Turretable.prototype.Init = function() +{ +}; + +/** + * @return {number} - The entity ID of the entity this entity is turreted on. + */ +Turretable.prototype.HolderID = function() +{ + return this.holder || INVALID_ENTITY; +}; + +/** + * @return {boolean} - Whether we're turreted. + */ +Turretable.prototype.IsTurreted = function() +{ + return !!this.holder; +}; + +/** + * @param {number} target - The entity ID to check. + * @return {boolean} - Whether we can occupy the turret. + */ +Turretable.prototype.CanOccupy = function(target) +{ + if (this.holder) + return false; + + let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder); + return cmpTurretHolder && cmpTurretHolder.CanOccupy(this.entity); +}; + +/** + * @param {number} target - The entity ID of the entity this entity is being turreted on. + * @param {string} turretPointName - Optionally the turret point name to occupy. + * @return {boolean} - Whether occupying succeeded. + */ +Turretable.prototype.OccupyTurret = function(target, turretPointName = "") +{ + if (!this.CanOccupy(target)) + return false; + + let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder); + if (!cmpTurretHolder || !cmpTurretHolder.OccupyNamedTurret(this.entity, turretPointName)) + return false; + + this.holder = target; + + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + if (cmpUnitAI) + { + cmpUnitAI.SetGarrisoned(); + cmpUnitAI.SetTurretStance(); + } + + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + if (cmpUnitMotion) + cmpUnitMotion.SetFacePointAfterMove(false); + + // Remove the unit's obstruction to avoid interfering with pathing. + let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); + if (cmpObstruction) + cmpObstruction.SetActive(false); + + Engine.PostMessage(this.entity, MT_TurretedStateChanged, { + "oldHolder": INVALID_ENTITY, + "holderID": target + }); + + return true; +}; + +/** + * @param {boolean} forced - Optionally whether the leaving the turret is forced. + * @return {boolean} - Whether leaving the turret succeeded. + */ +Turretable.prototype.LeaveTurret = function(forced = false) +{ + if (!this.holder) + return true; + + let pos = PositionHelper.GetSpawnPosition(this.holder, this.entity, forced); + if (!pos) + return false; + + let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); + if (!cmpTurretHolder || !cmpTurretHolder.LeaveTurret(this.entity)) + return false; + + let cmpUnitMotionEntity = Engine.QueryInterface(this.entity, IID_UnitMotion); + if (cmpUnitMotionEntity) + cmpUnitMotionEntity.SetFacePointAfterMove(true); + + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (cmpPosition) + { + cmpPosition.JumpTo(pos.x, pos.z); + cmpPosition.SetHeightOffset(0); + } + + let cmpHolderPosition = Engine.QueryInterface(this.holder, IID_Position); + if (cmpHolderPosition) + cmpPosition.SetYRotation(cmpHolderPosition.GetPosition().horizAngleTo(pos)); + + let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); + if (cmpUnitAI) + { + cmpUnitAI.Ungarrison(); + cmpUnitAI.UnsetGarrisoned(); + cmpUnitAI.ResetTurretStance(); + } + + // Reset the obstruction flags to template defaults. + let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); + if (cmpObstruction) + cmpObstruction.SetActive(true); + + Engine.PostMessage(this.entity, MT_TurretedStateChanged, { + "oldHolder": this.holder, + "holderID": INVALID_ENTITY + }); + + let cmpRallyPoint = Engine.QueryInterface(this.holder, IID_RallyPoint); + if (cmpRallyPoint) + cmpRallyPoint.OrderToRallyPoint(this.entity, ["occupy-turret"]); + + delete this.holder; + return true; +}; + +Turretable.prototype.OnEntityRenamed = function(msg) +{ + if (!this.holder) + return; + + let cmpTurretHolder = Engine.QueryInterface(this.holder, IID_TurretHolder); + if (!cmpTurretHolder) + return; + + let holder = this.holder; + let currentPoint = cmpTurretHolder.GetOccupiedTurretName(this.entity); + this.LeaveTurret(true); + let cmpTurretableNew = Engine.QueryInterface(msg.newentity, IID_Turretable); + if (cmpTurretableNew) + cmpTurretableNew.OccupyTurret(holder, currentPoint); +}; + +Turretable.prototype.OnOwnershipChanged = function(msg) +{ + if (!this.holder) + return; + + if (msg.to == INVALID_PLAYER) + this.LeaveTurret(true); + else if (!IsOwnedByMutualAllyOfEntity(this.entity, this.holder)) + this.LeaveTurret(); +}; + +Engine.RegisterComponentType(IID_Turretable, "Turretable", Turretable); diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 4aff58d285..96cd02eb9b 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -592,7 +592,8 @@ UnitAI.prototype.UnitFsmSpec = { return ACCEPT_ORDER; } - if (this.CheckGarrisonRange(msg.data.target)) + if (msg.data.garrison ? this.CheckGarrisonRange(msg.data.target) : + this.CheckOccupyTurretRange(msg.data.target)) this.SetNextState("INDIVIDUAL.GARRISON.GARRISONING"); else this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); @@ -744,9 +745,11 @@ UnitAI.prototype.UnitFsmSpec = { }, "Order.Garrison": function(msg) { - if (!Engine.QueryInterface(msg.data.target, IID_GarrisonHolder)) + if (!Engine.QueryInterface(msg.data.target, + msg.data.garrison ? IID_GarrisonHolder : IID_TurretHolder)) return this.FinishOrder(); - if (!this.CheckGarrisonRange(msg.data.target)) + if (!(msg.data.garrison ? this.CheckGarrisonRange(msg.data.target) : + this.CheckOccupyTurretRange(msg.data.target))) { if (!this.CheckTargetVisible(msg.data.target)) return this.FinishOrder(); @@ -1100,19 +1103,20 @@ UnitAI.prototype.UnitFsmSpec = { "GARRISON": { "APPROACHING": { "enter": function() { - let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); - cmpFormation.SetRearrange(true); - cmpFormation.MoveMembersIntoFormation(true, true); - - if (!this.MoveToGarrisonRange(this.order.data.target)) + if (!(this.order.data.garrison ? this.MoveToGarrisonRange(this.order.data.target) : + this.MoveToOccupyTurretRange(this.order.data.target))) { this.FinishOrder(); return true; } - // If the garrisonholder should pickup, warn it so it can take needed action. - let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); - if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) + let cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + cmpFormation.SetRearrange(true); + cmpFormation.MoveMembersIntoFormation(true, true); + + // If the holder should pickup, warn it so it can take needed action. + let cmpHolder = Engine.QueryInterface(this.order.data.target, this.order.data.garrison ? IID_GarrisonHolder : IID_TurretHolder); + if (cmpHolder && cmpHolder.CanPickup(this.entity)) { this.pickup = this.order.data.target; // temporary, deleted in "leave" Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); @@ -1137,7 +1141,7 @@ UnitAI.prototype.UnitFsmSpec = { "GARRISONING": { "enter": function() { - this.CallMemberFunction("Garrison", [this.order.data.target, false]); + this.CallMemberFunction(this.order.data.garrison ? "Garrison" : "OccupyTurret", [this.order.data.target, false]); // We might have been disbanded due to the lack of members. if (Engine.QueryInterface(this.entity, IID_Formation).GetMemberCount()) this.SetNextState("MEMBER"); @@ -3209,13 +3213,15 @@ UnitAI.prototype.UnitFsmSpec = { "GARRISON": { "APPROACHING": { "enter": function() { - if (!this.CanGarrison(this.order.data.target)) + if (this.order.data.garrison ? !this.CanGarrison(this.order.data.target) : + !this.CanOccupyTurret(this.order.data.target)) { this.FinishOrder(); return true; } - if (!this.MoveToGarrisonRange(this.order.data.target)) + if (this.order.data.garrison ? !this.MoveToGarrisonRange(this.order.data.target) : + !this.MoveToOccupyTurretRange(this.order.data.target)) { this.FinishOrder(); return true; @@ -3224,8 +3230,8 @@ UnitAI.prototype.UnitFsmSpec = { if (this.pickup) Engine.PostMessage(this.pickup, MT_PickupCanceled, { "entity": this.entity }); - let cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); - if (cmpGarrisonHolder && cmpGarrisonHolder.CanPickup(this.entity)) + let cmpHolder = Engine.QueryInterface(this.order.data.target, this.order.data.garrison ? IID_GarrisonHolder : IID_TurretHolder); + if (cmpHolder && cmpHolder.CanPickup(this.entity)) { this.pickup = this.order.data.target; Engine.PostMessage(this.pickup, MT_PickupRequested, { "entity": this.entity }); @@ -3246,7 +3252,8 @@ UnitAI.prototype.UnitFsmSpec = { if (!msg.likelyFailure && !msg.likelySuccess) return; - if (this.CheckGarrisonRange(this.order.data.target)) + if (this.order.data.garrison ? this.CheckGarrisonRange(this.order.data.target) : + this.CheckOccupyTurretRange(this.order.data.target)) this.SetNextState("GARRISONING"); else { @@ -3265,11 +3272,23 @@ UnitAI.prototype.UnitFsmSpec = { "GARRISONING": { "enter": function() { let target = this.order.data.target; - let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable); - if (!cmpGarrisonable || !cmpGarrisonable.Garrison(target)) + if (this.order.data.garrison) { - this.FinishOrder(); - return true; + let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable); + if (!cmpGarrisonable || !cmpGarrisonable.Garrison(target)) + { + this.FinishOrder(); + return true; + } + } + else + { + let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable); + if (!cmpTurretable || !cmpTurretable.OccupyTurret(target)) + { + this.FinishOrder(); + return true; + } } if (this.formationController) @@ -3459,8 +3478,10 @@ UnitAI.prototype.Init = function() UnitAI.prototype.IsTurret = function() { - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - return cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY; + if (!this.isGarrisoned) + return false; + let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable); + return cmpTurretable && cmpTurretable.HolderID() != INVALID_ENTITY; }; UnitAI.prototype.IsFormationController = function() @@ -4170,6 +4191,12 @@ UnitAI.prototype.BackToWork = function() if (this.isGarrisoned) { + if (this.IsTurret()) + { + let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable); + if (!cmpTurretable || !cmpTurretable.LeaveTurret()) + return false; + } let cmpGarrisonable = Engine.QueryInterface(this.entity, IID_Garrisonable); if (!cmpGarrisonable || !cmpGarrisonable.UnGarrison(false)) return false; @@ -4795,6 +4822,20 @@ UnitAI.prototype.MoveToGarrisonRange = function(target) return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; +UnitAI.prototype.MoveToOccupyTurretRange = function(target) +{ + if (!this.CheckTargetVisible(target)) + return false; + + let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder); + if (!cmpTurretHolder) + return false; + let range = cmpTurretHolder.GetLoadingRange(); + + let cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); + return this.AbleToMove(cmpUnitMotion) && cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); +}; + /** * Generic dispatcher for other Check...Range functions. * @param iid - Interface ID (optional) implementing GetRange @@ -4920,6 +4961,16 @@ UnitAI.prototype.CheckGarrisonRange = function(target) return this.CheckTargetRangeExplicit(target, range.min, range.max); }; +UnitAI.prototype.CheckOccupyTurretRange = function(target) +{ + let cmpTurretHolder = Engine.QueryInterface(target, IID_TurretHolder); + if (!cmpTurretHolder) + return false; + + let range = cmpTurretHolder.GetLoadingRange(); + return this.CheckTargetRangeExplicit(target, range.min, range.max); +}; + /** * Returns true if the target entity is visible through the FoW/SoD. */ @@ -5353,7 +5404,7 @@ UnitAI.prototype.AddOrder = function(type, data, queued, pushFront) // May happen if an order arrives on the same turn the unit is garrisoned // in that case, just forget the order as this will lead to an infinite loop. // ToDo: Fix that by checking for the ability to move on orders that need that. - if (this.isGarrisoned && !this.IsTurret() && type != "Ungarrison") + if (this.isGarrisoned && type != "Ungarrison") return; this.ReplaceOrder(type, data); } @@ -5591,7 +5642,7 @@ UnitAI.prototype.Garrison = function(target, queued, pushFront) this.WalkToTarget(target, queued); return; } - this.AddOrder("Garrison", { "target": target, "force": true }, queued, pushFront); + this.AddOrder("Garrison", { "target": target, "force": true, "garrison": true }, queued, pushFront); }; /** @@ -5604,6 +5655,21 @@ UnitAI.prototype.Ungarrison = function() this.AddOrder("Ungarrison", null, false); }; +/** + * Adds garrison order to the queue, forced by the player. + */ +UnitAI.prototype.OccupyTurret = function(target, queued, pushFront) +{ + if (target == this.entity) + return; + if (!this.CanOccupyTurret(target)) + { + this.WalkToTarget(target, queued); + return; + } + this.AddOrder("Garrison", { "target": target, "force": true, "garrison": false }, queued, pushFront); +}; + /** * Adds gather order to the queue, forced by the player * until the target is reached @@ -6443,6 +6509,17 @@ UnitAI.prototype.CanRepair = function(target) return cmpOwnership && IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target); }; +UnitAI.prototype.CanOccupyTurret = function(target) +{ + // Formation controllers should always respond to commands + // (then the individual units can make up their own minds). + if (this.IsFormationController()) + return true; + + let cmpTurretable = Engine.QueryInterface(this.entity, IID_Turretable); + return cmpTurretable && cmpTurretable.CanOccupy(target); +}; + UnitAI.prototype.CanPack = function() { var cmpPack = Engine.QueryInterface(this.entity, IID_Pack); diff --git a/binaries/data/mods/public/simulation/components/interfaces/Turretable.js b/binaries/data/mods/public/simulation/components/interfaces/Turretable.js new file mode 100644 index 0000000000..f7fc3b259b --- /dev/null +++ b/binaries/data/mods/public/simulation/components/interfaces/Turretable.js @@ -0,0 +1,7 @@ +Engine.RegisterInterface("Turretable"); + +/** + * Message of the form { "holderID": number } + * sent from the Turretable component whenever the turreted state changes. + */ +Engine.RegisterMessageType("TurretedStateChanged"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js b/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js index be7c65c80f..ad6f752b7f 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -2,7 +2,6 @@ Engine.LoadHelperScript("ValueModification.js"); Engine.LoadHelperScript("Player.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); -Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js b/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js index f36a5d75b3..26e91725ea 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js +++ b/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js @@ -1,5 +1,7 @@ +Engine.LoadHelperScript("Position.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); +Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("Garrisonable.js"); @@ -9,12 +11,15 @@ const garrisonHolderID = 1; const garrisonableID = 2; AddMock(garrisonHolderID, IID_GarrisonHolder, { "Garrison": () => true, - "GetSpawnPosition": () => new Vector3D(0, 0, 0), "IsAllowedToGarrison": () => true, - "OrderToRallyPoint": () => {}, "Eject": () => true }); +AddMock(garrisonHolderID, IID_Footprint, { + "PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30), + "PickSpawnPoint": entity => new Vector3D(4, 3, 30) +}); + let size = 1; let cmpGarrisonable = ConstructComponent(garrisonableID, "Garrisonable", { "Size": size @@ -39,7 +44,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID); TS_ASSERT(!cmpGarrisonable.Garrison(garrisonHolderID)); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), garrisonHolderID); -cmpGarrisonable.UnGarrison(); +TS_ASSERT(cmpGarrisonable.UnGarrison()); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonable.HolderID(), INVALID_ENTITY); // Test renaming. diff --git a/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js b/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js index e3a5fb3a45..49713dd7c5 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js +++ b/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js @@ -1,15 +1,14 @@ Engine.LoadHelperScript("ValueModification.js"); Engine.LoadHelperScript("Player.js"); +Engine.LoadHelperScript("Position.js"); Engine.LoadComponentScript("interfaces/Garrisonable.js"); Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); -Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("Garrisonable.js"); Engine.LoadComponentScript("GarrisonHolder.js"); -Engine.LoadComponentScript("TurretHolder.js"); const player = 1; const enemyPlayer = 2; @@ -34,7 +33,6 @@ let createGarrisonCmp = entity => { "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, "SetHeightOffset": height => {}, - "SetTurretParent": ent => {}, "SetYRotation": angle => {} }); @@ -88,7 +86,6 @@ AddMock(garrison, IID_Position, { "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, "SetHeightOffset": height => {}, - "SetTurretParent": entity => {}, "SetYRotation": angle => {} }); @@ -143,52 +140,3 @@ TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), entities TS_ASSERT(cmpGarrisonHolder.UnloadAll()); TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); -// Turrets! -AddMock(holder, IID_Position, { - "GetPosition": () => new Vector3D(4, 3, 25), - "GetRotation": () => new Vector3D(4, 0, 6) -}); - -let cmpTurretHolder = ConstructComponent(holder, "TurretHolder", { - "TurretPoints": { - "archer1": { - "X": "12.0", - "Y": "5.", - "Z": "6.0" - }, - "archer2": { - "X": "15.0", - "Y": "5.0", - "Z": "6.0" - } - } -}); - -TS_ASSERT(cmpGarrisonable.Garrison(holder)); -TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [garrison]); -TS_ASSERT(cmpTurretHolder.OccupiesTurret(garrison)); -TS_ASSERT(cmpGarrisonable.UnGarrison()); -TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []); -TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), []); - -// Test renaming on a turret. -// Ensure we test renaming from the second spot, not the first. -const newGarrison = 31; -let cmpGarrisonableNew = createGarrisonCmp(newGarrison); -TS_ASSERT(cmpGarrisonableNew.Garrison(holder)); -TS_ASSERT(cmpGarrisonable.Garrison(holder)); -TS_ASSERT(cmpGarrisonableNew.UnGarrison()); -let previousTurret = cmpTurretHolder.GetOccupiedTurretName(garrison); -cmpGarrisonable.OnEntityRenamed({ - "entity": garrison, - "newentity": newGarrison -}); -let newTurret = cmpTurretHolder.GetOccupiedTurretName(newGarrison); -TS_ASSERT_UNEVAL_EQUALS(newTurret, previousTurret); -TS_ASSERT(cmpGarrisonableNew.UnGarrison()); - -// Test initTurrets. -cmpTurretHolder.SetInitEntity("archer1", garrison); -cmpTurretHolder.SetInitEntity("archer2", newGarrison); -cmpTurretHolder.OnGlobalInitGame(); -TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [garrison, newGarrison]); diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js index 7d8c794b29..b8948869cb 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -34,6 +34,7 @@ Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/Treasure.js"); Engine.LoadComponentScript("interfaces/TreasureCollecter.js"); +Engine.LoadComponentScript("interfaces/Turretable.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/UnitAI.js"); diff --git a/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js b/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js index 7b5862d82d..ee12e7e0ba 100644 --- a/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js +++ b/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js @@ -122,20 +122,3 @@ TS_ASSERT(cmpTurretHolder.OccupyTurret(cavID, cmpTurretHolder.turretPoints[2])); TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID)); TS_ASSERT(!cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[1])); TS_ASSERT(cmpTurretHolder.LeaveTurret(cavID, cmpTurretHolder.turretPoints[2])); - -// Test renaming. -AddMock(turretHolderID, IID_GarrisonHolder, { - "IsGarrisoned": () => true -}); -TS_ASSERT(cmpTurretHolder.OccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[2])); -cmpTurretHolder.SwapEntities(siegeEngineID, archerID); -TS_ASSERT(!cmpTurretHolder.OccupiesTurret(siegeEngineID)); -TS_ASSERT(cmpTurretHolder.LeaveTurret(archerID)); - -// Renaming into an entity not allowed on the same turret point hides us. -TS_ASSERT(cmpTurretHolder.OccupyTurret(siegeEngineID, cmpTurretHolder.turretPoints[1])); -cmpTurretHolder.SwapEntities(siegeEngineID, archerID); -TS_ASSERT(!cmpTurretHolder.OccupiesTurret(siegeEngineID)); -TS_ASSERT(!cmpTurretHolder.OccupiesTurret(archerID)); - -// ToDo: Ownership changes are handled by GarrisonHolder.js. diff --git a/binaries/data/mods/public/simulation/components/tests/test_Turrets.js b/binaries/data/mods/public/simulation/components/tests/test_Turrets.js new file mode 100644 index 0000000000..7266bd2805 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/tests/test_Turrets.js @@ -0,0 +1,114 @@ +Engine.LoadHelperScript("ValueModification.js"); +Engine.LoadHelperScript("Player.js"); +Engine.LoadHelperScript("Position.js"); +Engine.LoadComponentScript("interfaces/Health.js"); +Engine.LoadComponentScript("interfaces/Turretable.js"); +Engine.LoadComponentScript("interfaces/TurretHolder.js"); +Engine.LoadComponentScript("interfaces/UnitAI.js"); +Engine.LoadComponentScript("Turretable.js"); +Engine.LoadComponentScript("TurretHolder.js"); + +const player = 1; +const enemyPlayer = 2; +const friendlyPlayer = 3; +const turret = 10; +const holder = 11; + +let createTurretCmp = entity => { + AddMock(entity, IID_Identity, { + "GetClassesList": () => ["Ranged"], + "GetSelectionGroupName": () => "mace_infantry_archer_a" + }); + + AddMock(entity, IID_Ownership, { + "GetOwner": () => player + }); + + AddMock(entity, IID_Position, { + "GetHeightOffset": () => 0, + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6), + "JumpTo": (posX, posZ) => {}, + "MoveOutOfWorld": () => {}, + "SetHeightOffset": height => {}, + "SetTurretParent": entity => {}, + "SetYRotation": angle => {} + }); + + return ConstructComponent(entity, "Turretable", null); +}; + +AddMock(holder, IID_Footprint, { + "PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30), + "PickSpawnPoint": entity => new Vector3D(4, 3, 30) +}); + +AddMock(holder, IID_Ownership, { + "GetOwner": () => player +}); + +AddMock(player, IID_Player, { + "IsAlly": id => id != enemyPlayer, + "IsMutualAlly": id => id != enemyPlayer, + "GetPlayerID": () => player +}); + +AddMock(friendlyPlayer, IID_Player, { + "IsAlly": id => true, + "IsMutualAlly": id => true, + "GetPlayerID": () => friendlyPlayer +}); + +AddMock(SYSTEM_ENTITY, IID_PlayerManager, { + "GetPlayerByID": id => id +}); + +AddMock(holder, IID_Position, { + "GetPosition": () => new Vector3D(4, 3, 25), + "GetRotation": () => new Vector3D(4, 0, 6) +}); + +let cmpTurretable = createTurretCmp(turret); + +let cmpTurretHolder = ConstructComponent(holder, "TurretHolder", { + "TurretPoints": { + "archer1": { + "X": "12.0", + "Y": "5.", + "Z": "6.0" + }, + "archer2": { + "X": "15.0", + "Y": "5.0", + "Z": "6.0" + } + } +}); + +TS_ASSERT(cmpTurretable.OccupyTurret(holder)); +TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [turret]); +TS_ASSERT(cmpTurretHolder.OccupiesTurret(turret)); +TS_ASSERT(cmpTurretable.LeaveTurret()); +TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), []); + +// Test renaming on a turret. +// Ensure we test renaming from the second spot, not the first. +const newTurret = 31; +let cmpTurretableNew = createTurretCmp(newTurret); +TS_ASSERT(cmpTurretableNew.OccupyTurret(holder)); +TS_ASSERT(cmpTurretable.OccupyTurret(holder)); +TS_ASSERT(cmpTurretableNew.LeaveTurret()); +let previousTurret = cmpTurretHolder.GetOccupiedTurretName(turret); +cmpTurretable.OnEntityRenamed({ + "entity": turret, + "newentity": newTurret +}); +let newTurretPos = cmpTurretHolder.GetOccupiedTurretName(newTurret); +TS_ASSERT_UNEVAL_EQUALS(newTurretPos, previousTurret); +TS_ASSERT(cmpTurretableNew.LeaveTurret()); + +// Test initTurrets. +cmpTurretHolder.SetInitEntity("archer1", turret); +cmpTurretHolder.SetInitEntity("archer2", newTurret); +cmpTurretHolder.OnGlobalInitGame(); +TS_ASSERT_UNEVAL_EQUALS(cmpTurretHolder.GetEntities(), [turret, newTurret]); diff --git a/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json b/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json index 33dd84ef0f..38ed34fdf3 100644 --- a/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json +++ b/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json @@ -1,5 +1,5 @@ { - "type": "garrisonedUnits", + "type": "turretedUnits", "affects": ["Soldier"], "modifications": [ { "value": "Resistance/Entity/Damage/Hack", "add": 3 }, @@ -8,5 +8,5 @@ { "value": "Vision/Range", "add": 20 } ], "auraName": "Wall Protection", - "auraDescription": "Garrisoned Soldiers +3 crush, hack, pierce resistance and +20 vision range." + "auraDescription": "Turreted Soldiers +3 crush, hack, pierce resistance and +20 vision range." } diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 3f018a7f1d..e7239b46aa 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -462,6 +462,13 @@ var g_Commands = { data.cmpPlayer.SetState("defeated", markForTranslation("%(player)s has resigned.")); }, + "occupy-turret": function(player, cmd, data) + { + GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { + cmpUnitAI.OccupyTurret(cmd.target, cmd.queued); + }); + }, + "garrison": function(player, cmd, data) { if (!CanPlayerOrAllyControlUnit(cmd.target, player, data.controlAllUnits)) @@ -497,6 +504,38 @@ var g_Commands = { }); }, + "leave-turret": function(player, cmd, data) + { + let notUnloaded = 0; + for (let ent of data.entities) + { + let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable); + if (!cmpTurretable || !cmpTurretable.LeaveTurret()) + ++notUnloaded; + } + + if (notUnloaded) + notifyUnloadFailure(player); + }, + + "unload-turrets": function(player, cmd, data) + { + let notUnloaded = 0; + for (let ent of data.entities) + { + let cmpTurretHolder = Engine.QueryInterface(ent, IID_TurretHolder); + for (let turret of cmpTurretHolder.GetEntities()) + { + let cmpTurretable = Engine.QueryInterface(turret, IID_Turretable); + if (!cmpTurretable || !cmpTurretable.LeaveTurret()) + ++notUnloaded; + } + } + + if (notUnloaded) + notifyUnloadFailure(player); + }, + "unload": function(player, cmd, data) { if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits)) @@ -843,13 +882,13 @@ var g_Commands = { /** * Sends a GUI notification about unit(s) that failed to ungarrison. */ -function notifyUnloadFailure(player, garrisonHolder) +function notifyUnloadFailure(player) { - var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); + let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "type": "text", "players": [player], - "message": markForTranslation("Unable to ungarrison unit(s)"), + "message": markForTranslation("Unable to unload unit(s)."), "translateMessage": true }); } diff --git a/binaries/data/mods/public/simulation/helpers/Position.js b/binaries/data/mods/public/simulation/helpers/Position.js index a5fe7a3449..66efa09b33 100644 --- a/binaries/data/mods/public/simulation/helpers/Position.js +++ b/binaries/data/mods/public/simulation/helpers/Position.js @@ -133,4 +133,39 @@ PositionHelper.prototype.PredictTimeToTarget = function(firstPosition, selfSpeed return false; }; +/** + * @param {number} target - EntityID to find the spawn position for. + * @param {number} entity - EntityID to find the spawn position for. + * @param {boolean} forced - Optionally whether the spawning is forced. + * @return {Vector3D} - An appropriate spawning position. + */ +PositionHelper.prototype.GetSpawnPosition = function(target, entity, forced) +{ + let cmpFootprint = Engine.QueryInterface(target, IID_Footprint); + let cmpHealth = Engine.QueryInterface(target, IID_Health); + let cmpIdentity = Engine.QueryInterface(target, IID_Identity); + + if (!cmpFootprint) + return null; + + // If the spawner is a sinking ship, restrict the location to the intersection of both passabilities. + // TODO: should use passability classes to be more generic. + let pos; + if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship")) + pos = cmpFootprint.PickSpawnPointBothPass(entity); + else + pos = cmpFootprint.PickSpawnPoint(entity); + + if (pos.y < 0) + { + if (!forced) + return null; + + // If ejection is forced, we need to continue, so use center of the entity. + let cmpPosition = Engine.QueryInterface(target, IID_Position); + pos = cmpPosition.GetPosition(); + } + return pos; +}; + Engine.RegisterGlobal("PositionHelper", new PositionHelper()); diff --git a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js index 5395417a3b..3a6727fae5 100644 --- a/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js +++ b/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js @@ -57,6 +57,14 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts) "autocontinue": i == rallyPos.length - 1 }); break; + case "occupy-turret": + ret.push({ + "type": "occupy-turret", + "entities": spawnedEnts, + "target": data[i].target, + "queued": true + }); + break; case "garrison": ret.push({ "type": "garrison", diff --git a/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml index 52fe1a2885..8a51b2494f 100644 --- a/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - athen Pylai diff --git a/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml index 8bc25f8190..b114537e76 100644 --- a/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml @@ -4,9 +4,6 @@ 18.0 - - 4 - brit Duoricos diff --git a/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml index 2b1a34db64..c093de638c 100644 --- a/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml @@ -4,9 +4,6 @@ 16.5 - - 8 - cart Mijdil-šaʿar diff --git a/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml index 42a1a1979e..e392a61453 100644 --- a/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml @@ -4,9 +4,6 @@ 12.0 - - 4 - gaul Duoricos diff --git a/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml index 0b9aa89225..a6024dd4e5 100644 --- a/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml @@ -4,9 +4,6 @@ 12.7 - - 8 - iber Biko Sarbide diff --git a/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml index ad26aaf1b6..526c354e8b 100644 --- a/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml @@ -4,9 +4,6 @@ 12.6 - - 6 - kush ʿryt diff --git a/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml index a2dd73f27c..c764fe6ee0 100644 --- a/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - mace Pylai diff --git a/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml b/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml index 79d9bb95e4..1b877c743d 100644 --- a/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml +++ b/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml @@ -9,10 +9,6 @@ 200 - - 20 - Infantry+Archer - 1200 diff --git a/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml index 9ba2e031bc..87e4a4410c 100644 --- a/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml @@ -10,9 +10,6 @@ 22.0 - - 4 - maur Dwara diff --git a/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml index f5e806f9eb..9732b5dec4 100644 --- a/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml @@ -4,9 +4,6 @@ 13.8 - - 6 - pers Duvarθiš diff --git a/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml index 4cb2dcb371..28032008b9 100644 --- a/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml @@ -4,9 +4,6 @@ 17.8 - - 10 - ptol Pylai diff --git a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml index 5e0274e353..53cddffb91 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml @@ -14,9 +14,6 @@ 12.5 - - 8 - 0.75 diff --git a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml index 6666093d7d..8264b36d94 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml @@ -14,9 +14,6 @@ 6.7 - - 7 - 0.75 diff --git a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml index 0f6b18a613..e8f76475b7 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml @@ -19,10 +19,6 @@ 12.5 - - 4 - -Support -Infantry - 0.75 diff --git a/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml index 9f87bba0a5..58f462850c 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml @@ -4,9 +4,6 @@ 11.9 - - 10 - rome Porta diff --git a/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml index cfe40f0ebe..556de90518 100644 --- a/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml @@ -4,9 +4,6 @@ 11.6 - - 5 - sele Pylai diff --git a/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml b/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml index 85a0c749ca..468c709e15 100644 --- a/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml +++ b/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - spart Pylai diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml index 342494c9b1..91c264e02c 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml @@ -20,14 +20,6 @@ 13.0 - - 1 - 0.1 - Unit - Infantry - 0 - 2 - 400 decay|rubble/rubble_stone_2x2 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml index 16f61b92a6..b63b11d6ce 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml @@ -11,14 +11,6 @@ 8.0 - - 1 - Ranged+Infantry - 0.1 - Unit - 0 - 2 - Wall template_structure_defensive_wall diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml index 3002b4c435..ab3462b18b 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml @@ -9,9 +9,6 @@ 36 - - 8 - 3000 decay|rubble/rubble_stone_wall_long diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml index 593b99bcba..53e6e006bf 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml @@ -9,9 +9,6 @@ 24 - - 4 - 2000 decay|rubble/rubble_stone_wall_medium diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml index c3f872541d..e9847634aa 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml @@ -6,7 +6,6 @@ 12 - 1000 decay|rubble/rubble_stone_wall_short diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml index 80a197a57d..2f4d7e31a3 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml @@ -40,6 +40,10 @@ 2 Support Infantry + 0.1 + Unit + 0 + 2 4000 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml index 501ff5625c..ca0f55528f 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml @@ -45,6 +45,7 @@ attack/weapon/bow_attack.xml + 1.2 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml index bf1be78574..b25e53d31e 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml @@ -45,6 +45,7 @@ attack/impact/javelin_impact.xml + 1.2 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml index 2f7a2f0130..6d4773474c 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml @@ -42,4 +42,5 @@ attack/weapon/bow_attack.xml + diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml index ea9addb0a5..eb206810c4 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml @@ -42,4 +42,5 @@ attack/impact/javelin_impact.xml + diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml index eecb6e3d01..a1efca2660 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml @@ -19,4 +19,5 @@ +