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

File #999-2012-03-23.patch, 54.4 KB (added by leper, 12 years ago)
  • binaries/data/mods/public/art/actors/units/carthaginians/healer.xml

     
    88        <animation file="female/f_walk_01.dae" name="Walk" speed="30"/>
    99        <animation file="female/f_walk_01.dae" name="Run" speed="45"/>
    1010        <animation file="female/f_death_01.dae" name="Death" speed="100"/>
     11        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1112      </animations>
    1213      <props>
    1314        <prop actor="props/units/heads/head_kart_priestess.xml" attachpoint="head"/>
  • binaries/data/mods/public/art/actors/units/celts/healer.xml

     
    88        <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
    99        <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
    1010        <animation file="biped/inf_staff_walk_a.dae" name="Run" speed="40"/>
     11        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1112      </animations>
    1213      <mesh>skeletal/m_dress_cuffs.dae</mesh>
    1314      <props>
  • binaries/data/mods/public/art/actors/units/hellenes/healer.xml

     
    77        <animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/>
    88        <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
    99        <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
     10        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1011      </animations>
    1112      <mesh>skeletal/m_dress_a.pmd</mesh>
    1213      <props>
  • binaries/data/mods/public/art/actors/units/iberians/healer.xml

     
    88        <animation file="female/f_walk_01.dae" name="Walk" speed="30"/>
    99        <animation file="female/f_walk_01.dae" name="Run" speed="45"/>
    1010        <animation file="female/f_death_01.dae" name="Death" speed="120"/>
     11        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1112      </animations>
    1213      <props>
    1314        <prop actor="props/units/heads/head_iber_healer.xml" attachpoint="head"/>
  • binaries/data/mods/public/art/actors/units/persians/healer.xml

     
    77        <animation file="biped/inf_staff_idle_a.dae" name="Idle" speed="200"/>
    88        <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
    99        <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
     10        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1011      </animations>
    1112      <mesh>skeletal/m_dress_cuffs.dae</mesh>
    1213      <props>
  • binaries/data/mods/public/art/actors/units/romans/healer.xml

     
    88        <animation file="biped/inf_staff_walk_a.dae" name="Walk" speed="20"/>
    99        <animation file="biped/inf_staff_walk_a.dae" name="Run" speed="20"/>
    1010        <animation file="infantry/general/death/inf_01.psa" name="Death" speed="400"/>
     11        <animation file="female/f_salute_01.dae" name="Heal" speed="30"/>
    1112      </animations>
    1213      <mesh>skeletal/m_dress_a.pmd</mesh>
    1314      <props>
  • binaries/data/mods/public/art/textures/cursors/action-heal.txt

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

     
    297297                return {"possible": true, "tooltip": tooltip};
    298298            }
    299299            break;
     300        case "heal":
     301            // The check if the target is unhealable is done by targetState.needsHeal
     302            if (entState.Healer && hasClass(targetState, "Unit") && 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;
    300323        case "gather":
    301324            if (targetState.resourceSupply && (playerOwned || gaiaOwned))
    302325            {
     
    417440            return {"type": "build", "cursor": "action-repair", "target": target};
    418441        else if ((actionInfo = getActionInfo("set-rallypoint", target)).possible)
    419442            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};
    420445        else if (getActionInfo("attack", target).possible)
    421446            return {"type": "attack", "cursor": "action-attack", "target": target};
    422447        else if (getActionInfo("unset-rallypoint", target).possible)
     
    10371062        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
    10381063        return true;
    10391064
     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
    10401071    case "build": // (same command as repair)
    10411072    case "repair":
    10421073        Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued});
  • binaries/data/mods/public/gui/session/selection_details.js

     
    7474        experienceSize.rtop = 100 - 100 * Math.max(0, Math.min(1, 1.0 * entState.promotion.curr / entState.promotion.req));
    7575        experienceBar.size = experienceSize;
    7676 
    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);
    7878        if (entState.promotion.curr < entState.promotion.req)
    7979            experience += "/" + entState.promotion.req;
    8080        getGUIObjectByName("experience").tooltip = experience;
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

     
    320320            var cmpHealth = Engine.QueryInterface(entity, IID_Health);
    321321            if (cmpHealth)
    322322            {
    323                 if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
     323                // We do not want to heal unhealable units
     324                if (!cmpHealth.IsUnhealable())
    324325                    cmpHealth.Increase(this.healRate);
    325326            }
    326327        }
  • 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();
    158159    }
    159160
    160161    var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
     
    273274        ret.barterMarket = { "prices": cmpBarter.GetPrices() };
    274275    }
    275276
     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
    276286    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    277287    ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
    278288
  • 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;
     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!==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
     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        || this.GetHitpoints() <= 0
     79        || this.GetHitpoints() >= this.GetMaxHitpoints());
     80};
     81
    7582Health.prototype.Kill = function()
    7683{
    7784    this.Reduce(this.hitpoints);
     
    8087Health.prototype.Reduce = function(amount)
    8188{
    8289    var state = { "killed": false };
     90    if (this.hitpoints == this.GetMaxHitpoints())
     91    {
     92        Engine.PostMessage(this.entity, MT_ShowAsModified, { "entity": this.entity, "showAsModified": true });
     93        // TODO send a second message after this turn to set showAsModified to false to make this show up only once
     94        // and not in every query. As a workaround we can set showAsModified false if we get max hitpoints in increase
     95        // or if we are in the else block of this if statement.
     96        // We could use Timer.SetTimeout with a timeout of the length of a turn to solve this.
     97        //Engine.PostMessage(this.entity, MT_ShowAsModified, { "entity": this.entity, "showAsModified": false });
     98    }
    8399    if (amount >= this.hitpoints)
    84100    {
    85101        // If this is the first time we reached 0, then die.
     
    131147{
    132148    // If we're already dead, don't allow resurrection
    133149    if (this.hitpoints == 0)
    134         return;
     150        return undefined;
    135151
    136152    var old = this.hitpoints;
    137153    this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints());
    138154
    139155    Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints });
     156    // We return the old and the actual hp
     157    return { "old": old, "new": this.hitpoints};
    140158};
    141159
    142160//// 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

     
    283283        this.FinishOrder();
    284284    },
    285285
     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
    286323    "Order.Gather": function(msg) {
    287324       
    288325        // If the target is still alive, we need to kill it first
     
    408445            cmpFormation.Disband();
    409446        },
    410447
     448        "Order.Heal": function(msg) {
     449            // TODO: see notes in Order.Attack
     450            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     451            cmpFormation.CallMemberFunction("Heal", [msg.data.target, false]);
     452            cmpFormation.Disband();
     453        },
     454
    411455        "Order.Repair": function(msg) {
    412456            // TODO: see notes in Order.Attack
    413457            var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation);
     
    547591                // remain idle
    548592                this.StartTimer(1000);
    549593
     594                // If a unit can heal and attack we first want to heal wounded units,
     595                // so check if we are a healer and find whether there's anybody nearby to heal.
     596                // If anyone approaches later it'll be handled via LosRangeUpdate.)
     597                // If anyone in sight gets hurt that will be handled via LosRangeUpdate.
     598                if (this.IsHealer() && this.FindNewHealTargets())
     599                    return true; // (abort the FSM transition since we may have already switched state)
     600
    550601                // If we entered the idle state we must have nothing better to do,
    551602                // so immediately check whether there's anybody nearby to attack.
    552603                // (If anyone approaches later, it'll be handled via LosRangeUpdate.)
     
    560611            "leave": function() {
    561612                var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    562613                rangeMan.DisableActiveQuery(this.losRangeQuery);
     614                if (this.losHealRangeQuery)
     615                    rangeMan.DisableActiveQuery(this.losHealRangeQuery);
    563616
    564617                this.StopTimer();
    565618
     
    571624            },
    572625
    573626            "LosRangeUpdate": function(msg) {
     627                if (this.IsHealer())
     628                {
     629                    // Start healing one of the newly-seen own or ally units (if any)
     630                    this.FindNewHealTargets();
     631                }
    574632                if (this.GetStance().targetVisibleEnemies)
    575633                {
    576634                    // Start attacking one of the newly-seen enemy (if any)
     
    10001058            },
    10011059        },
    10021060
     1061        "HEAL": {
     1062            "EntityRenamed": function(msg) {
     1063                if (this.order.data.target == msg.entity)
     1064                    this.order.data.target = msg.newentity;
     1065            },
     1066
     1067            "Attacked": function(msg) {
     1068                // If we stand ground we will rather die than flee
     1069                if (!this.GetStance().respondStandGround)
     1070                    this.Flee(msg.data.attacker, false);
     1071            },
     1072
     1073            "APPROACHING": {
     1074                "enter": function () {
     1075                    this.SelectAnimation("move");
     1076                    this.StartTimer(1000, 1000);
     1077                },
     1078
     1079                "leave": function() {
     1080                    this.StopTimer();
     1081                },
     1082
     1083                "Timer": function(msg) {
     1084                    if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
     1085                    {
     1086                        this.StopMoving();
     1087                        this.FinishOrder();
     1088
     1089                        // Return to our original position
     1090                        if (this.GetStance().respondHoldGround)
     1091                            this.WalkToHeldPosition();
     1092                    }
     1093                },
     1094
     1095                "MoveCompleted": function() {
     1096                    this.SetNextState("HEALING");
     1097                },
     1098
     1099                "Attacked": function(msg) {
     1100                    // If we stand ground we will rather die than flee
     1101                    if (!this.GetStance().respondStandGround)
     1102                        this.Flee(msg.data.attacker, false);
     1103                },
     1104            },
     1105
     1106            "HEALING": {
     1107                "enter": function() {
     1108                    var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     1109                    this.healTimers = cmpHeal.GetTimers();
     1110                    this.SelectAnimation("heal", false, 1.0, "heal");
     1111                    this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat);
     1112                    this.StartTimer(this.healTimers.prepare, this.healTimers.repeat);
     1113                    // TODO if .prepare is short, players can cheat by cycling heal/stop/heal
     1114                    // to beat the .repeat time; should enforce a minimum time
     1115                    // see comment in ATTACKING.enter
     1116                    this.FaceTowardsTarget(this.order.data.target);
     1117                },
     1118
     1119                "leave": function() {
     1120                    this.StopTimer();
     1121                },
     1122
     1123                "Timer": function(msg) {
     1124                    var target = this.order.data.target;
     1125                    // Check the target is still alive and healable
     1126                    if (this.TargetIsAlive(target) && this.CanHeal(target))
     1127                    {
     1128                        // Check if we can still reach the target
     1129                        if (this.CheckTargetRange(target, IID_Heal))
     1130                        {
     1131                            this.FaceTowardsTarget(target);
     1132                            var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     1133                            cmpHeal.PerformHeal(target);
     1134                            return;
     1135                        }
     1136                        // Can't reach it - try to chase after it
     1137                        if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
     1138                        {
     1139                            if (this.MoveToTargetRange(target, IID_Heal))
     1140                            {
     1141                                this.SetNextState("HEAL.CHASING");
     1142                                return;
     1143                            }
     1144                        }
     1145                    }
     1146                    // Can't reach it, healed to max hp or doesn't exist any more - give up
     1147                    if (this.FinishOrder())
     1148                        return;
     1149
     1150                    // Heal another one
     1151                    if (this.FindNewHealTargets())
     1152                        return;
     1153                   
     1154                    // Return to our original position
     1155                    if (this.GetStance().respondHoldGround)
     1156                        this.WalkToHeldPosition();
     1157                },
     1158                "Attacked": function(msg) {
     1159                    // If we stand ground we will rather die than flee
     1160                    if (!this.GetStance().respondStandGround)
     1161                        this.Flee(msg.data.attacker, false);
     1162                },
     1163            },
     1164            "CHASING": {
     1165                "enter": function () {
     1166                    this.SelectAnimation("move");
     1167                    this.StartTimer(1000, 1000);
     1168                },
     1169
     1170                "leave": function () {
     1171                    this.StopTimer();
     1172                },
     1173                "Timer": function(msg) {
     1174                    if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
     1175                    {
     1176                        this.StopMoving();
     1177                        this.FinishOrder();
     1178
     1179                        // Return to our original position
     1180                        if (this.GetStance().respondHoldGround)
     1181                            this.WalkToHeldPosition();
     1182                    }
     1183                },
     1184                "MoveCompleted": function () {
     1185                    this.SetNextState("HEALING");
     1186                },
     1187            }, 
     1188        },
     1189
    10031190        // Returning to dropsite
    10041191        "RETURNRESOURCE": {
    10051192            "APPROACHING": {
     
    14731660    return (this.template.NaturalBehaviour ? true : false);
    14741661};
    14751662
     1663UnitAI.prototype.IsHealer = function()
     1664{
     1665    return Engine.QueryInterface(this.entity, IID_Heal);
     1666};
     1667
    14761668UnitAI.prototype.IsIdle = function()
    14771669{
    14781670    return this.isIdle;
     
    14961688UnitAI.prototype.OnOwnershipChanged = function(msg)
    14971689{
    14981690    this.SetupRangeQuery();
     1691    if (this.IsHealer())
     1692        this.SetupHealRangeQuery();
    14991693};
    15001694
    15011695UnitAI.prototype.OnDestroy = function()
     
    15071701    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    15081702    if (this.losRangeQuery)
    15091703        rangeMan.DestroyActiveQuery(this.losRangeQuery);
     1704    if (this.losHealRangeQuery)
     1705        rangeMan.DestroyActiveQuery(this.losHealRangeQuery);
    15101706};
    15111707
    15121708// Set up a range query for all enemy units within LOS range
     
    15391735        }
    15401736    }
    15411737
    1542     var range = this.GetQueryRange();
     1738    var range = this.GetQueryRange(IID_Attack);
    15431739
    1544     this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver);
     1740    this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, false);
    15451741    rangeMan.EnableActiveQuery(this.losRangeQuery);
    15461742};
    15471743
     1744// Set up a range query for all own or ally units within LOS range
     1745// which can be healed.
     1746// This should be called whenever our ownership changes.
     1747UnitAI.prototype.SetupHealRangeQuery = function()
     1748{
     1749    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     1750    var owner = cmpOwnership.GetOwner();
     1751
     1752    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     1753    var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     1754
     1755    if (this.losHealRangeQuery)
     1756        rangeMan.DestroyActiveQuery(this.losHealRangeQuery);
     1757
     1758    var players = [owner];
     1759
     1760    if (owner != -1)
     1761    {
     1762        // If unit not just killed, get ally players via diplomacy
     1763        var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player);
     1764        var numPlayers = playerMan.GetNumPlayers();
     1765        for (var i = 1; i < numPlayers; ++i)
     1766        {
     1767            // Exclude gaia and enemies
     1768            if (cmpPlayer.IsAlly(i))
     1769                players.push(i);
     1770        }
     1771    }
     1772
     1773    var range = this.GetQueryRange(IID_Heal);
     1774
     1775    this.losHealRangeQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Health, true);
     1776    rangeMan.EnableActiveQuery(this.losHealRangeQuery);
     1777};
     1778
    15481779//// FSM linkage functions ////
    15491780
    15501781UnitAI.prototype.SetNextState = function(state)
     
    17661997
    17671998UnitAI.prototype.OnRangeUpdate = function(msg)
    17681999{
    1769     if (msg.tag == this.losRangeQuery)
     2000    if (msg.tag == this.losRangeQuery || msg.tag == this.losHealRangeQuery)
    17702001        UnitFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg});
    17712002};
    17722003
     
    22962527        case "Flee":
    22972528        case "LeaveFoundation":
    22982529        case "Attack":
     2530        case "Heal":
    22992531        case "Gather":
    23002532        case "ReturnResource":
    23012533        case "Repair":
     
    23582590{
    23592591    if (!this.CanAttack(target))
    23602592    {
    2361         this.WalkToTarget(target, queued);
     2593        // We don't want to let healers walk to the target unit so they can be easily killed.
     2594        // Instead we just let them get into healing range.
     2595        if (this.IsHealer())
     2596            this.MoveToTargetRange(target, IID_Heal);
     2597        else
     2598            this.WalkToTarget(target, queued);
    23622599        return;
    23632600    }
    23642601
     
    24192656    this.AddOrder("GatherNearPosition", { "type": type, "x": x, "z": z }, queued);
    24202657}
    24212658
     2659UnitAI.prototype.Heal = function(target, queued)
     2660{
     2661    if (!this.CanHeal(target))
     2662    {
     2663        this.WalkToTarget(target, queued);
     2664        return;
     2665    }
     2666   
     2667    this.AddOrder("Heal", { "target": target, "force": true }, queued);
     2668};
     2669
    24222670UnitAI.prototype.ReturnResource = function(target, queued)
    24232671{
    24242672    if (!this.CanReturnResource(target, true))
     
    25302778        this.stance = stance;
    25312779    else
    25322780        error("UnitAI: Setting to invalid stance '"+stance+"'");
    2533 }
     2781};
    25342782
    25352783UnitAI.prototype.SwitchToStance = function(stance)
    25362784{
     
    25482796
    25492797    // Reset the range query, since the range depends on stance
    25502798    this.SetupRangeQuery();
    2551 }
     2799    // Just if we are a healer
     2800    // TODO maybe move those two to a SetupRangeQuerys()
     2801    if (this.IsHealer())
     2802        this.SetupHealRangeQuery();
     2803};
    25522804
    25532805/**
    25542806 * Resets losRangeQuery, and if there are some targets in range that we can
     
    25692821    return this.RespondToTargetedEntities(ents);
    25702822};
    25712823
    2572 UnitAI.prototype.GetQueryRange = function()
     2824/**
     2825 * Resets losHealRangeQuery, and if there are some targets in range that we can heal
     2826 * then we start healing and this returns true; otherwise, returns false.
     2827 */
     2828UnitAI.prototype.FindNewHealTargets = function()
     2829{
     2830    if (!this.losHealRangeQuery)
     2831        return false;
     2832   
     2833    var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     2834    var ents = rangeMan.ResetActiveQuery(this.losHealRangeQuery);
     2835   
     2836    for each (var ent in ents)
     2837    {
     2838        if (this.CanHeal(ent))
     2839        {
     2840            this.PushOrderFront("Heal", { "target": ent, "force": false });
     2841            return true;
     2842        }
     2843    }
     2844    // We haven't found any target to heal
     2845    return false;
     2846};
     2847
     2848UnitAI.prototype.GetQueryRange = function(iid)
    25732849{
    25742850    var ret = { "min": 0, "max": 0 };
    25752851    if (this.GetStance().respondStandGround)
    25762852    {
    2577         var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
     2853        var cmpRanged = Engine.QueryInterface(this.entity, iid);
    25782854        if (!cmpRanged)
    25792855            return ret;
    2580         var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
     2856        var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetBestAttack();
    25812857        ret.min = range.min;
    25822858        ret.max = range.max;
    25832859    }
     
    25912867    }
    25922868    else if (this.GetStance().respondHoldGround)
    25932869    {
    2594         var cmpRanged = Engine.QueryInterface(this.entity, IID_Attack);
     2870        var cmpRanged = Engine.QueryInterface(this.entity, iid);
    25952871        if (!cmpRanged)
    25962872            return ret;
    2597         var range = cmpRanged.GetRange(cmpRanged.GetBestAttack());
     2873        var range = iid !== IID_Attack ? cmpRanged.GetRange() : cmpRanged.GetBestAttack();
    25982874        var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
    25992875        if (!cmpVision)
    26002876            return ret;
    26012877        var halfvision = cmpVision.GetRange() / 2;
    26022878        ret.max = range.max + halfvision;
    26032879    }
     2880    // We probably have stance 'passive' and we wouldn't have a range,
     2881    // but as it is the default for healers we need to set it to something sane.
     2882    else if (iid === IID_Heal)
     2883    {
     2884        var cmpRanged = Engine.QueryInterface(this.entity, iid);
     2885        if (!cmpRanged)
     2886            return ret;
     2887        var range = cmpRanged.GetRange();
     2888        ret.min = range.min;
     2889        ret.max = range.max;
     2890    }
    26042891    return ret;
    26052892};
    26062893
     
    26902977    return true;
    26912978};
    26922979
     2980UnitAI.prototype.CanHeal = function(target)
     2981{
     2982    // Formation controllers should always respond to commands
     2983    // (then the individual units can make up their own minds)
     2984    if (this.IsFormationController())
     2985        return true;
     2986
     2987    // Verify that we're able to respond to Heal commands
     2988    var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     2989    if (!cmpHeal)
     2990        return false;
     2991
     2992    // Verify that the target is alive
     2993    if (!this.TargetIsAlive(target))
     2994        return false;
     2995
     2996    // Verify that the target is owned by the same player as the entity or of an ally
     2997    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     2998    if (!cmpOwnership || !(IsOwnedByPlayer(cmpOwnership.GetOwner(), target) || IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target)))
     2999        return false;
     3000
     3001    // Verify that the target is not unhealable (or at max health)
     3002    var cmpHealth = Engine.QueryInterface(target, IID_Health);
     3003    if (!cmpHealth || cmpHealth.IsUnhealable())
     3004        return false;
     3005
     3006    // Verify that the target has no unhealable class
     3007    var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     3008    if (!cmpIdentity)
     3009        return false;
     3010    for each (var unhealableClass in cmpHeal.GetUnhealableClasses())
     3011    {
     3012        if (cmpIdentity.HasClass(unhealableClass) != -1)
     3013        {
     3014            return false;
     3015        }
     3016    }
     3017
     3018    // Verify that the target is a healable class
     3019    var healable = false;
     3020    for each (var healableClass in cmpHeal.GetHealableClasses())
     3021    {
     3022        if (cmpIdentity.HasClass(healableClass) != -1)
     3023        {
     3024            healable = true;
     3025        }
     3026    }
     3027    if (!healable)
     3028        return false;
     3029
     3030    return true;
     3031};
     3032
    26933033UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
    26943034{
    26953035    // 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");
     
    271272    GetHitpoints: function() { return 50; },
    272273    GetMaxHitpoints: function() { return 60; },
    273274    IsRepairable: function() { return false; },
     275    IsUnhealable: function() { return false; },
    274276});
    275277
    276278AddMock(10, IID_Identity, {
     
    304306    hitpoints: 50,
    305307    maxHitpoints: 60,
    306308    needsRepair: false,
     309    needsHeal: true,
    307310    buildEntities: ["test1", "test2"],
    308311    barterMarket: {
    309312        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 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
    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>
    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>
    1415  <Cost>
    1516    <Resources>
    1617      <metal>120</metal>
     
    2324  <Identity>
    2425    <Classes datatype="tokens">Healer</Classes>
    2526    <GenericName>Healer</GenericName>
    26     <Tooltip>Heal units within his Aura. (Not implemented yet)</Tooltip>
     27    <Tooltip>Heal units.</Tooltip>
    2728  </Identity>
     29  <Promotion>
     30    <RequiredXp>100</RequiredXp>
     31  </Promotion>
    2832  <Sound>
    2933    <SoundGroups>
    3034      <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>
  • source/scriptinterface/ScriptInterface.h

     
    3838// Set the maximum number of function arguments that can be handled
    3939// (This should be as small as possible (for compiler efficiency),
    4040// but as large as necessary for all wrapped functions)
    41 #define SCRIPT_INTERFACE_MAX_ARGS 6
     41#define SCRIPT_INTERFACE_MAX_ARGS 7
    4242
    4343// TODO: what's a good default?
    4444#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024
  • source/simulation2/MessageTypes.h

     
    230230    int32_t to;
    231231};
    232232
     233class CMessageShowAsModified : public CMessage
     234{
     235public:
     236    DEFAULT_MESSAGE_IMPL(ShowAsModified)
     237
     238    CMessageShowAsModified(entity_id_t entity, bool showAsModified) :
     239        entity(entity), showAsModified(showAsModified)
     240    {
     241    }
     242
     243    entity_id_t entity;
     244    bool showAsModified;
     245};
     246
    233247/**
    234248 * Sent during TurnStart.
    235249 *
  • source/simulation2/TypeList.h

     
    4141MESSAGE(Create)
    4242MESSAGE(Destroy)
    4343MESSAGE(OwnershipChanged)
     44MESSAGE(ShowAsModified)
    4445MESSAGE(PositionChanged)
    4546MESSAGE(MotionChanged)
    4647MESSAGE(RangeUpdate)
  • source/simulation2/components/CCmpRangeManager.cpp

     
    5050    u32 ownersMask;
    5151    i32 interface;
    5252    std::vector<entity_id_t> lastMatch;
     53    bool showModified;
    5354};
    5455
    5556/**
     
    6970 */
    7071struct EntityData
    7172{
    72     EntityData() : retainInFog(0), owner(-1), inWorld(0) { }
     73    EntityData() : retainInFog(0), owner(-1), inWorld(0), showAsModified(0) { }
    7374    entity_pos_t x, z;
    7475    entity_pos_t visionRange;
    7576    u8 retainInFog; // boolean
    7677    i8 owner;
    7778    u8 inWorld; // boolean
     79    u8 showAsModified; // boolean
    7880};
    7981
    8082cassert(sizeof(EntityData) == 16);
     
    9597        serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
    9698        serialize.NumberI32_Unbounded("interface", value.interface);
    9799        SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
     100        serialize.Bool("show modified", value.showModified);
    98101    }
    99102};
    100103
     
    112115        serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
    113116        serialize.NumberI8_Unbounded("owner", value.owner);
    114117        serialize.NumberU8("in world", value.inWorld, 0, 1);
     118        serialize.NumberU8("show as modified", value.showAsModified, 0, 1);
    115119    }
    116120};
    117121
     
    161165        componentManager.SubscribeGloballyToMessageType(MT_Create);
    162166        componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
    163167        componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
     168        componentManager.SubscribeGloballyToMessageType(MT_ShowAsModified);
    164169        componentManager.SubscribeGloballyToMessageType(MT_Destroy);
    165170
    166171        componentManager.SubscribeToMessageType(MT_Update);
     
    384389
    385390            break;
    386391        }
     392        case MT_ShowAsModified:
     393        {
     394            const CMessageShowAsModified& msgData = static_cast<const CMessageShowAsModified&> (msg);
     395            entity_id_t ent = msgData.entity;
     396           
     397            std::map<u32, EntityData>::iterator it = m_EntityData.find(ent);
     398           
     399            // Ignore if we're not already tracking this entity
     400            if (it == m_EntityData.end())
     401                break;
     402           
     403            it->second.showAsModified = msgData.showAsModified ? 1 : 0;
     404           
     405            break;
     406        }
    387407        case MT_Destroy:
    388408        {
    389409            const CMessageDestroy& msgData = static_cast<const CMessageDestroy&> (msg);
     
    514534
    515535    virtual tag_t CreateActiveQuery(entity_id_t source,
    516536        entity_pos_t minRange, entity_pos_t maxRange,
    517         std::vector<int> owners, int requiredInterface)
     537        std::vector<int> owners, int requiredInterface, bool showModified)
    518538    {
    519539        tag_t id = m_QueryNext++;
    520         m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface);
     540        m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, showModified);
    521541
    522542        return id;
    523543    }
     
    565585    {
    566586        PROFILE("ExecuteQuery");
    567587
    568         Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface);
     588        Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, false);
    569589
    570590        std::vector<entity_id_t> r;
    571591
     
    677697            std::set_difference(r.begin(), r.end(), q.lastMatch.begin(), q.lastMatch.end(), std::back_inserter(added));
    678698            std::set_difference(q.lastMatch.begin(), q.lastMatch.end(), r.begin(), r.end(), std::back_inserter(removed));
    679699
     700            if (q.showModified)
     701            {
     702                for (std::vector<entity_id_t>::iterator iter = r.begin(); iter != r.end(); ++iter)
     703                {
     704                    std::map<u32, EntityData>::iterator bla = m_EntityData.find(*iter);
     705                    if (bla == m_EntityData.end())
     706                        continue;
     707                    if (bla->second.showAsModified)
     708                    {
     709                        // We just add the entity to removed and added if it is not there yet
     710                        if (find(removed.begin(), removed.end(), *iter) == removed.end()
     711                            && find(added.begin(), added.end(), *iter) == added.end())
     712                        {
     713                            removed.push_back(*iter);
     714                            added.push_back(*iter);
     715                        }
     716                    }
     717                }
     718            }
    680719            if (added.empty() && removed.empty())
    681720                continue;
    682721
     
    773812
    774813    Query ConstructQuery(entity_id_t source,
    775814        entity_pos_t minRange, entity_pos_t maxRange,
    776         const std::vector<int>& owners, int requiredInterface)
     815        const std::vector<int>& owners, int requiredInterface, bool showModified)
    777816    {
    778817        // Min range must be non-negative
    779818        if (minRange < entity_pos_t::Zero())
     
    794833            q.ownersMask |= CalcOwnerMask(owners[i]);
    795834
    796835        q.interface = requiredInterface;
     836        q.showModified = showModified;
    797837
    798838        return q;
    799839    }
  • source/simulation2/components/ICmpRangeManager.cpp

     
    3535
    3636BEGIN_INTERFACE_WRAPPER(RangeManager)
    3737DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
    38 DEFINE_INTERFACE_METHOD_5("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
     38DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, bool)
    3939DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
    4040DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
    4141DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
  • source/simulation2/components/ICmpRangeManager.h

     
    9696     * @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance.
    9797     * @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
    9898     * @param requiredInterface if non-zero, an interface ID that matching entities must implement.
     99     * @param showModified show modified entitys that are within range.
    99100     * @return unique non-zero identifier of query.
    100101     */
    101102    virtual tag_t CreateActiveQuery(entity_id_t source,
    102         entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface) = 0;
     103        entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, bool showModified) = 0;
    103104
    104105    /**
    105106     * Destroy a query and clean up resources. This must be called when an entity no longer needs its
  • source/simulation2/scripting/MessageTypeConversions.cpp

     
    190190
    191191////////////////////////////////
    192192
     193jsval CMessageShowAsModified::ToJSVal(ScriptInterface& scriptInterface) const
     194{
     195    TOJSVAL_SETUP();
     196    SET_MSG_PROPERTY(entity);
     197    SET_MSG_PROPERTY(showAsModified);
     198    return OBJECT_TO_JSVAL(obj);
     199}
     200
     201CMessage* CMessageShowAsModified::FromJSVal(ScriptInterface& scriptInterface, jsval val)
     202{
     203    FROMJSVAL_SETUP();
     204    GET_MSG_PROPERTY(entity_id_t, entity);
     205    GET_MSG_PROPERTY(bool, showAsModified);
     206    return new CMessageShowAsModified(entity, showAsModified);
     207}
     208
     209////////////////////////////////
     210
    193211jsval CMessagePositionChanged::ToJSVal(ScriptInterface& scriptInterface) const
    194212{
    195213    TOJSVAL_SETUP();
  • source/simulation2/system/InterfaceScripted.h

     
    7979        5, \
    8080        JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
    8181
     82#define DEFINE_INTERFACE_METHOD_6(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6) \
     83    { scriptname, \
     84        ScriptInterface::callMethod<rettype, arg1, arg2, arg3, arg4, arg5, arg6, &class_##classname, classname, &classname::methodname>, \
     85        6, \
     86        JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
     87
    8288#endif // INCLUDED_INTERFACE_SCRIPTED