2 * Specifies which template should indicate the target location of a player command,
3 * given a command type.
6 "move": "special/target_marker"
10 * Which enemy entity types will be attacked on sight when patroling.
12 var g_PatrolTargets = ["Unit"];
15 * List of different actions units can execute,
16 * this is mostly used to determine which actions can be executed
18 * "execute" is meant to send the command to the engine
20 * The next functions will always return false
21 * in case you have to continue to seek
22 * (i.e. look at the next entity for getActionInfo, the next
23 * possible action for the actionCheck ...)
24 * They will return an object when the searching is finished
26 * "getActionInfo" is used to determine if the action is possible,
27 * and also give visual feedback to the user (tooltips, cursors, ...)
29 * "preSelectedActionCheck" is used to select actions when the gui buttons
30 * were used to set them, but still require a target (like the guard button)
32 * "hotkeyActionCheck" is used to check the possibility of actions when
35 * "actionCheck" is used to check the possibilty of actions without specific
36 * command. For that, the specificness variable is used
38 * "specificness" is used to determine how specific an action is,
39 * The lower the number, the more specific an action is, and the bigger
40 * the chance of selecting that action when multiple actions are possible
46 "execute": function(target, action, selection, queued)
48 Engine.PostNetworkCommand({
50 "entities": selection,
56 DrawTargetMarker(target);
58 Engine.GuiInterfaceCall("PlaySound", {
60 "entity": selection[0]
65 "getActionInfo": function(entState, targetState)
67 return { "possible": true };
69 "hotkeyActionCheck": function(target, selection)
71 if (!someUnitAI(selection) ||
72 !Engine.HotkeyIsPressed("session.move") ||
73 !getActionInfo("move", target, selection).possible)
76 return { "type": "move" };
78 "actionCheck": function(target, selection)
80 if (!someUnitAI(selection) || !getActionInfo("move", target, selection).possible)
83 return { "type": "move" };
90 "execute": function(target, action, selection, queued)
93 if (Engine.HotkeyIsPressed("session.attackmoveUnit"))
94 targetClasses = { "attack": ["Unit"] };
96 targetClasses = { "attack": ["Unit", "Structure"] };
98 Engine.PostNetworkCommand({
99 "type": "attack-walk",
100 "entities": selection,
103 "targetClasses": targetClasses,
107 DrawTargetMarker(target);
109 Engine.GuiInterfaceCall("PlaySound", {
110 "name": "order_walk",
111 "entity": selection[0]
116 "getActionInfo": function(entState, targetState)
118 return { "possible": true };
120 "hotkeyActionCheck": function(target, selection)
122 if (!someUnitAI(selection) ||
123 !Engine.HotkeyIsPressed("session.attackmove") ||
124 !getActionInfo("attack-move", target, selection).possible)
128 "type": "attack-move",
129 "cursor": "action-attack-move"
137 "execute": function(target, action, selection, queued)
139 Engine.PostNetworkCommand({
141 "entities": selection,
142 "target": action.target,
143 "allowCapture": true,
147 Engine.GuiInterfaceCall("PlaySound", {
148 "name": "order_attack",
149 "entity": selection[0]
154 "getActionInfo": function(entState, targetState)
156 if (!entState.attack || !targetState.hitpoints)
160 "possible": Engine.GuiInterfaceCall("CanAttack", {
161 "entity": entState.id,
162 "target": targetState.id,
167 "actionCheck": function(target, selection)
169 if (!getActionInfo("capture", target, selection).possible)
174 "cursor": "action-capture",
183 "execute": function(target, action, selection, queued)
185 Engine.PostNetworkCommand({
187 "entities": selection,
188 "target": action.target,
190 "allowCapture": false
193 Engine.GuiInterfaceCall("PlaySound", {
194 "name": "order_attack",
195 "entity": selection[0]
200 "getActionInfo": function(entState, targetState)
202 if (!entState.attack || !targetState.hitpoints)
206 "possible": Engine.GuiInterfaceCall("CanAttack", {
207 "entity": entState.id,
208 "target": targetState.id,
209 "types": ["!Capture"]
213 "hotkeyActionCheck": function(target, selection)
215 if (!Engine.HotkeyIsPressed("session.attack") ||
216 !getActionInfo("attack", target, selection).possible)
221 "cursor": "action-attack",
225 "actionCheck": function(target, selection)
227 if (!getActionInfo("attack", target, selection).possible)
232 "cursor": "action-attack",
241 "execute": function(target, action, selection, queued)
243 Engine.PostNetworkCommand({
245 "entities": selection,
248 "target": action.target,
249 "targetClasses": { "attack": g_PatrolTargets },
251 "allowCapture": false
254 DrawTargetMarker(target);
256 Engine.GuiInterfaceCall("PlaySound", { "name": "order_patrol", "entity": selection[0] });
259 "getActionInfo": function(entState, targetState)
261 if (!entState.unitAI || !entState.unitAI.canPatrol)
264 return { "possible": true };
266 "hotkeyActionCheck": function(target, selection)
268 if (!someCanPatrol(selection) ||
269 !Engine.HotkeyIsPressed("session.patrol") ||
270 !getActionInfo("patrol", target, selection).possible)
274 "cursor": "action-patrol",
278 "preSelectedActionCheck": function(target, selection)
280 if (preSelectedAction != ACTION_PATROL || !getActionInfo("patrol", target, selection).possible)
284 "cursor": "action-patrol",
293 "execute": function(target, action, selection, queued)
295 Engine.PostNetworkCommand({
297 "entities": selection,
298 "target": action.target,
302 Engine.GuiInterfaceCall("PlaySound", {
303 "name": "order_heal",
304 "entity": selection[0]
309 "getActionInfo": function(entState, targetState)
311 if (!entState.heal ||
312 !hasClass(targetState, "Unit") || !targetState.needsHeal ||
313 !playerCheck(entState, targetState, ["Player", "Ally"]) ||
314 entState.id == targetState.id) // Healers can't heal themselves.
317 let unhealableClasses = entState.heal.unhealableClasses;
318 if (MatchesClassList(targetState.identity.classes, unhealableClasses))
321 let healableClasses = entState.heal.healableClasses;
322 if (!MatchesClassList(targetState.identity.classes, healableClasses))
325 return { "possible": true };
327 "actionCheck": function(target, selection)
329 if (!getActionInfo("heal", target, selection).possible)
334 "cursor": "action-heal",
343 "execute": function(target, action, selection, queued)
345 Engine.PostNetworkCommand({
347 "entities": selection,
348 "target": action.target,
349 "autocontinue": true,
353 Engine.GuiInterfaceCall("PlaySound", {
354 "name": "order_repair",
355 "entity": selection[0]
360 "getActionInfo": function(entState, targetState)
362 if (!entState.builder ||
363 !targetState.needsRepair && !targetState.foundation ||
364 !playerCheck(entState, targetState, ["Player", "Ally"]))
367 return { "possible": true };
369 "preSelectedActionCheck": function(target, selection)
371 if (preSelectedAction != ACTION_REPAIR)
374 if (getActionInfo("repair", target, selection).possible)
377 "cursor": "action-repair",
383 "cursor": "action-repair-disabled",
387 "hotkeyActionCheck": function(target, selection)
389 if (!Engine.HotkeyIsPressed("session.repair") ||
390 !getActionInfo("repair", target, selection).possible)
395 "cursor": "action-repair",
399 "actionCheck": function(target, selection)
401 if (!getActionInfo("repair", target, selection).possible)
406 "cursor": "action-repair",
415 "execute": function(target, action, selection, queued)
417 Engine.PostNetworkCommand({
419 "entities": selection,
420 "target": action.target,
424 Engine.GuiInterfaceCall("PlaySound", {
425 "name": "order_gather",
426 "entity": selection[0]
431 "getActionInfo": function(entState, targetState)
433 if (!targetState.resourceSupply)
436 let resource = findGatherType(entState, targetState.resourceSupply);
442 "cursor": "action-gather-" + resource
445 "actionCheck": function(target, selection)
447 let actionInfo = getActionInfo("gather", target, selection);
449 if (!actionInfo.possible)
454 "cursor": actionInfo.cursor,
463 "execute": function(target, action, selection, queued)
465 Engine.PostNetworkCommand({
466 "type": "returnresource",
467 "entities": selection,
468 "target": action.target,
472 Engine.GuiInterfaceCall("PlaySound", {
473 "name": "order_gather",
474 "entity": selection[0]
479 "getActionInfo": function(entState, targetState)
481 if (!targetState.resourceDropsite)
484 let playerState = GetSimState().players[entState.player];
485 if (playerState.hasSharedDropsites && targetState.resourceDropsite.shared)
487 if (!playerCheck(entState, targetState, ["Player", "MutualAlly"]))
490 else if (!playerCheck(entState, targetState, ["Player"]))
493 if (!entState.resourceCarrying || !entState.resourceCarrying.length)
496 let carriedType = entState.resourceCarrying[0].type;
497 if (targetState.resourceDropsite.types.indexOf(carriedType) == -1)
502 "cursor": "action-return-" + carriedType
505 "actionCheck": function(target, selection)
507 let actionInfo = getActionInfo("returnresource", target, selection);
508 if (!actionInfo.possible)
512 "type": "returnresource",
513 "cursor": actionInfo.cursor,
522 "execute": function(target, action, selection, queued)
524 Engine.PostNetworkCommand({
525 "type": "setup-trade-route",
526 "entities": selection,
527 "target": action.target,
533 Engine.GuiInterfaceCall("PlaySound", {
534 "name": "order_trade",
535 "entity": selection[0]
540 "getActionInfo": function(entState, targetState)
542 if (targetState.foundation || !entState.trader || !targetState.market ||
543 playerCheck(entState, targetState, ["Enemy"]) ||
544 !(targetState.market.land && hasClass(entState, "Organic") ||
545 targetState.market.naval && hasClass(entState, "Ship")))
548 let tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", {
549 "trader": entState.id,
550 "target": targetState.id
557 switch (tradingDetails.type)
560 tooltip = translate("Origin trade market.") + "\n";
561 if (tradingDetails.hasBothMarkets)
562 tooltip += sprintf(translate("Gain: %(gain)s"), {
563 "gain": getTradingTooltip(tradingDetails.gain)
566 tooltip += translate("Right-click on another market to set it as a destination trade market.");
570 tooltip = translate("Destination trade market.") + "\n" +
571 sprintf(translate("Gain: %(gain)s"), {
572 "gain": getTradingTooltip(tradingDetails.gain)
577 tooltip = translate("Right-click to set as origin trade market");
581 if (tradingDetails.gain.traderGain == 0) // markets too close
584 tooltip = translate("Right-click to set as destination trade market.") + "\n" +
585 sprintf(translate("Gain: %(gain)s"), {
586 "gain": getTradingTooltip(tradingDetails.gain)
597 "actionCheck": function(target, selection)
599 let actionInfo = getActionInfo("setup-trade-route", target, selection);
600 if (!actionInfo.possible)
604 "type": "setup-trade-route",
605 "cursor": "action-setup-trade-route",
606 "tooltip": actionInfo.tooltip,
615 "execute": function(target, action, selection, queued)
617 Engine.PostNetworkCommand({
619 "entities": selection,
620 "target": action.target,
624 Engine.GuiInterfaceCall("PlaySound", {
625 "name": "order_garrison",
626 "entity": selection[0]
631 "getActionInfo": function(entState, targetState)
633 if (!entState.canGarrison || !targetState.garrisonHolder ||
634 !playerCheck(entState, targetState, ["Player", "MutualAlly"]))
637 let tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
638 "garrisoned": targetState.garrisonHolder.garrisonedEntitiesCount,
639 "capacity": targetState.garrisonHolder.capacity
643 if (entState.garrisonHolder)
644 extraCount += entState.garrisonHolder.garrisonedEntitiesCount;
646 if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
647 tooltip = coloredText(tooltip, "orange");
649 if (!MatchesClassList(entState.identity.classes, targetState.garrisonHolder.allowedClasses))
657 "preSelectedActionCheck": function(target, selection)
659 if (preSelectedAction != ACTION_GARRISON)
662 let actionInfo = getActionInfo("garrison", target, selection);
663 if (!actionInfo.possible)
666 "cursor": "action-garrison-disabled",
672 "cursor": "action-garrison",
673 "tooltip": actionInfo.tooltip,
677 "hotkeyActionCheck": function(target, selection)
679 let actionInfo = getActionInfo("garrison", target, selection);
681 if (!Engine.HotkeyIsPressed("session.garrison") || !actionInfo.possible)
686 "cursor": "action-garrison",
687 "tooltip": actionInfo.tooltip,
696 "execute": function(target, action, selection, queued)
698 Engine.PostNetworkCommand({
700 "entities": selection,
701 "target": action.target,
705 Engine.GuiInterfaceCall("PlaySound", {
706 "name": "order_guard",
707 "entity": selection[0]
712 "getActionInfo": function(entState, targetState)
714 if (!targetState.guard ||
715 !playerCheck(entState, targetState, ["Player", "Ally"]) ||
716 !entState.unitAI || !entState.unitAI.canGuard ||
717 targetState.unitAI && targetState.unitAI.isGuarding)
720 return { "possible": true };
722 "preSelectedActionCheck": function(target, selection)
724 if (preSelectedAction != ACTION_GUARD)
727 if (getActionInfo("guard", target, selection).possible)
730 "cursor": "action-guard",
736 "cursor": "action-guard-disabled",
740 "hotkeyActionCheck": function(target, selection)
742 if (!Engine.HotkeyIsPressed("session.guard") ||
743 !getActionInfo("guard", target, selection).possible)
748 "cursor": "action-guard",
757 "execute": function(target, action, selection, queued)
759 Engine.PostNetworkCommand({
760 "type": "remove-guard",
761 "entities": selection,
762 "target": action.target,
766 Engine.GuiInterfaceCall("PlaySound", {
767 "name": "order_guard",
768 "entity": selection[0]
773 "hotkeyActionCheck": function(target, selection)
775 if (!Engine.HotkeyIsPressed("session.guard") ||
776 !getActionInfo("remove-guard", target, selection).possible ||
777 !someGuarding(selection))
781 "type": "remove-guard",
782 "cursor": "action-remove-guard"
791 "execute": function(target, action, selection, queued)
793 // if there is a position set in the action then use this so that when setting a
794 // rally point on an entity it is centered on that entity
796 target = action.position;
798 Engine.PostNetworkCommand({
799 "type": "set-rallypoint",
800 "entities": selection,
807 // Display rally point at the new coordinates, to avoid display lag
808 Engine.GuiInterfaceCall("DisplayRallyPoint", {
809 "entities": selection,
817 "getActionInfo": function(entState, targetState)
820 // default to walking there (or attack-walking if hotkey pressed)
821 let data = { "command": "walk" };
824 if (Engine.HotkeyIsPressed("session.attackmove"))
827 if (Engine.HotkeyIsPressed("session.attackmoveUnit"))
828 targetClasses = { "attack": ["Unit"] };
830 targetClasses = { "attack": ["Unit", "Structure"] };
832 data.command = "attack-walk";
833 data.targetClasses = targetClasses;
834 cursor = "action-attack-move";
837 if (Engine.HotkeyIsPressed("session.repair") &&
838 (targetState.needsRepair || targetState.foundation) &&
839 playerCheck(entState, targetState, ["Player", "Ally"]))
841 data.command = "repair";
842 data.target = targetState.id;
843 cursor = "action-repair";
845 else if (targetState.garrisonHolder &&
846 playerCheck(entState, targetState, ["Player", "MutualAlly"]))
848 data.command = "garrison";
849 data.target = targetState.id;
850 cursor = "action-garrison";
852 tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
853 "garrisoned": targetState.garrisonHolder.garrisonedEntitiesCount,
854 "capacity": targetState.garrisonHolder.capacity
857 if (targetState.garrisonHolder.garrisonedEntitiesCount >=
858 targetState.garrisonHolder.capacity)
859 tooltip = coloredText(tooltip, "orange");
861 else if (targetState.resourceSupply)
863 let resourceType = targetState.resourceSupply.type;
864 if (resourceType.generic == "treasure")
865 cursor = "action-gather-" + resourceType.generic;
867 cursor = "action-gather-" + resourceType.specific;
869 data.command = "gather-near-position";
870 data.resourceType = resourceType;
871 data.resourceTemplate = targetState.template;
872 if (!targetState.speed)
874 data.command = "gather";
875 data.target = targetState.id;
878 else if (entState.market && targetState.market &&
879 entState.id != targetState.id &&
880 (!entState.market.naval || targetState.market.naval) &&
881 !playerCheck(entState, targetState, ["Enemy"]))
883 // Find a trader (if any) that this building can produce.
885 if (entState.production && entState.production.entities.length)
886 for (let i = 0; i < entState.production.entities.length; ++i)
887 if ((trader = GetTemplateData(entState.production.entities[i]).trader))
891 "firstMarket": entState.id,
892 "secondMarket": targetState.id,
896 let gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
897 if (gain && gain.traderGain)
899 data.command = "trade";
900 data.target = traderData.secondMarket;
901 data.source = traderData.firstMarket;
902 cursor = "action-setup-trade-route";
904 tooltip = translate("Right-click to establish a default route for new traders.") + "\n" +
907 translate("Gain: %(gain)s") :
908 translate("Expected gain: %(gain)s"),
909 { "gain": getTradingTooltip(gain) });
912 else if ((targetState.needsRepair || targetState.foundation) && playerCheck(entState, targetState, ["Ally"]))
914 data.command = "repair";
915 data.target = targetState.id;
916 cursor = "action-repair";
918 else if (playerCheck(entState, targetState, ["Enemy"]))
920 data.target = targetState.id;
921 data.command = "attack";
922 cursor = "action-attack";
925 // Don't allow the rally point to be set on any of the currently selected entities (used for unset)
926 // except if the autorallypoint hotkey is pressed and the target can produce entities
927 if (!Engine.HotkeyIsPressed("session.autorallypoint") ||
928 !targetState.production ||
929 !targetState.production.entities.length)
930 for (let ent in g_Selection.selected)
931 if (targetState.id == +ent)
937 "position": targetState.position,
943 "actionCheck": function(target, selection)
945 if (someUnitAI(selection) || !someRallyPoints(selection))
948 let actionInfo = getActionInfo("set-rallypoint", target, selection);
949 if (!actionInfo.possible)
953 "type": "set-rallypoint",
954 "cursor": actionInfo.cursor,
955 "data": actionInfo.data,
956 "tooltip": actionInfo.tooltip,
957 "position": actionInfo.position
965 "execute": function(target, action, selection, queued)
967 Engine.PostNetworkCommand({
968 "type": "unset-rallypoint",
969 "entities": selection
972 // Remove displayed rally point
973 Engine.GuiInterfaceCall("DisplayRallyPoint", {
979 "getActionInfo": function(entState, targetState)
981 if (entState.id != targetState.id ||
982 !entState.rallyPoint || !entState.rallyPoint.position)
985 return { "possible": true };
987 "actionCheck": function(target, selection)
989 if (someUnitAI(selection) || !someRallyPoints(selection) ||
990 !getActionInfo("unset-rallypoint", target, selection).possible)
994 "type": "unset-rallypoint",
995 "cursor": "action-unset-rally"
1003 "execute": function(target, action, selection, queued)
1007 "specificness": 100,
1012 * Info and actions for the entity commands
1013 * Currently displayed in the bottom of the central panel
1015 var g_EntityCommands =
1018 "getInfo": function(entStates)
1021 for (let entState of entStates)
1022 if (entState.garrisonHolder)
1023 count += entState.garrisonHolder.entities.length;
1029 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unload") +
1030 translate("Unload All."),
1031 "icon": "garrison-out.png",
1035 "execute": function()
1042 "getInfo": function(entStates)
1044 return entStates.some(entState => !isUndeletable(entState)) ?
1047 colorizeHotkey("%(hotkey)s" + " ", "session.kill") +
1048 translate("Destroy the selected units or buildings.") + "\n" +
1050 translate("Use %(hotkey)s to avoid the confirmation dialog."),
1051 "session.noconfirmation"
1053 "icon": "kill_small.png"
1056 // Get all delete reasons and remove duplications
1057 "tooltip": entStates.map(entState => isUndeletable(entState))
1058 .filter((reason, pos, self) =>
1059 self.indexOf(reason) == pos && reason
1061 "icon": "kill_small_disabled.png"
1064 "execute": function(entStates)
1066 if (!entStates.length || entStates.every(entState => isUndeletable(entState)))
1069 if (Engine.HotkeyIsPressed("session.noconfirmation"))
1070 Engine.PostNetworkCommand({
1071 "type": "delete-entities",
1072 "entities": entStates.map(entState => entState.id)
1075 openDeleteDialog(entStates.map(entState => entState.id));
1080 "getInfo": function(entStates)
1082 if (entStates.every(entState => !entState.unitAI))
1086 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.stop") +
1087 translate("Abort the current order."),
1091 "execute": function(entStates)
1093 if (entStates.length)
1094 stopUnits(entStates.map(entState => entState.id));
1099 "getInfo": function(entStates)
1101 if (entStates.every(entState => !entState.unitAI || entState.turretParent))
1105 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.garrison") +
1106 translate("Order the selected units to garrison in a building or unit."),
1107 "icon": "garrison.png"
1110 "execute": function()
1112 inputState = INPUT_PRESELECTEDACTION;
1113 preSelectedAction = ACTION_GARRISON;
1118 "getInfo": function(entStates)
1120 if (entStates.every(entState => {
1121 if (!entState.unitAI || !entState.turretParent)
1123 let parent = GetEntityState(entState.turretParent);
1124 return !parent || !parent.garrisonHolder || parent.garrisonHolder.entities.indexOf(entState.id) == -1;
1129 "tooltip": translate("Unload"),
1130 "icon": "garrison-out.png"
1133 "execute": function()
1140 "getInfo": function(entStates)
1142 if (entStates.every(entState => !entState.builder))
1146 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.repair") +
1147 translate("Order the selected units to repair a building or mechanical unit."),
1148 "icon": "repair.png"
1151 "execute": function()
1153 inputState = INPUT_PRESELECTEDACTION;
1154 preSelectedAction = ACTION_REPAIR;
1159 "getInfo": function(entStates)
1161 if (entStates.every(entState => !entState.rallyPoint))
1165 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "camera.rallypointfocus") +
1166 translate("Focus on Rally Point."),
1167 "icon": "focus-rally.png"
1170 "execute": function(entStates)
1172 // TODO: Would be nicer to cycle between the rallypoints of multiple entities instead of just using the first
1174 for (let entState of entStates)
1175 if (entState.rallyPoint && entState.rallyPoint.position)
1177 focusTarget = entState.rallyPoint.position;
1181 for (let entState of entStates)
1182 if (entState.position)
1184 focusTarget = entState.position;
1189 Engine.CameraMoveTo(focusTarget.x, focusTarget.z);
1194 "getInfo": function(entStates)
1196 if (entStates.every(entState => !entState.unitAI || !entState.unitAI.hasWorkOrders))
1200 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.backtowork") +
1201 translate("Back to Work"),
1202 "icon": "production.png"
1205 "execute": function()
1212 "getInfo": function(entStates)
1214 if (entStates.every(entState =>
1215 !entState.unitAI || !entState.unitAI.canGuard || entState.unitAI.isGuarding))
1219 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.guard") +
1220 translate("Order the selected units to guard a building or unit."),
1221 "icon": "add-guard.png"
1224 "execute": function()
1226 inputState = INPUT_PRESELECTEDACTION;
1227 preSelectedAction = ACTION_GUARD;
1232 "getInfo": function(entStates)
1234 if (entStates.every(entState => !entState.unitAI || !entState.unitAI.isGuarding))
1238 "tooltip": translate("Remove guard"),
1239 "icon": "remove-guard.png"
1242 "execute": function()
1248 "select-trading-goods": {
1249 "getInfo": function(entStates)
1251 if (entStates.every(entState => !entState.market))
1255 "tooltip": translate("Barter & Trade"),
1256 "icon": "economics.png"
1259 "execute": function()
1266 "getInfo": function(entStates)
1268 if (!entStates.some(entState => entState.unitAI && entState.unitAI.canPatrol))
1272 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.patrol") +
1273 translate("Patrol") + "\n" +
1274 translate("Attack all encountered enemy units while avoiding buildings."),
1275 "icon": "patrol.png"
1278 "execute": function()
1280 inputState = INPUT_PRESELECTEDACTION;
1281 preSelectedAction = ACTION_PATROL;
1286 "getInfo": function(entStates)
1288 let sharableEntities = entStates.filter(
1289 entState => entState.resourceDropsite && entState.resourceDropsite.sharable);
1290 if (!sharableEntities.length)
1293 // Returns if none of the entities belong to a player with a mutual ally
1294 if (entStates.every(entState => !GetSimState().players[entState.player].isMutualAlly.some(
1295 (isAlly, playerId) => isAlly && playerId != entState.player)))
1298 return sharableEntities.some(entState => !entState.resourceDropsite.shared) ?
1300 "tooltip": translate("Press to allow allies to use this dropsite"),
1301 "icon": "locked_small.png"
1304 "tooltip": translate("Press to prevent allies from using this dropsite"),
1305 "icon": "unlocked_small.png"
1308 "execute": function(entStates)
1310 let sharableEntities = entStates.filter(
1311 entState => entState.resourceDropsite && entState.resourceDropsite.sharable);
1312 Engine.PostNetworkCommand({
1313 "type": "set-dropsite-sharing",
1314 "entities": sharableEntities.map(entState => entState.id),
1315 "shared": sharableEntities.some(entState => !entState.resourceDropsite.shared)
1321 var g_AllyEntityCommands =
1324 "getInfo": function(entState)
1326 if (!entState.garrisonHolder)
1329 let player = Engine.GetPlayerID();
1332 for (let ent in g_Selection.selected)
1334 let selectedEntState = GetEntityState(+ent);
1335 if (!selectedEntState.garrisonHolder)
1338 for (let entity of selectedEntState.garrisonHolder.entities)
1340 let state = GetEntityState(entity);
1341 if (state.player == player)
1347 "tooltip": colorizeHotkey("%(hotkey)s" + " ", "session.unload") +
1348 translate("Unload All."),
1349 "icon": "garrison-out.png",
1353 "execute": function(entState)
1360 "getInfo": function(entState)
1362 if (Engine.GetPlayerID() == -1 ||
1363 !GetSimState().players[Engine.GetPlayerID()].hasSharedDropsites ||
1364 !entState.resourceDropsite || !entState.resourceDropsite.sharable)
1367 if (entState.resourceDropsite.shared)
1369 "tooltip": translate("You are allowed to use this dropsite"),
1370 "icon": "unlocked_small.png"
1374 "tooltip": translate("The use of this dropsite is prohibited"),
1375 "icon": "locked_small.png"
1378 "execute": function(entState)
1380 // This command button is always disabled
1385 function playerCheck(entState, targetState, validPlayers)
1387 let playerState = GetSimState().players[entState.player];
1388 for (let player of validPlayers)
1389 if (player == "Gaia" && targetState.player == 0 ||
1390 player == "Player" && targetState.player == entState.player ||
1391 playerState["is" + player] && playerState["is" + player][targetState.player])
1397 function hasClass(entState, className)
1399 // note: use the functions in globalscripts/Templates.js for more versatile matching
1400 return entState.identity && entState.identity.classes.indexOf(className) != -1;
1404 * Work out whether at least part of the selected entities have UnitAI.
1406 function someUnitAI(entities)
1408 return entities.some(ent => {
1409 let entState = GetEntityState(ent);
1410 return entState && entState.unitAI;
1414 function someRallyPoints(entities)
1416 return entities.some(ent => {
1417 let entState = GetEntityState(ent);
1418 return entState && entState.rallyPoint;
1422 function someGuarding(entities)
1424 return entities.some(ent => {
1425 let entState = GetEntityState(ent);
1426 return entState && entState.unitAI && entState.unitAI.isGuarding;
1430 function someCanPatrol(entities)
1432 return entities.some(ent => {
1433 let entState = GetEntityState(ent);
1434 return entState && entState.unitAI && entState.unitAI.canPatrol;
1439 * Keep in sync with Commands.js.
1441 function isUndeletable(entState)
1443 if (g_DevSettings.controlAll)
1446 if (entState.resourceSupply && entState.resourceSupply.killBeforeGather)
1447 return translate("The entity has to be killed before it can be gathered from");
1449 if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2)
1450 return translate("You cannot destroy this entity as you own less than half the capture points");
1452 if (!entState.identity.canDelete)
1453 return translate("This entity is undeletable");
1458 function DrawTargetMarker(target)
1460 Engine.GuiInterfaceCall("AddTargetMarker", {
1461 "template": g_TargetMarker.move,
1467 function findGatherType(gatherer, supply)
1469 if (!gatherer.resourceGatherRates || !supply)
1472 if (gatherer.resourceGatherRates[supply.type.generic + "." + supply.type.specific])
1473 return supply.type.specific;
1475 if (gatherer.resourceGatherRates[supply.type.generic])
1476 return supply.type.generic;
1481 function getActionInfo(action, target, selection)
1483 let simState = GetSimState();
1485 // If the selection doesn't exist, no action
1486 if (!GetEntityState(selection[0]))
1487 return { "possible": false };
1489 if (!target) // TODO move these non-target actions to an object like unit_actions.js
1491 if (action == "set-rallypoint")
1494 let data = { "command": "walk" };
1495 if (Engine.HotkeyIsPressed("session.attackmove"))
1497 data.command = "attack-walk";
1498 data.targetClasses = Engine.HotkeyIsPressed("session.attackmoveUnit") ?
1499 { "attack": ["Unit"] } : { "attack": ["Unit", "Structure"] };
1500 cursor = "action-attack-move";
1502 else if (Engine.HotkeyIsPressed("session.patrol"))
1504 data.command = "patrol";
1505 data.targetClasses = { "attack": g_PatrolTargets };
1506 cursor = "action-patrol";
1516 "possible": ["move", "attack-move", "remove-guard", "patrol"].indexOf(action) != -1
1520 // Look at the first targeted entity
1521 // (TODO: maybe we eventually want to look at more, and be more context-sensitive?
1522 // e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse)
1523 let targetState = GetEntityState(target);
1525 // Check if any entities in the selection can do some of the available actions with target
1526 for (let entityID of selection)
1528 let entState = GetEntityState(entityID);
1532 if (g_UnitActions[action] && g_UnitActions[action].getActionInfo)
1534 let r = g_UnitActions[action].getActionInfo(entState, targetState, simState);
1535 if (r && r.possible) // return true if it's possible for one of the entities
1539 return { "possible": false };