From e8ba5dffcfcff1841bd658137f3af2cd9a27c17b Mon Sep 17 00:00:00 2001 From: rca Date: Tue, 4 Jun 2024 22:10:51 -0700 Subject: [PATCH] restore random arrows. --- community-mod/gui/session/unit_actions.js | 1963 ----------------- .../simulation/components/BuildingAI.js | 590 +++-- community-mod/simulation/helpers/Commands.js | 676 ++---- .../structures/han/civil_centre_court.xml | 86 +- .../structures/iber/defense_tower.xml | 94 +- .../template_structure_civic_civil_centre.xml | 297 ++- ...ure_civic_civil_centre_military_colony.xml | 103 +- .../template_structure_defensive_tower.xml | 138 +- ...plate_structure_defensive_tower_sentry.xml | 132 +- ...mplate_structure_defensive_tower_stone.xml | 115 +- .../template_structure_military_fortress.xml | 218 +- 11 files changed, 1093 insertions(+), 3319 deletions(-) delete mode 100644 community-mod/gui/session/unit_actions.js diff --git a/community-mod/gui/session/unit_actions.js b/community-mod/gui/session/unit_actions.js deleted file mode 100644 index 51dc8e9..0000000 --- a/community-mod/gui/session/unit_actions.js +++ /dev/null @@ -1,1963 +0,0 @@ -/** - * Specifies which template should indicate the target location of a player command, - * given a command type. - */ -var g_TargetMarker = { - "move": "special/target_marker", - "map_flare": "special/flare_target_marker" -}; - -/** - * Sound we play when displaying a flare. - */ -var g_FlareSound = "audio/interface/alarm/alarmally_1.ogg"; - -/** - * Which enemy entity types will be attacked on sight when patroling. - */ -var g_PatrolTargets = ["Unit"]; - -const g_DisabledTags = { "color": "255 140 0" }; - -/** - * List of different actions units can execute, - * this is mostly used to determine which actions can be executed - * - * "execute" is meant to send the command to the engine - * - * The next functions will always return false - * in case you have to continue to seek - * (i.e. look at the next entity for getActionInfo, the next - * possible action for the actionCheck ...) - * They will return an object when the searching is finished - * - * "getActionInfo" is used to determine if the action is possible, - * and also give visual feedback to the user (tooltips, cursors, ...) - * - * "preSelectedActionCheck" is used to select actions when the gui buttons - * were used to set them, but still require a target (like the guard button) - * - * "hotkeyActionCheck" is used to check the possibility of actions when - * a hotkey is pressed - * - * "actionCheck" is used to check the possibilty of actions without specific - * command. For that, the specificness variable is used - * - * "specificness" is used to determine how specific an action is, - * The lower the number, the more specific an action is, and the bigger - * the chance of selecting that action when multiple actions are possible - */ -var g_UnitActions = -{ - "move": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "walk", - "entities": selection, - "x": target.x, - "z": target.z, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getDefault() - }); - - DrawTargetMarker(target); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_walk", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.unitAI) - return false; - return { "possible": true }; - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.move") && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("move", target, selection); - return actionInfo.possible && { - "type": "move", - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 12, - }, - - "attack-move": - { - "execute": function(target, action, selection, queued, pushFront) - { - let targetClasses; - if (Engine.HotkeyIsPressed("session.attackmoveUnit")) - targetClasses = { "attack": ["Unit"] }; - else - targetClasses = { "attack": ["Unit", "Structure"] }; - - Engine.PostNetworkCommand({ - "type": "attack-walk", - "entities": selection, - "x": target.x, - "z": target.z, - "targetClasses": targetClasses, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - DrawTargetMarker(target); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_attack_move", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.unitAI) - return false; - return { "possible": true }; - }, - "hotkeyActionCheck": function(target, selection) - { - return isAttackMovePressed() && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("attack-move", target, selection); - return actionInfo.possible && { - "type": "attack-move", - "cursor": "action-attack-move", - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 30, - }, - - "capture": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "attack", - "entities": selection, - "target": action.target, - "allowCapture": true, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_attack", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.attack || !targetState || !targetState.capturePoints) - return false; - - return { - "possible": Engine.GuiInterfaceCall("CanAttack", { - "entity": entState.id, - "target": targetState.id, - "types": ["Capture"] - }) - }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("capture", target, selection); - return actionInfo.possible && { - "type": "capture", - "cursor": "action-capture", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 9, - }, - - "attack": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "attack", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront, - "allowCapture": false, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_attack", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.attack || !targetState || !targetState.hitpoints) - return false; - - return { - "possible": Engine.GuiInterfaceCall("CanAttack", { - "entity": entState.id, - "target": targetState.id, - "types": ["!Capture"] - }) - }; - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.attack") && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("attack", target, selection); - return actionInfo.possible && { - "type": "attack", - "cursor": "action-attack", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 10, - }, - - "call-to-arms": { - "execute": function(target, action, selection, queued, pushFront) - { - let targetClasses; - if (Engine.HotkeyIsPressed("session.attackmoveUnit")) - targetClasses = { "attack": ["Unit"] }; - else - targetClasses = { "attack": ["Unit", "Structure"] }; - Engine.PostNetworkCommand({ - "type": "call-to-arms", - "entities": selection, - "target": target, - "targetClasses": targetClasses, - "queued": queued, - "pushFront": pushFront, - "allowCapture": true, - "formation": g_AutoFormation.getNull() - }); - return true; - }, - "getActionInfo": function(entState, targetState) - { - return { "possible": !!entState.unitAI }; - }, - "actionCheck": function(target, selection) - { - const actionInfo = getActionInfo("call-to-arms", target, selection); - return actionInfo.possible && { - "type": "call-to-arms", - "cursor": "action-attack", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.calltoarms") && - this.actionCheck(target, selection); - }, - "preSelectedActionCheck": function(target, selection) - { - return preSelectedAction == ACTION_CALLTOARMS && - this.actionCheck(target, selection); - }, - "specificness": 50, - }, - - "patrol": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "patrol", - "entities": selection, - "x": target.x, - "z": target.z, - "target": action.target, - "targetClasses": { "attack": g_PatrolTargets }, - "queued": queued, - "allowCapture": false, - "formation": g_AutoFormation.getDefault() - }); - - DrawTargetMarker(target); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_patrol", - "entity": action.firstAbleEntity - }); - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.unitAI || !entState.unitAI.canPatrol) - return false; - - return { "possible": true }; - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.patrol") && - this.actionCheck(target, selection); - }, - "preSelectedActionCheck": function(target, selection) - { - return preSelectedAction == ACTION_PATROL && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("patrol", target, selection); - return actionInfo.possible && { - "type": "patrol", - "cursor": "action-patrol", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 37, - }, - - "heal": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "heal", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_heal", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.heal || !targetState || - !hasClass(targetState, "Unit") || !targetState.needsHeal || - !playerCheck(entState, targetState, ["Player", "Ally"]) || - entState.id == targetState.id) // Healers can't heal themselves. - return false; - - let unhealableClasses = entState.heal.unhealableClasses; - if (MatchesClassList(targetState.identity.classes, unhealableClasses)) - return false; - - let healableClasses = entState.heal.healableClasses; - if (!MatchesClassList(targetState.identity.classes, healableClasses)) - return false; - - return { "possible": true }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("heal", target, selection); - return actionInfo.possible && { - "type": "heal", - "cursor": "action-heal", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 7, - }, - - // "Fake" action to check if an entity can be ordered to "construct" - // which is handled differently from repair as the target does not exist. - "construct": - { - "preSelectedActionCheck": function(target, selection) - { - let state = GetEntityState(selection[0]); - if (state && state.builder && - target && target.constructor && target.constructor.name == "PlacementSupport") - return { "type": "construct" }; - return false; - }, - "specificness": 0, - }, - - "repair": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "repair", - "entities": selection, - "target": action.target, - "autocontinue": true, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": action.foundation ? "order_build" : "order_repair", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.builder || !targetState || - !targetState.needsRepair && !targetState.foundation || - !playerCheck(entState, targetState, ["Player", "Ally"])) - return false; - - return { - "possible": true, - "foundation": targetState.foundation - }; - }, - "preSelectedActionCheck": function(target, selection) - { - return preSelectedAction == ACTION_REPAIR && (this.actionCheck(target, selection) || { - "type": "none", - "cursor": "action-repair-disabled", - "target": null - }); - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.repair") && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("repair", target, selection); - return actionInfo.possible && { - "type": "repair", - "cursor": "action-repair", - "target": target, - "foundation": actionInfo.foundation, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 11, - }, - - "gather": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "gather", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_gather", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.resourceGatherRates || - !targetState || !targetState.resourceSupply) - return false; - - let resource; - if (entState.resourceGatherRates[targetState.resourceSupply.type.generic + "." + targetState.resourceSupply.type.specific]) - resource = targetState.resourceSupply.type.specific; - else if (entState.resourceGatherRates[targetState.resourceSupply.type.generic]) - resource = targetState.resourceSupply.type.generic; - if (!resource) - return false; - - return { - "possible": true, - "cursor": "action-gather-" + resource - }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("gather", target, selection); - return actionInfo.possible && { - "type": "gather", - "cursor": actionInfo.cursor, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 1, - }, - - "returnresource": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "returnresource", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_gather", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!targetState || !targetState.resourceDropsite) - return false; - - let playerState = GetSimState().players[entState.player]; - if (playerState.hasSharedDropsites && targetState.resourceDropsite.shared) - { - if (!playerCheck(entState, targetState, ["Player", "MutualAlly"])) - return false; - } - else if (!playerCheck(entState, targetState, ["Player"])) - return false; - - if (!entState.resourceCarrying || !entState.resourceCarrying.length) - return false; - - let carriedType = entState.resourceCarrying[0].type; - if (targetState.resourceDropsite.types.indexOf(carriedType) == -1) - return false; - - return { - "possible": true, - "cursor": "action-return-" + carriedType - }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("returnresource", target, selection); - return actionInfo.possible && { - "type": "returnresource", - "cursor": actionInfo.cursor, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 2, - }, - - "cancel-setup-trade-route": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "cancel-setup-trade-route", - "entities": selection, - "target": action.target, - "queued": queued - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!targetState || targetState.foundation || !entState.trader || !targetState.market || - playerCheck(entState, targetState, ["Enemy"]) || - !(targetState.market.land && hasClass(entState, "Organic") || - targetState.market.naval && hasClass(entState, "Ship"))) - return false; - - let tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", { - "trader": entState.id, - "target": targetState.id - }); - - if (!tradingDetails || !tradingDetails.type) - return false; - - if (tradingDetails.type == "is first" && !tradingDetails.hasBothMarkets) - return { - "possible": true, - "tooltip": translate("This is the origin trade market.\nRight-click to cancel trade route.") - }; - return false; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("cancel-setup-trade-route", target, selection); - return actionInfo.possible && { - "type": "cancel-setup-trade-route", - "cursor": "action-cancel-setup-trade-route", - "tooltip": actionInfo.tooltip, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 2, - }, - - "setup-trade-route": - { - "execute": function(target, action, selection, queued) - { - Engine.PostNetworkCommand({ - "type": "setup-trade-route", - "entities": selection, - "target": action.target, - "source": null, - "route": null, - "queued": queued, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_trade", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!targetState || targetState.foundation || !entState.trader || !targetState.market || - playerCheck(entState, targetState, ["Enemy"]) || - !(targetState.market.land && hasClass(entState, "Organic") || - targetState.market.naval && hasClass(entState, "Ship"))) - return false; - - let tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", { - "trader": entState.id, - "target": targetState.id - }); - - if (!tradingDetails) - return false; - - let tooltip; - switch (tradingDetails.type) - { - case "is first": - tooltip = translate("Origin trade market.") + "\n"; - if (tradingDetails.hasBothMarkets) - tooltip += sprintf(translate("Gain: %(gain)s"), { - "gain": getTradingTooltip(tradingDetails.gain) - }); - else - return false; - break; - - case "is second": - tooltip = translate("Destination trade market.") + "\n" + - sprintf(translate("Gain: %(gain)s"), { - "gain": getTradingTooltip(tradingDetails.gain) - }); - break; - - case "set first": - tooltip = translate("Right-click to set as origin trade market"); - break; - - case "set second": - if (tradingDetails.gain.traderGain == 0) - return { - "possible": true, - "tooltip": setStringTags(translate("This market is too close to the origin market."), g_DisabledTags), - "disabled": true - }; - - tooltip = translate("Right-click to set as destination trade market.") + "\n" + - sprintf(translate("Gain: %(gain)s"), { - "gain": getTradingTooltip(tradingDetails.gain) - }); - break; - } - - return { - "possible": true, - "tooltip": tooltip - }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("setup-trade-route", target, selection); - if (actionInfo.disabled) - return { - "type": "none", - "cursor": "action-setup-trade-route-disabled", - "target": null, - "tooltip": actionInfo.tooltip - }; - - return actionInfo.possible && { - "type": "setup-trade-route", - "cursor": "action-setup-trade-route", - "tooltip": actionInfo.tooltip, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "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_OCCUPY_TURRET && (this.actionCheck(target, selection) || { - "type": "none", - "cursor": "action-occupy-turret-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-occupy-turret", - "tooltip": actionInfo.tooltip, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 21, - }, - - "garrison": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "garrison", - "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.garrisonable || !targetState || !targetState.garrisonHolder || - !playerCheck(entState, targetState, ["Player", "MutualAlly"])) - return false; - - let tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), { - "garrisoned": targetState.garrisonHolder.occupiedSlots, - "capacity": targetState.garrisonHolder.capacity - }); - - let extraCount = entState.garrisonable.size; - if (entState.garrisonHolder) - extraCount += entState.garrisonHolder.occupiedSlots; - - if (targetState.garrisonHolder.occupiedSlots + extraCount > targetState.garrisonHolder.capacity) - tooltip = coloredText(tooltip, "orange"); - - if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses)) - return false; - - 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.garrison") && - this.actionCheck(target, selection); - - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("garrison", target, selection); - return actionInfo.possible && { - "type": "garrison", - "cursor": "action-garrison", - "tooltip": actionInfo.tooltip, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 20, - }, - - "guard": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "guard", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_guard", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!targetState || !targetState.guard || entState.id == targetState.id || - !playerCheck(entState, targetState, ["Player", "Ally"]) || - !entState.unitAI || !entState.unitAI.canGuard) - return false; - - return { "possible": true }; - }, - "preSelectedActionCheck": function(target, selection) - { - return preSelectedAction == ACTION_GUARD && (this.actionCheck(target, selection) || { - "type": "none", - "cursor": "action-guard-disabled", - "target": null - }); - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.guard") && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("guard", target, selection); - return actionInfo.possible && { - "type": "guard", - "cursor": "action-guard", - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 40, - }, - - "collect-treasure": - { - "execute": function(target, action, selection, queued) - { - Engine.PostNetworkCommand({ - "type": "collect-treasure", - "entities": selection, - "target": action.target, - "queued": queued, - "formation": g_AutoFormation.getNull() - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_collect_treasure", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.treasureCollector || - !targetState || !targetState.treasure) - return false; - - return { - "possible": true, - "cursor": "action-collect-treasure" - }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("collect-treasure", target, selection); - return actionInfo.possible && { - "type": "collect-treasure", - "cursor": actionInfo.cursor, - "target": target, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 1, - }, - - "remove-guard": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "remove-guard", - "entities": selection, - "target": action.target, - "queued": queued, - "pushFront": pushFront - }); - - Engine.GuiInterfaceCall("PlaySound", { - "name": "order_guard", - "entity": action.firstAbleEntity - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.unitAI || !entState.unitAI.isGuarding) - return false; - return { "possible": true }; - }, - "hotkeyActionCheck": function(target, selection) - { - return Engine.HotkeyIsPressed("session.guard") && - this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("remove-guard", target, selection); - return actionInfo.possible && { - "type": "remove-guard", - "cursor": "action-remove-guard", - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 41, - }, - - "set-rallypoint": - { - "execute": function(target, action, selection, queued, pushFront) - { - // if there is a position set in the action then use this so that when setting a - // rally point on an entity it is centered on that entity - if (action.position) - target = action.position; - - Engine.PostNetworkCommand({ - "type": "set-rallypoint", - "entities": selection, - "x": target.x, - "z": target.z, - "target": action.target, - "data": action.data, - "queued": queued, - "pushFront": pushFront - }); - - // Display rally point at the new coordinates, to avoid display lag - Engine.GuiInterfaceCall("DisplayRallyPoint", { - "entities": selection, - "x": target.x, - "z": target.z, - "queued": queued - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!entState.rallyPoint) - return false; - - // Don't allow the rally point to be set on any of the currently selected entities (used for unset) - // except if the autorallypoint hotkey is pressed and the target can produce entities. - if (targetState && (!Engine.HotkeyIsPressed("session.autorallypoint") || - !targetState.trainer || - !targetState.trainer.entities.length)) - for (const ent of g_Selection.toList()) - if (targetState.id == ent) - return false; - - let tooltip; - let disabled = false; - // default to walking there (or attack-walking if hotkey pressed) - let data = { "command": "walk" }; - let cursor = ""; - - if (isAttackMovePressed()) - { - let targetClasses; - if (Engine.HotkeyIsPressed("session.attackmoveUnit")) - targetClasses = { "attack": ["Unit"] }; - else - targetClasses = { "attack": ["Unit", "Structure"] }; - - data.command = "attack-walk"; - data.targetClasses = targetClasses; - cursor = "action-attack-move"; - } - - if (Engine.HotkeyIsPressed("session.repair") && targetState && - (targetState.needsRepair || targetState.foundation) && - playerCheck(entState, targetState, ["Player", "Ally"])) - { - data.command = "repair"; - data.target = targetState.id; - cursor = "action-repair"; - } - else if (targetState && targetState.garrisonHolder && - playerCheck(entState, targetState, ["Player", "MutualAlly"])) - { - data.command = "garrison"; - data.target = targetState.id; - cursor = "action-garrison"; - - tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), { - "garrisoned": targetState.garrisonHolder.occupiedSlots, - "capacity": targetState.garrisonHolder.capacity - }); - - if (targetState.garrisonHolder.occupiedSlots >= - 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; - cursor = "action-gather-" + resourceType.specific; - - data.command = "gather-near-position"; - data.resourceType = resourceType; - data.resourceTemplate = targetState.template; - if (!targetState.speed) - { - data.command = "gather"; - data.target = targetState.id; - } - } - else if (targetState && targetState.treasure) - { - cursor = "action-collect-treasure"; - data.command = "collect-treasure-near-position"; - if (!targetState.speed) - { - data.command = "collect-treasure"; - data.target = targetState.id; - } - } - else if (entState.market && targetState && targetState.market && - entState.id != targetState.id && - (!entState.market.naval || targetState.market.naval) && - !playerCheck(entState, targetState, ["Enemy"])) - { - // Find a trader (if any) that this structure can train. - let trader; - if (entState.trainer?.entities?.length) - for (let i = 0; i < entState.trainer.entities.length; ++i) - if ((trader = GetTemplateData(entState.trainer.entities[i]).trader)) - break; - - let traderData = { - "firstMarket": entState.id, - "secondMarket": targetState.id, - "template": trader - }; - - let gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData); - if (gain) - { - data.command = "trade"; - data.target = traderData.secondMarket; - data.source = traderData.firstMarket; - cursor = "action-setup-trade-route"; - - if (gain.traderGain) - tooltip = translate("Right-click to establish a default route for new traders.") + "\n" + - sprintf( - trader ? - translate("Gain: %(gain)s") : - translate("Expected gain: %(gain)s"), - { "gain": getTradingTooltip(gain) }); - else - { - disabled = true; - tooltip = setStringTags(translate("This market is too close to the origin market."), g_DisabledTags); - cursor = "action-setup-trade-route-disabled"; - } - } - } - else if (targetState && (targetState.needsRepair || targetState.foundation) && playerCheck(entState, targetState, ["Ally"])) - { - data.command = "repair"; - data.target = targetState.id; - cursor = "action-repair"; - } - else if (targetState && playerCheck(entState, targetState, ["Enemy"])) - { - data.target = targetState.id; - data.command = "attack"; - cursor = "action-attack"; - } - - return { - "possible": true, - "data": data, - "position": targetState && targetState.position, - "cursor": cursor, - "disabled": disabled, - "tooltip": tooltip - }; - - }, - "hotkeyActionCheck": function(target, selection) - { - // Hotkeys are checked in the actionInfo. - return this.actionCheck(target, selection); - }, - "actionCheck": function(target, selection) - { - // We want commands to units take precedence. - if (selection.some(ent => { - let entState = GetEntityState(ent); - return entState && !!entState.unitAI; - })) - return false; - - let actionInfo = getActionInfo("set-rallypoint", target, selection); - if (actionInfo.disabled) - return { - "type": "none", - "cursor": actionInfo.cursor, - "target": null, - "tooltip": actionInfo.tooltip - }; - - if (Engine.HotkeyIsPressed("session.autorallypoint")) - target = null; - - return actionInfo.possible && { - "type": "set-rallypoint", - "cursor": actionInfo.cursor, - "data": actionInfo.data, - "target": target, - "tooltip": actionInfo.tooltip, - "position": actionInfo.position, - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 6, - }, - - "unset-rallypoint": - { - "execute": function(target, action, selection, queued, pushFront) - { - Engine.PostNetworkCommand({ - "type": "unset-rallypoint", - "entities": selection - }); - - // Remove displayed rally point - Engine.GuiInterfaceCall("DisplayRallyPoint", { - "entities": [] - }); - - return true; - }, - "getActionInfo": function(entState, targetState) - { - if (!targetState || - entState.id != targetState.id || entState.unitAI || - !entState.rallyPoint || !entState.rallyPoint.position) - return false; - - return { "possible": true }; - }, - "actionCheck": function(target, selection) - { - let actionInfo = getActionInfo("unset-rallypoint", target, selection); - return actionInfo.possible && { - "type": "unset-rallypoint", - "cursor": "action-unset-rally", - "firstAbleEntity": actionInfo.entity - }; - }, - "specificness": 11, - }, - - // This is a "fake" action to show a failure cursor - // when only uncontrollable entities are selected. - "uncontrollable": - { - "execute": function(target, action, selection, queued) - { - return true; - }, - "actionCheck": function(target, selection) - { - // Only show this action if all entities are marked uncontrollable. - let playerState = g_SimState.players[g_ViewedPlayer]; - if (playerState && playerState.controlsAll || selection.some(ent => { - let entState = GetEntityState(ent); - return entState && entState.identity && entState.identity.controllable; - })) - return false; - - return { - "type": "none", - "cursor": "cursor-no", - "tooltip": translatePlural("This entity cannot be controlled.", "These entities cannot be controlled.", selection.length) - }; - }, - "specificness": 100, - }, - - "none": - { - "execute": function(target, action, selection, queued) - { - return true; - }, - "specificness": 100, - }, -}; - -var g_UnitActionsSortedKeys = Object.keys(g_UnitActions).sort((a, b) => g_UnitActions[a].specificness - g_UnitActions[b].specificness); - -/** - * Info and actions for the entity commands - * Currently displayed in the bottom of the central panel - */ -var g_EntityCommands = -{ - "unload-all": { - "getInfo": function(entStates) - { - let count = 0; - for (let entState of entStates) - { - if (!entState.garrisonHolder) - continue; - - if (allowedPlayersCheck([entState], ["Player"])) - count += entState.garrisonHolder.entities.length; - else - for (let entity of entState.garrisonHolder.entities) - if (allowedPlayersCheck([GetEntityState(entity)], ["Player"])) - ++count; - } - - if (!count) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unload") + - translate("Unload All."), - "icon": "garrison-out.png", - "count": count, - "enabled": true - }; - }, - "execute": function() - { - unloadAll(); - }, - "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 && turretPoint.ejectable).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) - { - return entStates.some(entState => !isUndeletable(entState)) ? - { - "tooltip": - colorizeHotkey("%(hotkey)s" + " ", "session.kill") + - translate("Destroy the selected units or structures.") + "\n" + - colorizeHotkey( - translate("Use %(hotkey)s to avoid the confirmation dialog."), - "session.noconfirmation" - ), - "icon": "kill_small.png", - "enabled": true - } : - { - // Get all delete reasons and remove duplications - "tooltip": entStates.map(entState => isUndeletable(entState)) - .filter((reason, pos, self) => - self.indexOf(reason) == pos && reason - ).join("\n"), - "icon": "kill_small_disabled.png", - "enabled": false - }; - }, - "execute": function(entStates) - { - let entityIDs = entStates.reduce( - (ids, entState) => { - if (!isUndeletable(entState)) - ids.push(entState.id); - return ids; - }, - []); - - if (!entityIDs.length) - return; - - let deleteSelection = () => Engine.PostNetworkCommand({ - "type": "delete-entities", - "entities": entityIDs - }); - - if (Engine.HotkeyIsPressed("session.noconfirmation")) - deleteSelection(); - else - (new DeleteSelectionConfirmation(deleteSelection)).display(); - }, - "allowedPlayers": ["Player"] - }, - - "stop": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.unitAI)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.stop") + - translate("Abort the current order."), - "icon": "stop.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - if (entStates.length) - stopUnits(entStates.map(entState => entState.id)); - }, - "allowedPlayers": ["Player"] - }, - - "call-to-arms": { - "getInfo": function(entStates) - { - const classes = ["Soldier", "Warship", "Siege", "Healer"]; - if (entStates.every(entState => !MatchesClassList(entState.identity.classes, classes))) - return false; - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.calltoarms") + - translate("Send the selected units on attack move to the specified location after dropping resources."), - "icon": "call-to-arms.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_CALLTOARMS; - }, - "allowedPlayers": ["Player"] - }, - - "garrison": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.garrisonable || - entState.garrisonable.holder != INVALID_ENTITY)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.garrison") + - translate("Order the selected units to garrison in a structure or unit."), - "icon": "garrison.png", - "enabled": true - }; - }, - "execute": function() - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_GARRISON; - }, - "allowedPlayers": ["Player"] - }, - - "occupy-turret": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.turretable || - entState.turretable.holder != INVALID_ENTITY)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.occupyturret") + - translate("Order the selected units to occupy a turret point."), - "icon": "occupy-turret.png", - "enabled": true - }; - }, - "execute": function() - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_OCCUPY_TURRET; - }, - "allowedPlayers": ["Player"] - }, - - "leave-turret": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.turretable || - entState.turretable.holder == INVALID_ENTITY || - !entState.turretable.ejectable)) - return false; - - return { - "tooltip": translate("Unload"), - "icon": "leave-turret.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - leaveTurretPoints(); - }, - "allowedPlayers": ["Player"] - }, - - "repair": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.builder)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.repair") + - translate("Order the selected units to repair a structure, ship, or siege engine."), - "icon": "repair.png", - "enabled": true - }; - }, - "execute": function() - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_REPAIR; - }, - "allowedPlayers": ["Player"] - }, - - "focus-rally": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.rallyPoint)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "camera.rallypointfocus") + - translate("Focus on Rally Point."), - "icon": "focus-rally.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - // TODO: Would be nicer to cycle between the rallypoints of multiple entities instead of just using the first - let focusTarget; - for (let entState of entStates) - if (entState.rallyPoint && entState.rallyPoint.position) - { - focusTarget = entState.rallyPoint.position; - break; - } - if (!focusTarget) - for (let entState of entStates) - if (entState.position) - { - focusTarget = entState.position; - break; - } - - if (focusTarget) - Engine.CameraMoveTo(focusTarget.x, focusTarget.z); - }, - "allowedPlayers": ["Player", "Observer"] - }, - - "back-to-work": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.unitAI || !entState.unitAI.hasWorkOrders)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.backtowork") + - translate("Back to Work"), - "icon": "back-to-work.png", - "enabled": true - }; - }, - "execute": function() - { - backToWork(); - }, - "allowedPlayers": ["Player"] - }, - - "add-guard": { - "getInfo": function(entStates) - { - if (entStates.every(entState => - !entState.unitAI || !entState.unitAI.canGuard || entState.unitAI.isGuarding)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.guard") + - translate("Order the selected units to guard a structure or unit."), - "icon": "add-guard.png", - "enabled": true - }; - }, - "execute": function() - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_GUARD; - }, - "allowedPlayers": ["Player"] - }, - - "remove-guard": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.unitAI || !entState.unitAI.isGuarding)) - return false; - - return { - "tooltip": translate("Remove guard"), - "icon": "remove-guard.png", - "enabled": true - }; - }, - "execute": function() - { - removeGuard(); - }, - "allowedPlayers": ["Player"] - }, - - "select-trading-goods": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.market)) - return false; - - return { - "tooltip": translate("Barter & Trade"), - "icon": "economics.png", - "enabled": true - }; - }, - "execute": function() - { - g_TradeDialog.toggle(); - }, - "allowedPlayers": ["Player"] - }, - - "patrol": { - "getInfo": function(entStates) - { - if (!entStates.some(entState => entState.unitAI && entState.unitAI.canPatrol)) - return false; - - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.patrol") + - translate("Patrol") + "\n" + - translate("Attack all encountered enemy units while avoiding structures."), - "icon": "patrol.png", - "enabled": true - }; - }, - "execute": function() - { - inputState = INPUT_PRESELECTEDACTION; - preSelectedAction = ACTION_PATROL; - }, - "allowedPlayers": ["Player"] - }, - - "share-dropsite": { - "getInfo": function(entStates) - { - let sharableEntities = entStates.filter( - entState => entState.resourceDropsite && entState.resourceDropsite.sharable); - if (!sharableEntities.length) - return false; - - // Returns if none of the entities belong to a player with a mutual ally. - if (entStates.every(entState => !GetSimState().players[entState.player].isMutualAlly.some( - (isAlly, playerId) => isAlly && playerId != entState.player))) - return false; - - return sharableEntities.some(entState => !entState.resourceDropsite.shared) ? - { - "tooltip": translate("Press to allow allies to use this dropsite"), - "icon": "locked_small.png", - "enabled": true - } : - { - "tooltip": translate("Press to prevent allies from using this dropsite"), - "icon": "unlocked_small.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - let sharableEntities = entStates.filter( - entState => entState.resourceDropsite && entState.resourceDropsite.sharable); - if (sharableEntities) - Engine.PostNetworkCommand({ - "type": "set-dropsite-sharing", - "entities": sharableEntities.map(entState => entState.id), - "shared": sharableEntities.some(entState => !entState.resourceDropsite.shared) - }); - }, - "allowedPlayers": ["Player"] - }, - - "is-dropsite-shared": { - "getInfo": function(entStates) - { - let shareableEntities = entStates.filter( - entState => entState.resourceDropsite && entState.resourceDropsite.sharable); - if (!shareableEntities.length) - return false; - - let player = Engine.GetPlayerID(); - let simState = GetSimState(); - if (!g_IsObserver && !simState.players[player].hasSharedDropsites || - shareableEntities.every(entState => controlsPlayer(entState.player))) - return false; - - if (!shareableEntities.every(entState => entState.resourceDropsite.shared)) - return { - "tooltip": translate("The use of this dropsite is prohibited"), - "icon": "locked_small.png", - "enabled": false - }; - - return { - "tooltip": g_IsObserver ? translate("Allies are allowed to use this dropsite.") : - translate("You are allowed to use this dropsite"), - "icon": "unlocked_small.png", - "enabled": false - }; - }, - "execute": function(entState) - { - // This command button is always disabled. - }, - "allowedPlayers": ["Ally", "Observer"] - }, - - "autoqueue-on": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.trainer?.entities?.length || !entState.production || entState.production.autoqueue)) - return false; - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.queueunit.autoqueueon") + - translate("Activate auto-queue for selected structures."), - "icon": "autoqueue-on.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - if (entStates.length) - turnAutoQueueOn(); - }, - "allowedPlayers": ["Player"] - }, - - "autoqueue-off": { - "getInfo": function(entStates) - { - if (entStates.every(entState => !entState.production?.autoqueue)) - return false; - return { - "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.queueunit.autoqueueoff") + - translate("Deactivate auto-queue for selected structures."), - "icon": "autoqueue-off.png", - "enabled": true - }; - }, - "execute": function(entStates) - { - if (entStates.length) - turnAutoQueueOff(); - }, - "allowedPlayers": ["Player"] - }, -}; - -function playerCheck(entState, targetState, validPlayers) -{ - let playerState = GetSimState().players[entState.player]; - for (let player of validPlayers) - if (player == "Gaia" && targetState.player == 0 || - player == "Player" && targetState.player == entState.player || - playerState["is" + player] && playerState["is" + player][targetState.player]) - return true; - - return false; -} - -/** - * Checks whether the entities have the right diplomatic status - * with respect to the currently active player. - * Also "Observer" can be used. - * - * @param {Object[]} entStates - An array containing the entity states to check. - * @param {string[]} validPlayers - An array containing the diplomatic statuses. - * - * @return {boolean} - Whether the currently active player is allowed. - */ -function allowedPlayersCheck(entStates, validPlayers) -{ - // Assume we can only select entities from one player, - // or it does not matter (e.g. observer). - let targetState = entStates[0]; - let playerState = GetSimState().players[Engine.GetPlayerID()]; - - return validPlayers.some(player => - player == "Observer" && g_IsObserver || - player == "Player" && controlsPlayer(targetState.player) || - playerState && playerState["is" + player] && playerState["is" + player][targetState.player]); -} - -function hasClass(entState, className) -{ - // note: use the functions in globalscripts/Templates.js for more versatile matching - return entState.identity && entState.identity.classes.indexOf(className) != -1; -} - -/** - * Keep in sync with Commands.js. - */ -function isUndeletable(entState) -{ - let playerState = g_SimState.players[entState.player]; - if (playerState && playerState.controlsAll) - return false; - - if (entState.resourceSupply && entState.resourceSupply.killBeforeGather) - return translate("The entity has to be killed before it can be gathered from"); - - if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2) - return translate("You cannot destroy this entity as you own less than half the capture points"); - - if (!entState.identity.canDelete) - return translate("This entity is undeletable"); - - return false; -} - -function DrawTargetMarker(target) -{ - Engine.GuiInterfaceCall("AddTargetMarker", { - "template": g_TargetMarker.move, - "x": target.x, - "z": target.z - }); -} - -function displayFlare(target, playerID) -{ - Engine.GuiInterfaceCall("AddTargetMarker", { - "template": g_TargetMarker.map_flare, - "x": target.x, - "z": target.z, - "owner": playerID - }); - g_MiniMapPanel.flare(target, playerID); -} - -function getCommandInfo(command, entStates) -{ - return entStates && g_EntityCommands[command] && - allowedPlayersCheck(entStates, g_EntityCommands[command].allowedPlayers) && - g_EntityCommands[command].getInfo(entStates); -} - -function getActionInfo(action, target, selection) -{ - if (!selection || !selection.length || !GetEntityState(selection[0])) - return { "possible": false }; - - // Look at the first targeted entity - // (TODO: maybe we eventually want to look at more, and be more context-sensitive? - // e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse) - let targetState = GetEntityState(target); - - let simState = GetSimState(); - let playerState = g_SimState.players[g_ViewedPlayer]; - - // Check if any entities in the selection can do some of the available actions. - for (let entityID of selection) - { - let entState = GetEntityState(entityID); - if (!entState) - continue; - - if (playerState && !playerState.controlsAll && !entState.identity.controllable) - continue; - - if (g_UnitActions[action] && g_UnitActions[action].getActionInfo) - { - let r = g_UnitActions[action].getActionInfo(entState, targetState, simState); - if (r && r.possible) - { - r.entity = entityID; - return r; - } - } - } - return { "possible": false }; -} diff --git a/community-mod/simulation/components/BuildingAI.js b/community-mod/simulation/components/BuildingAI.js index c0b9be0..97b6369 100644 --- a/community-mod/simulation/components/BuildingAI.js +++ b/community-mod/simulation/components/BuildingAI.js @@ -1,414 +1,354 @@ -// Number of rounds of firing per 2 seconds. +// Number of rounds of firing per 2 seconds. const roundCount = 10; const attackType = "Ranged"; -function BuildingAI() {} +function BuildingAI() { } BuildingAI.prototype.Schema = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; -BuildingAI.prototype.Init = function() -{ - this.currentRound = 0; - this.archersGarrisoned = 0; - this.arrowsLeft = 0; - this.targetUnits = []; - this.focusTargets = []; +BuildingAI.prototype.Init = function () { + this.currentRound = 0; + this.archersGarrisoned = 0; + this.arrowsLeft = 0; + this.targetUnits = []; }; -BuildingAI.prototype.OnGarrisonedUnitsChanged = function(msg) -{ - let classes = this.template.GarrisonArrowClasses; - for (let ent of msg.added) - { - let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); - if (cmpIdentity && 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.OnGarrisonedUnitsChanged = function (msg) { + let classes = this.template.GarrisonArrowClasses; + for (let ent of msg.added) { + let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); + 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) -{ - this.targetUnits = []; - this.focusTargets = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); +BuildingAI.prototype.OnOwnershipChanged = function (msg) { + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); }; -BuildingAI.prototype.OnDiplomacyChanged = function(msg) -{ - if (!IsOwnedByPlayer(msg.player, this.entity)) - return; +BuildingAI.prototype.OnDiplomacyChanged = function (msg) { + if (!IsOwnedByPlayer(msg.player, this.entity)) + return; - // Remove maybe now allied/neutral units. - this.targetUnits = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); + // Remove maybe now allied/neutral units. + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); }; -BuildingAI.prototype.OnDestroy = function() -{ - if (this.timer) - { - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.timer); - this.timer = undefined; - } +BuildingAI.prototype.OnDestroy = function () { + if (this.timer) { + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } - // Clean up range queries. - let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.enemyUnitsQuery) - cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); - if (this.gaiaUnitsQuery) - cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); + // Clean up range queries. + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + if (this.gaiaUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); }; -/** - * React on Attack value modifications, as it might influence the range. +/** + * React on Attack value modifications, as it might influence the range. */ -BuildingAI.prototype.OnValueModification = function(msg) -{ - if (msg.component != "Attack") - return; +BuildingAI.prototype.OnValueModification = function (msg) { + if (msg.component != "Attack") + return; - this.targetUnits = []; - this.SetupRangeQuery(); - this.SetupGaiaRangeQuery(); + this.targetUnits = []; + this.SetupRangeQuery(); + this.SetupGaiaRangeQuery(); }; -/** - * Setup the Range Query to detect units coming in & out of range. +/** + * Setup the Range Query to detect units coming in & out of range. */ -BuildingAI.prototype.SetupRangeQuery = function() -{ - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; +BuildingAI.prototype.SetupRangeQuery = function () { + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.enemyUnitsQuery) - { - cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); - this.enemyUnitsQuery = undefined; - } + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) { + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + this.enemyUnitsQuery = undefined; + } - var cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer) - return; + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer) + return; - var enemies = cmpPlayer.GetEnemies(); - // Remove gaia. - if (enemies.length && enemies[0] == 0) - enemies.shift(); + var enemies = cmpPlayer.GetEnemies(); + // Remove gaia. + if (enemies.length && enemies[0] == 0) + enemies.shift(); - if (!enemies.length) - return; + if (!enemies.length) + return; - const range = cmpAttack.GetRange(attackType); - const yOrigin = cmpAttack.GetAttackYOrigin(attackType); - // This takes entity sizes into accounts, so no need to compensate for structure size. - this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, yOrigin, - enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal")); + const range = cmpAttack.GetRange(attackType); + const yOrigin = cmpAttack.GetAttackYOrigin(attackType); + // This takes entity sizes into accounts, so no need to compensate for structure size. + this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, yOrigin, + enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal")); - cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); + cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); }; -// Set up a range query for Gaia units within LOS range which can be attacked. -// This should be called whenever our ownership changes. -BuildingAI.prototype.SetupGaiaRangeQuery = function() -{ - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; +// Set up a range query for Gaia units within LOS range which can be attacked. +// This should be called whenever our ownership changes. +BuildingAI.prototype.SetupGaiaRangeQuery = function () { + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; - var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - if (this.gaiaUnitsQuery) - { - cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); - this.gaiaUnitsQuery = undefined; - } + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.gaiaUnitsQuery) { + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); + this.gaiaUnitsQuery = undefined; + } - var cmpPlayer = QueryOwnerInterface(this.entity); - if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) - return; + var cmpPlayer = QueryOwnerInterface(this.entity); + if (!cmpPlayer || !cmpPlayer.IsEnemy(0)) + return; - const range = cmpAttack.GetRange(attackType); - const yOrigin = cmpAttack.GetAttackYOrigin(attackType); + const range = cmpAttack.GetRange(attackType); + const yOrigin = cmpAttack.GetAttackYOrigin(attackType); - // This query is only interested in Gaia entities that can attack. - // This takes entity sizes into accounts, so no need to compensate for structure size. - this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( - this.entity, range.min, range.max, yOrigin, - [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); + // This query is only interested in Gaia entities that can attack. + // This takes entity sizes into accounts, so no need to compensate for structure size. + this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery( + this.entity, range.min, range.max, yOrigin, + [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); - cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); + cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); }; -/** - * Called when units enter or leave range. +/** + * Called when units enter or leave range. */ -BuildingAI.prototype.OnRangeUpdate = function(msg) -{ +BuildingAI.prototype.OnRangeUpdate = function (msg) { - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; - // Target enemy units except non-dangerous animals. - if (msg.tag == this.gaiaUnitsQuery) - { - msg.added = msg.added.filter(e => { - let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); - return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); - }); - } - else if (msg.tag != this.enemyUnitsQuery) - return; + // Target enemy units except non-dangerous animals. + if (msg.tag == this.gaiaUnitsQuery) { + msg.added = msg.added.filter(e => { + let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); + return cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal()); + }); + } + else if (msg.tag != this.enemyUnitsQuery) + return; - // Add new targets. - for (let entity of msg.added) - if (cmpAttack.CanAttack(entity)) - this.targetUnits.push(entity); + // Add new targets. + for (let entity of msg.added) + if (cmpAttack.CanAttack(entity)) + this.targetUnits.push(entity); - // Remove targets outside of vision-range. - for (let entity of msg.removed) - { - let index = this.targetUnits.indexOf(entity); - if (index > -1) - this.targetUnits.splice(index, 1); - } + // Remove targets outside of vision-range. + for (let entity of msg.removed) { + let index = this.targetUnits.indexOf(entity); + if (index > -1) + this.targetUnits.splice(index, 1); + } - if (this.targetUnits.length) - this.StartTimer(); + if (this.targetUnits.length) + this.StartTimer(); }; -BuildingAI.prototype.StartTimer = function() -{ - if (this.timer) - return; +BuildingAI.prototype.StartTimer = function () { + if (this.timer) + return; - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - var attackTimers = cmpAttack.GetTimers(attackType); + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + var attackTimers = cmpAttack.GetTimers(attackType); - this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", - attackTimers.prepare, attackTimers.repeat / roundCount, null); + this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", + attackTimers.prepare, attackTimers.repeat / roundCount, null); }; -BuildingAI.prototype.GetDefaultArrowCount = function() -{ - var arrowCount = +this.template.DefaultArrowCount; - return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity)); +BuildingAI.prototype.GetDefaultArrowCount = function () { + var arrowCount = +this.template.DefaultArrowCount; + return Math.round(ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity)); }; -BuildingAI.prototype.GetMaxArrowCount = function() -{ - if (!this.template.MaxArrowCount) - return Infinity; +BuildingAI.prototype.GetMaxArrowCount = function () { + if (!this.template.MaxArrowCount) + return Infinity; - let maxArrowCount = +this.template.MaxArrowCount; - return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity)); + let maxArrowCount = +this.template.MaxArrowCount; + return Math.round(ApplyValueModificationsToEntity("BuildingAI/MaxArrowCount", maxArrowCount, this.entity)); }; -BuildingAI.prototype.GetGarrisonArrowMultiplier = function() -{ - var arrowMult = +this.template.GarrisonArrowMultiplier; - return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); +BuildingAI.prototype.GetGarrisonArrowMultiplier = function () { + var arrowMult = +this.template.GarrisonArrowMultiplier; + return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); }; -BuildingAI.prototype.GetGarrisonArrowClasses = function() -{ - var string = this.template.GarrisonArrowClasses; - if (string) - return string.split(/\s+/); - return []; +BuildingAI.prototype.GetGarrisonArrowClasses = function () { + var string = this.template.GarrisonArrowClasses; + if (string) + return string.split(/\s+/); + return []; }; -/** - * Returns the number of arrows which needs to be fired. - * DefaultArrowCount + Garrisoned Archers (i.e., any unit capable - * of shooting arrows from inside buildings). +/** + * Returns the number of arrows which needs to be fired. + * DefaultArrowCount + Garrisoned Archers (i.e., any unit capable + * of shooting arrows from inside buildings). */ -BuildingAI.prototype.GetArrowCount = function() -{ - let count = this.GetDefaultArrowCount() + - Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); +BuildingAI.prototype.GetArrowCount = function () { + let count = this.GetDefaultArrowCount() + + Math.round(this.archersGarrisoned * this.GetGarrisonArrowMultiplier()); - return Math.min(count, this.GetMaxArrowCount()); + return Math.min(count, this.GetMaxArrowCount()); }; -BuildingAI.prototype.SetUnitAITarget = function(ent) -{ - this.unitAITarget = ent; - if (ent) - this.StartTimer(); +BuildingAI.prototype.SetUnitAITarget = function (ent) { + this.unitAITarget = ent; + if (ent) + this.StartTimer(); }; -/** - * Adds index to keep track of the user-targeted units supporting a queue - * @param {ent} - Target of rallypoint selection when selection is an enemy unit from unit_actions.js +/** + * Fire arrows with random temporal distribution on prefered targets. + * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. */ -BuildingAI.prototype.AddFocusTarget = function(ent, queued, push) -{ - if (!ent || this.targetUnits.indexOf(ent) === -1) - return; - if (queued) - this.focusTargets.push({"entityId": ent}); - else if (push) - this.focusTargets.unshift({"entityId": ent}); - else - this.focusTargets = [{"entityId": ent}]; -}; +BuildingAI.prototype.FireArrows = function () { + if (!this.targetUnits.length && !this.unitAITarget) { + if (!this.timer) + return; -/** - * Fire arrows with random temporal distribution on prefered targets. - * Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range. - */ -BuildingAI.prototype.FireArrows = function() -{ - if (!this.targetUnits.length && !this.unitAITarget) - { - if (!this.timer) - return; + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + return; + } - let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.CancelTimer(this.timer); - this.timer = undefined; - return; - } + let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; - let cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return; + if (this.currentRound > roundCount - 1) + this.currentRound = 0; - if (this.currentRound > roundCount - 1) - this.currentRound = 0; + if (this.currentRound == 0) + this.arrowsLeft = this.GetArrowCount(); - if (this.currentRound == 0) - this.arrowsLeft = this.GetArrowCount(); + let arrowsToFire = 0; + if (this.currentRound == roundCount - 1) + arrowsToFire = this.arrowsLeft; + else + arrowsToFire = Math.min( + randIntInclusive(0, 2 * this.GetArrowCount() / roundCount), + this.arrowsLeft + ); - let arrowsToFire = 0; - if (this.currentRound == roundCount - 1) - arrowsToFire = this.arrowsLeft; - else - arrowsToFire = Math.min( - randIntInclusive(0, 2 * this.GetArrowCount() / roundCount), - this.arrowsLeft - ); + if (arrowsToFire <= 0) { + ++this.currentRound; + return; + } - if (arrowsToFire <= 0) - { - ++this.currentRound; - return; - } + // Add targets to a weighted list, to allow preferences. + let targets = new WeightedList(); + let maxPreference = this.MAX_PREFERENCE_BONUS; + let addTarget = function (target) { + let preference = cmpAttack.GetPreference(target); + let weight = 1; + + if (preference !== null && preference !== undefined) + weight += maxPreference / (1 + preference); + + targets.push(target, weight); + }; - // Add targets to a list. - let targets = []; - let addTarget = function(target) - { - const pref = (cmpAttack.GetPreference(target) ?? 49); - targets.push({"entityId": target, "preference": pref}); - }; // Add the UnitAI target separately, as the UnitMotion and RangeManager implementations differ. if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) == -1) - addTarget(this.unitAITarget); - else if (this.unitAITarget && this.targetUnits.indexOf(this.unitAITarget) != -1) - this.focusTargets = [{"entityId": this.unitAITarget}]; - if (!this.focusTargets.length) - { - for (let target of this.targetUnits) - addTarget(target); - // Sort targets by preference and then by proximity. - targets.sort( (a,b) => { - if (a.preference > b.preference) return 1; - else if (a.preference < b.preference) return -1; - else if (PositionHelper.DistanceBetweenEntities(this.entity,a.entityId) > PositionHelper.DistanceBetweenEntities(this.entity,b.entityId)) return 1; - return -1; - }); + addTarget(this.unitAITarget); + for (let target of this.targetUnits) + addTarget(target); + + // The obstruction manager performs approximate range checks. + // so we need to verify them here. + // TODO: perhaps an optional 'precise' mode to range queries would be more performant. + const cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + const range = cmpAttack.GetRange(attackType); + const yOrigin = cmpAttack.GetAttackYOrigin(attackType); + + let firedArrows = 0; + while (firedArrows < arrowsToFire && targets.length()) { + const selectedTarget = targets.randomItem(); + if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( + this.entity, + selectedTarget, + range.min, + range.max, + yOrigin, + false)) { + cmpAttack.PerformAttack(attackType, selectedTarget); + PlaySound("attack_" + attackType.toLowerCase(), this.entity); + ++firedArrows; + continue; + } + + // Could not attack target, try a different target. + targets.remove(selectedTarget); } - else - targets = this.focusTargets; - // The obstruction manager performs approximate range checks. - // so we need to verify them here. - // TODO: perhaps an optional 'precise' mode to range queries would be more performant. - const cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); - const range = cmpAttack.GetRange(attackType); - const yOrigin = cmpAttack.GetAttackYOrigin(attackType); - - let firedArrows = 0; - let killedTargets = 0; - while (firedArrows < arrowsToFire && killedTargets < targets.length) - { - - let selectedTarget = targets[killedTargets].entityId; - if (this.CheckTargetVisible(selectedTarget) && cmpObstructionManager.IsInTargetParabolicRange( - this.entity, - selectedTarget, - range.min, - range.max, - yOrigin, - false)) - { - cmpAttack.PerformAttack(attackType, selectedTarget); - PlaySound("attack_" + attackType.toLowerCase(), this.entity); - ++firedArrows; - continue; - } - else - { - // Could not attack target, try a different target. - ++killedTargets; - } - } - targets.splice(0,killedTargets); - killedTargets = 0;//not sure if this is necessary - this.arrowsLeft -= firedArrows; - ++this.currentRound; + this.arrowsLeft -= firedArrows; + ++this.currentRound; }; -/** - * Returns true if the target entity is visible through the FoW/SoD. +/** + * Returns true if the target entity is visible through the FoW/SoD. */ -BuildingAI.prototype.CheckTargetVisible = function(target) -{ - var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - if (!cmpOwnership) - return false; +BuildingAI.prototype.CheckTargetVisible = function (target) { + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (!cmpOwnership) + return false; - // Entities that are hidden and miraged are considered visible. - var cmpFogging = Engine.QueryInterface(target, IID_Fogging); - if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) - return true; + // Entities that are hidden and miraged are considered visible. + var cmpFogging = Engine.QueryInterface(target, IID_Fogging); + if (cmpFogging && cmpFogging.IsMiraged(cmpOwnership.GetOwner())) + return true; - // Either visible directly, or visible in fog. - let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); - return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; + // Either visible directly, or visible in fog. + let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + return cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner()) != "hidden"; }; Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); diff --git a/community-mod/simulation/helpers/Commands.js b/community-mod/simulation/helpers/Commands.js index c50f99c..b2283f2 100644 --- a/community-mod/simulation/helpers/Commands.js +++ b/community-mod/simulation/helpers/Commands.js @@ -2,8 +2,7 @@ // are likely to fail, which may be useful for debugging AIs var g_DebugCommands = false; -function ProcessCommand(player, cmd) -{ +function ProcessCommand(player, cmd) { let cmpPlayer = QueryPlayerIDInterface(player); if (!cmpPlayer) return; @@ -30,8 +29,7 @@ function ProcessCommand(player, cmd) }; // Save the position, since the GUI event is received after the unit died - if (cmd.type == "delete-entities") - { + if (cmd.type == "delete-entities") { let cmpPosition = cmd.entities[0] && Engine.QueryInterface(cmd.entities[0], IID_Position); commandData.position = cmpPosition && cmpPosition.IsInWorld() && cmpPosition.GetPosition2D(); } @@ -46,20 +44,18 @@ function ProcessCommand(player, cmd) // moves the entities closer to the target before giving up.) // Now handle various commands - if (g_Commands[cmd.type]) - { + if (g_Commands[cmd.type]) { var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); cmpTrigger.CallEvent("OnPlayerCommand", { "player": player, "cmd": cmd }); g_Commands[cmd.type](player, cmd, data); } else - error("Invalid command: unknown command type: "+uneval(cmd)); + error("Invalid command: unknown command type: " + uneval(cmd)); } var g_Commands = { - "aichat": function(player, cmd, data) - { + "aichat": function (player, cmd, data) { var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); var notification = { "players": [player] }; for (var key in cmd) @@ -67,45 +63,40 @@ var g_Commands = { cmpGuiInterface.PushNotification(notification); }, - "cheat": function(player, cmd, data) - { + "cheat": function (player, cmd, data) { Cheat(cmd); }, - "collect-treasure": function(player, cmd, data) - { + "collect-treasure": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.CollectTreasure(cmd.target, cmd.queued); }); }, - "collect-treasure-near-position": function(player, cmd, data) - { + "collect-treasure-near-position": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.CollectTreasureNearPosition(cmd.x, cmd.z, cmd.queued); }); }, - "diplomacy": function(player, cmd, data) - { + "diplomacy": function (player, cmd, data) { let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager); if (data.cmpPlayer.GetLockTeams() || - cmpCeasefireManager && cmpCeasefireManager.IsCeasefireActive()) + cmpCeasefireManager && cmpCeasefireManager.IsCeasefireActive()) return; - switch(cmd.to) - { - case "ally": - data.cmpPlayer.SetAlly(cmd.player); - break; - case "neutral": - data.cmpPlayer.SetNeutral(cmd.player); - break; - case "enemy": - data.cmpPlayer.SetEnemy(cmd.player); - break; - default: - warn("Invalid command: Could not set "+player+" diplomacy status of player "+cmd.player+" to "+cmd.to); + switch (cmd.to) { + case "ally": + data.cmpPlayer.SetAlly(cmd.player); + break; + case "neutral": + data.cmpPlayer.SetNeutral(cmd.player); + break; + case "enemy": + data.cmpPlayer.SetEnemy(cmd.player); + break; + default: + warn("Invalid command: Could not set " + player + " diplomacy status of player " + cmd.player + " to " + cmd.to); } var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); @@ -117,13 +108,11 @@ var g_Commands = { }); }, - "tribute": function(player, cmd, data) - { + "tribute": function (player, cmd, data) { data.cmpPlayer.TributeResource(cmd.player, cmd.amounts); }, - "control-all": function(player, cmd, data) - { + "control-all": function (player, cmd, data) { if (!data.cmpPlayer.GetCheatsEnabled()) return; @@ -137,8 +126,7 @@ var g_Commands = { data.cmpPlayer.SetControlAllUnits(cmd.flag); }, - "reveal-map": function(player, cmd, data) - { + "reveal-map": function (player, cmd, data) { if (!data.cmpPlayer.GetCheatsEnabled()) return; @@ -155,34 +143,29 @@ var g_Commands = { cmpRangeManager.SetLosRevealAll(-1, cmd.enable); }, - "walk": function(player, cmd, data) - { + "walk": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued, cmd.pushFront); }); }, - "walk-custom": function(player, cmd, data) - { + "walk-custom": function (player, cmd, data) { for (let ent in data.entities) GetFormationUnitAIs([data.entities[ent]], player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Walk(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.queued, cmd.pushFront); }); }, - "walk-to-range": function(player, cmd, data) - { + "walk-to-range": function (player, cmd, data) { // Only used by the AI - for (let ent of data.entities) - { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) cmpUnitAI.WalkToPointRange(cmd.x, cmd.z, cmd.min, cmd.max, cmd.queued, cmd.pushFront); } }, - "attack-walk": function(player, cmd, data) - { + "attack-walk": function (player, cmd, data) { let allowCapture = cmd.allowCapture || cmd.allowCapture == null; GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { @@ -190,8 +173,7 @@ var g_Commands = { }); }, - "attack-walk-custom": function(player, cmd, data) - { + "attack-walk-custom": function (player, cmd, data) { let allowCapture = cmd.allowCapture || cmd.allowCapture == null; for (let ent in data.entities) GetFormationUnitAIs([data.entities[ent]], player, cmd, data.formation).forEach(cmpUnitAI => { @@ -199,21 +181,19 @@ var g_Commands = { }); }, - "attack": function(player, cmd, data) - { + "attack": function (player, cmd, data) { let allowCapture = cmd.allowCapture || cmd.allowCapture == null; if (g_DebugCommands && !allowCapture && - !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) - warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); + !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) + warn("Invalid command: attack target is not owned by enemy of player " + player + ": " + uneval(cmd)); GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Attack(cmd.target, allowCapture, cmd.queued, cmd.pushFront); }); }, - "patrol": function(player, cmd, data) - { + "patrol": function (player, cmd, data) { let allowCapture = cmd.allowCapture || cmd.allowCapture == null; GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => @@ -221,99 +201,85 @@ var g_Commands = { ); }, - "heal": function(player, cmd, data) - { + "heal": function (player, cmd, data) { if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) - warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd)); + warn("Invalid command: heal target is not owned by player " + player + " or their ally: " + uneval(cmd)); GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Heal(cmd.target, cmd.queued, cmd.pushFront); }); }, - "repair": function(player, cmd, data) - { + "repair": function (player, cmd, data) { // This covers both repairing damaged buildings, and constructing unfinished foundations if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) - warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd)); + warn("Invalid command: repair target is not owned by ally of player " + player + ": " + uneval(cmd)); GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued, cmd.pushFront); }); }, - "gather": function(player, cmd, data) - { + "gather": function (player, cmd, data) { if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByGaia(cmd.target))) - warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd)); + warn("Invalid command: resource is not owned by gaia or player " + player + ": " + uneval(cmd)); GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Gather(cmd.target, cmd.queued, cmd.pushFront); }); }, - "gather-near-position": function(player, cmd, data) - { + "gather-near-position": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued, cmd.pushFront); }); }, - "returnresource": function(player, cmd, data) - { + "returnresource": function (player, cmd, data) { if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target)) - warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd)); + warn("Invalid command: dropsite is not owned by player " + player + ": " + uneval(cmd)); GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.ReturnResource(cmd.target, cmd.queued, cmd.pushFront); }); }, - "back-to-work": function(player, cmd, data) - { - for (let ent of data.entities) - { + "back-to-work": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); - if(!cmpUnitAI || !cmpUnitAI.BackToWork()) + if (!cmpUnitAI || !cmpUnitAI.BackToWork()) notifyBackToWorkFailure(player); } }, - "call-to-arms": function(player, cmd, data) - { + "call-to-arms": function (player, cmd, data) { const unitsToMove = data.entities.filter(ent => MatchesClassList(Engine.QueryInterface(ent, IID_Identity).GetClassesList(), ["Soldier", "Warship", "Siege", "Healer"]) ); GetFormationUnitAIs(unitsToMove, player, cmd, data.formation).forEach(cmpUnitAI => { const target = cmd.target; - if (cmd.pushFront) - { + if (cmd.pushFront) { cmpUnitAI.WalkAndFight(target.x, target.z, cmd.targetClasses, cmd.allowCapture, false, cmd.pushFront); cmpUnitAI.DropAtNearestDropSite(false, cmd.pushFront); } - else - { + else { cmpUnitAI.DropAtNearestDropSite(cmd.queued, false) cmpUnitAI.WalkAndFight(target.x, target.z, cmd.targetClasses, cmd.allowCapture, true, false); } }); }, - "remove-guard": function(player, cmd, data) - { - for (let ent of data.entities) - { + "remove-guard": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) cmpUnitAI.RemoveGuard(); } }, - "train": function(player, cmd, data) - { - if (!Number.isInteger(cmd.count) || cmd.count <= 0) - { + "train": function (player, cmd, data) { + if (!Number.isInteger(cmd.count) || cmd.count <= 0) { warn("Invalid command: can't train " + uneval(cmd.count) + " units"); return; } @@ -325,20 +291,16 @@ var g_Commands = { unitCategory = template.TrainingRestrictions.Category; // Verify that the building(s) can be controlled by the player - if (data.entities.length <= 0) - { + if (data.entities.length <= 0) { if (g_DebugCommands) - warn("Invalid command: training building(s) cannot be controlled by player "+player+": "+uneval(cmd)); + warn("Invalid command: training building(s) cannot be controlled by player " + player + ": " + uneval(cmd)); return; } - for (let ent of data.entities) - { - if (unitCategory) - { + for (let ent of data.entities) { + if (unitCategory) { var cmpPlayerEntityLimits = QueryOwnerInterface(ent, IID_EntityLimits); - if (cmpPlayerEntityLimits && !cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count, cmd.template, template.TrainingRestrictions.MatchLimit)) - { + if (cmpPlayerEntityLimits && !cmpPlayerEntityLimits.AllowedToTrain(unitCategory, cmd.count, cmd.template, template.TrainingRestrictions.MatchLimit)) { if (g_DebugCommands) warn(unitCategory + " train limit is reached: " + uneval(cmd)); continue; @@ -346,8 +308,7 @@ var g_Commands = { } var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager); - if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template)) - { + if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template)) { if (g_DebugCommands) warn("Invalid command: training requires unresearched technology: " + uneval(cmd)); continue; @@ -369,11 +330,9 @@ var g_Commands = { } }, - "research": function(player, cmd, data) - { + "research": function (player, cmd, data) { var cmpTechnologyManager = QueryOwnerInterface(cmd.entity, IID_TechnologyManager); - if (cmpTechnologyManager && !cmpTechnologyManager.CanResearch(cmd.template)) - { + if (cmpTechnologyManager && !cmpTechnologyManager.CanResearch(cmd.template)) { if (g_DebugCommands) warn("Invalid command: Requirements to research technology are not met: " + uneval(cmd)); return; @@ -384,36 +343,30 @@ var g_Commands = { queue.AddItem(cmd.template, "technology", undefined, cmd.metadata, cmd.pushFront); }, - "stop-production": function(player, cmd, data) - { + "stop-production": function (player, cmd, data) { let cmpProductionQueue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue); if (cmpProductionQueue) cmpProductionQueue.RemoveItem(cmd.id); }, - "construct": function(player, cmd, data) - { + "construct": function (player, cmd, data) { TryConstructBuilding(player, data.cmpPlayer, data.controlAllUnits, cmd); }, - "construct-wall": function(player, cmd, data) - { + "construct-wall": function (player, cmd, data) { TryConstructWall(player, data.cmpPlayer, data.controlAllUnits, cmd); }, - "delete-entities": function(player, cmd, data) - { - for (let ent of data.entities) - { - if (!data.controlAllUnits) - { + "delete-entities": function (player, cmd, data) { + for (let ent of data.entities) { + if (!data.controlAllUnits) { let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); if (cmpIdentity && cmpIdentity.IsUndeletable()) continue; let cmpCapturable = QueryMiragedInterface(ent, IID_Capturable); if (cmpCapturable && - cmpCapturable.GetCapturePoints()[player] < cmpCapturable.GetMaxCapturePoints() / 2) + cmpCapturable.GetCapturePoints()[player] < cmpCapturable.GetMaxCapturePoints() / 2) continue; let cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply); @@ -422,8 +375,7 @@ var g_Commands = { } let cmpMirage = Engine.QueryInterface(ent, IID_Mirage); - if (cmpMirage) - { + if (cmpMirage) { let cmpMiragedHealth = Engine.QueryInterface(cmpMirage.parent, IID_Health); if (cmpMiragedHealth) cmpMiragedHealth.Kill(); @@ -442,15 +394,10 @@ var g_Commands = { } }, - "set-rallypoint": function(player, cmd, data) - { - for (let ent of data.entities) - { - if (cmd.data.command == "attack" && cmd.data.target) - Engine.QueryInterface(ent, IID_BuildingAI)?.AddFocusTarget(cmd.target, cmd.queued, cmd.pushFront); + "set-rallypoint": function (player, cmd, data) { + for (let ent of data.entities) { var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); - if (cmpRallyPoint) - { + if (cmpRallyPoint) { if (!cmd.queued) cmpRallyPoint.Unset(); @@ -460,34 +407,28 @@ var g_Commands = { } }, - "unset-rallypoint": function(player, cmd, data) - { - for (let ent of data.entities) - { + "unset-rallypoint": function (player, cmd, data) { + for (let ent of data.entities) { var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) cmpRallyPoint.Reset(); } }, - "resign": function(player, cmd, data) - { + "resign": function (player, cmd, data) { data.cmpPlayer.SetState("defeated", markForTranslation("%(player)s has resigned.")); }, - "occupy-turret": function(player, cmd, data) - { + "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)) - { + "garrison": function (player, cmd, data) { + if (!CanPlayerOrAllyControlUnit(cmd.target, player, data.controlAllUnits)) { if (g_DebugCommands) - warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd)); + warn("Invalid command: garrison target cannot be controlled by player " + player + " (or ally): " + uneval(cmd)); return; } @@ -496,10 +437,8 @@ var g_Commands = { }); }, - "guard": function(player, cmd, data) - { - if (!IsOwnedByPlayerOrMutualAlly(cmd.target, player, data.controlAllUnits)) - { + "guard": function (player, cmd, data) { + if (!IsOwnedByPlayerOrMutualAlly(cmd.target, player, data.controlAllUnits)) { if (g_DebugCommands) warn("Invalid command: Guard/escort target is not owned by player " + player + " or ally thereof: " + uneval(cmd)); return; @@ -510,18 +449,15 @@ var g_Commands = { }); }, - "stop": function(player, cmd, data) - { + "stop": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.Stop(cmd.queued); }); }, - "leave-turret": function(player, cmd, data) - { + "leave-turret": function (player, cmd, data) { let notUnloaded = 0; - for (let ent of data.entities) - { + for (let ent of data.entities) { let cmpTurretable = Engine.QueryInterface(ent, IID_Turretable); if (!cmpTurretable || !cmpTurretable.LeaveTurret()) ++notUnloaded; @@ -531,14 +467,11 @@ var g_Commands = { notifyUnloadFailure(player); }, - "unload-turrets": function(player, cmd, data) - { + "unload-turrets": function (player, cmd, data) { let notUnloaded = 0; - for (let ent of data.entities) - { + for (let ent of data.entities) { let cmpTurretHolder = Engine.QueryInterface(ent, IID_TurretHolder); - for (let turret of cmpTurretHolder.GetEntities()) - { + for (let turret of cmpTurretHolder.GetEntities()) { let cmpTurretable = Engine.QueryInterface(turret, IID_Turretable); if (!cmpTurretable || !cmpTurretable.LeaveTurret()) ++notUnloaded; @@ -549,12 +482,10 @@ var g_Commands = { notifyUnloadFailure(player); }, - "unload": function(player, cmd, data) - { - if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits)) - { + "unload": function (player, cmd, data) { + if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits)) { if (g_DebugCommands) - warn("Invalid command: unload target cannot be controlled by player "+player+" (or ally): "+uneval(cmd)); + warn("Invalid command: unload target cannot be controlled by player " + player + " (or ally): " + uneval(cmd)); return; } @@ -573,18 +504,15 @@ var g_Commands = { notifyUnloadFailure(player, cmd.garrisonHolder); }, - "unload-template": function(player, cmd, data) - { + "unload-template": function (player, cmd, data) { var entities = FilterEntityListWithAllies(cmd.garrisonHolders, player, data.controlAllUnits); - for (let garrisonHolder of entities) - { + for (let garrisonHolder of entities) { var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder); - if (cmpGarrisonHolder) - { + if (cmpGarrisonHolder) { // Only the owner of the garrisonHolder may unload entities from any owners if (!IsOwnedByPlayer(player, garrisonHolder) && !data.controlAllUnits - && player != +cmd.owner) - continue; + && player != +cmd.owner) + continue; if (!cmpGarrisonHolder.UnloadTemplate(cmd.template, cmd.owner, cmd.all)) notifyUnloadFailure(player, garrisonHolder); @@ -592,57 +520,47 @@ var g_Commands = { } }, - "unload-all-by-owner": function(player, cmd, data) - { + "unload-all-by-owner": function (player, cmd, data) { var entities = FilterEntityListWithAllies(cmd.garrisonHolders, player, data.controlAllUnits); - for (let garrisonHolder of entities) - { + for (let garrisonHolder of entities) { var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder); if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadAllByOwner(player)) notifyUnloadFailure(player, garrisonHolder); } }, - "unload-all": function(player, cmd, data) - { + "unload-all": function (player, cmd, data) { var entities = FilterEntityList(cmd.garrisonHolders, player, data.controlAllUnits); - for (let garrisonHolder of entities) - { + for (let garrisonHolder of entities) { var cmpGarrisonHolder = Engine.QueryInterface(garrisonHolder, IID_GarrisonHolder); if (!cmpGarrisonHolder || !cmpGarrisonHolder.UnloadAll()) notifyUnloadFailure(player, garrisonHolder); } }, - "alert-raise": function(player, cmd, data) - { - for (let ent of data.entities) - { + "alert-raise": function (player, cmd, data) { + for (let ent of data.entities) { var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser); if (cmpAlertRaiser) cmpAlertRaiser.RaiseAlert(); } }, - "alert-end": function(player, cmd, data) - { - for (let ent of data.entities) - { + "alert-end": function (player, cmd, data) { + for (let ent of data.entities) { var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser); if (cmpAlertRaiser) cmpAlertRaiser.EndOfAlert(); } }, - "formation": function(player, cmd, data) - { + "formation": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation, true).forEach(cmpUnitAI => { cmpUnitAI.MoveIntoFormation(cmd); }); }, - "promote": function(player, cmd, data) - { + "promote": function (player, cmd, data) { if (!data.cmpPlayer.GetCheatsEnabled()) return; @@ -654,28 +572,23 @@ var g_Commands = { "translateMessage": true }); - for (let ent of cmd.entities) - { + for (let ent of cmd.entities) { var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion); if (cmpPromotion) cmpPromotion.IncreaseXp(cmpPromotion.GetRequiredXp() - cmpPromotion.GetCurrentXp()); } }, - "stance": function(player, cmd, data) - { - for (let ent of data.entities) - { + "stance": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI && !cmpUnitAI.IsTurret()) cmpUnitAI.SwitchToStance(cmd.name); } }, - "lock-gate": function(player, cmd, data) - { - for (let ent of data.entities) - { + "lock-gate": function (player, cmd, data) { + for (let ent of data.entities) { var cmpGate = Engine.QueryInterface(ent, IID_Gate); if (!cmpGate) continue; @@ -687,50 +600,42 @@ var g_Commands = { } }, - "setup-trade-route": function(player, cmd, data) - { + "setup-trade-route": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source, cmd.route, cmd.queued); }); }, - "cancel-setup-trade-route": function(player, cmd, data) - { + "cancel-setup-trade-route": function (player, cmd, data) { GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => { cmpUnitAI.CancelSetupTradeRoute(cmd.target); }); }, - "set-trading-goods": function(player, cmd, data) - { + "set-trading-goods": function (player, cmd, data) { data.cmpPlayer.SetTradingGoods(cmd.tradingGoods); }, - "barter": function(player, cmd, data) - { + "barter": function (player, cmd, data) { var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter); cmpBarter.ExchangeResources(player, cmd.sell, cmd.buy, cmd.amount); }, - "set-shading-color": function(player, cmd, data) - { + "set-shading-color": function (player, cmd, data) { // Prevent multiplayer abuse if (!data.cmpPlayer.IsAI()) return; // Debug command to make an entity brightly colored - for (let ent of cmd.entities) - { + for (let ent of cmd.entities) { var cmpVisual = Engine.QueryInterface(ent, IID_Visual); if (cmpVisual) cmpVisual.SetShadingColor(cmd.rgb[0], cmd.rgb[1], cmd.rgb[2], 0); // alpha isn't used so just send 0 } }, - "pack": function(player, cmd, data) - { - for (let ent of data.entities) - { + "pack": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (!cmpUnitAI) continue; @@ -742,10 +647,8 @@ var g_Commands = { } }, - "cancel-pack": function(player, cmd, data) - { - for (let ent of data.entities) - { + "cancel-pack": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (!cmpUnitAI) continue; @@ -757,17 +660,14 @@ var g_Commands = { } }, - "upgrade": function(player, cmd, data) - { - for (let ent of data.entities) - { + "upgrade": function (player, cmd, data) { + for (let ent of data.entities) { var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade); if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template)) continue; - if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template)) - { + if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template)) { var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "players": [player], @@ -778,8 +678,7 @@ var g_Commands = { // Check entity limits var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits); - if (cmpEntityLimits && !cmpEntityLimits.AllowedToReplace(ent, cmd.template)) - { + if (cmpEntityLimits && !cmpEntityLimits.AllowedToReplace(ent, cmd.template)) { if (g_DebugCommands) warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd)); continue; @@ -788,8 +687,7 @@ var g_Commands = { let cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager); let requiredTechnology = cmpUpgrade.GetRequiredTechnology(cmd.template); - if (requiredTechnology && (!cmpTechnologyManager || !cmpTechnologyManager.IsTechnologyResearched(requiredTechnology))) - { + if (requiredTechnology && (!cmpTechnologyManager || !cmpTechnologyManager.IsTechnologyResearched(requiredTechnology))) { if (g_DebugCommands) warn("Invalid command: upgrading is not possible for this player or requires unresearched technology: " + uneval(cmd)); continue; @@ -799,18 +697,15 @@ var g_Commands = { } }, - "cancel-upgrade": function(player, cmd, data) - { - for (let ent of data.entities) - { + "cancel-upgrade": function (player, cmd, data) { + for (let ent of data.entities) { let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade); if (cmpUpgrade) cmpUpgrade.CancelUpgrade(player); } }, - "attack-request": function(player, cmd, data) - { + "attack-request": function (player, cmd, data) { // Send a chat message to human players var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ @@ -827,8 +722,7 @@ var g_Commands = { cmpAIInterface.PushEvent("AttackRequest", cmd); }, - "spy-request": function(player, cmd, data) - { + "spy-request": function (player, cmd, data) { let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); let ent = pickRandom(cmpRangeManager.GetEntitiesByPlayer(cmd.player).filter(ent => { let cmpVisionSharing = Engine.QueryInterface(ent, IID_VisionSharing); @@ -844,8 +738,7 @@ var g_Commands = { }); if (ent) Engine.QueryInterface(ent, IID_VisionSharing).AddSpy(cmd.source); - else - { + else { let template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate("special/spy"); IncurBribeCost(template, player, cmd.player, true); // update statistics for failed bribes @@ -861,38 +754,32 @@ var g_Commands = { } }, - "diplomacy-request": function(player, cmd, data) - { + "diplomacy-request": function (player, cmd, data) { let cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); if (cmpAIInterface) cmpAIInterface.PushEvent("DiplomacyRequest", cmd); }, - "tribute-request": function(player, cmd, data) - { + "tribute-request": function (player, cmd, data) { let cmpAIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface); if (cmpAIInterface) cmpAIInterface.PushEvent("TributeRequest", cmd); }, - "dialog-answer": function(player, cmd, data) - { + "dialog-answer": function (player, cmd, data) { // Currently nothing. Triggers can read it anyway, and send this // message to any component you like. }, - "set-dropsite-sharing": function(player, cmd, data) - { - for (let ent of data.entities) - { + "set-dropsite-sharing": function (player, cmd, data) { + for (let ent of data.entities) { let cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite); if (cmpResourceDropsite && cmpResourceDropsite.IsSharable()) cmpResourceDropsite.SetSharing(cmd.shared); } }, - "map-flare": function(player, cmd, data) - { + "map-flare": function (player, cmd, data) { let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ "type": "map-flare", @@ -901,20 +788,16 @@ var g_Commands = { }); }, - "autoqueue-on": function(player, cmd, data) - { - for (let ent of data.entities) - { + "autoqueue-on": function (player, cmd, data) { + for (let ent of data.entities) { let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue); if (cmpProductionQueue) cmpProductionQueue.EnableAutoQueue(); } }, - "autoqueue-off": function(player, cmd, data) - { - for (let ent of data.entities) - { + "autoqueue-off": function (player, cmd, data) { + for (let ent of data.entities) { let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue); if (cmpProductionQueue) cmpProductionQueue.DisableAutoQueue(); @@ -926,8 +809,7 @@ var g_Commands = { /** * Sends a GUI notification about unit(s) that failed to ungarrison. */ -function notifyUnloadFailure(player) -{ +function notifyUnloadFailure(player) { let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "type": "text", @@ -940,8 +822,7 @@ function notifyUnloadFailure(player) /** * Sends a GUI notification about worker(s) that failed to go back to work. */ -function notifyBackToWorkFailure(player) -{ +function notifyBackToWorkFailure(player) { var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGUIInterface.PushNotification({ "type": "text", @@ -955,8 +836,7 @@ function notifyBackToWorkFailure(player) * Sends a GUI notification about entities that can't be controlled. * @param {number} player - The player-ID of the player that needs to receive this message. */ -function notifyOrderFailure(entity, player) -{ +function notifyOrderFailure(entity, player) { let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); if (!cmpIdentity) return; @@ -975,13 +855,11 @@ function notifyOrderFailure(entity, player) /** * Get some information about the formations used by entities. */ -function ExtractFormations(ents) -{ +function ExtractFormations(ents) { let entities = []; // Entities with UnitAI. let members = {}; // { formationentity: [ent, ent, ...], ... } let templates = {}; // { formationentity: template } - for (let ent of ents) - { + for (let ent of ents) { let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (!cmpUnitAI) continue; @@ -992,8 +870,7 @@ function ExtractFormations(ents) if (fid == INVALID_ENTITY) continue; - if (!members[fid]) - { + if (!members[fid]) { members[fid] = []; templates[fid] = cmpUnitAI.GetFormationTemplate(); } @@ -1011,8 +888,7 @@ function ExtractFormations(ents) * Tries to find the best angle to put a dock at a given position * Taken from GuiInterface.js */ -function GetDockAngle(template, x, z) -{ +function GetDockAngle(template, x, z) { var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); if (!cmpTerrain || !cmpWaterManager) @@ -1021,7 +897,7 @@ function GetDockAngle(template, x, z) // Get footprint size var halfSize = 0; if (template.Footprint.Square) - halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2; + halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"]) / 2; else if (template.Footprint.Circle) halfSize = template.Footprint.Circle["@radius"]; @@ -1035,15 +911,13 @@ function GetDockAngle(template, x, z) * 6. Calculate angle using average of sequence */ const numPoints = 16; - for (var dist = 0; dist < 4; ++dist) - { + for (var dist = 0; dist < 4; ++dist) { var waterPoints = []; - for (var i = 0; i < numPoints; ++i) - { - var angle = (i/numPoints)*2*Math.PI; - var d = halfSize*(dist+1); - var nx = x - d*Math.sin(angle); - var nz = z + d*Math.cos(angle); + for (var i = 0; i < numPoints; ++i) { + var angle = (i / numPoints) * 2 * Math.PI; + var d = halfSize * (dist + 1); + var nx = x - d * Math.sin(angle); + var nz = z + d * Math.cos(angle); if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz)) waterPoints.push(i); @@ -1052,11 +926,9 @@ function GetDockAngle(template, x, z) var length = waterPoints.length; if (!length) continue; - for (var i = 0; i < length; ++i) - { + for (var i = 0; i < length; ++i) { var count = 0; - for (let j = 0; j < length - 1; ++j) - { + for (let j = 0; j < length - 1; ++j) { if ((waterPoints[(i + j) % length] + 1) % numPoints == waterPoints[(i + j + 1) % length]) ++count; else @@ -1066,18 +938,16 @@ function GetDockAngle(template, x, z) } var start = 0; var count = 0; - for (var c in consec) - { - if (consec[c] > count) - { + for (var c in consec) { + if (consec[c] > count) { start = c; count = consec[c]; } } // If we've found a shoreline, stop searching - if (count != numPoints-1) - return -((waterPoints[start] + consec[start]/2) % numPoints) / numPoints * 2 * Math.PI; + if (count != numPoints - 1) + return -((waterPoints[start] + consec[start] / 2) % numPoints) / numPoints * 2 * Math.PI; } return undefined; } @@ -1086,8 +956,7 @@ function GetDockAngle(template, x, z) * Attempts to construct a building using the specified parameters. * Returns true on success, false on failure. */ -function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) -{ +function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) { // Message structure: // { // "type": "construct", @@ -1125,8 +994,7 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) // Tentatively create the foundation (we might find later that it's a invalid build command) var ent = Engine.AddEntity(foundationTemplate); - if (ent == INVALID_ENTITY) - { + if (ent == INVALID_ENTITY) { // Error (e.g. invalid template names) error("Error creating foundation entity for '" + cmd.template + "'"); return false; @@ -1135,8 +1003,7 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) // If it's a dock, get the right angle. var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(cmd.template); var angle = cmd.angle; - if (template.BuildRestrictions.PlacementType === "shore") - { + if (template.BuildRestrictions.PlacementType === "shore") { let angleDock = GetDockAngle(template, cmd.x, cmd.z); if (angleDock !== undefined) angle = angleDock; @@ -1148,13 +1015,11 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) cmpPosition.SetYRotation(angle); // Set the obstruction control group if needed - if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2) - { + if (cmd.obstructionControlGroup || cmd.obstructionControlGroup2) { var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); // primary control group must always be valid - if (cmd.obstructionControlGroup) - { + if (cmd.obstructionControlGroup) { if (cmd.obstructionControlGroup <= 0) warn("[TryConstructBuilding] Invalid primary obstruction control group " + cmd.obstructionControlGroup + " received; must be > 0"); @@ -1171,13 +1036,11 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) // Check whether building placement is valid var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions); - if (cmpBuildRestrictions) - { + if (cmpBuildRestrictions) { var ret = cmpBuildRestrictions.CheckPlacement(); - if (!ret.success) - { + if (!ret.success) { if (g_DebugCommands) - warn("Invalid command: build restrictions check failed with '"+ret.message+"' for player "+player+": "+uneval(cmd)); + warn("Invalid command: build restrictions check failed with '" + ret.message + "' for player " + player + ": " + uneval(cmd)); var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); ret.players = [player]; @@ -1195,10 +1058,9 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) // Check entity limits var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits); - if (cmpEntityLimits && !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) - { + if (cmpEntityLimits && !cmpEntityLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory())) { if (g_DebugCommands) - warn("Invalid command: build limits check failed for player "+player+": "+uneval(cmd)); + warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd)); // Remove the foundation because the construction was aborted cmpPosition.MoveOutOfWorld(); @@ -1207,10 +1069,9 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) } var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager); - if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template)) - { + if (cmpTechnologyManager && !cmpTechnologyManager.CanProduce(cmd.template)) { if (g_DebugCommands) - warn("Invalid command: required technology check failed for player "+player+": "+uneval(cmd)); + warn("Invalid command: required technology check failed for player " + player + ": " + uneval(cmd)); var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); cmpGuiInterface.PushNotification({ @@ -1229,10 +1090,9 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) let cmpCost = Engine.QueryInterface(ent, IID_Cost); let costs = cmpCost.GetResourceCosts(); - if (!cmpPlayer.TrySubtractResources(costs)) - { + if (!cmpPlayer.TrySubtractResources(costs)) { if (g_DebugCommands) - warn("Invalid command: building cost check failed for player "+player+": "+uneval(cmd)); + warn("Invalid command: building cost check failed for player " + player + ": " + uneval(cmd)); Engine.DestroyEntity(ent); cmpPosition.MoveOutOfWorld(); @@ -1249,11 +1109,10 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) // send Metadata info if any if (cmd.metadata) - Engine.PostMessage(ent, MT_AIMetadata, { "id": ent, "metadata" : cmd.metadata, "owner" : player } ); + Engine.PostMessage(ent, MT_AIMetadata, { "id": ent, "metadata": cmd.metadata, "owner": player }); // Tell the units to start building this new entity - if (cmd.autorepair) - { + if (cmd.autorepair) { ProcessCommand(player, { "type": "repair", "entities": entities, @@ -1268,8 +1127,7 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) return ent; } -function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) -{ +function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) { // 'cmd' message structure: // { // "type": "construct-wall", @@ -1302,14 +1160,12 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) if (cmd.pieces.length <= 0) return; - if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower) - { + if (cmd.startSnappedEntity && cmd.pieces[0].template == cmd.wallSet.templates.tower) { error("[TryConstructWall] Starting wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the starting side"); return; } - if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower) - { + if (cmd.endSnappedEntity && cmd.pieces[cmd.pieces.length - 1].template == cmd.wallSet.templates.tower) { error("[TryConstructWall] Ending wall piece cannot be a tower (" + cmd.wallSet.templates.tower + ") when snapping at the ending side"); return; } @@ -1349,11 +1205,9 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) // If we're snapping to an existing entity at the starting end, set lastTowerControlGroup to its control group ID so that // the first wall piece can be built while overlapping it. - if (cmd.startSnappedEntity) - { + if (cmd.startSnappedEntity) { var cmpSnappedStartObstruction = Engine.QueryInterface(cmd.startSnappedEntity, IID_Obstruction); - if (!cmpSnappedStartObstruction) - { + if (!cmpSnappedStartObstruction) { error("[TryConstructWall] Snapped entity on starting side does not have an obstruction component"); return; } @@ -1365,8 +1219,7 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) var i = 0; var queued = cmd.queued; var pieces = clone(cmd.pieces); - for (; i < pieces.length; ++i) - { + for (; i < pieces.length; ++i) { var piece = pieces[i]; // All wall pieces after the first must be queued. @@ -1375,12 +1228,10 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) // 'lastTowerControlGroup' must always be defined and valid here, except if we're at the first piece and we didn't do // start position snapping (implying that the first entity we build must be a tower) - if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) - { - if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity)) - { - error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")"); - break; + if (lastTowerControlGroup === null || lastTowerControlGroup == INVALID_ENTITY) { + if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity)) { + error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")"); + break; } } @@ -1402,29 +1253,25 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) // If we're building the last piece and we're attaching to a snapped entity, we need to add in the snapped entity's // control group directly at construction time (instead of setting it in the second pass) to allow it to be built // while overlapping the snapped entity. - if (i == pieces.length - 1 && cmd.endSnappedEntity) - { + if (i == pieces.length - 1 && cmd.endSnappedEntity) { var cmpEndSnappedObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); if (cmpEndSnappedObstruction) constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup(); } var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd); - if (pieceEntityId) - { + if (pieceEntityId) { // wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later piece.ent = pieceEntityId; // if we built a tower, do the control group dance (see outline above) and update lastTowerControlGroup and lastTowerIndex - if (piece.template == cmd.wallSet.templates.tower) - { + if (piece.template == cmd.wallSet.templates.tower) { var cmpTowerObstruction = Engine.QueryInterface(pieceEntityId, IID_Obstruction); var newTowerControlGroup = pieceEntityId; - if (i > 0) - { + if (i > 0) { //warn(" updating previous wall piece's secondary control group to " + newTowerControlGroup); - var cmpPreviousObstruction = Engine.QueryInterface(pieces[i-1].ent, IID_Obstruction); + var cmpPreviousObstruction = Engine.QueryInterface(pieces[i - 1].ent, IID_Obstruction); // TODO: ensure that cmpPreviousObstruction exists // TODO: ensure that the previous obstruction does not yet have a secondary control group set cmpPreviousObstruction.SetControlGroup2(newTowerControlGroup); @@ -1451,11 +1298,9 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) lastTowerControlGroup = null; // control group of the last tower we've encountered, to assign to non-tower pieces // only start off with the ending side's snapped tower's control group if we were able to build the entire wall - if (cmd.endSnappedEntity && wallComplete) - { + if (cmd.endSnappedEntity && wallComplete) { var cmpSnappedEndObstruction = Engine.QueryInterface(cmd.endSnappedEntity, IID_Obstruction); - if (!cmpSnappedEndObstruction) - { + if (!cmpSnappedEndObstruction) { error("[TryConstructWall] Snapped entity on ending side does not have an obstruction component"); return; } @@ -1463,44 +1308,36 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) lastTowerControlGroup = cmpSnappedEndObstruction.GetControlGroup(); } - for (var j = lastBuiltPieceIndex; j >= 0; --j) - { + for (var j = lastBuiltPieceIndex; j >= 0; --j) { var piece = pieces[j]; - if (!piece.ent) - { + if (!piece.ent) { error("[TryConstructWall] No entity ID set for constructed entity of template '" + piece.template + "'"); continue; } var cmpPieceObstruction = Engine.QueryInterface(piece.ent, IID_Obstruction); - if (!cmpPieceObstruction) - { + if (!cmpPieceObstruction) { error("[TryConstructWall] Wall piece of template '" + piece.template + "' has no Obstruction component"); continue; } - if (piece.template == cmd.wallSet.templates.tower) - { + if (piece.template == cmd.wallSet.templates.tower) { // encountered a tower entity, update the last tower control group lastTowerControlGroup = cmpPieceObstruction.GetControlGroup(); } - else - { + else { // Encountered a non-tower entity, update its secondary control group to 'lastTowerControlGroup'. // Note that the wall piece may already have its secondary control group set to the tower's entity ID from a control group // dance during the first pass, in which case we should validate it against 'lastTowerControlGroup'. var existingSecondaryControlGroup = cmpPieceObstruction.GetControlGroup2(); - if (existingSecondaryControlGroup == INVALID_ENTITY) - { - if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY) - { + if (existingSecondaryControlGroup == INVALID_ENTITY) { + if (lastTowerControlGroup != null && lastTowerControlGroup != INVALID_ENTITY) { cmpPieceObstruction.SetControlGroup2(lastTowerControlGroup); } } - else if (existingSecondaryControlGroup != lastTowerControlGroup) - { + else if (existingSecondaryControlGroup != lastTowerControlGroup) { error("[TryConstructWall] Existing secondary control group of non-tower entity does not match expected value (2nd pass, iteration " + j + ")"); break; } @@ -1511,11 +1348,9 @@ function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) /** * Remove the given list of entities from their current formations. */ -function RemoveFromFormation(ents) -{ +function RemoveFromFormation(ents) { let formation = ExtractFormations(ents); - for (let fid in formation.members) - { + for (let fid in formation.members) { let cmpFormation = Engine.QueryInterface(+fid, IID_Formation); if (cmpFormation) cmpFormation.RemoveMembers(formation.members[fid]); @@ -1526,61 +1361,52 @@ function RemoveFromFormation(ents) * Returns a list of UnitAI components, each belonging either to a * selected unit or to a formation entity for groups of the selected units. */ -function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate) -{ +function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate) { // If an individual was selected, remove it from any formation // and command it individually. - if (ents.length == 1) - { + if (ents.length == 1) { let cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI); if (!cmpUnitAI) return []; RemoveFromFormation(ents); - return [ cmpUnitAI ]; + return [cmpUnitAI]; } let formationUnitAIs = []; // Find what formations the selected entities are currently in, // and default to that unless the formation is forced or it's the null formation // (we want that to reset whatever formations units are in). - if (formationTemplate != NULL_FORMATION) - { + if (formationTemplate != NULL_FORMATION) { let formation = ExtractFormations(ents); let formationIds = Object.keys(formation.members); - if (formationIds.length == 1) - { + if (formationIds.length == 1) { // Selected units either belong to this formation or have no formation. let fid = formationIds[0]; let cmpFormation = Engine.QueryInterface(+fid, IID_Formation); if (cmpFormation && cmpFormation.GetMemberCount() == formation.members[fid].length && - cmpFormation.GetMemberCount() == formation.entities.length) - { + cmpFormation.GetMemberCount() == formation.entities.length) { cmpFormation.DeleteTwinFormations(); // The whole formation was selected, so reuse its controller for this command. - if (!forceTemplate || formationTemplate == formation.templates[fid]) - { + if (!forceTemplate || formationTemplate == formation.templates[fid]) { formationTemplate = formation.templates[fid]; formationUnitAIs = [Engine.QueryInterface(+fid, IID_UnitAI)]; } else if (formationTemplate && CanMoveEntsIntoFormation(formation.entities, formationTemplate)) formationUnitAIs = [cmpFormation.LoadFormation(formationTemplate)]; } - else if (cmpFormation && !forceTemplate) - { + else if (cmpFormation && !forceTemplate) { // Just reuse the template. formationTemplate = formation.templates[fid]; } } - else if (formationIds.length) - { + else if (formationIds.length) { // Check if all entities share a common formation, if so reuse this template. let template = formation.templates[formationIds[0]]; for (let i = 1; i < formationIds.length; ++i) - if (formation.templates[formationIds[i]] != template) - { + if (formation.templates[formationIds[i]] != template) { template = null; break; } @@ -1592,8 +1418,7 @@ function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate // Separate out the units that don't support the chosen formation. let formedUnits = []; let nonformedUnitAIs = []; - for (let ent of ents) - { + for (let ent of ents) { let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); let cmpPosition = Engine.QueryInterface(ent, IID_Position); if (!cmpUnitAI || !cmpPosition || !cmpPosition.IsInWorld()) @@ -1603,8 +1428,7 @@ function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate // if we move them to it. We should check if we can use formations // for the other cases. let nullFormation = (formationTemplate || cmpUnitAI.GetFormationTemplate()) == NULL_FORMATION; - if (nullFormation || !cmpUnitAI.CanUseFormation(formationTemplate || NULL_FORMATION)) - { + if (nullFormation || !cmpUnitAI.CanUseFormation(formationTemplate || NULL_FORMATION)) { if (nullFormation && cmpUnitAI.GetFormationController()) cmpUnitAI.LeaveFormation(cmd.queued || false); nonformedUnitAIs.push(cmpUnitAI); @@ -1612,26 +1436,22 @@ function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate else formedUnits.push(ent); } - if (nonformedUnitAIs.length == ents.length) - { + if (nonformedUnitAIs.length == ents.length) { // No units support the formation. return nonformedUnitAIs; } - if (!formationUnitAIs.length) - { + if (!formationUnitAIs.length) { // We need to give the selected units a new formation controller. // TODO replace the fixed 60 with something sensible, based on vision range f.e. let formationSeparation = 60; let clusters = ClusterEntities(formedUnits, formationSeparation); let formationEnts = []; - for (let cluster of clusters) - { + for (let cluster of clusters) { RemoveFromFormation(cluster); - if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate)) - { + if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate)) { for (let ent of cluster) nonformedUnitAIs.push(Engine.QueryInterface(ent, IID_UnitAI)); @@ -1660,8 +1480,7 @@ function GetFormationUnitAIs(ents, player, cmd, formationTemplate, forceTemplate /** * Group a list of entities in clusters via single-links */ -function ClusterEntities(ents, separationDistance) -{ +function ClusterEntities(ents, separationDistance) { let clusters = []; if (!ents.length) return clusters; @@ -1671,8 +1490,7 @@ function ClusterEntities(ents, separationDistance) // triangular matrix with the (squared) distances between the different clusters // the other half is not initialised let matrix = []; - for (let i = 0; i < ents.length; ++i) - { + for (let i = 0; i < ents.length; ++i) { matrix[i] = []; clusters.push([ents[i]]); let cmpPosition = Engine.QueryInterface(ents[i], IID_Position); @@ -1680,15 +1498,14 @@ function ClusterEntities(ents, separationDistance) for (let j = 0; j < i; ++j) matrix[i][j] = positions[i].distanceToSquared(positions[j]); } - while (clusters.length > 1) - { + while (clusters.length > 1) { // search two clusters that are closer than the required distance let closeClusters = undefined; for (let i = matrix.length - 1; i >= 0 && !closeClusters; --i) for (let j = i - 1; j >= 0 && !closeClusters; --j) if (matrix[i][j] < distSq) - closeClusters = [i,j]; + closeClusters = [i, j]; // if no more close clusters found, just return all found clusters so far if (!closeClusters) @@ -1700,8 +1517,7 @@ function ClusterEntities(ents, separationDistance) // calculate the minimum distance between the new cluster and all other remaining // clusters by taking the minimum of the two distances. let distances = []; - for (let i = 0; i < clusters.length; ++i) - { + for (let i = 0; i < clusters.length; ++i) { let a = closeClusters[1]; let b = closeClusters[0]; if (i == a || i == b) @@ -1712,16 +1528,15 @@ function ClusterEntities(ents, separationDistance) } // remove the rows and columns in the matrix for the merged clusters, // and the clusters themselves from the cluster list - clusters.splice(closeClusters[0],1); - clusters.splice(closeClusters[1],1); - matrix.splice(closeClusters[0],1); - matrix.splice(closeClusters[1],1); - for (let i = 0; i < matrix.length; ++i) - { + clusters.splice(closeClusters[0], 1); + clusters.splice(closeClusters[1], 1); + matrix.splice(closeClusters[0], 1); + matrix.splice(closeClusters[1], 1); + for (let i = 0; i < matrix.length; ++i) { if (matrix[i].length > closeClusters[0]) - matrix[i].splice(closeClusters[0],1); + matrix[i].splice(closeClusters[0], 1); if (matrix[i].length > closeClusters[1]) - matrix[i].splice(closeClusters[1],1); + matrix[i].splice(closeClusters[1], 1); } // add a new row of distances to the matrix and the new cluster clusters.push(newCluster); @@ -1730,8 +1545,7 @@ function ClusterEntities(ents, separationDistance) return clusters; } -function GetFormationRequirements(formationTemplate) -{ +function GetFormationRequirements(formationTemplate) { var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(formationTemplate); if (!template.Formation) return false; @@ -1740,8 +1554,7 @@ function GetFormationRequirements(formationTemplate) } -function CanMoveEntsIntoFormation(ents, formationTemplate) -{ +function CanMoveEntsIntoFormation(ents, formationTemplate) { // TODO: should check the player's civ is allowed to use this formation // See simulation/components/Player.js GetFormations() for a list of all allowed formations @@ -1762,8 +1575,7 @@ function CanMoveEntsIntoFormation(ents, formationTemplate) * returns: true if the entity is owned by the player and controllable * or control all units is activated, else false */ -function CanControlUnit(entity, player, controlAll) -{ +function CanControlUnit(entity, player, controlAll) { let cmpIdentity = Engine.QueryInterface(entity, IID_Identity); let canBeControlled = IsOwnedByPlayer(player, entity) && (!cmpIdentity || cmpIdentity.IsControllable()) || @@ -1780,8 +1592,7 @@ function CanControlUnit(entity, player, controlAll) * @param {number} player - The playerID to check against. * @return {boolean}. */ -function IsOwnedByPlayerOrMutualAlly(entity, player) -{ +function IsOwnedByPlayerOrMutualAlly(entity, player) { return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity); } @@ -1791,8 +1602,7 @@ function IsOwnedByPlayerOrMutualAlly(entity, player) * or the entity is owned by an mutualAlly and can be controlled * or control all units is activated, else false. */ -function CanPlayerOrAllyControlUnit(entity, player, controlAll) -{ +function CanPlayerOrAllyControlUnit(entity, player, controlAll) { return CanControlUnit(player, entity, controlAll) || IsOwnedByMutualAllyOfPlayer(player, entity) && CanOwnerControlEntity(entity); } @@ -1800,8 +1610,7 @@ function CanPlayerOrAllyControlUnit(entity, player, controlAll) /** * @return {boolean} - Whether the owner of this entity can control the entity. */ -function CanOwnerControlEntity(entity) -{ +function CanOwnerControlEntity(entity) { let cmpOwner = QueryOwnerInterface(entity); return cmpOwner && CanControlUnit(entity, cmpOwner.GetPlayerID()); } @@ -1809,16 +1618,14 @@ function CanOwnerControlEntity(entity) /** * Filter entities which the player can control. */ -function FilterEntityList(entities, player, controlAll) -{ +function FilterEntityList(entities, player, controlAll) { return entities.filter(ent => CanControlUnit(ent, player, controlAll)); } /** * Filter entities which the player can control or are mutualAlly */ -function FilterEntityListWithAllies(entities, player, controlAll) -{ +function FilterEntityListWithAllies(entities, player, controlAll) { return entities.filter(ent => CanPlayerOrAllyControlUnit(ent, player, controlAll)); } @@ -1826,8 +1633,7 @@ function FilterEntityListWithAllies(entities, player, controlAll) * Incur the player with the cost of a bribe, optionally multiply the cost with * the additionalMultiplier */ -function IncurBribeCost(template, player, playerBribed, failedBribe) -{ +function IncurBribeCost(template, player, playerBribed, failedBribe) { let cmpPlayerBribed = QueryPlayerIDInterface(playerBribed); if (!cmpPlayerBribed) return false; diff --git a/community-mod/simulation/templates/structures/han/civil_centre_court.xml b/community-mod/simulation/templates/structures/han/civil_centre_court.xml index 4eb7d61..be5b8e4 100644 --- a/community-mod/simulation/templates/structures/han/civil_centre_court.xml +++ b/community-mod/simulation/templates/structures/han/civil_centre_court.xml @@ -1,48 +1,44 @@ - - ImperialCourt - - - 1.5 - - - 1.5 - - - 10 - 24 - - - 1.5 - - - han - Imperial Court - Cháotíng - Defensive ImperialCourt City - CivCentre CivSpecific - phase_city - structures/military_settlement.png - - - 30 - - - - -phase_town_{civ} - - - - 0.75 - - units/{civ}/hero_han_xin_horse - units/{civ}/hero_liu_bang_horse - units/{civ}/hero_wei_qing_chariot - - - - - structures/han/imperial_court.xml - + + ImperialCourt + + + 1.5 + + + 1.5 + + + 1.5 + + + han + Imperial Court + Cháotíng + Defensive ImperialCourt City + CivCentre CivSpecific + phase_city + structures/military_settlement.png + + + 30 + + + + -phase_town_{civ} + + + + 0.5 + + units/{civ}/hero_han_xin_horse + units/{civ}/hero_liu_bang_horse + units/{civ}/hero_wei_qing_chariot + + + + + structures/han/imperial_court.xml + diff --git a/community-mod/simulation/templates/structures/iber/defense_tower.xml b/community-mod/simulation/templates/structures/iber/defense_tower.xml index 2309248..f3705c4 100644 --- a/community-mod/simulation/templates/structures/iber/defense_tower.xml +++ b/community-mod/simulation/templates/structures/iber/defense_tower.xml @@ -1,50 +1,50 @@ - - - 12 - - - - 4 - - - 200 - - 50 - 250 - - - - - 12.0 - - - 8 - - - 2400 - decay|rubble/rubble_stone_3x3 - - - iber - Dorre - - - 10 - 50 - - - - - - 17.0 - - - 38 - - - structures/iberians/scout_tower.xml - structures/fndn_4x4.xml - + + + 12 + + + + 2 + + + 200 + + 50 + 250 + + + + + 12.0 + + + 8 + + + 2400 + decay|rubble/rubble_stone_3x3 + + + iber + Dorre + + + 10 + 50 + + + + + + 17.0 + + + 38 + + + structures/iberians/scout_tower.xml + structures/fndn_4x4.xml + diff --git a/community-mod/simulation/templates/template_structure_civic_civil_centre.xml b/community-mod/simulation/templates/template_structure_civic_civil_centre.xml index b16e377..133fce7 100644 --- a/community-mod/simulation/templates/template_structure_civic_civil_centre.xml +++ b/community-mod/simulation/templates/template_structure_civic_civil_centre.xml @@ -1,152 +1,151 @@ - - FemaleCitizen - 120 - 180 - 100 - - - - Bow - - 10 - - 60 - 1200 - 4000 - - 100 - 2 - 50 - false - - - Human - - outline_border.png - outline_border_mask.png - 0.175 - - - - - own neutral - CivilCentre - - CivilCentre - 200 - - - - 8 - 1 - Soldier - 18 - - - 2500 - 5.0 - - - 500 - - 300 - 300 - 250 - - - - - 8.0 - - - 20 - 0.1 - Unit - Support Infantry Cavalry - 1 - 1 - - - 3000 - decay|rubble/rubble_stone_6x6 - - - Civic Center - template_structure_civic_civil_centre - Build in own or neutral territory. Acquire large tracts of territory. Territory root. Train Citizens and research technologies. Garrison Soldiers for additional arrows. - CivCentre - Defensive CivilCentre - structures/civic_centre.png - - - 60 - 60 - 50 - - - structure - civil_centre.png - - - - - - 20 - - - - - phase_town_{civ} - phase_city_{civ} - unlock_shared_los - unlock_shared_dropsites - unlock_spies - spy_counter - archery_tradition - hoplite_tradition - hellenistic_metropolis - - - - - - 5 - 5 - - - - - food wood stone metal - true - - - - - interface/complete/building/complete_civ_center.xml - interface/complete/building/complete_civ_center.xml - interface/alarm/alarm_alert_0.xml - interface/alarm/alarm_alert_1.xml - attack/weapon/bow_attack.xml - attack/impact/arrow_impact.xml - - - - true - 140 - 10000 - - - 0.8 - - units/{civ}/support_female_citizen - - - - 90 - - - structures/fndn_8x8.xml - + + FemaleCitizen + 120 + 180 + 100 + + + + Bow + + 10 + + 60 + 1200 + 2000 + + 100 + 1.5 + 50 + false + + + Human + + outline_border.png + outline_border_mask.png + 0.175 + + + + + own neutral + CivilCentre + + CivilCentre + 200 + + + + 3 + 1 + Soldier + + + 2500 + 5.0 + + + 500 + + 300 + 300 + 250 + + + + + 8.0 + + + 20 + 0.1 + Unit + Support Infantry Cavalry + 1 + 1 + + + 3000 + decay|rubble/rubble_stone_6x6 + + + Civic Center + template_structure_civic_civil_centre + Build in own or neutral territory. Acquire large tracts of territory. Territory root. Train Citizens and research technologies. Garrison Soldiers for additional arrows. + CivCentre + Defensive CivilCentre + structures/civic_centre.png + + + 60 + 60 + 50 + + + structure + civil_centre.png + + + + + + 20 + + + + + phase_town_{civ} + phase_city_{civ} + unlock_shared_los + unlock_shared_dropsites + unlock_spies + spy_counter + archery_tradition + hoplite_tradition + hellenistic_metropolis + + + + + + 5 + 5 + + + + + food wood stone metal + true + + + + + interface/complete/building/complete_civ_center.xml + interface/complete/building/complete_civ_center.xml + interface/alarm/alarm_alert_0.xml + interface/alarm/alarm_alert_1.xml + attack/weapon/bow_attack.xml + attack/impact/arrow_impact.xml + + + + true + 140 + 10000 + + + 0.8 + + units/{civ}/support_female_citizen + + + + 90 + + + structures/fndn_8x8.xml + diff --git a/community-mod/simulation/templates/template_structure_civic_civil_centre_military_colony.xml b/community-mod/simulation/templates/template_structure_civic_civil_centre_military_colony.xml index dc2e674..2bcdfd8 100644 --- a/community-mod/simulation/templates/template_structure_civic_civil_centre_military_colony.xml +++ b/community-mod/simulation/templates/template_structure_civic_civil_centre_military_colony.xml @@ -1,55 +1,54 @@ - - own neutral - Colony - - CivilCentre - 120 - - - - 6 - 16 - - - 300 - - 200 - 200 - 150 - - - - 2000 - decay|rubble/rubble_stone_5x5 - - - Military Colony - template_structure_civic_civil_centre_military_colony - Colony - structures/military_settlement.png - phase_town - - - 40 - 40 - 30 - - - - -phase_town_{civ} - -phase_city_{civ} - -hellenistic_metropolis - - - - - - interface/complete/building/complete_gymnasium.xml - - - - 75 - + + own neutral + Colony + + CivilCentre + 120 + + + + 1 + + + 300 + + 200 + 200 + 150 + + + + 2000 + decay|rubble/rubble_stone_5x5 + + + Military Colony + template_structure_civic_civil_centre_military_colony + Colony + structures/military_settlement.png + phase_town + + + 40 + 40 + 30 + + + + -phase_town_{civ} + -phase_city_{civ} + -hellenistic_metropolis + + + + + + interface/complete/building/complete_gymnasium.xml + + + + 75 + diff --git a/community-mod/simulation/templates/template_structure_defensive_tower.xml b/community-mod/simulation/templates/template_structure_defensive_tower.xml index 4a062f1..3d45b3d 100644 --- a/community-mod/simulation/templates/template_structure_defensive_tower.xml +++ b/community-mod/simulation/templates/template_structure_defensive_tower.xml @@ -1,72 +1,72 @@ - - - Bow - - 11 - - 60 - 10 - 1200 - 4000 - - 100 - 1.5 - 50 - false - - - Human - - outline_border.png - outline_border_mask.png - 0.175 - - - - - 2 - 1 - Infantry - - - Tower - - Tower - 60 - - - - 0.1 - Unit - Support Infantry - 0 - 2 - - - 1000 - decay|rubble/rubble_stone_2x2 - - - Tower - Tower - - - - interface/complete/building/complete_tower.xml - - attack/weapon/bow_attack.xml - attack/impact/arrow_impact.xml - - - - 18.0 - - - 80 - - - structures/fndn_3x3.xml - + + + Bow + + 11 + + 60 + 10 + 1200 + 2000 + + 100 + 1.5 + 50 + false + + + Human + + outline_border.png + outline_border_mask.png + 0.175 + + + + + 1 + 1 + Infantry + + + Tower + + Tower + 60 + + + + 0.1 + Unit + Support Infantry + 0 + 2 + + + 1000 + decay|rubble/rubble_stone_2x2 + + + Tower + Tower + + + + interface/complete/building/complete_tower.xml + + attack/weapon/bow_attack.xml + attack/impact/arrow_impact.xml + + + + 18.0 + + + 80 + + + structures/fndn_3x3.xml + diff --git a/community-mod/simulation/templates/template_structure_defensive_tower_sentry.xml b/community-mod/simulation/templates/template_structure_defensive_tower_sentry.xml index 4874b12..3bbcd2c 100644 --- a/community-mod/simulation/templates/template_structure_defensive_tower_sentry.xml +++ b/community-mod/simulation/templates/template_structure_defensive_tower_sentry.xml @@ -1,69 +1,69 @@ - - - - 0 - 9 - 0 - - - - - 5 - - - 40 - - 125 - - - - - 9.0 - - - 3 - - - 400 - - - Sentry Tower - template_structure_defensive_tower_sentry - Garrison Infantry for additional arrows. Needs the “Murder Holes” technology to protect its foot. - SentryTower - structures/sentry_tower.png - phase_village - - - 25 - - - - - - - - tower_watch - - - - false - 16 - 30000 - - - - structures/{civ}/defense_tower - Reinforce with stone and upgrade to a defense tower. - phase_town - - 25 - 100 - - upgrading - - - + + + + 0 + 9 + 0 + + + + + 4 + + + 40 + + 100 + + + + + 9.0 + + + 3 + + + 400 + + + Sentry Tower + template_structure_defensive_tower_sentry + Garrison Infantry for additional arrows. Needs the “Murder Holes” technology to protect its foot. + SentryTower + structures/sentry_tower.png + phase_village + + + 20 + + + + + + + + tower_watch + + + + false + 16 + 30000 + + + + structures/{civ}/defense_tower + Reinforce with stone and upgrade to a defense tower. + phase_town + + 50 + 100 + + upgrading + + + diff --git a/community-mod/simulation/templates/template_structure_defensive_tower_stone.xml b/community-mod/simulation/templates/template_structure_defensive_tower_stone.xml index e29413d..2795e74 100644 --- a/community-mod/simulation/templates/template_structure_defensive_tower_stone.xml +++ b/community-mod/simulation/templates/template_structure_defensive_tower_stone.xml @@ -1,62 +1,59 @@ - - - - 0 - 15 - 0 - - - - - 150 - - 100 - 100 - - - - - 15.0 - - - 3 - - - 5 - - - 1000 - - - Stone Tower - template_structure_defensive_tower_stone - Garrison Infantry for additional arrows. Needs the “Murder Holes” technology to protect its foot. - StoneTower - structures/defense_tower.png - phase_town - - - 20 - 20 - - - - - - - - tower_watch - tower_crenellations - tower_range - tower_murderholes - tower_health - - - - false - 32 - 30000 - + + + + 0 + 15 + 0 + + + + + 150 + + 100 + 100 + + + + + 15.0 + + + 5 + + + 1000 + + + Stone Tower + template_structure_defensive_tower_stone + Garrison Infantry for additional arrows. Needs the “Murder Holes” technology to protect its foot. + StoneTower + structures/defense_tower.png + phase_town + + + 20 + 20 + + + + + + + + tower_watch + tower_crenellations + tower_range + tower_murderholes + tower_health + + + + false + 32 + 30000 + diff --git a/community-mod/simulation/templates/template_structure_military_fortress.xml b/community-mod/simulation/templates/template_structure_military_fortress.xml index ba4c1e2..53ace10 100644 --- a/community-mod/simulation/templates/template_structure_military_fortress.xml +++ b/community-mod/simulation/templates/template_structure_military_fortress.xml @@ -1,112 +1,112 @@ - - - Bow - - 10 - - 60 - 1200 - 4000 - - 100 - 2.5 - 50 - false - - - Human - - outline_border.png - outline_border_mask.png - 0.175 - - - - - Fortress - - Fortress - 80 - - - - 12 - 1 - Soldier - - - 8 - 10.0 - - - 450 - - 300 - 600 - - - - - 8.0 - - - 20 - 0.075 - Support Infantry Cavalry Siege - 6 - - - 5200 - decay|rubble/rubble_stone_6x6 - - - Fortress - template_structure_military_fortress - Garrison Soldiers for additional arrows. - GarrisonFortress - Defensive Fortress - structures/fortress.png - phase_city - - - 60 - 120 - - - - - - - - attack_soldiers_will - art_of_war - poison_arrows - poison_blades - - - - - - interface/complete/building/complete_fortress.xml - attack/weapon/bow_attack.xml - attack/impact/arrow_impact.xml - - - - 2 - - - 80 - - - 0.8 - - - 90 - - - structures/fndn_8x8.xml - + + + Bow + + 10 + + 60 + 1200 + 2000 + + 100 + 1.5 + 50 + false + + + Human + + outline_border.png + outline_border_mask.png + 0.175 + + + + + Fortress + + Fortress + 80 + + + + 4 + 1 + Soldier + + + 8 + 10.0 + + + 450 + + 300 + 600 + + + + + 8.0 + + + 20 + 0.075 + Support Infantry Cavalry Siege + 6 + + + 5200 + decay|rubble/rubble_stone_6x6 + + + Fortress + template_structure_military_fortress + Garrison Soldiers for additional arrows. + GarrisonFortress + Defensive Fortress + structures/fortress.png + phase_city + + + 60 + 120 + + + + + + + + attack_soldiers_will + art_of_war + poison_arrows + poison_blades + + + + + + interface/complete/building/complete_fortress.xml + attack/weapon/bow_attack.xml + attack/impact/arrow_impact.xml + + + + 2 + + + 80 + + + 0.8 + + + 90 + + + structures/fndn_8x8.xml +