Ticket #999: #999-2012-03-08.patch
File #999-2012-03-08.patch, 46.9 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; … … 297 298 return {"possible": true, "tooltip": tooltip}; 298 299 } 299 300 break; 301 case "heal": 302 // The check if the target is unhealable is done by targetState.needsHeal 303 if (entState.Ability && isUnit(targetState) && targetState.needsHeal && (playerOwned || allyOwned)) 304 { 305 var unhealableClasses = entState.Ability.Healer.unhealableClasses; 306 for each (var unitClass in targetState.identity.classes) 307 { 308 if (unhealableClasses.indexOf(unitClass) != -1) 309 { 310 return {"possible": false}; 311 } 312 } 313 314 var healableClasses = entState.Ability.Healer.healableClasses; 315 for each (var unitClass in targetState.identity.classes) 316 { 317 if (healableClasses.indexOf(unitClass) != -1) 318 { 319 return {"possible": true}; 320 } 321 } 322 } 323 break; 300 324 case "gather": 301 325 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 302 326 { … … 393 417 else 394 418 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 395 419 break; 420 case ACTION_HEAL: 421 if (getActionInfo("heal", target).possible) 422 return {"type": "heal", "cursor": "action-heal", "target": target}; 423 else 424 return {"type": "none", "cursor": "action-heal-disabled", "target": undefined}; 425 break; 396 426 } 397 427 } 398 428 else if (Engine.HotkeyIsPressed("session.garrison")) … … 417 447 return {"type": "build", "cursor": "action-repair", "target": target}; 418 448 else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible) 419 449 return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position}; 450 else if (getActionInfo("heal", target).possible) 451 return {"type": "heal", "cursor": "action-heal", "target": target}; 420 452 else if (getActionInfo("attack", target).possible) 421 453 return {"type": "attack", "cursor": "action-attack", "target": target}; 422 454 else if (getActionInfo("unset-rallypoint", target).possible) … … 1037 1069 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 1038 1070 return true; 1039 1071 1072 case "promote": 1073 Engine.PostNetworkCommand({"type": "promote", "entities": selection, "target": action.target, "queued": queued}); 1074 // TODO:Play a sound? 1075 return true; 1076 1077 case "heal": 1078 Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 1079 // TODO: Play a sound? 1080 // Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 1081 return true; 1082 1040 1083 case "build": // (same command as repair) 1041 1084 case "repair": 1042 1085 Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); … … 1258 1301 inputState = INPUT_PRESELECTEDACTION; 1259 1302 preSelectedAction = ACTION_REPAIR; 1260 1303 break; 1304 case "heal": 1305 inputState = INPUT_PRESELECTEDACTION; 1306 preSelectedAction = ACTION_HEAL; 1307 break; 1308 case "promote": 1309 doAction({ "type": "promote"}) 1310 break; 1261 1311 case "unload-all": 1262 1312 unloadAll(entity); 1263 1313 break; -
binaries/data/mods/public/gui/session/session.xml
729 729 </object> 730 730 </object> 731 731 732 <object name="unitAbilityPanel" 733 size="14 12 100% 100%" 734 > 735 <object size="0 0 100% 100%"> 736 <repeat count="24"> 737 <object name="unitAbilityButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom"> 738 <object name="unitAbilityIcon[n]" type="image" ghost="true" size="3 3 43 43"/> 739 </object> 740 </repeat> 741 </object> 742 </object> 743 732 744 <object name="unitResearchPanel" 733 745 style="TranslucentPanelThinBorder" 734 746 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 … … 22 23 const BARTER_RESOURCES = ["food", "wood", "stone", "metal"]; 23 24 const BARTER_ACTIONS = ["Sell", "Buy"]; 24 25 25 // The number of currently visible buttons (used to optimise showing/hiding) 26 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Barter": 0, "Trading": 0, "Construction": 0, " Command": 0, "Stance": 0};26 // The number of currently visible buttons (used to optimise showing/hiding) 27 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Ability": 0, "Command": 0, "Stance": 0}; 27 28 28 29 // Unit panels are panels with row(s) of buttons 29 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", " Research", "Stance", "Command"];30 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Ability", "Research", "Stance", "Command"]; 30 31 31 32 // Indexes of resources to sell and buy on barter panel 32 33 var g_barterSell = 0; … … 178 179 numberOfItems = 24; 179 180 break; 180 181 182 case ABILITY: 183 if (numberOfItems > 24) 184 numberOfItems = 24; 185 break; 186 181 187 case COMMAND: 182 188 if (numberOfItems > 6) 183 189 numberOfItems = 6; … … 194 200 var item = items[i]; 195 201 var entType = ((guiName == "Queue")? item.template : item); 196 202 var template; 197 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" )203 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" && guiName != "Ability") 198 204 { 199 205 template = GetTemplateData(entType); 200 206 if (!template) … … 273 279 274 280 break; 275 281 282 case ABILITY: 283 // TODO read tooltips from some file or template based on 'item' 284 var tooltip; 285 switch(item) 286 { 287 case "heal": 288 tooltip = "Heal units"; 289 break; 290 case "promote": 291 tooltip = "Promote this unit"; 292 break; 293 default: 294 tooltip = "No tooltip defined"; 295 break; 296 } 297 break; 298 276 299 case COMMAND: 277 300 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 278 301 if (item.name == "unload-all") … … 342 365 icon.sprite = "stretched:session/icons/single/" + item.icon; 343 366 344 367 } 368 else if (guiName == "Ability") 369 { 370 icon.sprite = "stretched:session/icons/single/"+item+".png"; 371 } 345 372 else if (template.icon) 346 373 { 347 374 icon.sprite = "stretched:session/portraits/" + template.icon; … … 534 561 setupUnitBarterPanel(entState); 535 562 } 536 563 564 if (entState.Ability) 565 { 566 var abilities = []; 567 if (entState.Ability.Healer) 568 abilities.push("heal"); 569 if (entState.Ability.Promote) 570 abilities.push("promote"); 571 setupUnitPanel("Ability", usedPanels, entState, abilities, function (item) { performCommand(entState.id, item); }); 572 } 573 537 574 if (entState.buildEntities && entState.buildEntities.length) 538 575 { 539 576 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); … … 273 274 ret.barterMarket = { "prices": cmpBarter.GetPrices() }; 274 275 } 275 276 277 // Abilities 278 var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 279 // Check if we have abilities 280 if (cmpHeal || (cmpHeal && cmpPromotion)) 281 ret.Ability = []; 282 if (cmpHeal) 283 { 284 ret.Ability.Healer = { 285 "unhealableClasses": cmpHeal.GetUnhealableClasses(), 286 "healableClasses": cmpHeal.GetHealableClasses(), 287 }; 288 } 289 290 // TODO remove this; This is just used to test/demonstrate the extensibility 291 // of the Ability system 292 // promoteAbility (just for healers) 293 if (cmpPromotion && cmpHeal) 294 { 295 ret.Ability.Promote = true ; 296 } 297 276 298 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 277 299 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 278 300 -
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 … … 410 452 cmpFormation.Disband(); 411 453 }, 412 454 455 "Order.Heal": function(msg) { 456 // TODO: see notes in Order.Attack 457 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 458 cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 459 cmpFormation.Disband(); 460 }, 461 413 462 "Order.Repair": function(msg) { 414 463 // TODO: see notes in Order.Attack 415 464 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); … … 548 597 // So we'll set a timer here and only report the idle event if we 549 598 // remain idle 550 599 this.StartTimer(1000); 600 601 // TODO: We need to set up a Query that watches own and ally units in LOS 602 // and calls something if they loose health (or change it to simplify) 603 // to replace the really UGLY EVIL HACK using GlobalHealthChanged as that can 604 // have a performance impact as it gets called for each healer for every entity 605 // in the whole map. 606 607 // If a unit can heal and attack we first want to heal wounded units, 608 // so check if we are a healer and find whether there's anybody nearby to heal. 609 // If anyone approaches later it'll be handled via LosRangeUpdate.) 610 if (this.IsHealer() && this.FindNewHealTargets()) 611 return true; // (abort the FSM transition since we may have already switched state) 551 612 552 613 // If we entered the idle state we must have nothing better to do, 553 614 // so immediately check whether there's anybody nearby to attack. 554 615 // (If anyone approaches later, it'll be handled via LosRangeUpdate.) 555 616 if (this.FindNewTargets()) 556 617 return true; // (abort the FSM transition since we may have already switched state) 557 618 558 619 // Nobody to attack - stay in idle 559 620 return false; 560 621 }, … … 562 623 "leave": function() { 563 624 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 564 625 rangeMan.DisableActiveQuery(this.losRangeQuery); 626 if (this.losHealRangeQuery) 627 rangeMan.DisableActiveQuery(this.losHealRangeQuery); 565 628 566 629 this.StopTimer(); 567 630 … … 571 634 Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); 572 635 } 573 636 }, 574 637 638 // TODO: This is part of an really UGLY EVIL HACK 639 "GlobalHealthChanged": function() { 640 if (this.IsHealer()) 641 { 642 this.FindNewHealTargets(); 643 } 644 }, 645 575 646 "LosRangeUpdate": function(msg) { 647 if (this.IsHealer()) 648 { 649 // Start healing one of the newly-seen own or ally units (if any) 650 this.FindNewHealTargets(); 651 } 576 652 if (this.GetStance().targetVisibleEnemies) 577 653 { 578 654 // Start attacking one of the newly-seen enemy (if any) … … 1002 1078 }, 1003 1079 }, 1004 1080 1081 "HEAL": { 1082 "EntityRenamed": function(msg) { 1083 if (this.order.data.target == msg.entity) 1084 this.order.data.target = msg.newentity; 1085 }, 1086 1087 "Attacked": function(msg) { 1088 // If we stand ground we will rather die than flee 1089 if (!this.GetStance().respondStandGround) 1090 this.Flee(msg.data.attacker, false); 1091 }, 1092 1093 "APPROACHING": { 1094 "enter": function () { 1095 this.SelectAnimation("move"); 1096 this.StartTimer(1000, 1000); 1097 }, 1098 1099 "leave": function() { 1100 this.StopTimer(); 1101 }, 1102 1103 "Timer": function(msg) { 1104 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1105 { 1106 this.StopMoving(); 1107 this.FinishOrder(); 1108 1109 // Return to our original position 1110 if (this.GetStance().respondHoldGround) 1111 this.WalkToHeldPosition(); 1112 } 1113 }, 1114 1115 "MoveCompleted": function() { 1116 this.SetNextState("HEALING"); 1117 }, 1118 1119 "Attacked": function(msg) { 1120 // If we stand ground we will rather die than flee 1121 if (!this.GetStance().respondStandGround) 1122 this.Flee(msg.data.attacker, false); 1123 }, 1124 }, 1125 1126 "HEALING": { 1127 "enter": function() { 1128 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1129 this.healTimers = cmpHeal.GetTimers(); 1130 // this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation 1131 // this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 1132 this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 1133 // TODO if .prepare is short, players can cheat by cycling heal/stop/heal 1134 // to beat the .repeat time; should enforce a minimum time 1135 // see comment in ATTACKING.enter 1136 this.FaceTowardsTarget(this.order.data.target); 1137 }, 1138 1139 "leave": function() { 1140 this.StopTimer(); 1141 }, 1142 1143 "Timer": function(msg) { 1144 var target = this.order.data.target; 1145 // Check the target is still alive and healable 1146 if (this.TargetIsAlive(target) && this.CanHeal(target)) 1147 { 1148 // Check if we can still reach the target 1149 if (this.CheckTargetRange(target, IID_Heal)) 1150 { 1151 this.FaceTowardsTarget(target); 1152 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1153 cmpHeal.PerformHeal(target); 1154 return; 1155 } 1156 // Can't reach it - try to chase after it 1157 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1158 { 1159 if (this.MoveToTargetRange(target, IID_Heal)) 1160 { 1161 this.SetNextState("HEAL.CHASING"); 1162 return; 1163 } 1164 } 1165 } 1166 // Can't reach it, healed to max hp or doesn't exist any more - give up 1167 if (this.FinishOrder()) 1168 return; 1169 1170 // Heal another one 1171 if (this.FindNewHealTargets()) 1172 return; 1173 1174 // Return to our original position 1175 if (this.GetStance().respondHoldGround) 1176 this.WalkToHeldPosition(); 1177 }, 1178 "Attacked": function(msg) { 1179 // If we stand ground we will rather die than flee 1180 if (!this.GetStance().respondStandGround) 1181 this.Flee(msg.data.attacker, false); 1182 }, 1183 }, 1184 "CHASING": { 1185 "enter": function () { 1186 this.SelectAnimation("move"); 1187 this.StartTimer(1000, 1000); 1188 }, 1189 1190 "leave": function () { 1191 this.StopTimer(); 1192 }, 1193 "Timer": function(msg) { 1194 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1195 { 1196 this.StopMoving(); 1197 this.FinishOrder(); 1198 1199 // Return to our original position 1200 if (this.GetStance().respondHoldGround) 1201 this.WalkToHeldPosition(); 1202 } 1203 }, 1204 "MoveCompleted": function () { 1205 this.SetNextState("HEALING"); 1206 }, 1207 }, 1208 }, 1209 1005 1210 // Returning to dropsite 1006 1211 "RETURNRESOURCE": { 1007 1212 "APPROACHING": { … … 1457 1662 return (this.template.NaturalBehaviour ? true : false); 1458 1663 }; 1459 1664 1665 UnitAI.prototype.IsHealer = function() 1666 { 1667 return Engine.QueryInterface(this.entity, IID_Heal); 1668 }; 1669 1460 1670 UnitAI.prototype.IsIdle = function() 1461 1671 { 1462 1672 return this.isIdle; … … 1480 1690 UnitAI.prototype.OnOwnershipChanged = function(msg) 1481 1691 { 1482 1692 this.SetupRangeQuery(); 1693 if (this.IsHealer()) 1694 this.SetupHealRangeQuery(); 1483 1695 }; 1484 1696 1485 1697 UnitAI.prototype.OnDestroy = function() … … 1491 1703 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1492 1704 if (this.losRangeQuery) 1493 1705 rangeMan.DestroyActiveQuery(this.losRangeQuery); 1706 if (this.losHealRangeQuery) 1707 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1494 1708 }; 1495 1709 1496 1710 // Set up a range query for all enemy units within LOS range … … 1529 1743 rangeMan.EnableActiveQuery(this.losRangeQuery); 1530 1744 }; 1531 1745 1746 // Set up a range query for all own or ally units within LOS range 1747 // which can be healed. 1748 // This should be called whenever our ownership changes. 1749 UnitAI.prototype.SetupHealRangeQuery = function() 1750 { 1751 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 1752 var owner = cmpOwnership.GetOwner(); 1753 1754 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1755 var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 1756 1757 if (this.losHealRangeQuery) 1758 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1759 1760 var players = [owner]; 1761 1762 if (owner != -1) 1763 { 1764 // If unit not just killed, get ally players via diplomacy 1765 var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); 1766 var numPlayers = playerMan.GetNumPlayers(); 1767 for (var i = 1; i < numPlayers; ++i) 1768 { 1769 // Exclude gaia and enemies 1770 if (cmpPlayer.IsAlly(i)) 1771 players.push(i); 1772 } 1773 } 1774 1775 var range = this.GetQueryRange(true); 1776 1777 this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health); 1778 rangeMan.EnableActiveQuery(this.losHealRangeQuery); 1779 }; 1780 1532 1781 //// FSM linkage functions //// 1533 1782 1534 1783 UnitAI.prototype.SetNextState = function(state) … … 1750 1999 1751 2000 UnitAI.prototype.OnRangeUpdate = function(msg) 1752 2001 { 1753 if (msg.tag == this.losRangeQuery )2002 if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery) 1754 2003 UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 1755 2004 }; 1756 2005 2006 // really UGLY EVIL HACK 2007 // TODO This should be replaced with a c++ component that tracks all entities within a range and notifies 2008 // Healers if any of the entities receives a HealthChanged message. 2009 // If any entity changes health we call this, so this probably has a huge performance impact. 2010 UnitAI.prototype.OnGlobalHealthChanged = function(msg) 2011 { 2012 if (this.IsHealer()) 2013 { 2014 UnitFsm.ProcessMessage(this, {"type": "GlobalHealthChanged"}); 2015 } 2016 } 2017 1757 2018 //// Helper functions to be called by the FSM //// 1758 2019 1759 2020 UnitAI.prototype.GetWalkSpeed = function() … … 1781 2042 }; 1782 2043 1783 2044 /** 2045 * Returns true if the target exists and the current hitpoints are at maximum. 2046 */ 2047 UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 2048 { 2049 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2050 if (!cmpHealth) 2051 return false; 2052 2053 return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 2054 }; 2055 2056 /** 2057 * Returns true if the target exists and is unhealable. 2058 */ 2059 UnitAI.prototype.TargetIsUnhealable = function(ent) 2060 { 2061 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2062 if (!cmpHealth) 2063 return false; 2064 2065 return cmpHealth.IsUnhealable(); 2066 }; 2067 2068 /** 1784 2069 * Returns true if the target exists and needs to be killed before 1785 2070 * beginning to gather resources from it. 1786 2071 */ … … 2271 2556 case "Flee": 2272 2557 case "LeaveFoundation": 2273 2558 case "Attack": 2559 case "Heal": 2274 2560 case "Gather": 2275 2561 case "ReturnResource": 2276 2562 case "Repair": … … 2394 2680 this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued); 2395 2681 } 2396 2682 2683 UnitAI.prototype.Heal = function(target, queued) 2684 { 2685 if (!this.CanHeal(target)) 2686 { 2687 this.WalkToTarget(target, queued); 2688 return; 2689 } 2690 2691 this.AddOrder("Heal", { "target": target, "force": true }, queued); 2692 }; 2693 2397 2694 UnitAI.prototype.ReturnResource = function(target, queued) 2398 2695 { 2399 2696 if (!this.CanReturnResource(target, true)) … … 2505 2802 this.stance = stance; 2506 2803 else 2507 2804 error("UnitAI: Setting to invalid stance '"+stance+"'"); 2508 } 2805 }; 2509 2806 2510 2807 UnitAI.prototype.SwitchToStance = function(stance) 2511 2808 { … … 2521 2818 2522 2819 // Reset the range query, since the range depends on stance 2523 2820 this.SetupRangeQuery(); 2524 } 2821 // Just if we are a healer 2822 // TODO maybe move those two to a SetupRangeQuerys() 2823 if (this.IsHealer()) 2824 this.SetupHealRangeQuery(); 2825 }; 2525 2826 2526 2827 /** 2527 2828 * Resets losRangeQuery, and if there are some targets in range that we can … … 2542 2843 return this.RespondToTargetedEntities(ents); 2543 2844 }; 2544 2845 2545 UnitAI.prototype.GetQueryRange = function() 2846 /** 2847 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 2848 * then we start healing and this returns true; otherwise, returns false. 2849 */ 2850 UnitAI.prototype.FindNewHealTargets = function() 2851 { 2852 if (!this.losHealRangeQuery) 2853 return false; 2854 2855 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 2856 var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 2857 2858 for each (var ent in ents) 2859 { 2860 if (this.CanHeal(ent)) 2861 { 2862 this.PushOrderFront("Heal", { "target": ent, "force": false }); 2863 return true; 2864 } 2865 } 2866 // We haven't found any target to heal 2867 return false; 2868 }; 2869 2870 UnitAI.prototype.GetQueryRange = function(healer) 2546 2871 { 2547 2872 var ret = { "min": 0, "max": 0 }; 2548 if (this.GetStance().respondStandGround) 2873 // If we have a healer with passive stance we need to set some defaults (eg same as stand ground) 2874 if (this.GetStance().respondStandGround || (healer && !this.GetStance().respondStandGround && !this.GetStance().respondChase && !this.GetStance().respondHoldGround)) 2549 2875 { 2550 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2876 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2551 2877 if (!cmpRanged) 2552 2878 return ret; 2553 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2879 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2554 2880 ret.min = range.min; 2555 2881 ret.max = range.max; 2556 2882 } … … 2564 2890 } 2565 2891 else if (this.GetStance().respondHoldGround) 2566 2892 { 2567 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2893 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2568 2894 if (!cmpRanged) 2569 2895 return ret; 2570 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2896 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2571 2897 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 2572 2898 if (!cmpVision) 2573 2899 return ret; … … 2663 2989 return true; 2664 2990 }; 2665 2991 2992 UnitAI.prototype.CanHeal = function(target) 2993 { 2994 // Formation controllers should always respond to commands 2995 // (then the individual units can make up their own minds) 2996 if (this.IsFormationController()) 2997 return true; 2998 2999 // Verify that we're able to respond to Heal commands 3000 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 3001 if (!cmpHeal) 3002 return false; 3003 3004 // Verify that the target is alive 3005 if (!this.TargetIsAlive(target)) 3006 return false; 3007 3008 // Verify that the target is owned by the same player as the entity or of an ally 3009 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 3010 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 3011 return false; 3012 3013 // Verify that the target is not unhealable 3014 if (this.TargetIsUnhealable(target)) 3015 { 3016 return false; 3017 } 3018 3019 // Verify that the target has no unhealable class 3020 // We could also use cmpIdentity.GetClassesList but this way is cleaner 3021 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 3022 if (!cmpIdentity) 3023 return false; 3024 for each (var unhealableClass in cmpHeal.GetUnhealableClasses()) 3025 { 3026 if (cmpIdentity.HasClass(unhealableClass) != -1) 3027 { 3028 return false; 3029 } 3030 } 3031 3032 // Verify that the target is a healable class 3033 // We could also use cmpIdentity.GetClassesList but this way is cleaner 3034 var healable = false; 3035 for each (var healableClass in cmpHeal.GetHealableClasses()) 3036 { 3037 if (cmpIdentity.HasClass(healableClass) != -1) 3038 { 3039 healable = true; 3040 } 3041 } 3042 if (!healable) 3043 return false; 3044 3045 // Check that the target is not at MaxHealth 3046 if (this.TargetIsAtMaxHitpoints(target)) 3047 return false; 3048 3049 return true; 3050 }; 3051 2666 3052 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2667 3053 { 2668 3054 // 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"); … … 271 272 GetHitpoints: function() { return 50; }, 272 273 GetMaxHitpoints: function() { return 60; }, 273 274 IsRepairable: function() { return false; }, 275 IsUnhealable: function() { return false; }, 274 276 }); 275 277 276 278 AddMock(10, IID_Identity, { … … 304 306 hitpoints: 50, 305 307 maxHitpoints: 60, 306 308 needsRepair: false, 309 needsHeal: true, 307 310 buildEntities: ["test1", "test2"], 308 311 barterMarket: { 309 312 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>