Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -309,11 +309,13 @@ 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 Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js +++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js @@ -491,6 +491,11 @@ ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource); } + if (template.TurretHolder) + ret.turretHolder = { + "turretPoints": template.TurretHolder.TurretPoints + }; + if (template.WallSet) { ret.wallSet = { Index: ps/trunk/binaries/data/mods/public/gui/common/tooltips.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/tooltips.js +++ ps/trunk/binaries/data/mods/public/gui/common/tooltips.js @@ -588,6 +588,16 @@ 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) Index: ps/trunk/binaries/data/mods/public/gui/hotkeys/spec/ingame.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/hotkeys/spec/ingame.json +++ ps/trunk/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." Index: ps/trunk/binaries/data/mods/public/gui/reference/common/ReferencePage.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/reference/common/ReferencePage.js +++ ps/trunk/binaries/data/mods/public/gui/reference/common/ReferencePage.js @@ -57,6 +57,7 @@ getHealerTooltip, getResistanceTooltip, getGarrisonTooltip, + getTurretsTooltip, getProjectilesTooltip, getSpeedTooltip, getGatherTooltip, Index: ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml +++ ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml @@ -32,6 +32,10 @@ unloadAll(); + + unloadAllTurrets(); + + stopUnits(g_Selection.toList()); Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js @@ -325,6 +325,7 @@ getGatherTooltip, getSpeedTooltip, getGarrisonTooltip, + getTurretsTooltip, getPopulationBonusTooltip, getProjectilesTooltip, getResourceTrickleTooltip, Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels.js @@ -193,6 +193,7 @@ getEntityCostTooltip(template, data.player), getResourceDropsiteTooltip(template), getGarrisonTooltip(template), + getTurretsTooltip(template), getPopulationBonusTooltip(template), showTemplateViewerOnRightClickTooltip(template) ); @@ -975,6 +976,7 @@ getHealerTooltip, getResistanceTooltip, getGarrisonTooltip, + getTurretsTooltip, getProjectilesTooltip, getSpeedTooltip, getResourceDropsiteTooltip Index: ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js +++ ps/trunk/binaries/data/mods/public/gui/session/selection_panels_helpers.js @@ -415,31 +415,6 @@ }); } -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 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({ Index: ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js +++ ps/trunk/binaries/data/mods/public/gui/session/unit_actions.js @@ -684,6 +684,77 @@ "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 @@ 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 @@ "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 @@ "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 @@ "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"] }, Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entity.js @@ -543,6 +543,8 @@ "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 @@ "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 @@ 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; + }, + + "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; }, - "attack": function(unitId, allowCapture = true, queued = false) { - Engine.PostCommand(PlayerID, { "type": "attack", "entities": [this.id()], "target": unitId, "allowCapture": allowCapture, "queued": queued }); + "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) { + "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; }, Index: ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js +++ ps/trunk/binaries/data/mods/public/simulation/ai/common-api/entitycollection.js @@ -175,6 +175,12 @@ 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() }); Index: ps/trunk/binaries/data/mods/public/simulation/components/Auras.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Auras.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Auras.js @@ -180,6 +180,11 @@ 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.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 @@ if (msg.holderID != INVALID_ENTITY) this.ApplyGarrisonAura(msg.holderID); - if (msg.olderHolder != INVALID_ENTITY) + if (msg.oldHolder != INVALID_ENTITY) this.RemoveGarrisonAura(msg.oldHolder); }; Index: ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/BuildingAI.js @@ -30,36 +30,21 @@ 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()) - { - // Only count non-visible garrisoned entities towards extra arrows. - if (cmpTurretHolder && cmpTurretHolder.OccupiesTurret(ent)) - continue; - + for (let ent of msg.added) + { 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) Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js @@ -200,38 +200,6 @@ }; /** - * @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). * @return {boolean} Whether the entity was ejected. @@ -257,32 +225,6 @@ }; /** - * @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. * @return {boolean} Whether the command was successful. Index: ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js +++ ps/trunk/binaries/data/mods/public/simulation/components/Garrisonable.js @@ -69,7 +69,7 @@ * @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 @@ "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 @@ "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 @@ 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); Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js +++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -388,6 +388,12 @@ "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 = { Index: ps/trunk/binaries/data/mods/public/simulation/components/RallyPoint.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/RallyPoint.js +++ ps/trunk/binaries/data/mods/public/simulation/components/RallyPoint.js @@ -100,6 +100,31 @@ 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) Index: ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js +++ ps/trunk/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 { @@ -55,6 +53,15 @@ } /** + * @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. * @param {Object} requestedTurretPoint - Optionally the specific turret point to occupy. @@ -87,6 +94,7 @@ 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 @@ 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 @@ 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 @@ */ GetOccupiedTurretName(entity) { - return this.GetOccupiedTurret(entity).name || ""; + let turret = this.GetOccupiedTurret(entity); + return turret ? turret.name : ""; } /** @@ -221,6 +205,60 @@ } /** + * @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. * @param {number} entity - The entity-ID to be placed. @@ -238,20 +276,6 @@ } /** - * @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. */ OnGlobalSkirmishReplacerReplaced(msg) @@ -274,10 +298,7 @@ } /** - * 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 @@ 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 @@ "" + "" + "" + - ""; + "" + + "" + + "" + + "" + + "" + + "" + "" + + "" + + "" + + "" + + ""; Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); Index: ps/trunk/binaries/data/mods/public/simulation/components/Turretable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Turretable.js +++ ps/trunk/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); Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -592,7 +592,8 @@ 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 @@ }, "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 @@ "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 @@ "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 @@ "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 @@ 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 @@ 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 @@ "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.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 @@ 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 @@ 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 @@ 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 @@ // 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 @@ 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); }; /** @@ -5605,6 +5656,21 @@ }; /** + * 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 @@ 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); Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Turretable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/Turretable.js +++ ps/trunk/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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js @@ -2,7 +2,6 @@ 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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisonable.js +++ ps/trunk/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 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(!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. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Garrisoning.js +++ ps/trunk/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 @@ "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, "SetHeightOffset": height => {}, - "SetTurretParent": ent => {}, "SetYRotation": angle => {} }); @@ -88,7 +86,6 @@ "JumpTo": (posX, posZ) => {}, "MoveOutOfWorld": () => {}, "SetHeightOffset": height => {}, - "SetTurretParent": entity => {}, "SetYRotation": angle => {} }); @@ -143,52 +140,3 @@ 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]); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js @@ -34,6 +34,7 @@ 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"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_TurretHolder.js @@ -122,20 +122,3 @@ 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. Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Turrets.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Turrets.js +++ ps/trunk/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]); Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/structures/wall_garrisoned.json +++ ps/trunk/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." } Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js @@ -462,6 +462,13 @@ 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 @@ }); }, + "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 @@ /** * 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 }); } Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Position.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/Position.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/Position.js @@ -133,4 +133,39 @@ 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()); Index: ps/trunk/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js +++ ps/trunk/binaries/data/mods/public/simulation/helpers/RallyPointCommands.js @@ -57,6 +57,14 @@ "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", Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - athen Pylai Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/brit/wall_gate.xml @@ -4,9 +4,6 @@ 18.0 - - 4 - brit Duoricos Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart/wall_gate.xml @@ -4,9 +4,6 @@ 16.5 - - 8 - cart Mijdil-šaʿar Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/gaul/wall_gate.xml @@ -4,9 +4,6 @@ 12.0 - - 4 - gaul Duoricos Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/iber/wall_gate.xml @@ -4,9 +4,6 @@ 12.7 - - 8 - iber Biko Sarbide Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/kush/wall_gate.xml @@ -4,9 +4,6 @@ 12.6 - - 6 - kush ʿryt Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - mace Pylai Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/tower_double.xml @@ -9,10 +9,6 @@ 200 - - 20 - Infantry+Archer - 1200 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/maur/wall_gate.xml @@ -10,9 +10,6 @@ 22.0 - - 4 - maur Dwara Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers/wall_gate.xml @@ -4,9 +4,6 @@ 13.8 - - 6 - pers Duvarθiš Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol/wall_gate.xml @@ -4,9 +4,6 @@ 17.8 - - 10 - ptol Pylai Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_gate.xml @@ -14,9 +14,6 @@ 12.5 - - 8 - 0.75 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_long.xml @@ -14,9 +14,6 @@ 6.7 - - 7 - 0.75 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/siege_wall_tower.xml @@ -19,10 +19,6 @@ 12.5 - - 4 - -Support -Infantry - 0.75 Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome/wall_gate.xml @@ -4,9 +4,6 @@ 11.9 - - 10 - rome Porta Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/sele/wall_gate.xml @@ -4,9 +4,6 @@ 11.6 - - 5 - sele Pylai Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart/wall_gate.xml @@ -4,9 +4,6 @@ 15.5 - - 8 - spart Pylai Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_outpost.xml +++ ps/trunk/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 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall.xml +++ ps/trunk/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 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_long.xml +++ ps/trunk/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 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_medium.xml +++ ps/trunk/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 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_short.xml @@ -6,7 +6,6 @@ 12 - 1000 decay|rubble/rubble_stone_wall_short Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml +++ ps/trunk/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 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml @@ -45,6 +45,7 @@ attack/weapon/bow_attack.xml + 1.2 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelineer.xml @@ -45,6 +45,7 @@ attack/impact/javelin_impact.xml + 1.2 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml @@ -42,4 +42,5 @@ attack/weapon/bow_attack.xml + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelineer.xml @@ -42,4 +42,5 @@ attack/impact/javelin_impact.xml + Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml @@ -19,4 +19,5 @@ +