Ticket #999: #999-2012-03-19.patch
File #999-2012-03-19.patch, 45.2 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()) 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(); 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!==undefined && 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 || this.GetHitpoints() <= 0 79 || this.GetHitpoints() >= this.GetMaxHitpoints()); 80 }; 81 75 82 Health.prototype.Kill = function() 76 83 { 77 84 this.Reduce(this.hitpoints); … … 131 138 { 132 139 // If we're already dead, don't allow resurrection 133 140 if (this.hitpoints == 0) 134 return ;141 return undefined; 135 142 136 143 var old = this.hitpoints; 137 144 this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); 138 145 139 146 Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints }); 147 // We return the old and the actual hp 148 return { "old": old, "new": this.hitpoints}; 140 149 }; 141 150 142 151 //// 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 … … 1539 1753 } 1540 1754 } 1541 1755 1542 var range = this.GetQueryRange( );1756 var range = this.GetQueryRange(IID_Attack); 1543 1757 1544 1758 this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver); 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(IID_Heal); 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() … … 2296 2554 case "Flee": 2297 2555 case "LeaveFoundation": 2298 2556 case "Attack": 2557 case "Heal": 2299 2558 case "Gather": 2300 2559 case "ReturnResource": 2301 2560 case "Repair": … … 2358 2617 { 2359 2618 if (!this.CanAttack(target)) 2360 2619 { 2361 this.WalkToTarget(target, queued); 2620 // We don't want to let healers walk to the target unit so they can be easily killed. 2621 // Instead we just let them get into healing range. 2622 if (this.IsHealer()) 2623 this.MoveToTargetRange(target, IID_Heal); 2624 else 2625 this.WalkToTarget(target, queued); 2362 2626 return; 2363 2627 } 2364 2628 … … 2419 2683 this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued); 2420 2684 } 2421 2685 2686 UnitAI.prototype.Heal = function(target, queued) 2687 { 2688 if (!this.CanHeal(target)) 2689 { 2690 this.WalkToTarget(target, queued); 2691 return; 2692 } 2693 2694 this.AddOrder("Heal", { "target": target, "force": true }, queued); 2695 }; 2696 2422 2697 UnitAI.prototype.ReturnResource = function(target, queued) 2423 2698 { 2424 2699 if (!this.CanReturnResource(target, true)) … … 2530 2805 this.stance = stance; 2531 2806 else 2532 2807 error("UnitAI: Setting to invalid stance '"+stance+"'"); 2533 } 2808 }; 2534 2809 2535 2810 UnitAI.prototype.SwitchToStance = function(stance) 2536 2811 { … … 2548 2823 2549 2824 // Reset the range query, since the range depends on stance 2550 2825 this.SetupRangeQuery(); 2551 } 2826 // Just if we are a healer 2827 // TODO maybe move those two to a SetupRangeQuerys() 2828 if (this.IsHealer()) 2829 this.SetupHealRangeQuery(); 2830 }; 2552 2831 2553 2832 /** 2554 2833 * Resets losRangeQuery, and if there are some targets in range that we can … … 2569 2848 return this.RespondToTargetedEntities(ents); 2570 2849 }; 2571 2850 2572 UnitAI.prototype.GetQueryRange = function() 2851 /** 2852 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 2853 * then we start healing and this returns true; otherwise, returns false. 2854 */ 2855 UnitAI.prototype.FindNewHealTargets = function() 2856 { 2857 if (!this.losHealRangeQuery) 2858 return false; 2859 2860 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 2861 var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 2862 2863 for each (var ent in ents) 2864 { 2865 if (this.CanHeal(ent)) 2866 { 2867 this.PushOrderFront("Heal", { "target": ent, "force": false }); 2868 return true; 2869 } 2870 } 2871 // We haven't found any target to heal 2872 return false; 2873 }; 2874 2875 UnitAI.prototype.GetQueryRange = function(iid) 2573 2876 { 2574 2877 var ret = { "min": 0, "max": 0 }; 2575 2878 if (this.GetStance().respondStandGround) 2576 2879 { 2577 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2880 var cmpRanged = Engine.QueryInterface(this.entity, iid); 2578 2881 if (!cmpRanged) 2579 2882 return ret; 2580 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2883 var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetBestAttack(); 2581 2884 ret.min = range.min; 2582 2885 ret.max = range.max; 2583 2886 } … … 2591 2894 } 2592 2895 else if (this.GetStance().respondHoldGround) 2593 2896 { 2594 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2897 var cmpRanged = Engine.QueryInterface(this.entity, iid); 2595 2898 if (!cmpRanged) 2596 2899 return ret; 2597 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2900 var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetBestAttack(); 2598 2901 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 2599 2902 if (!cmpVision) 2600 2903 return ret; 2601 2904 var halfvision = cmpVision.GetRange() / 2; 2602 2905 ret.max = range.max + halfvision; 2603 2906 } 2907 // We probably have stance 'passive' and we wouldn't have a range, 2908 // but as it is the default for healers we need to set it to something sane. 2909 else if (iid === IID_Heal) 2910 { 2911 var cmpRanged = Engine.QueryInterface(this.entity, iid); 2912 if (!cmpRanged) 2913 return ret; 2914 var range = cmpRanged.GetRange(); 2915 ret.min = range.min; 2916 ret.max = range.max; 2917 } 2604 2918 return ret; 2605 2919 }; 2606 2920 … … 2690 3004 return true; 2691 3005 }; 2692 3006 3007 UnitAI.prototype.CanHeal = function(target) 3008 { 3009 // Formation controllers should always respond to commands 3010 // (then the individual units can make up their own minds) 3011 if (this.IsFormationController()) 3012 return true; 3013 3014 // Verify that we're able to respond to Heal commands 3015 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 3016 if (!cmpHeal) 3017 return false; 3018 3019 // Verify that the target is alive 3020 if (!this.TargetIsAlive(target)) 3021 return false; 3022 3023 // Verify that the target is owned by the same player as the entity or of an ally 3024 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 3025 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 3026 return false; 3027 3028 // Verify that the target is not unhealable (or at max health) 3029 var cmpHealth = Engine.QueryInterface(target, IID_Health); 3030 if (!cmpHealth || cmpHealth.IsUnhealable()) 3031 return false; 3032 3033 // Verify that the target has no unhealable class 3034 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 3035 if (!cmpIdentity) 3036 return false; 3037 for each (var unhealableClass in cmpHeal.GetUnhealableClasses()) 3038 { 3039 if (cmpIdentity.HasClass(unhealableClass) != -1) 3040 { 3041 return false; 3042 } 3043 } 3044 3045 // Verify that the target is a healable class 3046 var healable = false; 3047 for each (var healableClass in cmpHeal.GetHealableClasses()) 3048 { 3049 if (cmpIdentity.HasClass(healableClass) != -1) 3050 { 3051 healable = true; 3052 } 3053 } 3054 if (!healable) 3055 return false; 3056 3057 return true; 3058 }; 3059 2693 3060 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2694 3061 { 2695 3062 // 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 player "+player+" or their ally: "+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> 9 <Heal> 10 <Radius>20</Radius> 11 <Speed>2000</Speed> 12 </Heal> 13 </Auras> 8 <Heal> 9 <Range>30</Range> 10 <HP>5</HP> 11 <Rate>2000</Rate> 12 <UnhealableClasses datatype="tokens"/> 13 <HealableClasses datatype="tokens">Support Infantry Cavalry</HealableClasses> 14 </Heal> 14 15 <Cost> 15 16 <Resources> 16 17 <metal>120</metal> … … 23 24 <Identity> 24 25 <Classes datatype="tokens">Healer</Classes> 25 26 <GenericName>Healer</GenericName> 26 <Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip>27 <Tooltip>Heal units.</Tooltip> 27 28 </Identity> 29 <Promotion> 30 <RequiredXp>100</RequiredXp> 31 </Promotion> 28 32 <Sound> 29 33 <SoundGroups> 30 34 <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>