Ticket #999: #999-2012-02-26.patch
File #999-2012-02-26.patch, 37.9 KB (added by , 12 years ago) |
---|
-
0ad/binaries/data/mods/public/simulation/helpers/Commands.js
66 66 }); 67 67 break; 68 68 69 case "heal": 70 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)) -
0ad/binaries/data/mods/public/simulation/components/GuiInterface.js
264 264 ret.barterMarket = { "prices": cmpBarter.GetPrices() }; 265 265 } 266 266 267 // Abilities 268 var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 269 // Check if we have abilities 270 if (cmpHeal || (cmpHeal && cmpPromotion)) 271 ret.Ability = []; 272 if (cmpHeal) 273 { 274 ret.Ability.Healer = { "healableClasses": cmpHeal.GetHealableClasses() }; 275 } 276 277 // TODO remove this; This is just used to test/demonstrate the extensibility 278 // of the Ability system 279 // promoteAbility (just for healers) 280 if (cmpPromotion && cmpHeal) 281 { 282 ret.Ability.Promote = true ; 283 } 284 267 285 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 268 286 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 269 287 -
0ad/binaries/data/mods/public/simulation/components/interfaces/Heal.js
1 Engine.RegisterInterface("Heal"); -
0ad/binaries/data/mods/public/simulation/components/Health.js
131 131 { 132 132 // If we're already dead, don't allow resurrection 133 133 if (this.hitpoints == 0) 134 return ;134 return false; 135 135 136 136 var old = this.hitpoints; 137 137 this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); 138 138 139 139 Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints }); 140 // We return the old and the actual hp 141 return { "old": old, "new": this.hitpoints}; 140 142 }; 141 143 142 144 //// Private functions //// -
0ad/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() -
0ad/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
5 5 Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 6 6 Engine.LoadComponentScript("interfaces/Foundation.js"); 7 7 Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); 8 Engine.LoadComponentScript("interfaces/Heal.js"); 8 9 Engine.LoadComponentScript("interfaces/Health.js"); 9 10 Engine.LoadComponentScript("interfaces/Promotion.js"); 10 11 Engine.LoadComponentScript("interfaces/RallyPoint.js"); -
0ad/binaries/data/mods/public/simulation/components/UnitAI.js
283 283 this.FinishOrder(); 284 284 }, 285 285 286 "Order.Heal": function(msg) { 287 // Check the target is alive 288 if (!this.TargetIsAlive(this.order.data.target)) 289 { 290 this.FinishOrder(); 291 return; 292 } 293 294 // Check if the target is in range 295 if (this.CheckTargetRange(this.order.data.target, IID_Heal)) 296 { 297 this.StopMoving(); 298 this.SetNextState("INDIVIDUAL.HEAL.HEALING"); 299 return; 300 } 301 302 // If we can't reach the target, but are standing ground, 303 // then abandon this heal order 304 if (this.GetStance().respondStandGround && !this.order.data.force) 305 { 306 this.FinishOrder(); 307 return; 308 } 309 310 // Try to move within heal range 311 if (this.MoveToTargetRange(this.order.data.target, IID_Heal)) 312 { 313 // We've started walking to the given point 314 this.SetNextState("INDIVIDUAL.HEAL.APPROACHING"); 315 return; 316 } 317 318 // We can't reach the target, and can't move towards it, 319 // so abandon this heal order 320 this.FinishOrder(); 321 }, 322 286 323 "Order.Gather": function(msg) { 287 324 288 325 // If the target is still alive, we need to kill it first … … 402 439 cmpFormation.Disband(); 403 440 }, 404 441 442 "Order.Heal": function(msg) { 443 // TODO: see notes in Order.Attack 444 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 445 cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 446 cmpFormation.Disband(); 447 }, 448 405 449 "Order.Repair": function(msg) { 406 450 // TODO: see notes in Order.Attack 407 451 var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); … … 540 584 // So we'll set a timer here and only report the idle event if we 541 585 // remain idle 542 586 this.StartTimer(1000); 587 588 // If a unit can heal and attack we first want to heal wounded units, 589 // so check if we are a healer and find whether there's anybody nearby to heal. 590 // If anyone approaches later it'll be handled via LosRangeUpdate.) 591 if (this.IsHealer() && this.FindNewHealTargets()) 592 return true; // (abort the FSM transition since we may have already switched state) 543 593 544 594 // If we entered the idle state we must have nothing better to do, 545 595 // so immediately check whether there's anybody nearby to attack. 546 596 // (If anyone approaches later, it'll be handled via LosRangeUpdate.) 547 597 if (this.FindNewTargets()) 548 598 return true; // (abort the FSM transition since we may have already switched state) 549 599 550 600 // Nobody to attack - stay in idle 551 601 return false; 552 602 }, … … 554 604 "leave": function() { 555 605 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 556 606 rangeMan.DisableActiveQuery(this.losRangeQuery); 607 if (this.losHealRangeQuery) 608 rangeMan.DisableActiveQuery(this.losHealRangeQuery); 557 609 558 610 this.StopTimer(); 559 611 … … 563 615 Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); 564 616 } 565 617 }, 566 618 567 619 "LosRangeUpdate": function(msg) { 620 if (this.IsHealer()) 621 { 622 // Start healing one of the newly-seen own or ally units (if any) 623 this.FindNewHealTargets(); 624 } 568 625 if (this.GetStance().targetVisibleEnemies) 569 626 { 570 627 // Start attacking one of the newly-seen enemy (if any) … … 995 1052 }, 996 1053 }, 997 1054 1055 "HEAL": { 1056 "EntityRenamed": function(msg) { 1057 if (this.order.data.target == msg.entity) 1058 this.order.data.target = msg.newentity; 1059 }, 1060 1061 "Attacked": function(msg) { 1062 // If we stand ground we will rather die than flee 1063 if (!this.GetStance().respondStandGround) 1064 this.Flee(msg.data.attacker, false); 1065 }, 1066 1067 "APPROACHING": { 1068 "enter": function () { 1069 this.SelectAnimation("move"); 1070 this.StartTimer(1000, 1000); 1071 }, 1072 1073 "leave": function() { 1074 this.StopTimer(); 1075 }, 1076 1077 "Timer": function(msg) { 1078 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1079 { 1080 this.StopMoving(); 1081 this.FinishOrder(); 1082 1083 // Return to our original position 1084 if (this.GetStance().respondHoldGround) 1085 this.WalkToHeldPosition(); 1086 } 1087 }, 1088 1089 "MoveCompleted": function() { 1090 this.SetNextState("HEALING"); 1091 }, 1092 1093 "Attacked": function(msg) { 1094 // If we stand ground we will rather die than flee 1095 if (!this.GetStance().respondStandGround) 1096 this.Flee(msg.data.attacker, false); 1097 }, 1098 }, 1099 1100 "HEALING": { 1101 "enter": function() { 1102 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1103 this.healTimers = cmpHeal.GetTimers(); 1104 // this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation 1105 // this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 1106 this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 1107 // TODO if .prepare is short, players can cheat by cycling heal/stop/heal 1108 // to beat the .repeat time; should enforce a minimum time 1109 // see comment in ATTACKING.enter 1110 this.FaceTowardsTarget(this.order.data.target); 1111 }, 1112 1113 "leave": function() { 1114 this.StopTimer(); 1115 }, 1116 1117 "Timer": function(msg) { 1118 var target = this.order.data.target; 1119 // Check the target is still alive and healable 1120 if (this.TargetIsAlive(target) && this.CanHeal(target)) 1121 { 1122 // Check if we can still reach the target 1123 if (this.CheckTargetRange(target, IID_Heal)) 1124 { 1125 this.FaceTowardsTarget(target); 1126 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 1127 cmpHeal.PerformHeal(target); 1128 return; 1129 } 1130 // Can't reach it - try to chase after it 1131 if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 1132 { 1133 if (this.MoveToTargetRange(target, IID_Heal)) 1134 { 1135 this.SetNextState("HEAL.CHASING"); 1136 return; 1137 } 1138 } 1139 } 1140 // Can't reach it, healed to max hp or doesn't exist any more - give up 1141 if (this.FinishOrder()) 1142 return; 1143 1144 // Heal another one 1145 if (this.FindNewHealTargets()) 1146 return; 1147 1148 // Return to our original position 1149 if (this.GetStance().respondHoldGround) 1150 this.WalkToHeldPosition(); 1151 }, 1152 "Attacked": function(msg) { 1153 // If we stand ground we will rather die than flee 1154 if (!this.GetStance().respondStandGround) 1155 this.Flee(msg.data.attacker, false); 1156 }, 1157 }, 1158 "CHASING": { 1159 "enter": function () { 1160 this.SelectAnimation("move"); 1161 this.StartTimer(1000, 1000); 1162 }, 1163 1164 "leave": function () { 1165 this.StopTimer(); 1166 }, 1167 "Timer": function(msg) { 1168 if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 1169 { 1170 this.StopMoving(); 1171 this.FinishOrder(); 1172 1173 // Return to our original position 1174 if (this.GetStance().respondHoldGround) 1175 this.WalkToHeldPosition(); 1176 } 1177 }, 1178 "MoveCompleted": function () { 1179 this.SetNextState("HEALING"); 1180 }, 1181 }, 1182 }, 1183 998 1184 // Returning to dropsite 999 1185 "RETURNRESOURCE": { 1000 1186 "APPROACHING": { … … 1422 1608 return (this.template.NaturalBehaviour ? true : false); 1423 1609 }; 1424 1610 1611 UnitAI.prototype.IsHealer = function() 1612 { 1613 return Engine.QueryInterface(this.entity, IID_Heal); 1614 }; 1615 1425 1616 UnitAI.prototype.IsIdle = function() 1426 1617 { 1427 1618 return this.isIdle; … … 1445 1636 UnitAI.prototype.OnOwnershipChanged = function(msg) 1446 1637 { 1447 1638 this.SetupRangeQuery(); 1639 if (this.IsHealer()) 1640 this.SetupHealRangeQuery(); 1448 1641 }; 1449 1642 1450 1643 UnitAI.prototype.OnDestroy = function() … … 1456 1649 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1457 1650 if (this.losRangeQuery) 1458 1651 rangeMan.DestroyActiveQuery(this.losRangeQuery); 1652 if (this.losHealRangeQuery) 1653 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1459 1654 }; 1460 1655 1461 1656 // Set up a range query for all enemy units within LOS range … … 1494 1689 rangeMan.EnableActiveQuery(this.losRangeQuery); 1495 1690 }; 1496 1691 1692 // Set up a range query for all own or ally units within LOS range 1693 // which can be healed. 1694 // This should be called whenever our ownership changes. 1695 UnitAI.prototype.SetupHealRangeQuery = function() 1696 { 1697 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 1698 var owner = cmpOwnership.GetOwner(); 1699 1700 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 1701 var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 1702 1703 if (this.losHealRangeQuery) 1704 rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 1705 1706 var players = [owner]; 1707 1708 if (owner != -1) 1709 { 1710 // If unit not just killed, get ally players via diplomacy 1711 var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); 1712 var numPlayers = playerMan.GetNumPlayers(); 1713 for (var i = 1; i < numPlayers; ++i) 1714 { 1715 // Exclude gaia and enemies 1716 if (cmpPlayer.IsAlly(i)) 1717 players.push(i); 1718 } 1719 } 1720 1721 var range = this.GetQueryRange(true); 1722 1723 this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health); 1724 rangeMan.EnableActiveQuery(this.losHealRangeQuery); 1725 }; 1726 1497 1727 //// FSM linkage functions //// 1498 1728 1499 1729 UnitAI.prototype.SetNextState = function(state) … … 1715 1945 1716 1946 UnitAI.prototype.OnRangeUpdate = function(msg) 1717 1947 { 1718 if (msg.tag == this.losRangeQuery )1948 if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery) 1719 1949 UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 1720 1950 }; 1721 1951 … … 1746 1976 }; 1747 1977 1748 1978 /** 1979 * Returns true if the target exists and the current hitpoints are at maximum. 1980 */ 1981 UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 1982 { 1983 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 1984 if(!cmpHealth) 1985 return false; 1986 1987 return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 1988 }; 1989 1990 /** 1749 1991 * Returns true if the target exists and needs to be killed before 1750 1992 * beginning to gather resources from it. 1751 1993 */ … … 2236 2478 case "Flee": 2237 2479 case "LeaveFoundation": 2238 2480 case "Attack": 2481 case "Heal": 2239 2482 case "Gather": 2240 2483 case "ReturnResource": 2241 2484 case "Repair": … … 2359 2602 this.AddOrder("GatherNearPosition", { "type": type, "x": position[0], "z": position[1] }, queued); 2360 2603 } 2361 2604 2605 UnitAI.prototype.Heal = function(target, queued) 2606 { 2607 if (!this.CanHeal(target)) 2608 { 2609 this.WalkToTarget(target, queued); 2610 return; 2611 } 2612 2613 this.AddOrder("Heal", { "target": target, "force": true }, queued); 2614 }; 2615 2362 2616 UnitAI.prototype.ReturnResource = function(target, queued) 2363 2617 { 2364 2618 if (!this.CanReturnResource(target, true)) … … 2397 2651 this.stance = stance; 2398 2652 else 2399 2653 error("UnitAI: Setting to invalid stance '"+stance+"'"); 2400 } 2654 }; 2401 2655 2402 2656 UnitAI.prototype.SwitchToStance = function(stance) 2403 2657 { … … 2413 2667 2414 2668 // Reset the range query, since the range depends on stance 2415 2669 this.SetupRangeQuery(); 2416 } 2670 // Just if we are a healer 2671 // TODO maybe move those two to a SetupRangeQuerys() 2672 if (this.IsHealer()) 2673 this.SetupHealRangeQuery(); 2674 }; 2417 2675 2418 2676 /** 2419 2677 * Resets losRangeQuery, and if there are some targets in range that we can … … 2434 2692 return this.RespondToTargetedEntities(ents); 2435 2693 }; 2436 2694 2437 UnitAI.prototype.GetQueryRange = function() 2695 /** 2696 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 2697 * then we start healing and this returns true; otherwise, returns false. 2698 */ 2699 UnitAI.prototype.FindNewHealTargets = function() 2438 2700 { 2701 if (!this.losHealRangeQuery) 2702 return false; 2703 2704 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 2705 var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 2706 2707 for each (var ent in ents) 2708 { 2709 if (this.CanHeal(ent)) 2710 { 2711 this.PushOrderFront("Heal", { "target": ent, "force": false }); 2712 return true; 2713 } 2714 } 2715 // We haven't found any target to heal 2716 return false; 2717 }; 2718 2719 UnitAI.prototype.GetQueryRange = function(healer) 2720 { 2439 2721 var ret = { "min": 0, "max": 0 }; 2440 if (this.GetStance().respondStandGround) 2722 // If we have a healer with passive stance we need to set some defaults (eg same as stand ground) 2723 if (this.GetStance().respondStandGround || (healer && !this.GetStance().respondStandGround && !this.GetStance().respondChase && !this.GetStance().respondHoldGround)) 2441 2724 { 2442 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2725 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2443 2726 if (!cmpRanged) 2444 2727 return ret; 2445 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2728 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2446 2729 ret.min = range.min; 2447 2730 ret.max = range.max; 2448 2731 } … … 2456 2739 } 2457 2740 else if (this.GetStance().respondHoldGround) 2458 2741 { 2459 var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);2742 var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 2460 2743 if (!cmpRanged) 2461 2744 return ret; 2462 var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());2745 var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 2463 2746 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 2464 2747 if (!cmpVision) 2465 2748 return ret; … … 2555 2838 return true; 2556 2839 }; 2557 2840 2841 // TODO should we adhere to the Healable element (Health.js?) 2842 UnitAI.prototype.CanHeal = function(target) 2843 { 2844 // Formation controllers should always respond to commands 2845 // (then the individual units can make up their own minds) 2846 if (this.IsFormationController()) 2847 return true; 2848 2849 // Verify that we're able to respond to Heal commands 2850 var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 2851 if (!cmpHeal) 2852 return false; 2853 2854 // Verify that the target is alive 2855 if (!this.TargetIsAlive(target)) 2856 return false; 2857 2858 // Verify that the target is owned by the same player as the entity or of an ally 2859 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 2860 if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 2861 return false; 2862 2863 // Verify that the target is a healable class 2864 // We could also use cmpIdentity.GetClassesList but this way is cleaner 2865 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 2866 if (!cmpIdentity) 2867 return false; 2868 var healable = false; 2869 for each (var healableClass in cmpHeal.GetHealableClasses()) 2870 { 2871 if (cmpIdentity.HasClass(healableClass) != -1) 2872 { 2873 healable = true; 2874 } 2875 } 2876 if (!healable) 2877 return false; 2878 2879 // Check that the target is not at MaxHealth 2880 if (this.TargetIsAtMaxHitpoints(target)) 2881 return false; 2882 2883 return true; 2884 }; 2885 2558 2886 UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 2559 2887 { 2560 2888 // Formation controllers should always respond to commands -
0ad/binaries/data/mods/public/simulation/components/Heal.js
1 function Heal() {} 2 3 Heal.prototype.Schema = 4 "<a:help>Controls the healing abilities of the unit.</a:help>" + 5 "<a:example>" + 6 "<Range>20</Range>" + 7 "<HP>5</HP>" + 8 "<Rate>2000</Rate>" + 9 "<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" + 10 "</a:example>" + 11 "<element name='Range' a:help='Range (in metres) where healing is possible'>" + 12 "<ref name='nonNegativeDecimal'/>" + 13 "</element>" + 14 "<element name='HP' a:help='Hitpoints healed per Rate'>" + 15 "<ref name='nonNegativeDecimal'/>" + 16 "</element>" + 17 "<element name='Rate' a:help='A heal is performed every Rate ms'>" + 18 "<ref name='nonNegativeDecimal'/>" + 19 "</element>" + 20 "<element name='HealableClasses'>" + 21 "<attribute name='datatype'>" + 22 "<value>tokens</value>" + 23 "</attribute>" + 24 "<text/>" + 25 "</element>"; 26 27 Heal.prototype.Init = function() 28 { 29 }; 30 31 Heal.prototype.Serialize = null; // we have no dynamic state to save 32 33 Heal.prototype.GetTimers = function() 34 { 35 var prepare = 1000; 36 var repeat = +(this.template.Rate || 1000); 37 return { "prepare": prepare, "repeat": repeat }; 38 } 39 40 Heal.prototype.GetRange = function() 41 { 42 var max = +this.template.Range; 43 var min = 0; 44 return { "max": max, "min": min }; 45 }; 46 47 Heal.prototype.GetHealableClasses = function() 48 { 49 var classes = this.template.HealableClasses._string; 50 return classes.split(/\s+/); 51 }; 52 53 /** 54 * Heal the target entity. This should only be called after a successful range 55 * check, and should only be called after GetTimers().repeat msec has passed 56 * since the last call to PerformHeal. 57 */ 58 Heal.prototype.PerformHeal = function(target) 59 { 60 this.CauseHeal({"target": target}); 61 }; 62 63 /** 64 * Heal target 65 */ 66 Heal.prototype.CauseHeal = function(data) 67 { 68 var cmpHealth = Engine.QueryInterface(data.target, IID_Health); 69 if (!cmpHealth) 70 return; 71 var targetState = cmpHealth.Increase(Math.max(0,this.template.HP)); 72 73 // Add XP 74 var cmpLoot = Engine.QueryInterface(data.target, IID_Loot); 75 var cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion); 76 if (targetState.old && targetState.new && cmpLoot && cmpPromotion) 77 { 78 // HP healed * XP per HP 79 cmpPromotion.IncreaseXp((targetState.new-targetState.old)*(cmpLoot.GetXp()/cmpHealth.GetMaxHitpoints())); 80 } 81 //TODO we need a sound file 82 // PlaySound("heal_impact", this.entity); 83 }; 84 85 Engine.RegisterComponentType(IID_Heal, "Heal", Heal); -
0ad/binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml
5 5 <Pierce>2.0</Pierce> 6 6 <Crush>2.0</Crush> 7 7 </Armour> 8 <Auras>8 <!-- <Auras> 9 9 <Heal> 10 10 <Radius>20</Radius> 11 11 <Speed>2000</Speed> 12 12 </Heal> 13 </Auras> 13 </Auras>--> 14 <Heal> 15 <Range>30</Range> 16 <HP>5</HP> 17 <Rate>2000</Rate> 18 <HealableClasses datatype="tokens">Support Infantry Cavalry</HealableClasses> 19 </Heal> 14 20 <Cost> 15 21 <Resources> 16 22 <metal>120</metal> … … 23 29 <Identity> 24 30 <Classes datatype="tokens">Healer</Classes> 25 31 <GenericName>Healer</GenericName> 26 <Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip>32 <Tooltip>Heal units.</Tooltip> 27 33 </Identity> 34 <Promotion> 35 <RequiredXp>100</RequiredXp> 36 </Promotion> 28 37 <Sound> 29 38 <SoundGroups> 30 39 <select>voice/hellenes/civ/civ_male_select.xml</select> -
0ad/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> -
0ad/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> -
0ad/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> -
0ad/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> -
0ad/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> -
0ad/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> -
0ad/binaries/data/mods/public/gui/session/session.xml
725 725 </object> 726 726 </object> 727 727 728 <object name="unitAbilityPanel" 729 size="14 12 100% 100%" 730 > 731 <object size="0 0 100% 100%"> 732 <repeat count="24"> 733 <object name="unitAbilityButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom"> 734 <object name="unitAbilityIcon[n]" type="image" ghost="true" size="3 3 43 43"/> 735 </object> 736 </repeat> 737 </object> 738 </object> 739 728 740 <object name="unitResearchPanel" 729 741 style="TranslucentPanelThinBorder" 730 742 size="0 100%-56 100% 100%" -
0ad/binaries/data/mods/public/gui/session/input.js
15 15 const ACTION_NONE = 0; 16 16 const ACTION_GARRISON = 1; 17 17 const ACTION_REPAIR = 2; 18 const ACTION_HEAL = 3; 18 19 var preSelectedAction = ACTION_NONE; 19 20 20 21 var INPUT_NORMAL = 0; … … 255 256 } 256 257 } 257 258 break; 259 case "heal": //TODO add something like && targetState.needsheal ? 260 if (isUnit(targetState) && (playerOwned || allyOwned)) 261 { 262 var healableClasses = entState.Ability.Healer.healableClasses; 263 for each (var unitClass in targetState.identity.classes) 264 { 265 if (healableClasses.indexOf(unitClass) != -1) 266 { 267 return {"possible": true}; 268 } 269 } 270 } 271 break; 258 272 case "gather": 259 273 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 260 274 { … … 350 364 else 351 365 return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 352 366 break; 367 case ACTION_HEAL: 368 if (getActionInfo("heal", target).possible) 369 return {"type": "heal", "cursor": "action-heal", "target": target}; 370 else 371 return {"type": "none", "cursor": "action-heal-disabled", "target": undefined}; 372 break; 353 373 } 354 374 } 355 375 else if (Engine.HotkeyIsPressed("session.garrison")) … … 992 1012 Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 993 1013 return true; 994 1014 1015 case "promote": 1016 Engine.PostNetworkCommand({"type": "promote", "entities": selection, "target": action.target, "queued": queued}); 1017 //TODO play sound??? 1018 return true; 1019 1020 case "heal": 1021 Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 1022 //TODO play sound 1023 // Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 1024 return true; 1025 995 1026 case "build": // (same command as repair) 996 1027 case "repair": 997 1028 Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); … … 1204 1235 inputState = INPUT_PRESELECTEDACTION; 1205 1236 preSelectedAction = ACTION_REPAIR; 1206 1237 break; 1238 case "heal": 1239 inputState = INPUT_PRESELECTEDACTION; 1240 preSelectedAction = ACTION_HEAL; 1241 break; 1242 case "promote": 1243 doAction({ "type": "promote"}) 1244 break; 1207 1245 case "unload-all": 1208 1246 unloadAll(entity); 1209 1247 break; -
0ad/binaries/data/mods/public/gui/session/unit_commands.js
5 5 const FORMATION = "Formation"; 6 6 const TRAINING = "Training"; 7 7 const CONSTRUCTION = "Construction"; 8 const ABILITY = "Ability"; 8 9 const COMMAND = "Command"; 9 10 const STANCE = "Stance"; 10 11 … … 20 21 const BARTER_ACTIONS = ["Sell", "Buy"]; 21 22 22 23 // The number of currently visible buttons (used to optimise showing/hiding) 23 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, " Command": 0, "Stance": 0};24 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Ability": 0, "Command": 0, "Stance": 0}; 24 25 25 26 // Unit panels are panels with row(s) of buttons 26 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", " Research", "Stance", "Command"];27 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Ability", "Research", "Stance", "Command"]; 27 28 28 29 // Indexes of resources to sell and buy on barter panel 29 30 var g_barterSell = 0; … … 174 175 numberOfItems = 24; 175 176 break; 176 177 178 case ABILITY: 179 if (numberOfItems > 24) 180 numberOfItems = 24; 181 break; 182 177 183 case COMMAND: 178 184 if (numberOfItems > 6) 179 185 numberOfItems = 6; … … 190 196 var item = items[i]; 191 197 var entType = ((guiName == "Queue")? item.template : item); 192 198 var template; 193 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" )199 if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" && guiName != "Ability") 194 200 { 195 201 template = GetTemplateData(entType); 196 202 if (!template) … … 269 275 270 276 break; 271 277 278 case ABILITY: 279 // TODO read tooltips from some file or template based on 'item' 280 var tooltip; 281 switch(item) 282 { 283 case "heal": 284 tooltip = "Heal units"; 285 break; 286 case "promote": 287 tooltip = "Promote this unit"; 288 break; 289 default: 290 tooltip = "No tooltip defined"; 291 break; 292 } 293 break; 294 272 295 case COMMAND: 273 296 // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 274 297 if (item.name == "unload-all") … … 338 361 icon.sprite = "stretched:session/icons/single/" + item.icon; 339 362 340 363 } 364 else if (guiName == "Ability") 365 { 366 icon.sprite = "stretched:session/icons/single/"+item+".png"; 367 } 341 368 else if (template.icon) 342 369 { 343 370 icon.sprite = "stretched:session/portraits/" + template.icon; … … 511 538 setupUnitBarterPanel(entState); 512 539 } 513 540 541 if (entState.Ability) 542 { 543 var abilities = []; 544 if (entState.Ability.Healer) 545 abilities.push("heal"); 546 if (entState.Ability.Promote) 547 abilities.push("promote"); 548 setupUnitPanel("Ability", usedPanels, entState, abilities, function (item) { performCommand(entState.id, item); }); 549 } 550 514 551 if (entState.buildEntities && entState.buildEntities.length) 515 552 { 516 553 setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement); -
0ad/binaries/data/mods/public/art/textures/cursors/action-heal-disabled.txt
1 1 1 -
0ad/binaries/data/mods/public/art/textures/cursors/action-heal.txt
1 1 1