Ticket #2034: escort.diff
File escort.diff, 23.8 KB (added by , 11 years ago) |
---|
-
binaries/data/config/default.cfg
278 278 hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting 279 279 hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise 280 280 hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise 281 hotkey.selection.includeorder = F1 ; Include in selection the units with special orders (i.e. escort) 282 hotkey.session.escort = AltGr ; Modifier to escort/guard when clicking on unit/building 281 283 hotkey.timewarp.fastforward = Space ; If timewarp mode enabled, speed up the game 282 284 hotkey.timewarp.rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game 283 285 -
binaries/data/mods/public/gui/session/input.js
416 416 if (entState.attack && targetState.hitpoints && (enemyOwned || neutralOwned)) 417 417 return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": target})}; 418 418 break; 419 case "escort": 420 if (targetState.escort && playerOwned) 421 { 422 var allowedClasses = targetState.escort.allowedClasses; 423 for each (var unitClass in entState.identity.classes) 424 { 425 if (allowedClasses.indexOf(unitClass) != -1) 426 { 427 return {"possible": true}; 428 } 429 } 430 } 431 break; 419 432 } 420 433 } 421 434 if (action == "move" || action == "attack-move") … … 503 516 { 504 517 return {"type": "attack-move", "cursor": "action-attack-move"}; 505 518 } 519 else if (Engine.HotkeyIsPressed("session.escort") && getActionInfo("escort", target).possible) 520 { 521 return {"type": "escort", "cursor": "action-guard", "target": target}; 522 } 506 523 else 507 524 { 508 525 if ((actionInfo = getActionInfo("setup-trade-route", target)).possible) … … 653 670 return preferredEnts; 654 671 } 655 672 673 // Removes any units with special orders (i.e. escort) 674 function getNoOrderEntities(ents) 675 { 676 var noOrderEnts = []; 677 for each (var ent in ents) 678 { 679 var keep = true; 680 var entState = GetEntityState(ent); 681 if (entState.unitAI) 682 { 683 for each (var order in entState.unitAI.orders) 684 { 685 if (order.type == "Escort") 686 { 687 keep = false; 688 break; 689 } 690 } 691 } 692 if (keep) 693 noOrderEnts.push(ent); 694 } 695 return noOrderEnts; 696 } 697 656 698 // Removes any support units from the passed list of entities 657 699 function getMilitaryEntities(ents) 658 700 { … … 750 792 } 751 793 } 752 794 795 // By default, we do not want to select units with special orders 796 if (!Engine.HotkeyIsPressed("selection.includeorder")) 797 ents = getNoOrderEntities(ents); 798 753 799 // Remove the bandbox hover highlighting 754 800 g_Selection.setHighlightList([]); 755 801 … … 1206 1252 1207 1253 // TODO: Should we handle "control all units" here as well? 1208 1254 ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false); 1255 1256 // By default, we do not want to select units with special orders 1257 if (!Engine.HotkeyIsPressed("selection.includeorder")) 1258 ents = getNoOrderEntities(ents); 1209 1259 } 1210 1260 else 1211 1261 { … … 1381 1431 Engine.GuiInterfaceCall("PlaySound", { "name": "order_garrison", "entity": selection[0] }); 1382 1432 return true; 1383 1433 1434 case "escort": 1435 Engine.PostNetworkCommand({"type": "escort", "entities": selection, "target": action.target, "queued": queued}); 1436 //Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 1437 return true; 1438 1384 1439 case "set-rallypoint": 1385 1440 var pos = undefined; 1386 1441 // if there is a position set in the action then use this so that when setting a -
binaries/data/mods/public/simulation/templates/template_unit_support.xml
15 15 <GenericName>Support</GenericName> 16 16 <Classes datatype="tokens">Support Organic</Classes> 17 17 </Identity> 18 <Escort> 19 <List datatype="tokens">Support Infantry Cavalry</List> 20 <LandEscortRange>8</LandEscortRange> 21 </Escort> 18 22 <Loot> 19 23 <xp>10</xp> 20 24 <food>1</food> -
binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
51 51 <Classes datatype="tokens">Infantry CitizenSoldier Worker Organic</Classes> 52 52 <Rank>Basic</Rank> 53 53 </Identity> 54 <Escort> 55 <List datatype="tokens">Support Infantry Cavalry</List> 56 <LandEscortRange>8</LandEscortRange> 57 </Escort> 54 58 <Loot> 55 59 <xp>100</xp> 56 60 <food>5</food> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship.xml
33 33 <GenericName>Ship</GenericName> 34 34 <Classes datatype="tokens">Ship</Classes> 35 35 </Identity> 36 <Escort> 37 <List datatype="tokens">Ship</List> 38 <NavalEscortRange>20</NavalEscortRange> 39 </Escort> 36 40 <Obstruction> 37 41 <Unit radius="8.0"/> 38 42 </Obstruction> -
binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
33 33 <GenericName>Cavalry</GenericName> 34 34 <Rank>Basic</Rank> 35 35 </Identity> 36 <Escort> 37 <List datatype="tokens">Cavalry</List> 38 <LandEscortRange>8</LandEscortRange> 39 </Escort> 36 40 <Loot> 37 41 <xp>130</xp> 38 42 <food>10</food> -
binaries/data/mods/public/simulation/templates/template_structure.xml
14 14 <DefaultArrowCount>0</DefaultArrowCount> 15 15 <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier> 16 16 </BuildingAI> 17 <Escort> 18 <List datatype="tokens">Infantry</List> 19 <LandEscortRange>12</LandEscortRange> 20 </Escort> 17 21 <BuildRestrictions> 18 22 <PlacementType>land</PlacementType> 19 23 <Territory>own</Territory> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml
14 14 <GenericName>Mechanical</GenericName> 15 15 <Classes datatype="tokens">Mechanical</Classes> 16 16 </Identity> 17 <Escort> 18 <List datatype="tokens">Infantry Cavalry</List> 19 <LandEscortRange>8</LandEscortRange> 20 </Escort> 17 21 <Loot> 18 22 <xp>60</xp> 19 23 <food>0</food> -
binaries/data/mods/public/simulation/templates/template_unit_champion.xml
8 8 <Classes datatype="tokens">Champion Organic</Classes> 9 9 <RequiredTechnology>phase_city</RequiredTechnology> 10 10 </Identity> 11 <Escort> 12 <List datatype="tokens">Support Infantry Cavalry</List> 13 <LandEscortRange>8</LandEscortRange> 14 </Escort> 11 15 <Loot> 12 16 <xp>150</xp> 13 17 <food>10</food> -
binaries/data/mods/public/simulation/templates/template_unit_hero.xml
38 38 <Classes datatype="tokens">Hero Organic</Classes> 39 39 <RequiredTechnology>phase_city</RequiredTechnology> 40 40 </Identity> 41 <Escort> 42 <List datatype="tokens">Support Infantry Cavalry</List> 43 <LandEscortRange>8</LandEscortRange> 44 </Escort> 41 45 <Loot> 42 46 <xp>400</xp> 43 47 <food>10</food> -
binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml
26 26 <Classes datatype="tokens">Village Naval Market NavalMarket Dock</Classes> 27 27 <Icon>structures/dock.png</Icon> 28 28 </Identity> 29 <Escort> 30 <List datatype="tokens">Infantry Ship</List> 31 <LandEscortRange>12</LandEscortRange> 32 <NavalEscortRange>20</NavalEscortRange> 33 </Escort> 29 34 <Loot> 30 35 <xp>100</xp> 31 36 <food>0</food> -
binaries/data/mods/public/simulation/helpers/Commands.js
319 319 } 320 320 break; 321 321 322 case "escort": 323 // Verify that the building can be controlled by the player 324 if (CanControlUnit(cmd.target, player, controlAllUnits)) 325 { 326 GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) { 327 cmpUnitAI.Escort(cmd.target, cmd.queued); 328 }); 329 } 330 else if (g_DebugCommands) 331 { 332 warn("Invalid command: escort target cannot be controlled by player "+player+": "+uneval(cmd)); 333 } 334 break; 335 322 336 case "stop": 323 337 GetFormationUnitAIs(entities, player).forEach(function(cmpUnitAI) { 324 338 cmpUnitAI.Stop(cmd.queued); -
binaries/data/mods/public/simulation/components/UnitAI.js
160 160 // ignore 161 161 }, 162 162 163 "EscortedAttacked": function(msg) { 164 // ignore 165 }, 166 163 167 // Formation handlers: 164 168 165 169 "FormationLeave": function(msg) { … … 328 332 } 329 333 }, 330 334 335 "Order.Escort": function(msg) { 336 var cmpEscort = Engine.QueryInterface(this.order.data.target, IID_Escort); 337 if (!cmpEscort || !cmpEscort.AllowedToEscort(this.entity)) 338 { 339 this.FinishOrder(); 340 return; 341 } 342 this.escortRange = cmpEscort.GetRange(this.entity); 343 344 var ok = this.MoveToTargetRangeExplicit(this.order.data.target, 0, this.escortRange); 345 if (ok) 346 this.SetNextState("INDIVIDUAL.ESCORT.ESCORTING"); 347 else 348 this.SetNextState("INDIVIDUAL.ESCORT.GUARDING"); 349 }, 350 331 351 "Order.Flee": function(msg) { 332 352 // We use the distance between the enities to account for ranged attacks 333 353 var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance); … … 685 705 this.FinishOrder(); 686 706 }, 687 707 708 "Order.Escort": function(msg) { 709 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 710 cmpFormation.CallMemberFunction("Escort", [msg.data.target, false]); 711 cmpFormation.Disband(); 712 }, 713 688 714 "Order.Stop": function(msg) { 689 715 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 690 716 cmpFormation.CallMemberFunction("Stop", [false]); … … 1148 1174 } 1149 1175 }, 1150 1176 1177 "EscortedAttacked": function(msg) { 1178 this.RespondToTargetedEntities([msg.data.attacker]); 1179 }, 1180 1151 1181 "IDLE": { 1152 1182 "enter": function() { 1153 1183 // Switch back to idle animation to guarantee we won't … … 1252 1282 }, 1253 1283 }, 1254 1284 1285 "ESCORT": { 1286 "enter": function () { 1287 if (this.escort && this.escort != this.order.data.target) 1288 { 1289 Engine.PostMessage(this.escort, MT_EscortChanged, { "escort": this.entity }); 1290 this.escort = undefined; 1291 } 1292 1293 if (!this.escort) 1294 { 1295 this.escort = this.order.data.target; 1296 Engine.PostMessage(this.escort, MT_EscortChanged, { "escort": this.entity }); 1297 } 1298 1299 this.order.data.status = "running"; 1300 }, 1301 1302 "leave": function () { 1303 // if not dead, check if it's just a temporary leave (i.e. to attack or repair) 1304 if (this.TargetIsAlive(this.entity)) 1305 { 1306 for each (var order in this.orderQueue) 1307 { 1308 if (order.type == "Escort" && order.data.target == this.escort && order.data.status == "running") 1309 return; 1310 } 1311 } 1312 Engine.PostMessage(this.escort, MT_EscortChanged, { "escort": this.entity }); 1313 delete this.escortRange; 1314 delete this.escort; 1315 }, 1316 1317 "ESCORTING": { 1318 "enter": function () { 1319 this.StartTimer(0, 1000); 1320 this.SelectAnimation("move"); 1321 var cmpPosition = Engine.QueryInterface(this.escort, IID_Position); 1322 if (cmpPosition && cmpPosition.IsInWorld()) 1323 { 1324 var pos = cmpPosition.GetPosition(); 1325 this.SetHeldPosition(pos.x, pos.z); 1326 } 1327 // adapt the speed to the one of the target 1328 var cmpUnitAI = Engine.QueryInterface(this.escort, IID_UnitAI); 1329 if (cmpUnitAI) 1330 { 1331 var speed = cmpUnitAI.GetWalkSpeed(); 1332 if (speed < this.GetWalkSpeed()) 1333 this.SetMoveSpeed(speed); 1334 } 1335 }, 1336 1337 "Timer": function(msg) { 1338 // Check the target is alive 1339 if (!this.TargetIsAlive(this.escort)) 1340 { 1341 this.FinishOrder(); 1342 return; 1343 } 1344 var cmpPosition = Engine.QueryInterface(this.escort, IID_Position); 1345 if (cmpPosition && cmpPosition.IsInWorld()) 1346 { 1347 var pos = cmpPosition.GetPosition(); 1348 this.SetHeldPosition(pos.x, pos.z); 1349 } 1350 this.FindNewTargets(); 1351 }, 1352 1353 "leave": function(msg) { 1354 this.SetMoveSpeed(this.GetWalkSpeed()); 1355 this.StopTimer(); 1356 }, 1357 1358 "MoveCompleted": function() { 1359 var ok = this.MoveToTargetRangeExplicit(this.escort, 0, this.escortRange); 1360 if (!ok) 1361 this.SetNextState("GUARDING"); 1362 }, 1363 }, 1364 1365 "GUARDING": { 1366 "enter": function () { 1367 this.StartTimer(1000, 1000); 1368 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 1369 if (cmpPosition && cmpPosition.IsInWorld()) 1370 { 1371 var pos = cmpPosition.GetPosition(); 1372 this.SetHeldPosition(pos.x, pos.z); 1373 } 1374 this.SelectAnimation("idle"); 1375 if (this.FindNewTargets()) 1376 return true; 1377 return false; 1378 }, 1379 1380 "LosRangeUpdate": function(msg) { 1381 // Start attacking one of the newly-seen enemy (if any) 1382 if (this.GetStance().targetVisibleEnemies) 1383 this.AttackEntitiesByPreference(msg.data.added); 1384 }, 1385 1386 "LosGaiaRangeUpdate": function(msg) { 1387 // Start attacking one of the newly-seen enemy (if any) 1388 if (this.GetStance().targetVisibleEnemies) 1389 this.AttackGaiaEntitiesByPreference(msg.data.added); 1390 }, 1391 1392 "Timer": function(msg) { 1393 // Check the target is alive 1394 if (!this.TargetIsAlive(this.escort)) 1395 { 1396 this.FinishOrder(); 1397 return; 1398 } 1399 // then check is the target has moved 1400 var ok = this.MoveToTargetRangeExplicit(this.escort, 0, this.escortRange); 1401 if (ok) 1402 this.SetNextState("ESCORTING"); 1403 else 1404 { 1405 // if nothing else to do, check if the escorted needs to be healed or repaired 1406 var cmpHealth = Engine.QueryInterface(this.escort, IID_Health); 1407 if (cmpHealth && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())) 1408 { 1409 if (this.CanHeal(this.escort)) 1410 this.PushOrderFront("Heal", { "target": this.escort, "force": false }); 1411 else if (this.CanRepair(this.escort) && cmpHealth.IsRepairable()) 1412 this.PushOrderFront("Repair", { "target": this.escort, "autocontinue": false, "force": false }); 1413 } 1414 } 1415 }, 1416 1417 "leave": function(msg) { 1418 this.StopTimer(); 1419 }, 1420 }, 1421 }, 1422 1255 1423 "FLEEING": { 1256 1424 "enter": function() { 1257 1425 this.PlaySound("panic"); … … 2099 2267 return false; 2100 2268 }, 2101 2269 2270 "LosRangeUpdate": function(msg) { 2271 // if we are escorting, stop repairing to be able to fight this ennemy 2272 if (this.escort && this.GetStance().targetVisibleEnemies) 2273 this.FinishOrder(); 2274 }, 2275 2276 "LosGaiaRangeUpdate": function(msg) { 2277 // if we are escorting, stop repairing to be able to fight this ennemy 2278 if (this.escort && this.GetStance().targetVisibleEnemies) 2279 this.FinishOrder(); 2280 }, 2281 2102 2282 "leave": function() { 2103 2283 var cmpFoundation = Engine.QueryInterface(this.repairTarget, IID_Foundation); 2104 2284 if (cmpFoundation) … … 2978 3158 UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); 2979 3159 }; 2980 3160 3161 UnitAI.prototype.OnEscortedAttacked = function(msg) 3162 { 3163 UnitFsm.ProcessMessage(this, {"type": "EscortedAttacked", "data": msg.data}); 3164 }; 3165 2981 3166 UnitAI.prototype.OnHealthChanged = function(msg) 2982 3167 { 2983 3168 UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to}); … … 3519 3704 if (force) 3520 3705 return false; 3521 3706 3707 // If we are escorting, don't stop as long as the attacker is within attack range of the escorted 3708 if (this.escort) 3709 { 3710 var cmpUnitAI = Engine.QueryInterface(target, IID_UnitAI); 3711 var cmpAttack = Engine.QueryInterface(target, IID_Attack); 3712 for each (var type in cmpAttack.GetAttackTypes()) 3713 if (cmpUnitAI.CheckTargetRange(this.escort, IID_Attack, type)) 3714 return false; 3715 } 3716 3522 3717 // Stop if we're in hold-ground mode and it's too far from the holding point 3523 3718 if (this.GetStance().respondHoldGround) 3524 3719 { … … 3553 3748 if (this.GetStance().respondChase) 3554 3749 return true; 3555 3750 3751 if (this.escort) 3752 { 3753 var cmpUnitAI = Engine.QueryInterface(target, IID_UnitAI); 3754 var cmpAttack = Engine.QueryInterface(target, IID_Attack); 3755 for each (var type in cmpAttack.GetAttackTypes()) 3756 if (cmpUnitAI.CheckTargetRange(this.escort, IID_Attack, type)) 3757 return true; 3758 } 3759 3556 3760 if (force) 3557 3761 return true; 3558 3762 … … 3653 3857 3654 3858 case "WalkToTarget": 3655 3859 case "WalkToTargetRange": // This doesn't move to the target (just into range), but a later order will. 3860 case "Escort": 3656 3861 case "Flee": 3657 3862 case "LeaveFoundation": 3658 3863 case "Attack": … … 3698 3903 }; 3699 3904 3700 3905 /** 3906 * Adds escort order to the queue, forced by the player. 3907 */ 3908 UnitAI.prototype.Escort = function(target, queued) 3909 { 3910 this.AddOrder("Escort", { "target": target, "status": "waiting", "force": false }, queued); 3911 }; 3912 3913 /** 3701 3914 * Adds walk order to queue, forced by the player. 3702 3915 */ 3703 3916 UnitAI.prototype.Walk = function(x, z, queued) … … 4161 4374 { 4162 4375 if (this.heldPosition) 4163 4376 { 4164 this. AddOrder("Walk", { "x": this.heldPosition.x, "z": this.heldPosition.z, "force": false }, false);4377 this.PushOrderFront("Walk", { "x": this.heldPosition.x, "z": this.heldPosition.z, "force": false }); 4165 4378 return true; 4166 4379 } 4167 4380 return false; -
binaries/data/mods/public/simulation/components/Escort.js
1 function Escort() {} 2 3 Escort.prototype.Schema = 4 "<element name='List' a:help='Classes of entities which are allowed to escort this escorted (from Identity)'>" + 5 "<attribute name='datatype'>" + 6 "<value>tokens</value>" + 7 "</attribute>" + 8 "<text/>" + 9 "</element>" + 10 "<optional><element name='LandEscortRange' a:help='Distance between the escort and this escorted'>" + 11 "<ref name='nonNegativeDecimal'/>" + 12 "</element></optional>" + 13 "<optional><element name='NavalEscortRange' a:help='Distance between the escort ship and this escorted'>" + 14 "<ref name='nonNegativeDecimal'/>" + 15 "</element></optional>"; 16 17 Escort.prototype.Init = function() 18 { 19 this.escortList = []; 20 }; 21 22 Escort.prototype.GetRange = function(entity) 23 { 24 var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList(); 25 if (entityClasses.indexOf("Ship") == -1) 26 return (+this.template.LandEscortRange || 8); 27 else 28 return (+this.template.NavalEscortRange || 20); 29 }; 30 31 /** 32 * Returns an array of unit classes which can escort this entity. 33 * Obtained from the entity's template 34 */ 35 Escort.prototype.GetAllowedClassesList = function() 36 { 37 var string = this.template.List._string || ""; 38 return string.split(/\s+/); 39 }; 40 41 /** 42 * Checks if an entity can be allowed to escort this escorted 43 * based on its class 44 */ 45 Escort.prototype.AllowedToEscort = function(entity) 46 { 47 var allowedClasses = this.GetAllowedClassesList(); 48 var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList(); 49 for each (var allowedClass in allowedClasses) 50 { 51 if (entityClasses.indexOf(allowedClass) != -1) 52 return true; 53 } 54 return false; 55 }; 56 57 Escort.prototype.OnAttacked = function(msg) 58 { 59 for each (var escort in this.escortList) 60 Engine.PostMessage(escort, MT_EscortedAttacked, { "escorted": this.entity, "data": msg }); 61 }; 62 63 Escort.prototype.OnEscortChanged = function(msg) 64 { 65 if (this.escortList.indexOf(msg.escort) == -1) 66 this.escortList.push(msg.escort); 67 else 68 this.escortList.splice(this.escortList.indexOf(msg.escort), 1); 69 }; 70 71 Escort.prototype.OnGlobalEntityRenamed = function(msg) 72 { 73 if (this.escortList.length == 0) 74 return; 75 var entityIndex = this.escortList.indexOf(msg.entity); 76 if (entityIndex != -1) 77 this.escortList[entityIndex] = msg.newentity; 78 }; 79 80 Engine.RegisterComponentType(IID_Escort, "Escort", Escort); -
binaries/data/mods/public/simulation/components/GuiInterface.js
323 323 if (cmpUnitAI.isGarrisoned && ret.player) 324 324 ret.template = "p" + ret.player + "&" + ret.template; 325 325 } 326 327 var cmpEscort = Engine.QueryInterface(ent, IID_Escort); 328 if (cmpEscort) 329 { 330 ret.escort = { 331 "allowedClasses": cmpEscort.GetAllowedClassesList() 332 } 333 } 326 334 327 335 var cmpGate = Engine.QueryInterface(ent, IID_Gate); 328 336 if (cmpGate) -
binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
11 11 // Message of the form { "to": orderData }. 12 12 // sent whenever the unit changes state 13 13 Engine.RegisterMessageType("UnitAIOrderDataChanged"); 14 15 // Message of the form { "escort": escort }. 16 // sent whenever an escort is added or removed 17 Engine.RegisterMessageType("EscortChanged"); -
binaries/data/mods/public/simulation/components/interfaces/Escort.js
1 Engine.RegisterInterface("Escort"); 2 3 // Message of the form { "escorted": entity, "data": msg }, 4 // sent whenever an escorted unit is attacked 5 Engine.RegisterMessageType("EscortedAttacked");