Ticket #999: #999-2012-03-16.patch
File #999-2012-03-16.patch, 45.8 KB (added by , 12 years ago) |
---|
-
binaries/data/mods/public/art/actors/units/carthaginians/healer.xml
8 8 <animation file="female/f_walk_01.dae" name="Walk" speed="30"/> 9 9 <animation file="female/f_walk_01.dae" name="Run" speed="45"/> 10 10 <animation file="female/f_death_01.dae" name="Death" speed="100"/> 11 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 11 12 </animations> 12 13 <props> 13 14 <prop actor="props/units/heads/head_kart_priestess.xml" attachpoint="head"/> -
binaries/data/mods/public/art/actors/units/celts/healer.xml
8 8 <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/> 9 9 <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/> 10 10 <animation file="biped/inf_staff_walk_a.dae" name="Run" speed="40"/> 11 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 11 12 </animations> 12 13 <mesh>skeletal/m_dress_cuffs.dae</mesh> 13 14 <props> -
binaries/data/mods/public/art/actors/units/hellenes/healer.xml
7 7 <animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/> 8 8 <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/> 9 9 <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/> 10 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 10 11 </animations> 11 12 <mesh>skeletal/m_dress_a.pmd</mesh> 12 13 <props> -
binaries/data/mods/public/art/actors/units/iberians/healer.xml
8 8 <animation file="female/f_walk_01.dae" name="Walk" speed="30"/> 9 9 <animation file="female/f_walk_01.dae" name="Run" speed="45"/> 10 10 <animation file="female/f_death_01.dae" name="Death" speed="120"/> 11 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 11 12 </animations> 12 13 <props> 13 14 <prop actor="props/units/heads/head_iber_healer.xml" attachpoint="head"/> -
binaries/data/mods/public/art/actors/units/persians/healer.xml
7 7 <animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/> 8 8 <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/> 9 9 <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/> 10 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 10 11 </animations> 11 12 <mesh>skeletal/m_dress_cuffs.dae</mesh> 12 13 <props> -
binaries/data/mods/public/art/actors/units/romans/healer.xml
8 8 <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/> 9 9 <animation file="biped/inf_staff_walk_a.dae" name="Run" speed="20"/> 10 10 <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/> 11 <animation file="female/f_salute_01.dae" name="Heal" speed="30"/> 11 12 </animations> 12 13 <mesh>skeletal/m_dress_a.pmd</mesh> 13 14 <props> -
binaries/data/mods/public/art/textures/cursors/action-heal.txt
1 1 1 -
binaries/data/mods/public/gui/session/input.js
297 297 return {"possible": true, "tooltip": tooltip}; 298 298 } 299 299 break; 300 case "heal": 301 // The check if the target is unhealable is done by targetState.needsHeal 302 if (entState.Healer && isUnit(targetState) && targetState.needsHeal && (playerOwned || allyOwned)) 303 { 304 var unhealableClasses = entState.Healer.unhealableClasses; 305 for each (var unitClass in targetState.identity.classes) 306 { 307 if (unhealableClasses.indexOf(unitClass) != -1) 308 { 309 return {"possible": false}; 310 } 311 } 312 313 var healableClasses = entState.Healer.healableClasses; 314 for each (var unitClass in targetState.identity.classes) 315 { 316 if (healableClasses.indexOf(unitClass) != -1) 317 { 318 return {"possible": true}; 319 } 320 } 321 } 322 break; 300 323 case "gather": 301 324 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 302 325 { … … 417 440 return {"type": "build", "cursor": "action-repair", "target": target}; 418 441 else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible) 419 442 return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position}; 443 else if (getActionInfo("heal", target).possible) 444 return {"type": "heal", "cursor": "action-heal", "target": target}; 420 445 else if (getActionInfo("attack", target).possible) 421 446 return {"type": "attack", "cursor": "action-attack", "target": target}; 422 447 else if (getActionInfo("unset-rallypoint", target).possible) … … 1037 1062 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 1038 1063 return true; 1039 1064 1065 case "heal": 1066 Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 1067 // TODO: Play a sound? 1068 // Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 1069 return true; 1070 1040 1071 case "build": // (same command as repair) 1041 1072 case "repair": 1042 1073 Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); -
binaries/data/mods/public/gui/session/selection_details.js
74 74 experienceSize.rtop = 100 - 100 * Math.max(0, Math.min(1, 1.0 * entState.promotion.curr / entState.promotion.req)); 75 75 experienceBar.size = experienceSize; 76 76 77 var experience = "[font=\"serif-bold-13\"]Experience [/font]" + entState.promotion.curr;77 var experience = "[font=\"serif-bold-13\"]Experience [/font]" + Math.floor(entState.promotion.curr); 78 78 if (entState.promotion.curr < entState.promotion.req) 79 79 experience += "/" + entState.promotion.req; 80 80 getGUIObjectByName("experience").tooltip = experience; -
binaries/data/mods/public/simulation/components/GarrisonHolder.js
320 320 var cmpHealth = Engine.QueryInterface(entity, IID_Health); 321 321 if (cmpHealth) 322 322 { 323 if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 323 // We do not want to heal unhealable units 324 if (!cmpHealth.IsUnhealable() && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 324 325 cmpHealth.Increase(this.healRate); 325 326 } 326 327 } -
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 var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 278 if (cmpHeal) 279 { 280 ret.Healer = { 281 "unhealableClasses": cmpHeal.GetUnhealableClasses(), 282 "healableClasses": cmpHeal.GetHealableClasses(), 283 }; 284 } 285 276 286 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 277 287 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 278 288 -
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; 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 undefined; 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 … … 408 450 cmpFormation.Disband(); 409 451 }, 410 452 453 "Order.Heal": function(msg) { 454 // TODO: see notes in Order.Attack 455 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 456 cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 457 cmpFormation.Disband(); 458 }, 459 411 460 "Order.Repair": function(msg) { 412 461 // TODO: see notes in Order.Attack 413 462 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); … … 546 595 // So we'll set a timer here and only report the idle event if we 547 596 // remain idle 548 597 this.StartTimer(1000); 598 599 // TODO: We need to set up a Query that watches own and ally units in LOS 600 // and calls something if they loose health (or change it to simplify) 601 // to replace the really UGLY EVIL HACK using GlobalHealthChanged as that can 602 // have a performance impact as it gets called for each healer for every entity 603 // in the whole map. 604 605 // If a unit can heal and attack we first want to heal wounded units, 606 // so check if we are a healer and find whether there's anybody nearby to heal. 607 // If anyone approaches later it'll be handled via LosRangeUpdate.) 608 if (this.IsHealer() && this.FindNewHealTargets()) 609 return true; // (abort the FSM transition since we may have already switched state) 549 610 550 611 // If we entered the idle state we must have nothing better to do, 551 612 // so immediately check whether there's anybody nearby to attack. 552 613 // (If anyone approaches later, it'll be handled via LosRangeUpdate.) 553 614 if (this.FindNewTargets()) 554 615 return true; // (abort the FSM transition since we may have already switched state) 555 616 556 617 // Nobody to attack - stay in idle 557 618 return false; 558 619 }, … … 560 621 "leave": function() { 561 622 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 562 623 rangeMan.DisableActiveQuery(this.losRangeQuery); 624 if (this.losHealRangeQuery) 625 rangeMan.DisableActiveQuery(this.losHealRangeQuery); 563 626 564 627 this.StopTimer(); 565 628 … … 569 632 Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); 570 633 } 571 634 }, 572 635 636 // TODO: This is part of an really UGLY EVIL HACK 637 "GlobalHealthChanged": function() { 638 if (this.IsHealer()) 639 { 640 this.FindNewHealTargets(); 641 } 642 }, 643 573 644 "LosRangeUpdate": function(msg) { 645 if (this.IsHealer()) 646 { 647 // Start healing one of the newly-seen own or ally units (if any) 648 this.FindNewHealTargets(); 649 } 574 650 if (this.GetStance().targetVisibleEnemies) 575 651 { 576 652 // Start attacking one of the newly-seen enemy (if any) … … 1000 1076 }, 1001 1077 }, 1002 1078 1079 "HEAL": { 1080 "EntityRenamed": function(msg) { 1081 if (this.order.data.target == msg.entity) 1082 this.order.data.target = msg.newentity; 1083 }, 1084 1085 "Attacked": function(msg) { 1086 // If we stand ground we will rather die than flee 1087 if (!this.GetStance().respondStandGround) 1088 this.Flee(msg.data.attacker, false); 1089 }, 1090 1091 "APPROACHING": { 1092 "enter": function () { 1093 this.SelectAnimation("move"); 1094 this.StartTimer(1000, 1000); 1095 }, 1096 1097 "leave": function() { 1098 this.StopTimer(); 1099 }, 1100 1101 "Timer": function(msg) { 1102 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1103 { 1104 this.StopMoving(); 1105 this.FinishOrder(); 1106 1107 // Return to our original position 1108 if (this.GetStance().respondHoldGround) 1109 this.WalkToHeldPosition(); 1110 } 1111 }, 1112 1113 "MoveCompleted": function() { 1114 this.SetNextState("HEALING"); 1115 }, 1116 1117 "Attacked": function(msg) { 1118 // If we stand ground we will rather die than flee 1119 if (!this.GetStance().respondStandGround) 1120 this.Flee(msg.data.attacker, false); 1121 }, 1122 }, 1123 1124 "HEALING": { 1125 "enter": function() { 1126 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1127 this.healTimers = cmpHeal.GetTimers(); 1128 this.SelectAnimation("heal", false, 1.0, "heal"); 1129 this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 1130 this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 1131 // TODO if .prepare is short, players can cheat by cycling heal/stop/heal 1132 // to beat the .repeat time; should enforce a minimum time 1133 // see comment in ATTACKING.enter 1134 this.FaceTowardsTarget(this.order.data.target); 1135 }, 1136 1137 "leave": function() { 1138 this.StopTimer(); 1139 }, 1140 1141 "Timer": function(msg) { 1142 var target = this.order.data.target; 1143 // Check the target is still alive and healable 1144 if (this.TargetIsAlive(target) && this.CanHeal(target)) 1145 { 1146 // Check if we can still reach the target 1147 if (this.CheckTargetRange(target, IID_Heal)) 1148 { 1149 this.FaceTowardsTarget(target); 1150 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1151 cmpHeal.PerformHeal(target); 1152 return; 1153 } 1154 // Can't reach it - try to chase after it 1155 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1156 { 1157 if (this.MoveToTargetRange(target, IID_Heal)) 1158 { 1159 this.SetNextState("HEAL.CHASING"); 1160 return; 1161 } 1162 } 1163 } 1164 // Can't reach it, healed to max hp or doesn't exist any more - give up 1165 if (this.FinishOrder()) 1166 return; 1167 1168 // Heal another one 1169 if (this.FindNewHealTargets()) 1170 return; 1171 1172 // Return to our original position 1173 if (this.GetStance().respondHoldGround) 1174 this.WalkToHeldPosition(); 1175 }, 1176 "Attacked": function(msg) { 1177 // If we stand ground we will rather die than flee 1178 if (!this.GetStance().respondStandGround) 1179 this.Flee(msg.data.attacker, false); 1180 }, 1181 }, 1182 "CHASING": { 1183 "enter": function () { 1184 this.SelectAnimation("move"); 1185 this.StartTimer(1000, 1000); 1186 }, 1187 1188 "leave": function () { 1189 this.StopTimer(); 1190 }, 1191 "Timer": function(msg) { 1192 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1193 { 1194 this.StopMoving(); 1195 this.FinishOrder(); 1196 1197 // Return to our original position 1198 if (this.GetStance().respondHoldGround) 1199 this.WalkToHeldPosition(); 1200 } 1201 }, 1202 "MoveCompleted": function () { 1203 this.SetNextState("HEALING"); 1204 }, 1205 }, 1206 }, 1207 1003 1208 // Returning to dropsite 1004 1209 "RETURNRESOURCE": { 1005 1210 "APPROACHING": { … … 1473 1678 return (this.template.NaturalBehaviour ? true : false); 1474 1679 }; 1475 1680 1681 UnitAI.prototype.IsHealer = function() 1682 { 1683 return Engine.QueryInterface(this.entity, IID_Heal); 1684 }; 1685 1476 1686 UnitAI.prototype.IsIdle = function() 1477 1687 { 1478 1688 return this.isIdle; … … 1496 1706 UnitAI.prototype.OnOwnershipChanged = function(msg) 1497 1707 { 1498 1708 this.SetupRangeQuery(); 1709 if (this.IsHealer()) 1710 this.SetupHealRangeQuery(); 1499 1711 }; 1500 1712 1501 1713 UnitAI.prototype.OnDestroy = function() … … 1507 1719 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1508 1720 if (this.losRangeQuery) 1509 1721 rangeMan.DestroyActiveQuery(this.losRangeQuery); 1722 if (this.losHealRangeQuery) 1723 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1510 1724 }; 1511 1725 1512 1726 // Set up a range query for all enemy units within LOS range … … 1545 1759 rangeMan.EnableActiveQuery(this.losRangeQuery); 1546 1760 }; 1547 1761 1762 // Set up a range query for all own or ally units within LOS range 1763 // which can be healed. 1764 // This should be called whenever our ownership changes. 1765 UnitAI.prototype.SetupHealRangeQuery = function() 1766 { 1767 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 1768 var owner = cmpOwnership.GetOwner(); 1769 1770 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1771 var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 1772 1773 if (this.losHealRangeQuery) 1774 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1775 1776 var players = [owner]; 1777 1778 if (owner != -1) 1779 { 1780 // If unit not just killed, get ally players via diplomacy 1781 var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); 1782 var numPlayers = playerMan.GetNumPlayers(); 1783 for (var i = 1; i < numPlayers; ++i) 1784 { 1785 // Exclude gaia and enemies 1786 if (cmpPlayer.IsAlly(i)) 1787 players.push(i); 1788 } 1789 } 1790 1791 var range = this.GetQueryRange(true); 1792 1793 this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health); 1794 rangeMan.EnableActiveQuery(this.losHealRangeQuery); 1795 }; 1796 1548 1797 //// FSM linkage functions //// 1549 1798 1550 1799 UnitAI.prototype.SetNextState = function(state) … … 1766 2015 1767 2016 UnitAI.prototype.OnRangeUpdate = function(msg) 1768 2017 { 1769 if (msg.tag == this.losRangeQuery )2018 if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery) 1770 2019 UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 1771 2020 }; 1772 2021 2022 // really UGLY EVIL HACK 2023 // TODO This should be replaced with a c++ component that tracks all entities within a range and notifies 2024 // Healers if any of the entities receives a HealthChanged message. 2025 // If any entity changes health we call this, so this probably has a huge performance impact. 2026 UnitAI.prototype.OnGlobalHealthChanged = function(msg) 2027 { 2028 UnitFsm.ProcessMessage(this, {"type": "GlobalHealthChanged"}); 2029 } 2030 1773 2031 //// Helper functions to be called by the FSM //// 1774 2032 1775 2033 UnitAI.prototype.GetWalkSpeed = function() … … 1797 2055 }; 1798 2056 1799 2057 /** 2058 * Returns true if the target exists and the current hitpoints are at maximum. 2059 */ 2060 UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 2061 { 2062 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2063 if (!cmpHealth) 2064 return false; 2065 2066 return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 2067 }; 2068 2069 /** 2070 * Returns true if the target exists and is unhealable. 2071 */ 2072 UnitAI.prototype.TargetIsUnhealable = function(ent) 2073 { 2074 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 2075 if (!cmpHealth) 2076 return false; 2077 2078 return cmpHealth.IsUnhealable(); 2079 }; 2080 2081 /** 1800 2082 * Returns true if the target exists and needs to be killed before 1801 2083 * beginning to gather resources from it. 1802 2084 */ … … 2296 2578 case "Flee": 2297 2579 case "LeaveFoundation": 2298 2580 case "Attack": 2581 case "Heal": 2299 2582 case "Gather": 2300 2583 case "ReturnResource": 2301 2584 case "Repair": … … 2358 2641 { 2359 2642 if (!this.CanAttack(target)) 2360 2643 { 2361 this.WalkToTarget(target, queued); 2644 // We don't want to let healers walk to the target unit so they can be easily killed. 2645 // Instead we just let them get into healing range. 2646 if (this.IsHealer()) 2647 this.MoveToTargetRange(target, IID_Heal); 2648 else 2649 this.WalkToTarget(target, queued); 2362 2650 return; 2363 2651 } 2364 2652 … … 2419 2707 this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued); 2420 2708 } 2421 2709 2710 UnitAI.prototype.Heal = function(target, queued) 2711 { 2712 if (!this.CanHeal(target)) 2713 { 2714 this.WalkToTarget(target, queued); 2715 return; 2716 } 2717 2718 this.AddOrder("Heal", { "target": target, "force": true }, queued); 2719 }; 2720 2422 2721 UnitAI.prototype.ReturnResource = function(target, queued) 2423 2722 { 2424 2723 if (!this.CanReturnResource(target, true)) … … 2530 2829 this.stance = stance; 2531 2830 else 2532 2831 error("UnitAI: Setting to invalid stance '"+stance+"'"); 2533 } 2832 }; 2534 2833 2535 2834 UnitAI.prototype.SwitchToStance = function(stance) 2536 2835 { … … 2548 2847 2549 2848 // Reset the range query, since the range depends on stance 2550 2849 this.SetupRangeQuery(); 2551 } 2850 // Just if we are a healer 2851 // TODO maybe move those two to a SetupRangeQuerys() 2852 if (this.IsHealer()) 2853 this.SetupHealRangeQuery(); 2854 }; 2552 2855 2553 2856 /** 2554 2857 * Resets losRangeQuery, and if there are some targets in range that we can … … 2569 2872 return this.RespondToTargetedEntities(ents); 2570 2873 }; 2571 2874 2572 UnitAI.prototype.GetQueryRange = function() 2875 /** 2876 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 2877 * then we start healing and this returns true; otherwise, returns false. 2878 */ 2879 UnitAI.prototype.FindNewHealTargets = function() 2880 { 2881 if (!this.losHealRangeQuery) 2882 return false; 2883 2884 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 2885 var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 2886 2887 for each (var ent in ents) 2888 { 2889 if (this.CanHeal(ent)) 2890 { 2891 this.PushOrderFront("Heal", { "target": ent, "force": false }); 2892 return true; 2893 } 2894 } 2895 // We haven't found any target to heal 2896 return false; 2897 }; 2898 2899 UnitAI.prototype.GetQueryRange = function(healer) 2573 2900 { 2574 2901 var ret = { "min": 0, "max": 0 }; 2575 if (this.GetStance().respondStandGround) 2902 // If we have a healer with passive stance we need to set some defaults (eg same as stand ground) 2903 if (this.GetStance().respondStandGround || (healer && !this.GetStance().respondStandGround && !this.GetStance().respondChase && !this.GetStance().respondHoldGround)) 2576 2904 { 2577 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2905 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2578 2906 if (!cmpRanged) 2579 2907 return ret; 2580 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2908 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2581 2909 ret.min = range.min; 2582 2910 ret.max = range.max; 2583 2911 } … … 2591 2919 } 2592 2920 else if (this.GetStance().respondHoldGround) 2593 2921 { 2594 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2922 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2595 2923 if (!cmpRanged) 2596 2924 return ret; 2597 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2925 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2598 2926 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 2599 2927 if (!cmpVision) 2600 2928 return ret; … … 2690 3018 return true; 2691 3019 }; 2692 3020 3021 UnitAI.prototype.CanHeal = function(target) 3022 { 3023 // Formation controllers should always respond to commands 3024 // (then the individual units can make up their own minds) 3025 if (this.IsFormationController()) 3026 return true; 3027 3028 // Verify that we're able to respond to Heal commands 3029 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 3030 if (!cmpHeal) 3031 return false; 3032 3033 // Verify that the target is alive 3034 if (!this.TargetIsAlive(target)) 3035 return false; 3036 3037 // Verify that the target is owned by the same player as the entity or of an ally 3038 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 3039 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 3040 return false; 3041 3042 // Verify that the target is not unhealable 3043 if (this.TargetIsUnhealable(target)) 3044 { 3045 return false; 3046 } 3047 3048 // Verify that the target has no unhealable class 3049 // We could also use cmpIdentity.GetClassesList but this way is cleaner 3050 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 3051 if (!cmpIdentity) 3052 return false; 3053 for each (var unhealableClass in cmpHeal.GetUnhealableClasses()) 3054 { 3055 if (cmpIdentity.HasClass(unhealableClass) != -1) 3056 { 3057 return false; 3058 } 3059 } 3060 3061 // Verify that the target is a healable class 3062 // We could also use cmpIdentity.GetClassesList but this way is cleaner 3063 var healable = false; 3064 for each (var healableClass in cmpHeal.GetHealableClasses()) 3065 { 3066 if (cmpIdentity.HasClass(healableClass) != -1) 3067 { 3068 healable = true; 3069 } 3070 } 3071 if (!healable) 3072 return false; 3073 3074 // Check that the target is not at MaxHealth 3075 if (this.TargetIsAtMaxHitpoints(target)) 3076 return false; 3077 3078 return true; 3079 }; 3080 2693 3081 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2694 3082 { 2695 3083 // 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>