Ticket #999: #999-2012-03-07.patch
File #999-2012-03-07.patch, 46.4 KB (added by , 12 years ago) |
---|
-
binaries/data/mods/public/art/textures/cursors/action-heal-disabled.txt
1 1 1 -
binaries/data/mods/public/art/textures/cursors/action-heal.txt
1 1 1 -
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": 260 // The check if the target is unhealable is done by targetState.needsHeal 261 if (entState.Ability && isUnit(targetState) && targetState.needsHeal && (playerOwned || allyOwned)) 262 { 263 var unhealableClasses = entState.Ability.Healer.unhealableClasses; 264 for each (var unitClass in targetState.identity.classes) 265 { 266 if (unhealableClasses.indexOf(unitClass) != -1) 267 { 268 return {"possible": false}; 269 } 270 } 271 272 var healableClasses = entState.Ability.Healer.healableClasses; 273 for each (var unitClass in targetState.identity.classes) 274 { 275 if (healableClasses.indexOf(unitClass) != -1) 276 { 277 return {"possible": true}; 278 } 279 } 280 } 281 break; 258 282 case "gather": 259 283 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 260 284 { … … 350 374 else 351 375 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 352 376 break; 377 case ACTION_HEAL: 378 if (getActionInfo("heal", target).possible) 379 return {"type": "heal", "cursor": "action-heal", "target": target}; 380 else 381 return {"type": "none", "cursor": "action-heal-disabled", "target": undefined}; 382 break; 353 383 } 354 384 } 355 385 else if (Engine.HotkeyIsPressed("session.garrison")) … … 372 402 return {"type": "build", "cursor": "action-repair", "target": target}; 373 403 else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible) 374 404 return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position}; 405 else if (getActionInfo("heal", target).possible) 406 return {"type": "heal", "cursor": "action-heal", "target": target}; 375 407 else if (getActionInfo("attack", target).possible) 376 408 return {"type": "attack", "cursor": "action-attack", "target": target}; 377 409 else if (getActionInfo("unset-rallypoint", target).possible) … … 993 1025 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 994 1026 return true; 995 1027 1028 case "promote": 1029 Engine.PostNetworkCommand({"type": "promote", "entities": selection, "target": action.target, "queued": queued}); 1030 // TODO:Play a sound? 1031 return true; 1032 1033 case "heal": 1034 Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 1035 // TODO: Play a sound? 1036 // Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 1037 return true; 1038 996 1039 case "build": // (same command as repair) 997 1040 case "repair": 998 1041 Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); … … 1205 1248 inputState = INPUT_PRESELECTEDACTION; 1206 1249 preSelectedAction = ACTION_REPAIR; 1207 1250 break; 1251 case "heal": 1252 inputState = INPUT_PRESELECTEDACTION; 1253 preSelectedAction = ACTION_HEAL; 1254 break; 1255 case "promote": 1256 doAction({ "type": "promote"}) 1257 break; 1208 1258 case "unload-all": 1209 1259 unloadAll(entity); 1210 1260 break; -
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%" -
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: 279 // TODO read tooltips from some file or template based on 'item' 280 var tooltip; 281 switch(item) 282 { 283 case "heal": 284 tooltip = "Heal units"; 285 break; 286 case "promote": 287 tooltip = "Promote this unit"; 288 break; 289 default: 290 tooltip = "No tooltip defined"; 291 break; 292 } 293 break; 294 272 295 case COMMAND: 273 296 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 274 297 if (item.name == "unload-all") … … 338 361 icon.sprite = "stretched:session/icons/single/" + item.icon; 339 362 340 363 } 364 else if (guiName == "Ability") 365 { 366 icon.sprite = "stretched:session/icons/single/"+item+".png"; 367 } 341 368 else if (template.icon) 342 369 { 343 370 icon.sprite = "stretched:session/portraits/" + template.icon; … … 511 538 setupUnitBarterPanel(entState); 512 539 } 513 540 541 if (entState.Ability) 542 { 543 var abilities = []; 544 if (entState.Ability.Healer) 545 abilities.push("heal"); 546 if (entState.Ability.Promote) 547 abilities.push("promote"); 548 setupUnitPanel("Ability", usedPanels, entState, abilities, function (item) { performCommand(entState.id, item); }); 549 } 550 514 551 if (entState.buildEntities && entState.buildEntities.length) 515 552 { 516 553 setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement); -
binaries/data/mods/public/simulation/components/GarrisonHolder.js
308 308 var cmpHealth = Engine.QueryInterface(entity, IID_Health); 309 309 if (cmpHealth) 310 310 { 311 if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 311 // We do not want to heal unhealable units 312 if (!cmpHealth.IsUnhealable() && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 312 313 cmpHealth.Increase(this.healRate); 313 314 } 314 315 } -
binaries/data/mods/public/simulation/components/GuiInterface.js
155 155 ret.hitpoints = cmpHealth.GetHitpoints(); 156 156 ret.maxHitpoints = cmpHealth.GetMaxHitpoints(); 157 157 ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()); 158 ret.needsHeal = !cmpHealth.IsUnhealable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()); 158 159 } 159 160 160 161 var cmpAttack = Engine.QueryInterface(ent, IID_Attack); … … 264 265 ret.barterMarket = { "prices": cmpBarter.GetPrices() }; 265 266 } 266 267 268 // Abilities 269 var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 270 // Check if we have abilities 271 if (cmpHeal || (cmpHeal && cmpPromotion)) 272 ret.Ability = []; 273 if (cmpHeal) 274 { 275 ret.Ability.Healer = { 276 "unhealableClasses": cmpHeal.GetUnhealableClasses(), 277 "healableClasses": cmpHeal.GetHealableClasses(), 278 }; 279 } 280 281 // TODO remove this; This is just used to test/demonstrate the extensibility 282 // of the Ability system 283 // promoteAbility (just for healers) 284 if (cmpPromotion && cmpHeal) 285 { 286 ret.Ability.Promote = true ; 287 } 288 267 289 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 268 290 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 269 291 -
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 "<HP>5</HP>" + 8 "<Rate>2000</Rate>" + 9 "<UnhealableClasses datatype=\"tokens\">Cavalry</UnhealableClasses>" + 10 "<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" + 11 "</a:example>" + 12 "<element name='Range' a:help='Range (in metres) where healing is possible'>" + 13 "<ref name='nonNegativeDecimal'/>" + 14 "</element>" + 15 "<element name='HP' a:help='Hitpoints healed per Rate'>" + 16 "<ref name='nonNegativeDecimal'/>" + 17 "</element>" + 18 "<element name='Rate' a:help='A heal is performed every Rate ms'>" + 19 "<ref name='nonNegativeDecimal'/>" + 20 "</element>" + 21 "<element name='UnhealableClasses' a:help='If the target has any of these classes it can not be healed (even if it has a class from HealableClasses)'>" + 22 "<attribute name='datatype'>" + 23 "<value>tokens</value>" + 24 "</attribute>" + 25 "<text/>" + 26 "</element>" + 27 "<element name='HealableClasses' a:help='The target must have one of these classes to be healable'>" + 28 "<attribute name='datatype'>" + 29 "<value>tokens</value>" + 30 "</attribute>" + 31 "<text/>" + 32 "</element>"; 33 34 Heal.prototype.Init = function() 35 { 36 }; 37 38 Heal.prototype.Serialize = null; // we have no dynamic state to save 39 40 Heal.prototype.GetTimers = function() 41 { 42 var prepare = 1000; 43 var repeat = +(this.template.Rate || 1000); 44 return { "prepare": prepare, "repeat": repeat }; 45 }; 46 47 Heal.prototype.GetRange = function() 48 { 49 var max = +this.template.Range; 50 var min = 0; 51 return { "max": max, "min": min }; 52 }; 53 54 Heal.prototype.GetUnhealableClasses = function() 55 { 56 var classes = this.template.UnhealableClasses._string; 57 // If we have no unhealable classes defined classes is undefined 58 return classes?classes.split(/\s+/):""; 59 }; 60 61 Heal.prototype.GetHealableClasses = function() 62 { 63 var classes = this.template.HealableClasses._string; 64 return classes.split(/\s+/); 65 }; 66 67 /** 68 * Heal the target entity. This should only be called after a successful range 69 * check, and should only be called after GetTimers().repeat msec has passed 70 * since the last call to PerformHeal. 71 */ 72 Heal.prototype.PerformHeal = function(target) 73 { 74 var cmpHealth = Engine.QueryInterface(target, IID_Health); 75 if (!cmpHealth) 76 return; 77 var targetState = cmpHealth.Increase(Math.max(0,this.template.HP)); 78 79 // Add XP 80 var cmpLoot = Engine.QueryInterface(target, IID_Loot); 81 var cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion); 82 if (targetState.old && targetState.new && cmpLoot && cmpPromotion) 83 { 84 // HP healed * XP per HP 85 cmpPromotion.IncreaseXp((targetState.new-targetState.old)*(cmpLoot.GetXp()/cmpHealth.GetMaxHitpoints())); 86 } 87 //TODO we need a sound file 88 // PlaySound("heal_impact", this.entity); 89 }; 90 91 Engine.RegisterComponentType(IID_Heal, "Heal", Heal); -
binaries/data/mods/public/simulation/components/Health.js
25 25 "<value a:help='Remain in the world with 0 health'>remain</value>" + 26 26 "</choice>" + 27 27 "</element>" + 28 "<element name=' Healable' a:help='Indicates that the entity canbe healed by healer units'>" +28 "<element name='Unhealable' a:help='Indicates that the entity can not be healed by healer units'>" + 29 29 "<data type='boolean'/>" + 30 30 "</element>" + 31 31 "<element name='Repairable' a:help='Indicates that the entity can be repaired by builder units'>" + … … 72 72 return (this.template.Repairable == "true"); 73 73 }; 74 74 75 Health.prototype.IsUnhealable = function() 76 { 77 return (this.template.Unhealable == "true"); 78 }; 79 75 80 Health.prototype.Kill = function() 76 81 { 77 82 this.Reduce(this.hitpoints); … … 131 136 { 132 137 // If we're already dead, don't allow resurrection 133 138 if (this.hitpoints == 0) 134 return ;139 return false; 135 140 136 141 var old = this.hitpoints; 137 142 this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); 138 143 139 144 Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints }); 145 // We return the old and the actual hp 146 return { "old": old, "new": this.hitpoints}; 140 147 }; 141 148 142 149 //// Private functions //// -
binaries/data/mods/public/simulation/components/Loot.js
21 21 22 22 Loot.prototype.GetXp = function() 23 23 { 24 return this.template.xp;24 return +(this.template.xp || 0); 25 25 }; 26 26 27 27 Loot.prototype.GetResources = function() -
binaries/data/mods/public/simulation/components/UnitAI.js
139 139 "HealthChanged": function(msg) { 140 140 // ignore 141 141 }, 142 143 // TODO: This is part of a really UGLY EVIL HACK 144 "GlobalHealthChanged": function() { 145 // ignore 146 }, 142 147 143 148 "EntityRenamed": function(msg) { 144 149 // ignore … … 283 288 this.FinishOrder(); 284 289 }, 285 290 291 "Order.Heal": function(msg) { 292 // Check the target is alive 293 if (!this.TargetIsAlive(this.order.data.target)) 294 { 295 this.FinishOrder(); 296 return; 297 } 298 299 // Check if the target is in range 300 if (this.CheckTargetRange(this.order.data.target, IID_Heal)) 301 { 302 this.StopMoving(); 303 this.SetNextState("INDIVIDUAL.HEAL.HEALING"); 304 return; 305 } 306 307 // If we can't reach the target, but are standing ground, 308 // then abandon this heal order 309 if (this.GetStance().respondStandGround && !this.order.data.force) 310 { 311 this.FinishOrder(); 312 return; 313 } 314 315 // Try to move within heal range 316 if (this.MoveToTargetRange(this.order.data.target, IID_Heal)) 317 { 318 // We've started walking to the given point 319 this.SetNextState("INDIVIDUAL.HEAL.APPROACHING"); 320 return; 321 } 322 323 // We can't reach the target, and can't move towards it, 324 // so abandon this heal order 325 this.FinishOrder(); 326 }, 327 286 328 "Order.Gather": function(msg) { 287 329 288 330 // If the target is still alive, we need to kill it first … … 402 444 cmpFormation.Disband(); 403 445 }, 404 446 447 "Order.Heal": function(msg) { 448 // TODO: see notes in Order.Attack 449 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 450 cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 451 cmpFormation.Disband(); 452 }, 453 405 454 "Order.Repair": function(msg) { 406 455 // TODO: see notes in Order.Attack 407 456 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); … … 540 589 // So we'll set a timer here and only report the idle event if we 541 590 // remain idle 542 591 this.StartTimer(1000); 592 593 // TODO: We need to set up a Query that watches own and ally units in LOS 594 // and calls something if they loose health (or change it to simplify) 595 // to replace the really UGLY EVIL HACK using GlobalHealthChanged as that can 596 // have a performance impact as it gets called for each healer for every entity 597 // in the whole map. 598 599 // If a unit can heal and attack we first want to heal wounded units, 600 // so check if we are a healer and find whether there's anybody nearby to heal. 601 // If anyone approaches later it'll be handled via LosRangeUpdate.) 602 if (this.IsHealer() && this.FindNewHealTargets()) 603 return true; // (abort the FSM transition since we may have already switched state) 543 604 544 605 // If we entered the idle state we must have nothing better to do, 545 606 // so immediately check whether there's anybody nearby to attack. 546 607 // (If anyone approaches later, it'll be handled via LosRangeUpdate.) 547 608 if (this.FindNewTargets()) 548 609 return true; // (abort the FSM transition since we may have already switched state) 549 610 550 611 // Nobody to attack - stay in idle 551 612 return false; 552 613 }, … … 554 615 "leave": function() { 555 616 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 556 617 rangeMan.DisableActiveQuery(this.losRangeQuery); 618 if (this.losHealRangeQuery) 619 rangeMan.DisableActiveQuery(this.losHealRangeQuery); 557 620 558 621 this.StopTimer(); 559 622 … … 563 626 Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); 564 627 } 565 628 }, 566 629 630 // TODO: This is part of an really UGLY EVIL HACK 631 "GlobalHealthChanged": function() { 632 if (this.IsHealer()) 633 { 634 this.FindNewHealTargets(); 635 } 636 }, 637 567 638 "LosRangeUpdate": function(msg) { 639 if (this.IsHealer()) 640 { 641 // Start healing one of the newly-seen own or ally units (if any) 642 this.FindNewHealTargets(); 643 } 568 644 if (this.GetStance().targetVisibleEnemies) 569 645 { 570 646 // Start attacking one of the newly-seen enemy (if any) … … 994 1070 }, 995 1071 }, 996 1072 1073 "HEAL": { 1074 "EntityRenamed": function(msg) { 1075 if (this.order.data.target == msg.entity) 1076 this.order.data.target = msg.newentity; 1077 }, 1078 1079 "Attacked": function(msg) { 1080 // If we stand ground we will rather die than flee 1081 if (!this.GetStance().respondStandGround) 1082 this.Flee(msg.data.attacker, false); 1083 }, 1084 1085 "APPROACHING": { 1086 "enter": function () { 1087 this.SelectAnimation("move"); 1088 this.StartTimer(1000, 1000); 1089 }, 1090 1091 "leave": function() { 1092 this.StopTimer(); 1093 }, 1094 1095 "Timer": function(msg) { 1096 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1097 { 1098 this.StopMoving(); 1099 this.FinishOrder(); 1100 1101 // Return to our original position 1102 if (this.GetStance().respondHoldGround) 1103 this.WalkToHeldPosition(); 1104 } 1105 }, 1106 1107 "MoveCompleted": function() { 1108 this.SetNextState("HEALING"); 1109 }, 1110 1111 "Attacked": function(msg) { 1112 // If we stand ground we will rather die than flee 1113 if (!this.GetStance().respondStandGround) 1114 this.Flee(msg.data.attacker, false); 1115 }, 1116 }, 1117 1118 "HEALING": { 1119 "enter": function() { 1120 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1121 this.healTimers = cmpHeal.GetTimers(); 1122 // this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation 1123 // this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 1124 this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 1125 // TODO if .prepare is short, players can cheat by cycling heal/stop/heal 1126 // to beat the .repeat time; should enforce a minimum time 1127 // see comment in ATTACKING.enter 1128 this.FaceTowardsTarget(this.order.data.target); 1129 }, 1130 1131 "leave": function() { 1132 this.StopTimer(); 1133 }, 1134 1135 "Timer": function(msg) { 1136 var target = this.order.data.target; 1137 // Check the target is still alive and healable 1138 if (this.TargetIsAlive(target) && this.CanHeal(target)) 1139 { 1140 // Check if we can still reach the target 1141 if (this.CheckTargetRange(target, IID_Heal)) 1142 { 1143 this.FaceTowardsTarget(target); 1144 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1145 cmpHeal.PerformHeal(target); 1146 return; 1147 } 1148 // Can't reach it - try to chase after it 1149 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1150 { 1151 if (this.MoveToTargetRange(target, IID_Heal)) 1152 { 1153 this.SetNextState("HEAL.CHASING"); 1154 return; 1155 } 1156 } 1157 } 1158 // Can't reach it, healed to max hp or doesn't exist any more - give up 1159 if (this.FinishOrder()) 1160 return; 1161 1162 // Heal another one 1163 if (this.FindNewHealTargets()) 1164 return; 1165 1166 // Return to our original position 1167 if (this.GetStance().respondHoldGround) 1168 this.WalkToHeldPosition(); 1169 }, 1170 "Attacked": function(msg) { 1171 // If we stand ground we will rather die than flee 1172 if (!this.GetStance().respondStandGround) 1173 this.Flee(msg.data.attacker, false); 1174 }, 1175 }, 1176 "CHASING": { 1177 "enter": function () { 1178 this.SelectAnimation("move"); 1179 this.StartTimer(1000, 1000); 1180 }, 1181 1182 "leave": function () { 1183 this.StopTimer(); 1184 }, 1185 "Timer": function(msg) { 1186 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1187 { 1188 this.StopMoving(); 1189 this.FinishOrder(); 1190 1191 // Return to our original position 1192 if (this.GetStance().respondHoldGround) 1193 this.WalkToHeldPosition(); 1194 } 1195 }, 1196 "MoveCompleted": function () { 1197 this.SetNextState("HEALING"); 1198 }, 1199 }, 1200 }, 1201 997 1202 // Returning to dropsite 998 1203 "RETURNRESOURCE": { 999 1204 "APPROACHING": { … … 1421 1626 return (this.template.NaturalBehaviour ? true : false); 1422 1627 }; 1423 1628 1629 UnitAI.prototype.IsHealer = function() 1630 { 1631 return Engine.QueryInterface(this.entity, IID_Heal); 1632 }; 1633 1424 1634 UnitAI.prototype.IsIdle = function() 1425 1635 { 1426 1636 return this.isIdle; … … 1444 1654 UnitAI.prototype.OnOwnershipChanged = function(msg) 1445 1655 { 1446 1656 this.SetupRangeQuery(); 1657 if (this.IsHealer()) 1658 this.SetupHealRangeQuery(); 1447 1659 }; 1448 1660 1449 1661 UnitAI.prototype.OnDestroy = function() … … 1455 1667 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1456 1668 if (this.losRangeQuery) 1457 1669 rangeMan.DestroyActiveQuery(this.losRangeQuery); 1670 if (this.losHealRangeQuery) 1671 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1458 1672 }; 1459 1673 1460 1674 // Set up a range query for all enemy units within LOS range … … 1493 1707 rangeMan.EnableActiveQuery(this.losRangeQuery); 1494 1708 }; 1495 1709 1710 // Set up a range query for all own or ally units within LOS range 1711 // which can be healed. 1712 // This should be called whenever our ownership changes. 1713 UnitAI.prototype.SetupHealRangeQuery = function() 1714 { 1715 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 1716 var owner = cmpOwnership.GetOwner(); 1717 1718 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1719 var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 1720 1721 if (this.losHealRangeQuery) 1722 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1723 1724 var players = [owner]; 1725 1726 if (owner != -1) 1727 { 1728 // If unit not just killed, get ally players via diplomacy 1729 var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); 1730 var numPlayers = playerMan.GetNumPlayers(); 1731 for (var i = 1; i < numPlayers; ++i) 1732 { 1733 // Exclude gaia and enemies 1734 if (cmpPlayer.IsAlly(i)) 1735 players.push(i); 1736 } 1737 } 1738 1739 var range = this.GetQueryRange(true); 1740 1741 this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health); 1742 rangeMan.EnableActiveQuery(this.losHealRangeQuery); 1743 }; 1744 1496 1745 //// FSM linkage functions //// 1497 1746 1498 1747 UnitAI.prototype.SetNextState = function(state) … … 1714 1963 1715 1964 UnitAI.prototype.OnRangeUpdate = function(msg) 1716 1965 { 1717 if (msg.tag == this.losRangeQuery )1966 if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery) 1718 1967 UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 1719 1968 }; 1720 1969 1970 // really UGLY EVIL HACK 1971 // TODO This should be replaced with a c++ component that tracks all entities within a range and notifies 1972 // Healers if any of the entities receives a HealthChanged message. 1973 // If any entity changes health we call this, so this probably has a huge performance impact. 1974 UnitAI.prototype.OnGlobalHealthChanged = function(msg) 1975 { 1976 if (this.IsHealer()) 1977 { 1978 UnitFsm.ProcessMessage(this, {"type": "GlobalHealthChanged"}); 1979 } 1980 } 1981 1721 1982 //// Helper functions to be called by the FSM //// 1722 1983 1723 1984 UnitAI.prototype.GetWalkSpeed = function() … … 1745 2006 }; 1746 2007 1747 2008 /** 2009 * Returns true if the target exists and the current hitpoints are at maximum. 2010 */ 2011 UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 2012 { 2013 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2014 if (!cmpHealth) 2015 return false; 2016 2017 return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 2018 }; 2019 2020 /** 2021 * Returns true if the target exists and is unhealable. 2022 */ 2023 UnitAI.prototype.TargetIsUnhealable = function(ent) 2024 { 2025 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2026 if (!cmpHealth) 2027 return false; 2028 2029 return cmpHealth.IsUnhealable(); 2030 }; 2031 2032 /** 1748 2033 * Returns true if the target exists and needs to be killed before 1749 2034 * beginning to gather resources from it. 1750 2035 */ … … 2235 2520 case "Flee": 2236 2521 case "LeaveFoundation": 2237 2522 case "Attack": 2523 case "Heal": 2238 2524 case "Gather": 2239 2525 case "ReturnResource": 2240 2526 case "Repair": … … 2358 2644 this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued); 2359 2645 } 2360 2646 2647 UnitAI.prototype.Heal = function(target, queued) 2648 { 2649 if (!this.CanHeal(target)) 2650 { 2651 this.WalkToTarget(target, queued); 2652 return; 2653 } 2654 2655 this.AddOrder("Heal", { "target": target, "force": true }, queued); 2656 }; 2657 2361 2658 UnitAI.prototype.ReturnResource = function(target, queued) 2362 2659 { 2363 2660 if (!this.CanReturnResource(target, true)) … … 2396 2693 this.stance = stance; 2397 2694 else 2398 2695 error("UnitAI: Setting to invalid stance '"+stance+"'"); 2399 } 2696 }; 2400 2697 2401 2698 UnitAI.prototype.SwitchToStance = function(stance) 2402 2699 { … … 2412 2709 2413 2710 // Reset the range query, since the range depends on stance 2414 2711 this.SetupRangeQuery(); 2415 } 2712 // Just if we are a healer 2713 // TODO maybe move those two to a SetupRangeQuerys() 2714 if (this.IsHealer()) 2715 this.SetupHealRangeQuery(); 2716 }; 2416 2717 2417 2718 /** 2418 2719 * Resets losRangeQuery, and if there are some targets in range that we can … … 2433 2734 return this.RespondToTargetedEntities(ents); 2434 2735 }; 2435 2736 2436 UnitAI.prototype.GetQueryRange = function() 2737 /** 2738 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 2739 * then we start healing and this returns true; otherwise, returns false. 2740 */ 2741 UnitAI.prototype.FindNewHealTargets = function() 2742 { 2743 if (!this.losHealRangeQuery) 2744 return false; 2745 2746 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 2747 var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 2748 2749 for each (var ent in ents) 2750 { 2751 if (this.CanHeal(ent)) 2752 { 2753 this.PushOrderFront("Heal", { "target": ent, "force": false }); 2754 return true; 2755 } 2756 } 2757 // We haven't found any target to heal 2758 return false; 2759 }; 2760 2761 UnitAI.prototype.GetQueryRange = function(healer) 2437 2762 { 2438 2763 var ret = { "min": 0, "max": 0 }; 2439 if (this.GetStance().respondStandGround) 2764 // If we have a healer with passive stance we need to set some defaults (eg same as stand ground) 2765 if (this.GetStance().respondStandGround || (healer && !this.GetStance().respondStandGround && !this.GetStance().respondChase && !this.GetStance().respondHoldGround)) 2440 2766 { 2441 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2767 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2442 2768 if (!cmpRanged) 2443 2769 return ret; 2444 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2770 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2445 2771 ret.min = range.min; 2446 2772 ret.max = range.max; 2447 2773 } … … 2455 2781 } 2456 2782 else if (this.GetStance().respondHoldGround) 2457 2783 { 2458 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2784 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2459 2785 if (!cmpRanged) 2460 2786 return ret; 2461 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2787 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2462 2788 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 2463 2789 if (!cmpVision) 2464 2790 return ret; … … 2554 2880 return true; 2555 2881 }; 2556 2882 2883 UnitAI.prototype.CanHeal = function(target) 2884 { 2885 // Formation controllers should always respond to commands 2886 // (then the individual units can make up their own minds) 2887 if (this.IsFormationController()) 2888 return true; 2889 2890 // Verify that we're able to respond to Heal commands 2891 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 2892 if (!cmpHeal) 2893 return false; 2894 2895 // Verify that the target is alive 2896 if (!this.TargetIsAlive(target)) 2897 return false; 2898 2899 // Verify that the target is owned by the same player as the entity or of an ally 2900 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2901 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 2902 return false; 2903 2904 // Verify that the target is not unhealable 2905 if (this.TargetIsUnhealable(target)) 2906 { 2907 return false; 2908 } 2909 2910 // Verify that the target has no unhealable class 2911 // We could also use cmpIdentity.GetClassesList but this way is cleaner 2912 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 2913 if (!cmpIdentity) 2914 return false; 2915 for each (var unhealableClass in cmpHeal.GetUnhealableClasses()) 2916 { 2917 if (cmpIdentity.HasClass(unhealableClass) != -1) 2918 { 2919 return false; 2920 } 2921 } 2922 2923 // Verify that the target is a healable class 2924 // We could also use cmpIdentity.GetClassesList but this way is cleaner 2925 var healable = false; 2926 for each (var healableClass in cmpHeal.GetHealableClasses()) 2927 { 2928 if (cmpIdentity.HasClass(healableClass) != -1) 2929 { 2930 healable = true; 2931 } 2932 } 2933 if (!healable) 2934 return false; 2935 2936 // Check that the target is not at MaxHealth 2937 if (this.TargetIsAtMaxHitpoints(target)) 2938 return false; 2939 2940 return true; 2941 }; 2942 2557 2943 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2558 2944 { 2559 2945 // Formation controllers should always respond to commands -
binaries/data/mods/public/simulation/components/interfaces/Heal.js
1 Engine.RegisterInterface("Heal"); -
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"); … … 270 271 GetHitpoints: function() { return 50; }, 271 272 GetMaxHitpoints: function() { return 60; }, 272 273 IsRepairable: function() { return false; }, 274 IsUnhealable: function() { return false; }, 273 275 }); 274 276 275 277 AddMock(10, IID_Identity, { … … 303 305 hitpoints: 50, 304 306 maxHitpoints: 60, 305 307 needsRepair: false, 308 needsHeal: true, 306 309 buildEntities: ["test1", "test2"], 307 310 barterMarket: { 308 311 prices: { "buy": {"food":150}, "sell": {"food":25} }, -
binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
4 4 Engine.LoadComponentScript("interfaces/Attack.js"); 5 5 Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 6 6 Engine.LoadComponentScript("interfaces/Formation.js"); 7 Engine.LoadComponentScript("interfaces/Heal.js"); 7 8 Engine.LoadComponentScript("interfaces/Health.js"); 8 9 Engine.LoadComponentScript("interfaces/ResourceSupply.js"); 9 10 Engine.LoadComponentScript("interfaces/Timer.js"); -
binaries/data/mods/public/simulation/helpers/Commands.js
66 66 }); 67 67 break; 68 68 69 case "heal": 70 if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) 71 { 72 // This check is for debugging only! 73 warn("Invalid command: heal target is not owned by an ally of or player "+player+" itself: "+uneval(cmd)); 74 } 75 76 // See UnitAI.CanHeal for target checks 77 var entities = FilterEntityList(cmd.entities, player, controlAllUnits); 78 GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) { 79 cmpUnitAI.Heal(cmd.target, cmd.queued); 80 }); 81 break; 82 69 83 case "repair": 70 84 // This covers both repairing damaged buildings, and constructing unfinished foundations 71 85 if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) -
binaries/data/mods/public/simulation/templates/template_structure.xml
33 33 <Health> 34 34 <DeathType>corpse</DeathType> 35 35 <RegenRate>0</RegenRate> 36 < Healable>false</Healable>36 <Unhealable>true</Unhealable> 37 37 <Repairable>true</Repairable> 38 38 </Health> 39 39 <Identity> -
binaries/data/mods/public/simulation/templates/template_unit.xml
30 30 <DeathType>corpse</DeathType> 31 31 <Max>100</Max> 32 32 <RegenRate>0</RegenRate> 33 < Healable>true</Healable>33 <Unhealable>false</Unhealable> 34 34 <Repairable>false</Repairable> 35 35 </Health> 36 36 <Identity> -
binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_whale.xml
4 4 <Max>100</Max> 5 5 <DeathType>remain</DeathType> 6 6 <RegenRate>1</RegenRate> 7 < Healable>false</Healable>7 <Unhealable>true</Unhealable> 8 8 <Repairable>false</Repairable> 9 9 </Health> 10 10 <Identity> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml
7 7 <SinkAccel>2.0</SinkAccel> 8 8 </Decay> 9 9 <Health> 10 < Healable>false</Healable>10 <Unhealable>true</Unhealable> 11 11 <Repairable>true</Repairable> 12 12 </Health> 13 13 <Identity> -
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 <HP>5</HP> 17 <Rate>2000</Rate> 18 <UnhealableClasses datatype="tokens"/> 19 <HealableClasses datatype="tokens">Support Infantry Cavalry</HealableClasses> 20 </Heal> 14 21 <Cost> 15 22 <Resources> 16 23 <metal>120</metal> … … 23 30 <Identity> 24 31 <Classes datatype="tokens">Healer</Classes> 25 32 <GenericName>Healer</GenericName> 26 <Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip>33 <Tooltip>Heal units.</Tooltip> 27 34 </Identity> 35 <Promotion> 36 <RequiredXp>100</RequiredXp> 37 </Promotion> 28 38 <Sound> 29 39 <SoundGroups> 30 40 <select>voice/hellenes/civ/civ_male_select.xml</select> -
binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml
36 36 </Resources> 37 37 </Cost> 38 38 <Health> 39 < Healable>false</Healable>39 <Unhealable>true</Unhealable> 40 40 </Health> 41 41 <Identity> 42 42 <Classes datatype="tokens">Slave</Classes> -
binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml
6 6 <History>Tanit (also spelled TINITH, TINNIT, or TINT), chief goddess of Carthage, equivalent of Astarte. Although she seems to have had some connection with the heavens, she was also a mother goddess, and fertility symbols often accompany representations of her. She was probably the consort of Baal Hammon (or Amon), the chief god of Carthage, and was often given the attribute "face of Baal." Although Tanit did not appear at Carthage before the 5th century BC, she soon eclipsed the more established cult of Baal Hammon and, in the Carthaginian area at least, was frequently listed before him on the monuments. In the worship of Tanit and Baal Hammon, children, probably firstborn, were sacrificed. Ample evidence of the practice has been found west of Carthage in the precinct of Tanit, where a tofet (a sanctuary for the sacrifice of children) was discovered. Tanit was also worshipped on Malta, Sardinia, and in Spain. There is no other reason for giving the Carthaginians a priestess instead of a priest in 0 A.D., although Tanit was the most popular of their two main gods with the people. </History> 7 7 <Tooltip>Heal units within her aura. (Not implemented yet)</Tooltip> 8 8 <Icon>units/cart_support_healer.png</Icon> 9 <Rank>Basic</Rank> 9 10 </Identity> 11 <Promotion> 12 <Entity>units/cart_support_healer_a</Entity> 13 </Promotion> 10 14 <VisualActor> 11 15 <Actor>units/carthaginians/healer.xml</Actor> 12 16 </VisualActor> -
binaries/data/mods/public/simulation/templates/units/celt_support_healer_b.xml
5 5 <SpecificName>Druides </SpecificName> 6 6 <History>A druid may be one of many different professions; priest, historian, lawyer, judges, teachers, philosophers, poets, composers, musicians, astronomers, prophets, councillors, high craftsmen like a blacksmith, the classes of the 'men of art', and sometimes kings, chieftains, or other politicians. Druids were very hierarchal, with classes and ranks based on the length of their education and what fields they practiced. They learned their trades through mnemonics by way of poetry and songs, as writing was rarely used by Celts outside of prayers on votive objects, or lists of names for migratory records.</History> 7 7 <Icon>units/celt_support_healer.png</Icon> 8 <Rank>Basic</Rank> 8 9 </Identity> 10 <Promotion> 11 <Entity>units/celt_support_healer_a</Entity> 12 </Promotion> 9 13 <VisualActor> 10 14 <Actor>units/celts/healer.xml</Actor> 11 15 </VisualActor> -
binaries/data/mods/public/simulation/templates/units/hele_support_healer_b.xml
5 5 <SpecificName>Hiereús</SpecificName> 6 6 <History>The art of medicine was widely practised in Classical Greece. Hippocrates was the first physician to separate religion and superstition from actual medicine, and many others followed his lead.</History> 7 7 <Icon>units/hele_support_healer.png</Icon> 8 <Rank>Basic</Rank> 8 9 </Identity> 10 <Promotion> 11 <Entity>units/hele_support_healer_a</Entity> 12 </Promotion> 9 13 <VisualActor> 10 14 <Actor>units/hellenes/healer.xml</Actor> 11 15 </VisualActor> -
binaries/data/mods/public/simulation/templates/units/iber_support_healer_b.xml
5 5 <SpecificName>Sacerdotisa de Ataekina</SpecificName> 6 6 <History> To the best of our knowledge, only one 'temple'-like structure has been found on the Iberian Peninsula dating from the times and the Iberians worshiped their pantheon of gods at small home altars; however, a very special sculptured head and torso was found in a farmer's field around the turn of the 20th century of a personage who was obviously someone of great substance. As the two principal gods, of the many worshiped, were male Endovellikos and female Ataekina, we thought it would be nice to adopt The Lady of Elche as our priestess-healer representing Ataekina. We know from archelogy and the Romans that Ataekina was associated with spring, the changing of seasons, and nature in general. Ataekina also seems to have been associated with the cycle of birth-death-rebirth.</History> 7 7 <Icon>units/iber_support_healer.png</Icon> 8 <Rank>Basic</Rank> 8 9 </Identity> 10 <Promotion> 11 <Entity>units/iber_support_healer_a</Entity> 12 </Promotion> 9 13 <VisualActor> 10 14 <Actor>units/iberians/healer.xml</Actor> 11 15 </VisualActor> -
binaries/data/mods/public/simulation/templates/units/pers_support_healer_b.xml
6 6 <SpecificName>Maguš Mada</SpecificName> 7 7 <History>Under both the Medes and later the Persian the tribe of the Magi or the Magians were the masters of religious and oral tradition, comparable to the Levites of the Bible. They were connected to Zoroastrianism, but likely tended to other Iranian cults as well. Aside from religious duties the Magians also functioned as the Great King's bureaucrats and kept his administration running.</History> 8 8 <Icon>units/pers_support_healer.png</Icon> 9 <Rank>Basic</Rank> 9 10 </Identity> 11 <Promotion> 12 <Entity>units/pers_support_healer_a</Entity> 13 </Promotion> 10 14 <VisualActor> 11 15 <Actor>units/persians/healer.xml</Actor> 12 16 </VisualActor> -
binaries/data/mods/public/simulation/templates/units/rome_support_healer_b.xml
6 6 <SpecificName>Pontifex Minoris</SpecificName> 7 7 <History>During the Republic, the position of priest was elevated and required a lot of responsibilities, which is why priests were by no means chosen randomly. The position of Pontifex Maximus, the high priest of the Roman religion, was occupied by such prominent figures as Julius Caesar, Marcus Aemilius Lepidus and Augustus.</History> 8 8 <Icon>units/rome_support_healer.png</Icon> 9 <Rank>Basic</Rank> 9 10 </Identity> 11 <Promotion> 12 <Entity>units/rome_support_healer_a</Entity> 13 </Promotion> 10 14 <VisualActor> 11 15 <Actor>units/romans/healer.xml</Actor> 12 16 </VisualActor>