Ticket #999: #999-2012-03-07.patch

File #999-2012-03-07.patch, 46.4 KB (added by leper, 15 months ago)
  • binaries/data/mods/public/art/textures/cursors/action-heal-disabled.txt

     
     11 1 
  • binaries/data/mods/public/art/textures/cursors/action-heal.txt

     
     11 1 
  • binaries/data/mods/public/gui/session/input.js

     
    1515const ACTION_NONE = 0; 
    1616const ACTION_GARRISON = 1; 
    1717const ACTION_REPAIR = 2; 
     18const ACTION_HEAL = 3; 
    1819var preSelectedAction = ACTION_NONE; 
    1920 
    2021var INPUT_NORMAL = 0; 
     
    255256                } 
    256257            } 
    257258            break; 
     259        case "heal": 
     260            // The check if the target is unhealable is done by targetState.needsHeal 
     261            if (entState.Ability && isUnit(targetState) && targetState.needsHeal && (playerOwned || allyOwned)) 
     262            { 
     263                var unhealableClasses = entState.Ability.Healer.unhealableClasses; 
     264                for each (var unitClass in targetState.identity.classes) 
     265                { 
     266                    if (unhealableClasses.indexOf(unitClass) != -1) 
     267                    { 
     268                        return {"possible": false}; 
     269                    } 
     270                } 
     271                 
     272                var healableClasses = entState.Ability.Healer.healableClasses; 
     273                for each (var unitClass in targetState.identity.classes) 
     274                { 
     275                    if (healableClasses.indexOf(unitClass) != -1) 
     276                    { 
     277                        return {"possible": true}; 
     278                    } 
     279                } 
     280            } 
     281            break; 
    258282        case "gather": 
    259283            if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 
    260284            { 
     
    350374            else 
    351375                return {"type": "none", "cursor": "action-repair-disabled", "target": undefined}; 
    352376            break; 
     377        case ACTION_HEAL: 
     378            if (getActionInfo("heal", target).possible) 
     379                return {"type": "heal", "cursor": "action-heal", "target": target}; 
     380            else 
     381                return {"type": "none", "cursor": "action-heal-disabled", "target": undefined}; 
     382            break; 
    353383        } 
    354384    } 
    355385    else if (Engine.HotkeyIsPressed("session.garrison")) 
     
    372402            return {"type": "build", "cursor": "action-repair", "target": target}; 
    373403        else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible) 
    374404            return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "position": actionInfo.position}; 
     405        else if (getActionInfo("heal", target).possible) 
     406            return {"type": "heal", "cursor": "action-heal", "target": target}; 
    375407        else if (getActionInfo("attack", target).possible) 
    376408            return {"type": "attack", "cursor": "action-attack", "target": target}; 
    377409        else if (getActionInfo("unset-rallypoint", target).possible) 
     
    9931025        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] }); 
    9941026        return true; 
    9951027 
     1028    case "promote": 
     1029        Engine.PostNetworkCommand({"type": "promote", "entities": selection, "target": action.target, "queued": queued}); 
     1030        // TODO:Play a sound?  
     1031        return true; 
     1032 
     1033    case "heal": 
     1034        Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued}); 
     1035        // TODO: Play a sound? 
     1036//      Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] }); 
     1037        return true; 
     1038 
    9961039    case "build": // (same command as repair) 
    9971040    case "repair": 
    9981041        Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued}); 
     
    12051248                inputState = INPUT_PRESELECTEDACTION; 
    12061249                preSelectedAction = ACTION_REPAIR; 
    12071250                break; 
     1251            case "heal": 
     1252                inputState = INPUT_PRESELECTEDACTION; 
     1253                preSelectedAction = ACTION_HEAL; 
     1254                break; 
     1255            case "promote": 
     1256                doAction({ "type": "promote"}) 
     1257                break; 
    12081258            case "unload-all": 
    12091259                unloadAll(entity); 
    12101260                break; 
  • binaries/data/mods/public/gui/session/session.xml

     
    725725                    </object> 
    726726                </object> 
    727727 
     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 
    728740                <object name="unitResearchPanel" 
    729741                    style="TranslucentPanelThinBorder" 
    730742                    size="0 100%-56 100% 100%" 
  • binaries/data/mods/public/gui/session/unit_commands.js

     
    55const FORMATION = "Formation"; 
    66const TRAINING = "Training"; 
    77const CONSTRUCTION = "Construction"; 
     8const ABILITY = "Ability"; 
    89const COMMAND = "Command"; 
    910const STANCE = "Stance"; 
    1011 
     
    2021const BARTER_ACTIONS = ["Sell", "Buy"]; 
    2122 
    2223// 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}; 
     24var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Barter": 0, "Training": 0, "Construction": 0, "Ability": 0, "Command": 0, "Stance": 0}; 
    2425 
    2526// Unit panels are panels with row(s) of buttons 
    26 var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Research", "Stance", "Command"]; 
     27var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Barter", "Training", "Construction", "Ability", "Research", "Stance", "Command"]; 
    2728 
    2829// Indexes of resources to sell and buy on barter panel 
    2930var g_barterSell = 0; 
     
    174175                numberOfItems =  24; 
    175176            break; 
    176177 
     178        case ABILITY: 
     179            if (numberOfItems > 24) 
     180                numberOfItems = 24; 
     181            break; 
     182 
    177183        case COMMAND: 
    178184            if (numberOfItems > 6) 
    179185                numberOfItems = 6; 
     
    190196        var item = items[i]; 
    191197        var entType = ((guiName == "Queue")? item.template : item); 
    192198        var template; 
    193         if (guiName != "Formation" && guiName != "Command" && guiName != "Stance") 
     199        if (guiName != "Formation" && guiName != "Command" && guiName != "Stance" && guiName != "Ability") 
    194200        { 
    195201            template = GetTemplateData(entType); 
    196202            if (!template) 
     
    269275 
    270276                break; 
    271277 
     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 
    272295            case COMMAND: 
    273296                // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single) 
    274297                if (item.name == "unload-all") 
     
    338361            icon.sprite = "stretched:session/icons/single/" + item.icon; 
    339362 
    340363        } 
     364        else if (guiName == "Ability") 
     365        { 
     366            icon.sprite = "stretched:session/icons/single/"+item+".png"; 
     367        } 
    341368        else if (template.icon) 
    342369        { 
    343370            icon.sprite = "stretched:session/portraits/" + template.icon; 
     
    511538            setupUnitBarterPanel(entState); 
    512539        } 
    513540 
     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 
    514551        if (entState.buildEntities && entState.buildEntities.length) 
    515552        { 
    516553            setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement); 
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

     
    308308            var cmpHealth = Engine.QueryInterface(entity, IID_Health); 
    309309            if (cmpHealth) 
    310310            { 
    311                 if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 
     311                // We do not want to heal unhealable units 
     312                if (!cmpHealth.IsUnhealable() && cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 
    312313                    cmpHealth.Increase(this.healRate); 
    313314            } 
    314315        } 
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    155155        ret.hitpoints = cmpHealth.GetHitpoints(); 
    156156        ret.maxHitpoints = cmpHealth.GetMaxHitpoints(); 
    157157        ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()); 
     158        ret.needsHeal = !cmpHealth.IsUnhealable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()); 
    158159    } 
    159160 
    160161    var cmpAttack = Engine.QueryInterface(ent, IID_Attack); 
     
    264265        ret.barterMarket = { "prices": cmpBarter.GetPrices() }; 
    265266    } 
    266267 
     268    // Abilities 
     269    var cmpHeal = Engine.QueryInterface(ent, IID_Heal); 
     270    // Check if we have abilities 
     271    if (cmpHeal || (cmpHeal && cmpPromotion)) 
     272        ret.Ability = []; 
     273    if (cmpHeal) 
     274    { 
     275        ret.Ability.Healer = {  
     276            "unhealableClasses": cmpHeal.GetUnhealableClasses(), 
     277            "healableClasses": cmpHeal.GetHealableClasses(), 
     278        }; 
     279    } 
     280 
     281    // TODO remove this; This is just used to test/demonstrate the extensibility 
     282    // of the Ability system 
     283    // promoteAbility (just for healers) 
     284    if (cmpPromotion && cmpHeal) 
     285    { 
     286        ret.Ability.Promote = true ; 
     287    } 
     288 
    267289    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 
    268290    ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false); 
    269291 
  • binaries/data/mods/public/simulation/components/Heal.js

     
     1function Heal() {} 
     2 
     3Heal.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 
     34Heal.prototype.Init = function() 
     35{ 
     36}; 
     37 
     38Heal.prototype.Serialize = null; // we have no dynamic state to save 
     39 
     40Heal.prototype.GetTimers = function() 
     41{ 
     42    var prepare = 1000; 
     43    var repeat = +(this.template.Rate || 1000); 
     44    return { "prepare": prepare, "repeat": repeat }; 
     45}; 
     46 
     47Heal.prototype.GetRange = function() 
     48{ 
     49    var max = +this.template.Range; 
     50    var min = 0; 
     51    return { "max": max, "min": min }; 
     52}; 
     53 
     54Heal.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 
     61Heal.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 */ 
     72Heal.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 
     91Engine.RegisterComponentType(IID_Heal, "Heal", Heal); 
  • binaries/data/mods/public/simulation/components/Health.js

     
    2525            "<value a:help='Remain in the world with 0 health'>remain</value>" + 
    2626        "</choice>" + 
    2727    "</element>" + 
    28     "<element name='Healable' a:help='Indicates that the entity can be healed by healer units'>" + 
     28    "<element name='Unhealable' a:help='Indicates that the entity can not be healed by healer units'>" + 
    2929        "<data type='boolean'/>" + 
    3030    "</element>" + 
    3131    "<element name='Repairable' a:help='Indicates that the entity can be repaired by builder units'>" + 
     
    7272    return (this.template.Repairable == "true"); 
    7373}; 
    7474 
     75Health.prototype.IsUnhealable = function() 
     76{ 
     77    return (this.template.Unhealable == "true"); 
     78}; 
     79 
    7580Health.prototype.Kill = function() 
    7681{ 
    7782    this.Reduce(this.hitpoints); 
     
    131136{ 
    132137    // If we're already dead, don't allow resurrection 
    133138    if (this.hitpoints == 0) 
    134         return; 
     139        return false; 
    135140 
    136141    var old = this.hitpoints; 
    137142    this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); 
    138143 
    139144    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}; 
    140147}; 
    141148 
    142149//// Private functions //// 
  • binaries/data/mods/public/simulation/components/Loot.js

     
    2121 
    2222Loot.prototype.GetXp = function() 
    2323{ 
    24     return this.template.xp; 
     24    return +(this.template.xp || 0); 
    2525}; 
    2626 
    2727Loot.prototype.GetResources = function() 
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    139139    "HealthChanged": function(msg) { 
    140140        // ignore 
    141141    }, 
     142     
     143    // TODO: This is part of a really UGLY EVIL HACK 
     144    "GlobalHealthChanged": function() { 
     145        // ignore 
     146    }, 
    142147 
    143148    "EntityRenamed": function(msg) { 
    144149        // ignore 
     
    283288        this.FinishOrder(); 
    284289    }, 
    285290 
     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 
    286328    "Order.Gather": function(msg) { 
    287329         
    288330        // If the target is still alive, we need to kill it first 
     
    402444            cmpFormation.Disband(); 
    403445        }, 
    404446 
     447        "Order.Heal": function(msg) { 
     448            // TODO: see notes in Order.Attack 
     449            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 
     450            cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]); 
     451            cmpFormation.Disband(); 
     452        }, 
     453 
    405454        "Order.Repair": function(msg) { 
    406455            // TODO: see notes in Order.Attack 
    407456            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); 
     
    540589                // So we'll set a timer here and only report the idle event if we 
    541590                // remain idle 
    542591                this.StartTimer(1000); 
     592                 
     593                // TODO: We need to set up a Query that watches own and ally units in LOS 
     594                // and calls something if they loose health (or change it to simplify) 
     595                // to replace the really UGLY EVIL HACK using GlobalHealthChanged as that can 
     596                // have a performance impact as it gets called for each healer for every entity 
     597                // in the whole map. 
     598                 
     599                // If a unit can heal and attack we first want to heal wounded units, 
     600                // so check if we are a healer and find whether there's anybody nearby to heal. 
     601                // If anyone approaches later it'll be handled via LosRangeUpdate.) 
     602                if (this.IsHealer() && this.FindNewHealTargets()) 
     603                    return true; // (abort the FSM transition since we may have already switched state) 
    543604 
    544605                // If we entered the idle state we must have nothing better to do, 
    545606                // so immediately check whether there's anybody nearby to attack. 
    546607                // (If anyone approaches later, it'll be handled via LosRangeUpdate.) 
    547608                if (this.FindNewTargets()) 
    548609                    return true; // (abort the FSM transition since we may have already switched state) 
    549  
     610                 
    550611                // Nobody to attack - stay in idle 
    551612                return false; 
    552613            }, 
     
    554615            "leave": function() { 
    555616                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 
    556617                rangeMan.DisableActiveQuery(this.losRangeQuery); 
     618                if (this.losHealRangeQuery) 
     619                    rangeMan.DisableActiveQuery(this.losHealRangeQuery); 
    557620 
    558621                this.StopTimer(); 
    559622 
     
    563626                    Engine.PostMessage(this.entity, MT_UnitIdleChanged, { "idle": this.isIdle }); 
    564627                } 
    565628            }, 
    566  
     629             
     630            // TODO: This is part of an really UGLY EVIL HACK 
     631            "GlobalHealthChanged": function() { 
     632                if (this.IsHealer()) 
     633                { 
     634                    this.FindNewHealTargets(); 
     635                } 
     636            }, 
     637             
    567638            "LosRangeUpdate": function(msg) { 
     639                if (this.IsHealer()) 
     640                { 
     641                    // Start healing one of the newly-seen own or ally units (if any) 
     642                    this.FindNewHealTargets(); 
     643                } 
    568644                if (this.GetStance().targetVisibleEnemies) 
    569645                { 
    570646                    // Start attacking one of the newly-seen enemy (if any) 
     
    9941070            }, 
    9951071        }, 
    9961072 
     1073        "HEAL": { 
     1074            "EntityRenamed": function(msg) { 
     1075                if (this.order.data.target == msg.entity) 
     1076                    this.order.data.target = msg.newentity; 
     1077            }, 
     1078 
     1079            "Attacked": function(msg) { 
     1080                // If we stand ground we will rather die than flee 
     1081                if (!this.GetStance().respondStandGround) 
     1082                    this.Flee(msg.data.attacker, false); 
     1083            }, 
     1084 
     1085            "APPROACHING": { 
     1086                "enter": function () { 
     1087                    this.SelectAnimation("move"); 
     1088                    this.StartTimer(1000, 1000); 
     1089                }, 
     1090 
     1091                "leave": function() { 
     1092                    this.StopTimer(); 
     1093                }, 
     1094 
     1095                "Timer": function(msg) { 
     1096                    if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 
     1097                    { 
     1098                        this.StopMoving(); 
     1099                        this.FinishOrder(); 
     1100 
     1101                        // Return to our original position 
     1102                        if (this.GetStance().respondHoldGround) 
     1103                            this.WalkToHeldPosition(); 
     1104                    } 
     1105                }, 
     1106 
     1107                "MoveCompleted": function() { 
     1108                    this.SetNextState("HEALING"); 
     1109                }, 
     1110 
     1111                "Attacked": function(msg) { 
     1112                    // If we stand ground we will rather die than flee 
     1113                    if (!this.GetStance().respondStandGround) 
     1114                        this.Flee(msg.data.attacker, false); 
     1115                }, 
     1116            }, 
     1117 
     1118            "HEALING": { 
     1119                "enter": function() { 
     1120                    var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 
     1121                    this.healTimers = cmpHeal.GetTimers(); 
     1122//                  this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation 
     1123//                  this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat); 
     1124                    this.StartTimer(this.healTimers.prepare, this.healTimers.repeat); 
     1125                    // TODO if .prepare is short, players can cheat by cycling heal/stop/heal 
     1126                    // to beat the .repeat time; should enforce a minimum time 
     1127                    // see comment in ATTACKING.enter 
     1128                    this.FaceTowardsTarget(this.order.data.target); 
     1129                }, 
     1130 
     1131                "leave": function() { 
     1132                    this.StopTimer(); 
     1133                }, 
     1134 
     1135                "Timer": function(msg) { 
     1136                    var target = this.order.data.target; 
     1137                    // Check the target is still alive and healable 
     1138                    if (this.TargetIsAlive(target) && this.CanHeal(target)) 
     1139                    { 
     1140                        // Check if we can still reach the target 
     1141                        if (this.CheckTargetRange(target, IID_Heal)) 
     1142                        { 
     1143                            this.FaceTowardsTarget(target); 
     1144                            var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 
     1145                            cmpHeal.PerformHeal(target); 
     1146                            return; 
     1147                        } 
     1148                        // Can't reach it - try to chase after it 
     1149                        if (this.ShouldChaseTargetedEntity(target, this.order.data.force)) 
     1150                        { 
     1151                            if (this.MoveToTargetRange(target, IID_Heal)) 
     1152                            { 
     1153                                this.SetNextState("HEAL.CHASING"); 
     1154                                return; 
     1155                            } 
     1156                        } 
     1157                    } 
     1158                    // Can't reach it, healed to max hp or doesn't exist any more - give up 
     1159                    if (this.FinishOrder()) 
     1160                        return; 
     1161 
     1162                    // Heal another one 
     1163                    if (this.FindNewHealTargets()) 
     1164                        return; 
     1165                     
     1166                    // Return to our original position 
     1167                    if (this.GetStance().respondHoldGround) 
     1168                        this.WalkToHeldPosition(); 
     1169                }, 
     1170                "Attacked": function(msg) { 
     1171                    // If we stand ground we will rather die than flee 
     1172                    if (!this.GetStance().respondStandGround) 
     1173                        this.Flee(msg.data.attacker, false); 
     1174                }, 
     1175            }, 
     1176            "CHASING": { 
     1177                "enter": function () { 
     1178                    this.SelectAnimation("move"); 
     1179                    this.StartTimer(1000, 1000); 
     1180                }, 
     1181 
     1182                "leave": function () { 
     1183                    this.StopTimer(); 
     1184                }, 
     1185                "Timer": function(msg) { 
     1186                    if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force)) 
     1187                    { 
     1188                        this.StopMoving(); 
     1189                        this.FinishOrder(); 
     1190 
     1191                        // Return to our original position 
     1192                        if (this.GetStance().respondHoldGround) 
     1193                            this.WalkToHeldPosition(); 
     1194                    } 
     1195                }, 
     1196                "MoveCompleted": function () { 
     1197                    this.SetNextState("HEALING"); 
     1198                }, 
     1199            },   
     1200        }, 
     1201 
    9971202        // Returning to dropsite 
    9981203        "RETURNRESOURCE": { 
    9991204            "APPROACHING": { 
     
    14211626    return (this.template.NaturalBehaviour ? true : false); 
    14221627}; 
    14231628 
     1629UnitAI.prototype.IsHealer = function() 
     1630{ 
     1631    return Engine.QueryInterface(this.entity, IID_Heal); 
     1632}; 
     1633 
    14241634UnitAI.prototype.IsIdle = function() 
    14251635{ 
    14261636    return this.isIdle; 
     
    14441654UnitAI.prototype.OnOwnershipChanged = function(msg) 
    14451655{ 
    14461656    this.SetupRangeQuery(); 
     1657    if (this.IsHealer()) 
     1658        this.SetupHealRangeQuery(); 
    14471659}; 
    14481660 
    14491661UnitAI.prototype.OnDestroy = function() 
     
    14551667    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 
    14561668    if (this.losRangeQuery) 
    14571669        rangeMan.DestroyActiveQuery(this.losRangeQuery); 
     1670    if (this.losHealRangeQuery) 
     1671        rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 
    14581672}; 
    14591673 
    14601674// Set up a range query for all enemy units within LOS range 
     
    14931707    rangeMan.EnableActiveQuery(this.losRangeQuery); 
    14941708}; 
    14951709 
     1710// Set up a range query for all own or ally units within LOS range 
     1711// which can be healed. 
     1712// This should be called whenever our ownership changes. 
     1713UnitAI.prototype.SetupHealRangeQuery = function() 
     1714{ 
     1715    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 
     1716    var owner = cmpOwnership.GetOwner(); 
     1717 
     1718    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 
     1719    var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 
     1720 
     1721    if (this.losHealRangeQuery) 
     1722        rangeMan.DestroyActiveQuery(this.losHealRangeQuery); 
     1723 
     1724    var players = [owner]; 
     1725 
     1726    if (owner != -1) 
     1727    { 
     1728        // If unit not just killed, get ally players via diplomacy 
     1729        var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); 
     1730        var numPlayers = playerMan.GetNumPlayers(); 
     1731        for (var i = 1; i < numPlayers; ++i) 
     1732        { 
     1733            // Exclude gaia and enemies 
     1734            if (cmpPlayer.IsAlly(i)) 
     1735                players.push(i); 
     1736        } 
     1737    } 
     1738 
     1739    var range = this.GetQueryRange(true); 
     1740 
     1741    this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health); 
     1742    rangeMan.EnableActiveQuery(this.losHealRangeQuery); 
     1743}; 
     1744 
    14961745//// FSM linkage functions //// 
    14971746 
    14981747UnitAI.prototype.SetNextState = function(state) 
     
    17141963 
    17151964UnitAI.prototype.OnRangeUpdate = function(msg) 
    17161965{ 
    1717     if (msg.tag == this.losRangeQuery) 
     1966    if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery) 
    17181967        UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 
    17191968}; 
    17201969 
     1970// really UGLY EVIL HACK 
     1971// TODO This should be replaced with a c++ component that tracks all entities within a range and notifies 
     1972// Healers if any of the entities receives a HealthChanged message. 
     1973// If any entity changes health we call this, so this probably has a huge performance impact. 
     1974UnitAI.prototype.OnGlobalHealthChanged = function(msg) 
     1975{ 
     1976    if (this.IsHealer()) 
     1977    { 
     1978        UnitFsm.ProcessMessage(this, {"type": "GlobalHealthChanged"}); 
     1979    } 
     1980} 
     1981 
    17211982//// Helper functions to be called by the FSM //// 
    17221983 
    17231984UnitAI.prototype.GetWalkSpeed = function() 
     
    17452006}; 
    17462007 
    17472008/** 
     2009 * Returns true if the target exists and the current hitpoints are at maximum. 
     2010 */ 
     2011UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent) 
     2012{ 
     2013    var cmpHealth = Engine.QueryInterface(ent, IID_Health); 
     2014    if (!cmpHealth) 
     2015        return false; 
     2016 
     2017    return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints()); 
     2018}; 
     2019 
     2020/** 
     2021 * Returns true if the target exists and is unhealable. 
     2022 */ 
     2023UnitAI.prototype.TargetIsUnhealable = function(ent) 
     2024{ 
     2025    var cmpHealth = Engine.QueryInterface(ent, IID_Health); 
     2026    if (!cmpHealth) 
     2027        return false; 
     2028 
     2029    return cmpHealth.IsUnhealable(); 
     2030}; 
     2031 
     2032/** 
    17482033 * Returns true if the target exists and needs to be killed before 
    17492034 * beginning to gather resources from it. 
    17502035 */ 
     
    22352520        case "Flee": 
    22362521        case "LeaveFoundation": 
    22372522        case "Attack": 
     2523        case "Heal": 
    22382524        case "Gather": 
    22392525        case "ReturnResource": 
    22402526        case "Repair": 
     
    23582644    this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued); 
    23592645} 
    23602646 
     2647UnitAI.prototype.Heal = function(target, queued) 
     2648{ 
     2649    if (!this.CanHeal(target)) 
     2650    { 
     2651        this.WalkToTarget(target, queued); 
     2652        return; 
     2653    } 
     2654     
     2655    this.AddOrder("Heal", { "target": target, "force": true }, queued); 
     2656}; 
     2657 
    23612658UnitAI.prototype.ReturnResource = function(target, queued) 
    23622659{ 
    23632660    if (!this.CanReturnResource(target, true)) 
     
    23962693        this.stance = stance; 
    23972694    else 
    23982695        error("UnitAI: Setting to invalid stance '"+stance+"'"); 
    2399 } 
     2696}; 
    24002697 
    24012698UnitAI.prototype.SwitchToStance = function(stance) 
    24022699{ 
     
    24122709 
    24132710    // Reset the range query, since the range depends on stance 
    24142711    this.SetupRangeQuery(); 
    2415 } 
     2712    // Just if we are a healer 
     2713    // TODO maybe move those two to a SetupRangeQuerys() 
     2714    if (this.IsHealer()) 
     2715        this.SetupHealRangeQuery(); 
     2716}; 
    24162717 
    24172718/** 
    24182719 * Resets losRangeQuery, and if there are some targets in range that we can 
     
    24332734    return this.RespondToTargetedEntities(ents); 
    24342735}; 
    24352736 
    2436 UnitAI.prototype.GetQueryRange = function() 
     2737/** 
     2738 * Resets losHealRangeQuery, and if there are some targets in range that we can heal 
     2739 * then we start healing and this returns true; otherwise, returns false. 
     2740 */ 
     2741UnitAI.prototype.FindNewHealTargets = function() 
     2742{ 
     2743    if (!this.losHealRangeQuery) 
     2744        return false; 
     2745     
     2746    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 
     2747    var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery); 
     2748     
     2749    for each (var ent in ents) 
     2750    { 
     2751        if (this.CanHeal(ent)) 
     2752        { 
     2753            this.PushOrderFront("Heal", { "target": ent, "force": false }); 
     2754            return true; 
     2755        } 
     2756    } 
     2757    // We haven't found any target to heal 
     2758    return false; 
     2759}; 
     2760 
     2761UnitAI.prototype.GetQueryRange = function(healer) 
    24372762{ 
    24382763    var ret = { "min": 0, "max": 0 }; 
    2439     if (this.GetStance().respondStandGround) 
     2764    // If we have a healer with passive stance we need to set some defaults (eg same as stand ground) 
     2765    if (this.GetStance().respondStandGround || (healer && !this.GetStance().respondStandGround && !this.GetStance().respondChase && !this.GetStance().respondHoldGround)) 
    24402766    { 
    2441         var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack); 
     2767        var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 
    24422768        if (!cmpRanged) 
    24432769            return ret; 
    2444         var range = cmpRanged.GetRange(cmpRanged.GetBestAttack()); 
     2770        var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 
    24452771        ret.min = range.min; 
    24462772        ret.max = range.max; 
    24472773    } 
     
    24552781    } 
    24562782    else if (this.GetStance().respondHoldGround) 
    24572783    { 
    2458         var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack); 
     2784        var cmpRanged = Engine.QueryInterface(this.entity, healer?IID_Heal:IID_Attack); 
    24592785        if (!cmpRanged) 
    24602786            return ret; 
    2461         var range = cmpRanged.GetRange(cmpRanged.GetBestAttack()); 
     2787        var range = healer?cmpRanged.GetRange():cmpRanged.GetRange(cmpRanged.GetBestAttack()); 
    24622788        var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 
    24632789        if (!cmpVision) 
    24642790            return ret; 
     
    25542880    return true; 
    25552881}; 
    25562882 
     2883UnitAI.prototype.CanHeal = function(target) 
     2884{ 
     2885    // Formation controllers should always respond to commands 
     2886    // (then the individual units can make up their own minds) 
     2887    if (this.IsFormationController()) 
     2888        return true; 
     2889 
     2890    // Verify that we're able to respond to Heal commands 
     2891    var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal); 
     2892    if (!cmpHeal) 
     2893        return false; 
     2894 
     2895    // Verify that the target is alive 
     2896    if (!this.TargetIsAlive(target)) 
     2897        return false; 
     2898 
     2899    // Verify that the target is owned by the same player as the entity or of an ally 
     2900    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 
     2901    if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))) 
     2902        return false; 
     2903     
     2904    // Verify that the target is not unhealable 
     2905    if (this.TargetIsUnhealable(target)) 
     2906    { 
     2907        return false; 
     2908    } 
     2909     
     2910    // Verify that the target has no unhealable class 
     2911    // We could also use cmpIdentity.GetClassesList but this way is cleaner 
     2912    var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 
     2913    if (!cmpIdentity) 
     2914        return false; 
     2915    for each (var unhealableClass in cmpHeal.GetUnhealableClasses()) 
     2916    { 
     2917        if (cmpIdentity.HasClass(unhealableClass) != -1) 
     2918        { 
     2919            return false; 
     2920        } 
     2921    } 
     2922 
     2923    // Verify that the target is a healable class 
     2924    // We could also use cmpIdentity.GetClassesList but this way is cleaner 
     2925    var healable = false; 
     2926    for each (var healableClass in cmpHeal.GetHealableClasses()) 
     2927    { 
     2928        if (cmpIdentity.HasClass(healableClass) != -1) 
     2929        { 
     2930            healable = true; 
     2931        } 
     2932    } 
     2933    if (!healable) 
     2934        return false; 
     2935 
     2936    // Check that the target is not at MaxHealth 
     2937    if (this.TargetIsAtMaxHitpoints(target)) 
     2938        return false;  
     2939 
     2940    return true; 
     2941}; 
     2942 
    25572943UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource) 
    25582944{ 
    25592945    // Formation controllers should always respond to commands 
  • binaries/data/mods/public/simulation/components/interfaces/Heal.js

     
     1Engine.RegisterInterface("Heal"); 
  • binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js

     
    55Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 
    66Engine.LoadComponentScript("interfaces/Foundation.js"); 
    77Engine.LoadComponentScript("interfaces/GarrisonHolder.js"); 
     8Engine.LoadComponentScript("interfaces/Heal.js"); 
    89Engine.LoadComponentScript("interfaces/Health.js"); 
    910Engine.LoadComponentScript("interfaces/Promotion.js"); 
    1011Engine.LoadComponentScript("interfaces/RallyPoint.js"); 
     
    270271    GetHitpoints: function() { return 50; }, 
    271272    GetMaxHitpoints: function() { return 60; }, 
    272273    IsRepairable: function() { return false; }, 
     274    IsUnhealable: function() { return false; }, 
    273275}); 
    274276 
    275277AddMock(10, IID_Identity, { 
     
    303305    hitpoints: 50, 
    304306    maxHitpoints: 60, 
    305307    needsRepair: false, 
     308    needsHeal: true, 
    306309    buildEntities: ["test1", "test2"], 
    307310    barterMarket: { 
    308311        prices: { "buy": {"food":150}, "sell": {"food":25} }, 
  • binaries/data/mods/public/simulation/components/tests/test_UnitAI.js

     
    44Engine.LoadComponentScript("interfaces/Attack.js"); 
    55Engine.LoadComponentScript("interfaces/DamageReceiver.js"); 
    66Engine.LoadComponentScript("interfaces/Formation.js"); 
     7Engine.LoadComponentScript("interfaces/Heal.js"); 
    78Engine.LoadComponentScript("interfaces/Health.js"); 
    89Engine.LoadComponentScript("interfaces/ResourceSupply.js"); 
    910Engine.LoadComponentScript("interfaces/Timer.js"); 
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    6666        }); 
    6767        break; 
    6868 
     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 
    6983    case "repair": 
    7084        // This covers both repairing damaged buildings, and constructing unfinished foundations 
    7185        if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) 
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    3333  <Health> 
    3434    <DeathType>corpse</DeathType> 
    3535    <RegenRate>0</RegenRate> 
    36     <Healable>false</Healable> 
     36    <Unhealable>true</Unhealable> 
    3737    <Repairable>true</Repairable> 
    3838  </Health> 
    3939  <Identity> 
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    3030    <DeathType>corpse</DeathType> 
    3131    <Max>100</Max> 
    3232    <RegenRate>0</RegenRate> 
    33     <Healable>true</Healable> 
     33    <Unhealable>false</Unhealable> 
    3434    <Repairable>false</Repairable> 
    3535  </Health> 
    3636  <Identity> 
  • binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_whale.xml

     
    44    <Max>100</Max> 
    55    <DeathType>remain</DeathType> 
    66    <RegenRate>1</RegenRate> 
    7     <Healable>false</Healable> 
     7    <Unhealable>true</Unhealable> 
    88    <Repairable>false</Repairable> 
    99  </Health> 
    1010  <Identity> 
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical.xml

     
    77    <SinkAccel>2.0</SinkAccel> 
    88  </Decay> 
    99  <Health> 
    10     <Healable>false</Healable> 
     10    <Unhealable>true</Unhealable> 
    1111    <Repairable>true</Repairable> 
    1212  </Health> 
    1313  <Identity> 
  • binaries/data/mods/public/simulation/templates/template_unit_support_healer.xml

     
    55    <Pierce>2.0</Pierce> 
    66    <Crush>2.0</Crush> 
    77  </Armour> 
    8   <Auras> 
     8<!--  <Auras> 
    99    <Heal> 
    1010      <Radius>20</Radius> 
    1111      <Speed>2000</Speed> 
    1212    </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> 
    1421  <Cost> 
    1522    <Resources> 
    1623      <metal>120</metal> 
     
    2330  <Identity> 
    2431    <Classes datatype="tokens">Healer</Classes> 
    2532    <GenericName>Healer</GenericName> 
    26     <Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip> 
     33    <Tooltip>Heal units.</Tooltip> 
    2734  </Identity> 
     35  <Promotion> 
     36    <RequiredXp>100</RequiredXp> 
     37  </Promotion> 
    2838  <Sound> 
    2939    <SoundGroups> 
    3040      <select>voice/hellenes/civ/civ_male_select.xml</select> 
  • binaries/data/mods/public/simulation/templates/template_unit_support_slave.xml

     
    3636    </Resources> 
    3737  </Cost> 
    3838  <Health> 
    39     <Healable>false</Healable> 
     39    <Unhealable>true</Unhealable> 
    4040  </Health> 
    4141  <Identity> 
    4242    <Classes datatype="tokens">Slave</Classes> 
  • binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml

     
    66    <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> 
    77    <Tooltip>Heal units within her aura. (Not implemented yet)</Tooltip> 
    88    <Icon>units/cart_support_healer.png</Icon> 
     9    <Rank>Basic</Rank> 
    910  </Identity> 
     11  <Promotion> 
     12    <Entity>units/cart_support_healer_a</Entity> 
     13  </Promotion> 
    1014  <VisualActor> 
    1115    <Actor>units/carthaginians/healer.xml</Actor> 
    1216  </VisualActor> 
  • binaries/data/mods/public/simulation/templates/units/celt_support_healer_b.xml

     
    55    <SpecificName>Druides </SpecificName> 
    66    <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> 
    77    <Icon>units/celt_support_healer.png</Icon> 
     8    <Rank>Basic</Rank> 
    89  </Identity> 
     10  <Promotion> 
     11    <Entity>units/celt_support_healer_a</Entity> 
     12  </Promotion> 
    913  <VisualActor> 
    1014    <Actor>units/celts/healer.xml</Actor> 
    1115  </VisualActor> 
  • binaries/data/mods/public/simulation/templates/units/hele_support_healer_b.xml

     
    55    <SpecificName>Hiereús</SpecificName> 
    66    <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> 
    77    <Icon>units/hele_support_healer.png</Icon> 
     8    <Rank>Basic</Rank> 
    89  </Identity> 
     10  <Promotion> 
     11    <Entity>units/hele_support_healer_a</Entity> 
     12  </Promotion> 
    913  <VisualActor> 
    1014    <Actor>units/hellenes/healer.xml</Actor> 
    1115  </VisualActor> 
  • binaries/data/mods/public/simulation/templates/units/iber_support_healer_b.xml

     
    55    <SpecificName>Sacerdotisa de Ataekina</SpecificName> 
    66    <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> 
    77    <Icon>units/iber_support_healer.png</Icon> 
     8    <Rank>Basic</Rank> 
    89  </Identity> 
     10  <Promotion> 
     11    <Entity>units/iber_support_healer_a</Entity> 
     12  </Promotion> 
    913  <VisualActor> 
    1014    <Actor>units/iberians/healer.xml</Actor> 
    1115  </VisualActor> 
  • binaries/data/mods/public/simulation/templates/units/pers_support_healer_b.xml

     
    66    <SpecificName>MaguÅ¡ Mada</SpecificName> 
    77    <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> 
    88    <Icon>units/pers_support_healer.png</Icon> 
     9    <Rank>Basic</Rank> 
    910  </Identity> 
     11  <Promotion> 
     12    <Entity>units/pers_support_healer_a</Entity> 
     13  </Promotion> 
    1014  <VisualActor> 
    1115    <Actor>units/persians/healer.xml</Actor> 
    1216  </VisualActor> 
  • binaries/data/mods/public/simulation/templates/units/rome_support_healer_b.xml

     
    66    <SpecificName>Pontifex Minoris</SpecificName> 
    77    <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> 
    88    <Icon>units/rome_support_healer.png</Icon> 
     9    <Rank>Basic</Rank> 
    910  </Identity> 
     11  <Promotion> 
     12    <Entity>units/rome_support_healer_a</Entity> 
     13  </Promotion> 
    1014  <VisualActor> 
    1115    <Actor>units/romans/healer.xml</Actor> 
    1216  </VisualActor>