Ticket #2034: escort-v2.diff
File escort-v2.diff, 39.1 KB (added by , 10 years ago) |
---|
-
binaries/data/config/default.cfg
272 272 hotkey.session.attackmove = Ctrl ; Modifier to attackmove when clicking on a point 273 273 hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building 274 274 hotkey.session.autorallypoint = Ctrl ; Modifier to set the rally point on the building itself 275 hotkey.session.guard = "G" ; Modifier to escort/guard when clicking on unit/building 275 276 hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing 276 277 hotkey.session.batchtrain = Shift ; Modifier to train units in batches 277 278 hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources … … 300 301 hotkey.menu.toggle = "F10" ; Toggle in-game menu 301 302 hotkey.timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter 302 303 hotkey.session.showstatusbars = Tab ; Toggle display of status bars 304 hotkey.session.highlightguarding = PgDn ; Toggle highlight of guarding units 305 hotkey.session.highlightguarded = PgUp ; Toggle highlight of guarded units 303 306 304 307 ; > HOTKEYS ONLY 305 308 hotkey.chat = Return ; Toggle chat window -
binaries/data/mods/public/art/textures/cursors/action-guard-disabled.png
Impossible d'afficher : fichier considéré comme binaire. svn:mime-type = application/octet-stream
-
binaries/data/mods/public/art/textures/cursors/action-guard-disabled.txt
Modification de propriétés sur binaries/data/mods/public/art/textures/cursors/action-guard-disabled.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property
1 1 1 -
binaries/data/mods/public/art/textures/ui/session/icons/add-guard.png
Impossible d'afficher : fichier considéré comme binaire. svn:mime-type = application/octet-stream
-
binaries/data/mods/public/art/textures/ui/session/icons/remove-guard.png
Modification de propriétés sur binaries/data/mods/public/art/textures/ui/session/icons/add-guard.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Impossible d'afficher : fichier considéré comme binaire. svn:mime-type = application/octet-stream
-
binaries/data/mods/public/gui/session/input.js
Modification de propriétés sur binaries/data/mods/public/art/textures/ui/session/icons/remove-guard.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property
15 15 const ACTION_NONE = 0; 16 16 const ACTION_GARRISON = 1; 17 17 const ACTION_REPAIR = 2; 18 const ACTION_GUARD = 3; 18 19 var preSelectedAction = ACTION_NONE; 19 20 20 21 const INPUT_NORMAL = 0; … … 426 427 if (entState.attack && targetState.hitpoints && (enemyOwned || neutralOwned)) 427 428 return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": target})}; 428 429 break; 430 case "guard": 431 if (targetState.guard && (playerOwned || mutualAllyOwned)) 432 return {"possible": (entState.unitAI && entState.unitAI.canGuard)}; 433 break; 429 434 } 430 435 } 431 436 if (action == "move" || action == "attack-move") … … 502 507 else 503 508 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 504 509 break; 510 case ACTION_GUARD: 511 if (getActionInfo("guard", target).possible) 512 return {"type": "guard", "cursor": "action-guard", "target": target}; 513 else 514 return {"type": "none", "cursor": "action-guard-disabled", "target": undefined}; 515 break; 505 516 } 506 517 } 507 518 else if (Engine.HotkeyIsPressed("session.attack") && getActionInfo("attack", target).possible) … … 516 527 { 517 528 return {"type": "attack-move", "cursor": "action-attack-move"}; 518 529 } 530 else if (Engine.HotkeyIsPressed("session.guard") && getActionInfo("guard", target).possible) 531 { 532 return {"type": "guard", "cursor": "action-guard", "target": target}; 533 } 519 534 else 520 535 { 521 536 if ((actionInfo = getActionInfo("setup-trade-route", target)).possible) … … 1053 1068 recalculateStatusBarDisplay(); 1054 1069 } 1055 1070 1071 if (ev.hotkey == "session.highlightguarding" && g_ShowGuarding != (ev.type == "hotkeydown")) 1072 { 1073 g_ShowGuarding = (ev.type == "hotkeydown"); 1074 updateHighlightSelectionGuards(); 1075 } 1076 1077 if (ev.hotkey == "session.highlightguarded" && g_ShowGuarded != (ev.type == "hotkeydown")) 1078 { 1079 g_ShowGuarded = (ev.type == "hotkeydown"); 1080 updateHighlightSelectionGuards(); 1081 } 1082 1056 1083 // State-machine processing: 1057 1084 1058 1085 switch (inputState) … … 1394 1421 Engine.GuiInterfaceCall("PlaySound", { "name": "order_garrison", "entity": selection[0] }); 1395 1422 return true; 1396 1423 1424 case "guard": 1425 Engine.PostNetworkCommand({"type": "guard", "entities": selection, "target": action.target, "queued": queued}); 1426 Engine.GuiInterfaceCall("PlaySound", { "name": "order_guard", "entity": selection[0] }); 1427 return true; 1428 1397 1429 case "set-rallypoint": 1398 1430 var pos = undefined; 1399 1431 // if there is a position set in the action then use this so that when setting a … … 1820 1852 inputState = INPUT_PRESELECTEDACTION; 1821 1853 preSelectedAction = ACTION_REPAIR; 1822 1854 break; 1855 case "add-guard": 1856 inputState = INPUT_PRESELECTEDACTION; 1857 preSelectedAction = ACTION_GUARD; 1858 break; 1859 case "remove-guard": 1860 removeGuard(); 1861 break; 1823 1862 case "unload-all": 1824 1863 unloadAll(); 1825 1864 break; … … 2106 2145 2107 2146 } 2108 2147 2148 function removeGuard() 2149 { 2150 // Filter out all entities that are currently guarding/escorting. 2151 var entities = g_Selection.toList().filter(function(e) { 2152 var state = GetEntityState(e); 2153 return (state && state.unitAI && state.unitAI.isGuarding); 2154 }); 2155 2156 Engine.PostNetworkCommand({"type": "remove-guard", "entities": entities}); 2157 } 2158 2109 2159 function clearSelection() 2110 2160 { 2111 2161 if(inputState==INPUT_BUILDING_PLACEMENT || inputState==INPUT_BUILDING_WALL_PATHING) -
binaries/data/mods/public/gui/session/session.js
46 46 const DEFAULT_POPULATION_COLOR = "white"; 47 47 const POPULATION_ALERT_COLOR = "orange"; 48 48 49 // List of additionnal entities to highlight 50 var g_ShowGuarding = false; 51 var g_ShowGuarded = false; 52 var g_AdditionnalHighlight = []; 53 49 54 function GetSimState() 50 55 { 51 56 if (!g_SimState) … … 331 336 332 337 // Display rally points for selected buildings 333 338 Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); 339 340 // Update the highlight of guards if needed 341 if (g_ShowGuarding || g_ShowGuarded) 342 updateHighlightSelectionGuards(); 334 343 } 335 344 336 345 // Run timers … … 665 674 Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars }); 666 675 } 667 676 677 // Update the additionnal list of entities to be highlighted. 678 function updateHighlightSelectionGuards() 679 { 680 var guarding = []; // guarded units will be highlighted with the player color 681 var guarded = []; // guarding units will be highlighted in black 682 var remove = []; 683 var selected = g_Selection.toList(); 684 685 if (g_ShowGuarding) 686 { 687 // flag the guarding entities to add in this additionnal highlight 688 for each (var sel in selected) 689 { 690 var state = GetEntityState(sel); 691 if (!state.guard || !state.guard.entities.length) 692 continue; 693 for each (var ent in state.guard.entities) 694 if (selected.indexOf(ent) == -1 && guarding.indexOf(ent) == -1) 695 guarding.push(ent); 696 } 697 } 698 699 if (g_ShowGuarded) 700 { 701 // flag the guarded entities to add in this additionnal highlight 702 for each (var sel in selected) 703 { 704 var state = GetEntityState(sel); 705 if (!state.unitAI || !state.unitAI.isGuarding) 706 continue; 707 var ent = state.unitAI.isGuarding; 708 if (selected.indexOf(ent) == -1 && guarding.indexOf(ent) == -1 && guarded.indexOf(ent) == -1) 709 guarded.push(ent); 710 } 711 } 712 713 // flag the entities to remove (from the previously added) from this additionnal highlight 714 for each (var ent in g_AdditionnalHighlight) 715 if (selected.indexOf(ent) == -1 && guarding.indexOf(ent) == -1 && guarded.indexOf(ent) == -1 && remove.indexOf(ent) == -1) 716 remove.push(ent); 717 718 Engine.GuiInterfaceCall("SetAdditionnalHighlight", { "entitiesBlack": guarding, "entitiesColor": guarded, "entitiesRemove": remove }); 719 g_AdditionnalHighlight = guarding.concat(guarded); 720 } 721 668 722 // Temporarily adding this here 669 723 const AMBIENT_TEMPERATE = "temperate"; 670 724 var currentAmbient; -
binaries/data/mods/public/gui/session/utility_functions.js
303 303 }); 304 304 } 305 305 306 if (entState.unitAI && !entState.unitAI.isGuarding && entState.unitAI.canGuard) 307 { 308 commands.push({ 309 "name": "add-guard", 310 "tooltip": "Guard", 311 "icon": "add-guard.png" 312 }); 313 } 314 315 if (entState.unitAI && entState.unitAI.isGuarding) 316 { 317 commands.push({ 318 "name": "remove-guard", 319 "tooltip": "Remove guard", 320 "icon": "remove-guard.png" 321 }); 322 } 323 306 324 return commands; 307 325 } 308 326 -
binaries/data/mods/public/simulation/components/Guard.js
1 function Guard() {} 2 3 Guard.prototype.Schema = 4 "<empty/>"; 5 6 Guard.prototype.Init = function() 7 { 8 this.entities = []; 9 }; 10 11 Guard.prototype.GetRange = function(entity) 12 { 13 var range = 8; 14 var cmpFootprint = Engine.QueryInterface(entity, IID_Footprint); 15 if (cmpFootprint) 16 { 17 var shape = cmpFootprint.GetShape(); 18 if (shape.type == "square") 19 range += Math.sqrt(shape.depth*shape.depth + shape.width*shape.width)*2/3; 20 else if (shape.type == "circle") 21 range += shape.radius*3/2; 22 } 23 return range; 24 }; 25 26 Guard.prototype.GetEntities = function() 27 { 28 return this.entities.slice(); 29 }; 30 31 Guard.prototype.SetEntities = function(entities) 32 { 33 this.entities = entities; 34 }; 35 36 Guard.prototype.AddGuard = function(ent) 37 { 38 if (this.entities.indexOf(ent) != -1) 39 return; 40 this.entities.push(ent); 41 }; 42 43 Guard.prototype.RemoveGuard = function(ent) 44 { 45 var index = this.entities.indexOf(ent); 46 if (index != -1) 47 this.entities.splice(index, 1); 48 }; 49 50 Guard.prototype.OnAttacked = function(msg) 51 { 52 for each (var ent in this.entities) 53 Engine.PostMessage(ent, MT_GuardedAttacked, { "guarded": this.entity, "data": msg }); 54 }; 55 56 /** 57 * Update list of guarding/escorting entities if one gets renamed (e.g. by promotion) 58 */ 59 Guard.prototype.OnGlobalEntityRenamed = function(msg) 60 { 61 var entityIndex = this.entities.indexOf(msg.entity); 62 if (entityIndex != -1) 63 this.entities[entityIndex] = msg.newentity; 64 }; 65 66 /** 67 * If an entity is captured, or about to be killed (so its owner 68 * changes to '-1'), update the guards list 69 */ 70 Guard.prototype.OnGlobalOwnershipChanged = function(msg) 71 { 72 // the ownership change may be on the guarded 73 if (this.entity == msg.entity) 74 { 75 var entities = this.GetEntities(); 76 for each (var entity in entities) 77 { 78 if (msg.to == -1 || !IsOwnedByMutualAllyOfEntity(this.entity, entity)) 79 { 80 var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); 81 if (cmpUnitAI && cmpUnitAI.IsGuardOf() && cmpUnitAI.IsGuardOf() == this.entity) 82 cmpUnitAI.RemoveGuard(); 83 else 84 this.RemoveGuard(entity); 85 } 86 } 87 this.entities = entities; 88 return; 89 } 90 91 // or on some of its guards 92 if (this.entities.indexOf(msg.entity) != -1) 93 { 94 if (msg.to == -1) 95 this.RemoveGuard(msg.entity); 96 else if(!IsOwnedByMutualAllyOfEntity(this.entity, msg.entity)) 97 { 98 var cmpUnitAI = Engine.QueryInterface(msg.entity, IID_UnitAI); 99 if (cmpUnitAI) 100 cmpUnitAI.RemoveGuard(); 101 else 102 this.RemoveGuard(msg.entity); 103 } 104 } 105 }; 106 107 Engine.RegisterComponentType(IID_Guard, "Guard", Guard); -
binaries/data/mods/public/simulation/components/GuiInterface.js
350 350 "state": cmpUnitAI.GetCurrentState(), 351 351 "orders": cmpUnitAI.GetOrders(), 352 352 "hasWorkOrders": cmpUnitAI.HasWorkOrders(), 353 "canGuard": cmpUnitAI.CanGuard(), 354 "isGuarding": cmpUnitAI.IsGuardOf(), 353 355 }; 354 356 // Add some information needed for ungarrisoning 355 357 if (cmpUnitAI.isGarrisoned && ret.player !== undefined) 356 358 ret.template = "p" + ret.player + "&" + ret.template; 357 359 } 358 360 361 var cmpGuard = Engine.QueryInterface(ent, IID_Guard); 362 if (cmpGuard) 363 { 364 ret.guard = { 365 "entities": cmpGuard.GetEntities(), 366 }; 367 } 368 359 369 var cmpGate = Engine.QueryInterface(ent, IID_Gate); 360 370 if (cmpGate) 361 371 { … … 783 793 } 784 794 }; 785 795 796 GuiInterface.prototype.SetAdditionnalHighlight = function(player, cmd) 797 { 798 var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 799 var playerColours = {}; // cache of owner -> colour map 800 801 // remove the previous highlight when no more needed 802 for each (var ent in cmd.entitiesRemove) 803 { 804 var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable); 805 if (!cmpSelectable) 806 continue; 807 808 // Find the entity's owner's colour: 809 var owner = -1; 810 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 811 if (cmpOwnership) 812 owner = cmpOwnership.GetOwner(); 813 814 var colour = playerColours[owner]; 815 if (!colour) 816 { 817 var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player); 818 colour = cmpPlayer.GetColour(); 819 playerColours[owner] = colour; 820 } 821 822 cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":0}, false); 823 } 824 825 // highlight the required units with player color 826 for each (var ent in cmd.entitiesColor) 827 { 828 var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable); 829 if (!cmpSelectable) 830 continue; 831 832 // Find the entity's owner's colour: 833 var owner = -1; 834 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 835 if (cmpOwnership) 836 owner = cmpOwnership.GetOwner(); 837 838 // Only units from allies should be highligted 839 var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player); 840 if (!cmpPlayer || !cmpPlayer.IsMutualAlly(player)) 841 continue; 842 843 var colour = playerColours[owner]; 844 if (!colour) 845 { 846 colour = cmpPlayer.GetColour(); 847 playerColours[owner] = colour; 848 } 849 850 cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":0.70}, true); 851 } 852 853 // highlight the required units in black 854 for each (var ent in cmd.entitiesBlack) 855 { 856 var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable); 857 if (!cmpSelectable) 858 continue; 859 860 // Find the entity's owner's colour: 861 var owner = -1; 862 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 863 if (cmpOwnership) 864 owner = cmpOwnership.GetOwner(); 865 866 // Only units from allies should be highligted 867 var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player); 868 if (!cmpPlayer || !cmpPlayer.IsMutualAlly(player)) 869 continue; 870 871 cmpSelectable.SetSelectionHighlight({"r": 0, "g": 0, "b": 0, "a":0.70}, true); 872 } 873 }; 874 786 875 GuiInterface.prototype.SetStatusBars = function(player, cmd) 787 876 { 788 877 for each (var ent in cmd.entities) … … 1801 1890 "IsStanceSelected": 1, 1802 1891 1803 1892 "SetSelectionHighlight": 1, 1893 "SetAdditionnalHighlight": 1, 1804 1894 "SetStatusBars": 1, 1805 1895 "GetPlayerEntities": 1, 1806 1896 "DisplayRallyPoint": 1, … … 1812 1902 "GetTradingRouteGain": 1, 1813 1903 "GetTradingDetails": 1, 1814 1904 "CanAttack": 1, 1905 "CanGuard": 1, 1815 1906 "GetBatchTime": 1, 1816 1907 1817 1908 "SetPathfinderDebugOverlay": 1, -
binaries/data/mods/public/simulation/components/Pack.js
162 162 if (cmpUnitAI.GetStanceName()) 163 163 cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName()); 164 164 cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders()); 165 cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf()); 165 166 } 166 167 168 // Maintain the list of guards 169 var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard); 170 var cmpNewGuard = Engine.QueryInterface(newEntity, IID_Guard); 171 if (cmpGuard && cmpNewGuard) 172 cmpNewGuard.SetEntities(cmpGuard.GetEntities()); 173 167 174 Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: newEntity }); 168 175 169 176 // Play notification sound -
binaries/data/mods/public/simulation/components/Promotion.js
80 80 cmpPromotedUnitAI.AddOrders(orders); 81 81 var workOrders = cmpCurrentUnitAI.GetWorkOrders(); 82 82 cmpPromotedUnitAI.SetWorkOrders(workOrders); 83 cmpPromotedUnitAI.SetGuardOf(cmpCurrentUnitAI.IsGuardOf()); 83 84 85 var cmpCurrentUnitGuard = Engine.QueryInterface(this.entity, IID_Guard); 86 var cmpPromotedUnitGuard = Engine.QueryInterface(promotedUnitEntity, IID_Guard); 87 if (cmpCurrentUnitGuard && cmpPromotedUnitGuard) 88 cmpPromotedUnitGuard.SetEntities(cmpCurrentUnitGuard.GetEntities()); 89 84 90 Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: promotedUnitEntity }); 85 91 86 92 // Destroy current entity -
binaries/data/mods/public/simulation/components/UnitAI.js
18 18 "<element name='FleeDistance'>" + 19 19 "<ref name='positiveDecimal'/>" + 20 20 "</element>" + 21 "<element name='CanGuard'>" + 22 "<data type='boolean'/>" + 23 "</element>" + 21 24 "<optional>" + 22 25 "<interleave>" + 23 26 "<element name='NaturalBehaviour' a:help='Behaviour of the unit in the absence of player commands (intended for animals)'>" + … … 164 167 // ignore 165 168 }, 166 169 170 "GuardedAttacked": function(msg) { 171 // ignore 172 }, 173 167 174 // Formation handlers: 168 175 169 176 "FormationLeave": function(msg) { … … 354 361 } 355 362 }, 356 363 364 "Order.Guard": function(msg) { 365 if (!this.AddGuard(this.order.data.target)) 366 { 367 this.FinishOrder(); 368 return; 369 } 370 371 if (this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) 372 this.SetNextState("INDIVIDUAL.GUARD.ESCORTING"); 373 else 374 this.SetNextState("INDIVIDUAL.GUARD.GUARDING"); 375 }, 376 357 377 "Order.Flee": function(msg) { 358 // We use the distance between the en ities to account for ranged attacks378 // We use the distance between the entities to account for ranged attacks 359 379 var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance); 360 380 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 361 381 if (cmpUnitMotion.MoveToTargetRange(this.order.data.target, distance, -1)) … … 740 760 this.FinishOrder(); 741 761 }, 742 762 763 "Order.Guard": function(msg) { 764 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 765 cmpFormation.CallMemberFunction("Guard", [msg.data.target, false]); 766 cmpFormation.Disband(); 767 }, 768 743 769 "Order.Stop": function(msg) { 744 770 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 745 771 cmpFormation.CallMemberFunction("Stop", [false]); … … 1259 1285 } 1260 1286 }, 1261 1287 1288 "GuardedAttacked": function(msg) { 1289 // do nothing if we have a forced order in queue before the guard order 1290 for (var i = 0; i < this.orderQueue.length; ++i) 1291 { 1292 if (this.orderQueue[i].type == "Guard") 1293 break; 1294 if (this.orderQueue[i].data && this.orderQueue[i].data.force) 1295 return; 1296 } 1297 // if we already are targeting another unit still alive, finish with it first 1298 if (this.order && this.order.type == "WalkAndFight") 1299 if (this.order.data.target != msg.data.attacker && this.TargetIsAlive(msg.data.attacker)) 1300 return; 1301 1302 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); 1303 var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); 1304 if (cmpIdentity && cmpIdentity.HasClass("Support") && 1305 cmpHealth && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 1306 { 1307 if (this.CanHeal(this.isGuardOf)) 1308 this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); 1309 else if (this.CanRepair(this.isGuardOf) && cmpHealth.IsRepairable()) 1310 this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); 1311 return; 1312 } 1313 1314 // target the unit 1315 var cmpPosition = Engine.QueryInterface(msg.data.attacker, IID_Position); 1316 if (!cmpPosition || !cmpPosition.IsInWorld()) 1317 return; 1318 var pos = cmpPosition.GetPosition(); 1319 this.PushOrderFront("WalkAndFight", { "x": pos.x, "z": pos.z, "target": msg.data.attacker, "force": false }); 1320 // if we already had a WalkAndFight, keep only the most recent one in case the target has moved 1321 if (this.orderQueue[1] && this.orderQueue[1].type == "WalkAndFight") 1322 this.orderQueue.splice(1, 1); 1323 }, 1324 1262 1325 "IDLE": { 1263 1326 "enter": function() { 1264 1327 // Switch back to idle animation to guarantee we won't 1265 1328 // get stuck with an incorrect animation 1266 1329 this.SelectAnimation("idle"); 1267 1330 1331 // If the unit is guarding/escorting, go back to its duty 1332 if (this.isGuardOf) 1333 { 1334 this.Guard(this.isGuardOf, false); 1335 return true; 1336 } 1337 1268 1338 // The GUI and AI want to know when a unit is idle, but we don't 1269 1339 // want to send frequent spurious messages if the unit's only 1270 1340 // idle for an instant and will quickly go off and do something else. … … 1366 1436 }, 1367 1437 }, 1368 1438 1439 "GUARD": { 1440 "enter": function () { 1441 }, 1442 1443 "RemoveGuard": function() { 1444 this.FinishOrder(); 1445 }, 1446 1447 "leave": function () { 1448 }, 1449 1450 "ESCORTING": { 1451 "enter": function () { 1452 // Show weapons rather than carried resources. 1453 this.SetGathererAnimationOverride(true); 1454 1455 this.StartTimer(0, 1000); 1456 this.SelectAnimation("move"); 1457 this.SetHeldPositionOnEntity(this.isGuardOf); 1458 return false; 1459 }, 1460 1461 "Timer": function(msg) { 1462 // Check the target is alive 1463 if (!this.TargetIsAlive(this.isGuardOf)) 1464 { 1465 this.StopMoving(); 1466 this.FinishOrder(); 1467 return; 1468 } 1469 this.SetHeldPositionOnEntity(this.isGuardOf); 1470 }, 1471 1472 "leave": function(msg) { 1473 this.SetMoveSpeed(this.GetWalkSpeed()); 1474 this.StopTimer(); 1475 }, 1476 1477 "MoveStarted": function(msg) { 1478 // Adapt the speed to the one of the target if needed 1479 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 1480 if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange)) 1481 { 1482 var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI); 1483 if (cmpUnitAI) 1484 { 1485 var speed = cmpUnitAI.GetWalkSpeed(); 1486 if (speed < this.GetWalkSpeed()) 1487 this.SetMoveSpeed(speed); 1488 } 1489 } 1490 }, 1491 1492 "MoveCompleted": function() { 1493 this.SetMoveSpeed(this.GetWalkSpeed()); 1494 if (!this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) 1495 this.SetNextState("GUARDING"); 1496 }, 1497 }, 1498 1499 "GUARDING": { 1500 "enter": function () { 1501 this.StartTimer(1000, 1000); 1502 this.SetHeldPositionOnEntity(this.entity); 1503 this.SelectAnimation("idle"); 1504 return false; 1505 }, 1506 1507 "LosRangeUpdate": function(msg) { 1508 // Start attacking one of the newly-seen enemy (if any) 1509 if (this.GetStance().targetVisibleEnemies) 1510 this.AttackEntitiesByPreference(msg.data.added); 1511 }, 1512 1513 "LosGaiaRangeUpdate": function(msg) { 1514 // Start attacking one of the newly-seen enemy (if any) 1515 if (this.GetStance().targetVisibleEnemies) 1516 this.AttackGaiaEntitiesByPreference(msg.data.added); 1517 }, 1518 1519 "Timer": function(msg) { 1520 // Check the target is alive 1521 if (!this.TargetIsAlive(this.isGuardOf)) 1522 { 1523 this.FinishOrder(); 1524 return; 1525 } 1526 // then check is the target has moved 1527 if (this.MoveToTargetRangeExplicit(this.isGuardOf, 0, this.guardRange)) 1528 this.SetNextState("ESCORTING"); 1529 else 1530 { 1531 // if nothing better to do, check if the guarded needs to be healed or repaired 1532 var cmpHealth = Engine.QueryInterface(this.isGuardOf, IID_Health); 1533 if (cmpHealth && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())) 1534 { 1535 if (this.CanHeal(this.isGuardOf)) 1536 this.PushOrderFront("Heal", { "target": this.isGuardOf, "force": false }); 1537 else if (this.CanRepair(this.isGuardOf) && cmpHealth.IsRepairable()) 1538 this.PushOrderFront("Repair", { "target": this.isGuardOf, "autocontinue": false, "force": false }); 1539 } 1540 } 1541 }, 1542 1543 "leave": function(msg) { 1544 this.StopTimer(); 1545 }, 1546 }, 1547 }, 1548 1369 1549 "FLEEING": { 1370 1550 "enter": function() { 1371 1551 this.PlaySound("panic"); … … 2270 2450 return false; 2271 2451 }, 2272 2452 2453 "LosRangeUpdate": function(msg) { 2454 }, 2455 2456 "LosGaiaRangeUpdate": function(msg) { 2457 }, 2458 2273 2459 "leave": function() { 2274 2460 var cmpFoundation = Engine.QueryInterface(this.repairTarget, IID_Foundation); 2275 2461 if (cmpFoundation) … … 2715 2901 // Queue of remembered works 2716 2902 this.workOrders = []; 2717 2903 2904 this.isGuardOf = undefined; 2905 2718 2906 // For preventing increased action rate due to Stop orders or target death. 2719 2907 this.lastAttacked = undefined; 2720 2908 this.lastHealed = undefined; … … 3039 3227 error("FinishOrder called for entity " + this.entity + " (" + template + ") when order queue is empty\n" + stack); 3040 3228 } 3041 3229 3042 // Remove the order from the queue3043 3230 this.orderQueue.shift(); 3044 3231 this.order = this.orderQueue[0]; 3045 3232 … … 3391 3578 for each (var order in this.orderQueue) 3392 3579 if (order.data && order.data.target && order.data.target == msg.entity) 3393 3580 order.data.target = msg.newentity; 3581 3582 if (this.isGuardOf && this.isGuardOf == msg.entity) 3583 this.isGuardOf = msg.newentity; 3394 3584 }; 3395 3585 3396 3586 UnitAI.prototype.OnAttacked = function(msg) … … 3398 3588 UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); 3399 3589 }; 3400 3590 3591 UnitAI.prototype.OnGuardedAttacked = function(msg) 3592 { 3593 UnitFsm.ProcessMessage(this, {"type": "GuardedAttacked", "data": msg.data}); 3594 }; 3595 3401 3596 UnitAI.prototype.OnHealthChanged = function(msg) 3402 3597 { 3403 3598 UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to}); … … 4043 4238 if (force) 4044 4239 return false; 4045 4240 4241 // If we are guarding/escorting, don't abandon as long as the guarded is in target range of the attacker 4242 if (this.isGuardOf) 4243 { 4244 var cmpUnitAI = Engine.QueryInterface(target, IID_UnitAI); 4245 var cmpAttack = Engine.QueryInterface(target, IID_Attack); 4246 if (cmpUnitAI && cmpAttack) 4247 { 4248 for each (var type in cmpAttack.GetAttackTypes()) 4249 if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type)) 4250 return false; 4251 } 4252 } 4253 4046 4254 // Stop if we're in hold-ground mode and it's too far from the holding point 4047 4255 if (this.GetStance().respondHoldGround) 4048 4256 { … … 4077 4285 if (this.GetStance().respondChase) 4078 4286 return true; 4079 4287 4288 // If we are guarding/escorting, chase at least as long as the guarded is in target range of the attacker 4289 if (this.isGuardOf) 4290 { 4291 var cmpUnitAI = Engine.QueryInterface(target, IID_UnitAI); 4292 var cmpAttack = Engine.QueryInterface(target, IID_Attack); 4293 if (cmpUnitAI && cmpAttack) 4294 { 4295 for each (var type in cmpAttack.GetAttackTypes()) 4296 if (cmpUnitAI.CheckTargetAttackRange(this.isGuardOf, IID_Attack, type)) 4297 return true; 4298 } 4299 } 4300 4080 4301 if (force) 4081 4302 return true; 4082 4303 … … 4177 4398 4178 4399 case "WalkToTarget": 4179 4400 case "WalkToTargetRange": // This doesn't move to the target (just into range), but a later order will. 4401 case "Guard": 4180 4402 case "Flee": 4181 4403 case "LeaveFoundation": 4182 4404 case "Attack": … … 4222 4444 }; 4223 4445 4224 4446 /** 4447 * Adds guard/escort order to the queue, forced by the player. 4448 */ 4449 UnitAI.prototype.Guard = function(target, queued) 4450 { 4451 if (!this.CanGuard()) 4452 { 4453 this.WalkToTarget(target, queued); 4454 return; 4455 } 4456 4457 // if we already had an old guard order, do nothing if the target is the same 4458 // and the order is running, otherwise remove the previous order 4459 if (this.isGuardOf) 4460 { 4461 if (this.isGuardOf == target && this.order && this.order.type == "Guard") 4462 return; 4463 else 4464 this.RemoveGuard(); 4465 } 4466 4467 this.AddOrder("Guard", { "target": target, "force": false }, queued); 4468 }; 4469 4470 UnitAI.prototype.AddGuard = function(target) 4471 { 4472 var cmpGuard = Engine.QueryInterface(target, IID_Guard); 4473 if (!cmpGuard || !this.CanGuard()) 4474 return false; 4475 this.isGuardOf = target; 4476 this.guardRange = cmpGuard.GetRange(this.entity); 4477 cmpGuard.AddGuard(this.entity); 4478 return true; 4479 }; 4480 4481 UnitAI.prototype.RemoveGuard = function() 4482 { 4483 if (this.isGuardOf) 4484 { 4485 var cmpGuard = Engine.QueryInterface(this.isGuardOf, IID_Guard); 4486 if (cmpGuard) 4487 cmpGuard.RemoveGuard(this.entity); 4488 this.guardRange = undefined; 4489 this.isGuardOf = undefined; 4490 } 4491 4492 if (!this.order) 4493 return; 4494 4495 if (this.order.type == "Guard") 4496 UnitFsm.ProcessMessage(this, {"type": "RemoveGuard"}); 4497 else 4498 for (var i = 1; i < this.orderQueue.length; ++i) 4499 if (this.orderQueue[i].type == "Guard") 4500 this.orderQueue.splice(i, 1); 4501 }; 4502 4503 UnitAI.prototype.IsGuardOf = function() 4504 { 4505 return this.isGuardOf; 4506 }; 4507 4508 UnitAI.prototype.SetGuardOf = function(entity) 4509 { 4510 // entity may be undefined 4511 this.isGuardOf = entity; 4512 }; 4513 4514 UnitAI.prototype.CanGuard = function() 4515 { 4516 // Formation controllers should always respond to commands 4517 // (then the individual units can make up their own minds) 4518 if (this.IsFormationController()) 4519 return true; 4520 4521 // Do not let a unit already guarded to guard. This would work in principle, 4522 // but would clutter the gui with too much buttons to take all cases into account 4523 var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard); 4524 if (cmpGuard && cmpGuard.GetEntities().length) 4525 return false; 4526 4527 return (this.template.CanGuard == "true"); 4528 }; 4529 4530 /** 4225 4531 * Adds walk order to queue, forced by the player. 4226 4532 */ 4227 4533 UnitAI.prototype.Walk = function(x, z, queued) … … 4682 4988 this.heldPosition = {"x": x, "z": z}; 4683 4989 }; 4684 4990 4991 UnitAI.prototype.SetHeldPositionOnEntity = function(entity) 4992 { 4993 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 4994 if (!cmpPosition || !cmpPosition.IsInWorld()) 4995 return; 4996 var pos = cmpPosition.GetPosition(); 4997 this.SetHeldPosition(pos.x, pos.z); 4998 }; 4999 4685 5000 UnitAI.prototype.GetHeldPosition = function(pos) 4686 5001 { 4687 5002 return this.heldPosition; -
binaries/data/mods/public/simulation/components/interfaces/Guard.js
1 Engine.RegisterInterface("Guard"); 2 3 // Message of the form { "guarded": entity, "data": msg }, 4 // sent whenever a guarded/escorted unit is attacked 5 Engine.RegisterMessageType("GuardedAttacked"); -
binaries/data/mods/public/simulation/helpers/Commands.js
180 180 } 181 181 break; 182 182 183 case "remove-guard": 184 for each (var ent in entities) 185 { 186 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 187 if(cmpUnitAI) 188 cmpUnitAI.RemoveGuard(); 189 } 190 break; 191 183 192 case "train": 184 193 // Check entity limits 185 194 var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); … … 328 337 } 329 338 break; 330 339 340 case "guard": 341 // Verify that the target can be controlled by the player or is mutualAlly 342 if (CanControlUnitOrIsAlly(cmd.target, player, controlAllUnits)) 343 { 344 GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) { 345 cmpUnitAI.Guard(cmd.target, cmd.queued); 346 }); 347 } 348 else if (g_DebugCommands) 349 { 350 warn("Invalid command: guard/escort target cannot be controlled by player "+player+": "+uneval(cmd)); 351 } 352 break; 353 331 354 case "stop": 332 355 GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) { 333 356 cmpUnitAI.Stop(cmd.queued); -
binaries/data/mods/public/simulation/templates/special/formation.xml
17 17 <DefaultStance>aggressive</DefaultStance> 18 18 <FleeDistance>12.0</FleeDistance> 19 19 <FormationController>true</FormationController> 20 <CanGuard>true</CanGuard> 20 21 </UnitAI> 21 22 <UnitMotion> 22 23 <FormationController>true</FormationController> -
binaries/data/mods/public/simulation/templates/template_structure.xml
15 15 <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier> 16 16 <GarrisonArrowClasses>Ranged Infantry</GarrisonArrowClasses> 17 17 </BuildingAI> 18 <Guard/> 18 19 <BuildRestrictions> 19 20 <PlacementType>land</PlacementType> 20 21 <Territory>own</Territory> -
binaries/data/mods/public/simulation/templates/template_unit.xml
102 102 <DefaultStance>aggressive</DefaultStance> 103 103 <FleeDistance>12.0</FleeDistance> 104 104 <FormationController>false</FormationController> 105 <CanGuard>true</CanGuard> 105 106 </UnitAI> 107 <Guard/> 106 108 <UnitMotion> 107 109 <FormationController>false</FormationController> 108 110 <WalkSpeed>7.5</WalkSpeed> -
binaries/data/mods/public/simulation/templates/template_unit_fauna.xml
22 22 </Minimap> 23 23 <ResourceGatherer disable=""/> 24 24 <UnitAI> 25 <CanGuard>false</CanGuard> 25 26 <RoamDistance>8.0</RoamDistance> 26 27 <FleeDistance>24.0</FleeDistance> 27 28 <RoamTimeMin>2000</RoamTimeMin> … … 29 30 <FeedTimeMin>15000</FeedTimeMin> 30 31 <FeedTimeMax>60000</FeedTimeMax> 31 32 </UnitAI> 33 <Guard disable=""/> 32 34 <UnitMotion> 33 35 <WalkSpeed>5.0</WalkSpeed> 34 36 <Run> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml
50 50 <order_attack>actor/ship/boat_move.xml</order_attack> 51 51 </SoundGroups> 52 52 </Sound> 53 <UnitAI> 54 <CanGuard>false</CanGuard> 55 </UnitAI> 53 56 <UnitMotion> 54 57 <WalkSpeed>8.5</WalkSpeed> 55 58 </UnitMotion> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml
47 47 <MaxDistance>10.0</MaxDistance> 48 48 <GainMultiplier>1.0</GainMultiplier> 49 49 </Trader> 50 <UnitAI> 51 <CanGuard>false</CanGuard> 52 </UnitAI> 50 53 <UnitMotion> 51 54 <WalkSpeed>10.5</WalkSpeed> 52 55 </UnitMotion> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege.xml
13 13 <GenericName>Siege</GenericName> 14 14 <RequiredTechnology>phase_city</RequiredTechnology> 15 15 </Identity> 16 <UnitAI> 17 <CanGuard>false</CanGuard> 18 </UnitAI> 16 19 <Position> 17 20 <Anchor>pitch-roll</Anchor> 18 21 </Position> -
binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml
40 40 <MaxDistance>2.0</MaxDistance> 41 41 <GainMultiplier>1.0</GainMultiplier> 42 42 </Trader> 43 <UnitAI> 44 <CanGuard>false</CanGuard> 45 </UnitAI> 43 46 <UnitMotion> 44 47 <WalkSpeed>8.0</WalkSpeed> 45 48 </UnitMotion>