Ticket #999: #999-2012-02-23.patch
File #999-2012-02-23.patch, 21.5 KB (added by , 12 years ago) |
---|
-
0ad/binaries/data/mods/public/simulation/helpers/Commands.js
66 66 }); 67 67 break; 68 68 69 case "heal": 70 //TODO fix this and UnitAI.CanHeal when healing allies is implemented 71 if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target)) 72 { 73 // This check is for debugging only! 74 warn("Invalid command: heal target is not owned by player "+player+": "+uneval(cmd)); 75 } 76 77 // See UnitAI.CanHeal for target checks 78 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 79 GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) { 80 cmpUnitAI.Heal(cmd.target, cmd.queued); 81 }); 82 break; 83 69 84 case "repair": 70 85 // This covers both repairing damaged buildings, and constructing unfinished foundations 71 86 if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) -
0ad/binaries/data/mods/public/simulation/components/GuiInterface.js
264 264 ret.barterMarket = { "prices": cmpBarter.GetPrices() }; 265 265 } 266 266 267 // TODO change ret.Healer to ret.Ability (see TODO-comment in unit-command.js 268 var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 269 if (cmpHeal) 270 { 271 ret.Healer = { "healableClasses": cmpHeal.GetHealableClasses() }; 272 } 273 267 274 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 268 275 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 269 276 -
0ad/binaries/data/mods/public/simulation/components/interfaces/Heal.js
1 Engine.RegisterInterface("Heal"); 2 3 //TODO register message type healed? -
0ad/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
5 5 Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 6 6 Engine.LoadComponentScript("interfaces/Foundation.js"); 7 7 Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); 8 Engine.LoadComponentScript("interfaces/Heal.js"); 8 9 Engine.LoadComponentScript("interfaces/Health.js"); 9 10 Engine.LoadComponentScript("interfaces/Promotion.js"); 10 11 Engine.LoadComponentScript("interfaces/RallyPoint.js"); -
0ad/binaries/data/mods/public/simulation/components/UnitAI.js
282 282 // so abandon this attack order 283 283 this.FinishOrder(); 284 284 }, 285 286 "Order.Heal": function(msg) { 287 // Check the target is alive 288 if (!this.TargetIsAlive(this.order.data.target)) 289 { 290 this.FinishOrder(); 291 return; 292 } 293 294 // Check if the target is in range 295 if (this.CheckTargetRange(this.order.data.target, IID_Heal)) 296 { 297 this.StopMoving(); 298 this.SetNextState("INDIVIDUAL.HEAL.HEALING"); 299 return; 300 } 301 302 // TODO Should we adhere to stances? 303 // check if standing ground stance -> abandon 285 304 305 // Try to move within heal range 306 if (this.MoveToTargetRange(this.order.data.target, IID_Heal)) 307 { 308 // We've started walking to the given point 309 this.SetNextState("INDIVIDUAL.HEAL.APPROACHING"); 310 return; 311 } 312 313 // We can't reach the target, and can't move towards it, 314 // so abandon this heal order 315 this.FinishOrder(); 316 }, 317 286 318 "Order.Gather": function(msg) { 287 319 288 320 // If the target is still alive, we need to kill it first … … 995 1027 }, 996 1028 }, 997 1029 1030 "HEAL": { 1031 // TODO How should we handle being attacked? maybe ignore attack like in COMBAT 1032 "APPROACHING": { 1033 "enter": function () { 1034 this.SelectAnimation("move"); 1035 this.StartTimer(1000, 1000); 1036 }, 1037 1038 "leave": function() { 1039 this.StopTimer(); 1040 }, 1041 1042 "Timer": function(msg) { 1043 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1044 { 1045 this.StopMoving(); 1046 this.FinishOrder(); 1047 1048 // TODO stances? 1049 } 1050 }, 1051 1052 "MoveCompleted": function() { 1053 this.SetNextState("HEALING"); 1054 }, 1055 1056 // "Attacked": function(msg) { 1057 //Should we ignore close enemys? 1058 // }, 1059 }, 1060 1061 "HEALING": { 1062 "enter": function() { 1063 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1064 this.healTimers = cmpHeal.GetTimers(); 1065 // this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation 1066 // this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 1067 this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 1068 this.FaceTowardsTarget(this.order.data.target); 1069 }, 1070 1071 "leave": function() { 1072 this.StopTimer(); 1073 }, 1074 1075 "Timer": function(msg) { 1076 var target = this.order.data.target; 1077 // Check the target is still alive and healable 1078 if (this.TargetIsAlive(target) && this.CanHeal(target)) 1079 { 1080 // Check if we can still reach the target 1081 if (this.CheckTargetRange(target, IID_Heal)) 1082 { 1083 this.FaceTowardsTarget(target); 1084 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1085 cmpHeal.PerformHeal(target); 1086 return; 1087 } 1088 // Can't reach it - chase it 1089 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1090 { 1091 if (this.MoveToTargetRange(target, IID_Heal)) 1092 { 1093 this.SetNextState("HEAL.CHASING"); 1094 return; 1095 } 1096 } 1097 } 1098 // Can't reach it, healed to max hp or doesn't exist any more - give up 1099 if (this.FinishOrder()) 1100 return; 1101 1102 // TODO implement autoheal next (like in ATTACK FindNewTargets()) 1103 // needs FindNewHealTargets() and maybe a something like RespondToTargetedEntities() ; figure out how SortEntitiesByPriority is working 1104 // if (this.FindNewHealTargets()) 1105 // return; 1106 1107 return; 1108 }, 1109 // TODO "Attacked" - what behaviour? 1110 // Should we ignore being attacked while healing? 1111 }, 1112 "CHASING": { 1113 "enter": function () { 1114 this.SelectAnimation("move"); 1115 this.StartTimer(1000, 1000); 1116 }, 1117 1118 "leave": function () { 1119 this.StopTimer(); 1120 }, 1121 "Timer": function(msg) { 1122 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1123 { 1124 this.StopMoving(); 1125 this.FinishOrder(); 1126 1127 } 1128 }, 1129 "MoveCompleted": function () { 1130 this.SetNextState("HEALING"); 1131 }, 1132 }, 1133 }, 1134 998 1135 // Returning to dropsite 999 1136 "RETURNRESOURCE": { 1000 1137 "APPROACHING": { … … 1746 1883 }; 1747 1884 1748 1885 /** 1886 * Returns true if the target exists and the current hitpoints are at maximum. 1887 */ 1888 UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 1889 { 1890 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 1891 if(!cmpHealth) 1892 return false; 1893 1894 return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 1895 }; 1896 1897 /** 1749 1898 * Returns true if the target exists and needs to be killed before 1750 1899 * beginning to gather resources from it. 1751 1900 */ … … 2359 2508 this.AddOrder("GatherNearPosition", { "type": type, "x": position[0], "z": position[1] }, queued); 2360 2509 } 2361 2510 2511 UnitAI.prototype.Heal = function(target, queued) 2512 { 2513 if (!this.CanHeal(target)) 2514 { 2515 // TODO should we really walk there if we can't heal 2516 // If resource for healing is implemented check if we have insufficient 2517 // resources and walk there. 2518 this.WalkToTarget(target, queued); 2519 return; 2520 } 2521 // We don't want to chase units that leave our visibility -> force = false 2522 this.AddOrder("Heal", { "target": target, "force": false }, queued); 2523 }; 2524 2362 2525 UnitAI.prototype.ReturnResource = function(target, queued) 2363 2526 { 2364 2527 if (!this.CanReturnResource(target, true)) … … 2555 2718 return true; 2556 2719 }; 2557 2720 2721 UnitAI.prototype.CanHeal = function(target) 2722 { 2723 // Formation controllers should always respond to commands 2724 // (then the individual units can make up their own minds) 2725 if (this.IsFormationController()) 2726 return true; 2727 2728 // Verify that we're able to respond to Heal commands 2729 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 2730 if (!cmpHeal) 2731 return false; 2732 2733 // Verify that the target is alive 2734 if (!this.TargetIsAlive(target)) 2735 return false; 2736 2737 // Verify that the target is owned by the same player as the entity 2738 // TODO or of an ally 2739 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2740 if (!cmpOwnership || !IsOwnedByPlayer(cmpOwnership.GetOwner(), target)) 2741 return false; 2742 2743 // Verify that the target is a healable class 2744 // We could also use cmpIdentity.GetClassesList but this way is cleaner 2745 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 2746 if (!cmpIdentity) 2747 return false; 2748 var healable = false; 2749 for each (var healableClass in cmpHeal.GetHealableClasses()) 2750 { 2751 if (cmpIdentity.HasClass(healableClass) != -1) 2752 { 2753 healable = true; 2754 } 2755 } 2756 if (!healable) 2757 return false; 2758 2759 // Check that the target is not at MaxHealth 2760 if (this.TargetIsAtMaxHitpoints(target)) 2761 return false; 2762 2763 return true; 2764 }; 2765 2558 2766 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2559 2767 { 2560 2768 // Formation controllers should always respond to commands -
0ad/binaries/data/mods/public/simulation/components/Heal.js
1 function Heal() {} 2 3 Heal.prototype.Schema = 4 "<a:help>Controls the healing abilities of the unit.</a:help>" + 5 "<a:example>" + 6 "<Range>20</Range>" + 7 "<Speed>5</Speed>" + 8 "<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" + 9 "</a:example>" + 10 "<element name='Range' a:help='Range (in metres) where healing is possible'>" + 11 "<ref name='nonNegativeDecimal'/>" + 12 "</element>" + 13 "<element name='Speed' a:help='Hp healed per second'>" + 14 "<ref name='nonNegativeDecimal'/>" + 15 "</element>" + 16 "<element name='HealableClasses'>" + 17 "<attribute name='datatype'>" + 18 "<value>tokens</value>" + 19 "</attribute>" + 20 "<text/>" + 21 "</element>"; 22 23 Heal.prototype.Init = function() 24 { 25 }; 26 27 Heal.prototype.Serialize = null; // we have no dynamic state to save 28 29 Heal.prototype.GetTimers = function(type) 30 { 31 var prepare = 1000; 32 var repeat = 1000; 33 return { "prepare": prepare, "repeat": repeat }; 34 } 35 36 Heal.prototype.GetRange = function() 37 { 38 var max = +this.template.Range; 39 var min = 0; 40 return { "max": max, "min": min }; 41 }; 42 43 Heal.prototype.GetHealableClasses = function() 44 { 45 var classes = this.template.HealableClasses._string; 46 return classes.split(/\s+/); 47 }; 48 49 /** 50 * Heal the target entity. This should only be called after a successful range 51 * check, and should only be called after GetTimers().repeat msec has passed 52 * since the last call to PerformHeal. 53 */ 54 Heal.prototype.PerformHeal = function(target) 55 { 56 this.CauseHeal({"target": target}); 57 }; 58 59 /** 60 * Heal target 61 */ 62 Heal.prototype.CauseHeal = function(data) 63 { 64 var cmpHealth = Engine.QueryInterface(data.target, IID_Health); 65 if(!cmpHealth) 66 return; 67 var targetState = cmpHealth.Increase(Math.max(0,this.template.Speed)); 68 //TODO add some Engine.PostMessage? - shouldn't be needed as Increase already posts a message 69 //TODO we need a sound file 70 // PlaySound("heal_impact", this.entity); 71 }; 72 73 Engine.RegisterComponentType(IID_Heal, "Heal", Heal); -
0ad/binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml
5 5 <Pierce>2.0</Pierce> 6 6 <Crush>2.0</Crush> 7 7 </Armour> 8 <Auras>8 <!-- <Auras> 9 9 <Heal> 10 10 <Radius>20</Radius> 11 11 <Speed>2000</Speed> 12 12 </Heal> 13 </Auras> 13 </Auras>--> 14 <Heal> 15 <Range>30</Range> 16 <Speed>5</Speed> 17 <HealableClasses datatype="tokens">Support Infantry</HealableClasses> 18 </Heal> 14 19 <Cost> 15 20 <Resources> 16 21 <metal>120</metal> -
0ad/binaries/data/mods/public/gui/session/session.xml
725 725 </object> 726 726 </object> 727 727 728 <object name="unitAbilityPanel" 729 size="14 12 100% 100%" 730 > 731 <object size="0 0 100% 100%"> 732 <repeat count="24"> 733 <object name="unitAbilityButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom"> 734 <object name="unitAbilityIcon[n]" type="image" ghost="true" size="3 3 43 43"/> 735 </object> 736 </repeat> 737 </object> 738 </object> 739 728 740 <object name="unitResearchPanel" 729 741 style="TranslucentPanelThinBorder" 730 742 size="0 100%-56 100% 100%" -
0ad/binaries/data/mods/public/gui/session/input.js
15 15 const ACTION_NONE = 0; 16 16 const ACTION_GARRISON = 1; 17 17 const ACTION_REPAIR = 2; 18 const ACTION_HEAL = 3; 18 19 var preSelectedAction = ACTION_NONE; 19 20 20 21 var INPUT_NORMAL = 0; … … 255 256 } 256 257 } 257 258 break; 259 case "heal": //TODO add something like && targetState.needsheal ? 260 if (isUnit(targetState) && playerOwned) //TODO fix for allies allyOwned 261 { 262 // TODO fix this for entState.Ability see unit_command.js and GuiInterface.js 263 var healableClasses = entState.Healer.healableClasses; 264 for each (var unitClass in targetState.identity.classes) 265 { 266 if (healableClasses.indexOf(unitClass) != -1) 267 { 268 return {"possible": true}; 269 } 270 } 271 } 272 break; 258 273 case "gather": 259 274 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 260 275 { … … 350 365 else 351 366 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 352 367 break; 368 case ACTION_HEAL: //TODO change disabled cursor 369 if (getActionInfo("heal", target).possible){ 370 return {"type": "heal", "cursor": "action-heal", "target": target}; 371 }else 372 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 373 break; 353 374 } 354 375 } 355 376 else if (Engine.HotkeyIsPressed("session.garrison")) … … 992 1013 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 993 1014 return true; 994 1015 1016 case "heal": 1017 Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 1018 //TODO play sound 1019 // Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 1020 return true; 1021 995 1022 case "build": // (same command as repair) 996 1023 case "repair": 997 1024 Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); … … 1204 1231 inputState = INPUT_PRESELECTEDACTION; 1205 1232 preSelectedAction = ACTION_REPAIR; 1206 1233 break; 1234 case "heal": 1235 inputState = INPUT_PRESELECTEDACTION; 1236 preSelectedAction = ACTION_HEAL; 1237 break; 1207 1238 case "unload-all": 1208 1239 unloadAll(entity); 1209 1240 break; -
0ad/binaries/data/mods/public/gui/session/unit_commands.js
5 5 const FORMATION = "Formation"; 6 6 const TRAINING = "Training"; 7 7 const CONSTRUCTION = "Construction"; 8 const ABILITY = "Ability"; 8 9 const COMMAND = "Command"; 9 10 const STANCE = "Stance"; 10 11 … … 20 21 const BARTER_ACTIONS = ["Sell", "Buy"]; 21 22 22 23 // The number of currently visible buttons (used to optimise showing/hiding) 23 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, " Command": 0, "Stance": 0};24 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Ability": 0, "Command": 0, "Stance": 0}; 24 25 25 26 // Unit panels are panels with row(s) of buttons 26 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", " Research", "Stance", "Command"];27 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Ability", "Research", "Stance", "Command"]; 27 28 28 29 // Indexes of resources to sell and buy on barter panel 29 30 var g_barterSell = 0; … … 174 175 numberOfItems = 24; 175 176 break; 176 177 178 case ABILITY: 179 if (numberOfItems > 24) 180 numberOfItems = 24; 181 break; 182 177 183 case COMMAND: 178 184 if (numberOfItems > 6) 179 185 numberOfItems = 6; … … 190 196 var item = items[i]; 191 197 var entType = ((guiName == "Queue")? item.template : item); 192 198 var template; 193 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" )199 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" && guiName != "Ability") 194 200 { 195 201 template = GetTemplateData(entType); 196 202 if (!template) … … 269 275 270 276 break; 271 277 278 case ABILITY: //TODO get some good tooltip 279 var tooltip = "abilitytooltip_unit_command.js"; 280 break; 281 272 282 case COMMAND: 273 283 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 274 284 if (item.name == "unload-all") … … 338 348 icon.sprite = "stretched:session/icons/single/" + item.icon; 339 349 340 350 } 351 else if (guiName == "Ability") 352 { 353 //TODO fix so that each ability has an icon with its name 354 icon.sprite = "stretched:session/portraits/abilities/heal.png"; 355 } 341 356 else if (template.icon) 342 357 { 343 358 icon.sprite = "stretched:session/portraits/" + template.icon; … … 511 526 setupUnitBarterPanel(entState); 512 527 } 513 528 529 // TODO change this to entState.Ability? to just have GuiInterface.js 530 // build the Ability list and use entState.Ability instead of ["heal] 531 if (entState.Healer) 532 { 533 setupUnitPanel("Ability", usedPanels, entState, ["heal"], function (item) { performCommand(entState.id, "heal"); });//item.name); }); 534 } 535 514 536 if (entState.buildEntities && entState.buildEntities.length) 515 537 { 516 538 setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement); -
0ad/binaries/data/mods/public/art/textures/cursors/action-heal.txt
1 1 1 -
0ad/binaries/data/mods/public/art/textures/ui/session/portraits/abilities/heal.png
Cannot display: file marked as a binary type. svn:mime-type = image/png