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

File #999-2012-02-23.patch, 21.5 KB (added by leper, 12 years ago)

Work in progress. Heal classes specified in template.

  • 0ad/binaries/data/mods/public/simulation/helpers/Commands.js

     
    6666        });
    6767        break;
    6868
     69  case "heal":
     70    //TODO fix this and UnitAI.CanHeal when healing allies is implemented
     71    if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target))
     72    {
     73      // This check is for debugging only!
     74      warn("Invalid command: heal target is not owned by player "+player+": "+uneval(cmd));
     75    }
     76   
     77    // See UnitAI.CanHeal for target checks
     78    var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
     79    GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
     80      cmpUnitAI.Heal(cmd.target, cmd.queued);
     81    });
     82    break;
     83
    6984    case "repair":
    7085        // This covers both repairing damaged buildings, and constructing unfinished foundations
    7186        if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target))
  • 0ad/binaries/data/mods/public/simulation/components/GuiInterface.js

     
    264264        ret.barterMarket = { "prices": cmpBarter.GetPrices() };
    265265    }
    266266
     267  // TODO change ret.Healer to ret.Ability (see TODO-comment in unit-command.js
     268  var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
     269  if (cmpHeal)
     270  {
     271    ret.Healer = { "healableClasses": cmpHeal.GetHealableClasses() };
     272  }
     273
    267274    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    268275    ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
    269276
  • 0ad/binaries/data/mods/public/simulation/components/interfaces/Heal.js

     
     1Engine.RegisterInterface("Heal");
     2
     3//TODO register message type healed?
  • 0ad/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");
  • 0ad/binaries/data/mods/public/simulation/components/UnitAI.js

     
    282282        // so abandon this attack order
    283283        this.FinishOrder();
    284284    },
     285 
     286  "Order.Heal": function(msg) {
     287    // Check the target is alive
     288    if (!this.TargetIsAlive(this.order.data.target))
     289    {
     290            this.FinishOrder();
     291            return;
     292    }
     293   
     294    // Check if the target is in range
     295    if (this.CheckTargetRange(this.order.data.target, IID_Heal))
     296    {
     297            this.StopMoving();
     298            this.SetNextState("INDIVIDUAL.HEAL.HEALING");
     299            return;
     300    }
     301   
     302    // TODO Should we adhere to stances?
     303    // check if standing ground stance -> abandon
    285304
     305    // Try to move within heal range
     306    if (this.MoveToTargetRange(this.order.data.target, IID_Heal))
     307    {
     308            // We've started walking to the given point
     309            this.SetNextState("INDIVIDUAL.HEAL.APPROACHING");
     310            return;
     311    }
     312
     313    // We can't reach the target, and can't move towards it,
     314    // so abandon this heal order
     315    this.FinishOrder();
     316  },
     317
    286318    "Order.Gather": function(msg) {
    287319       
    288320        // If the target is still alive, we need to kill it first
     
    9951027            },
    9961028        },
    9971029
     1030    "HEAL": {
     1031      // TODO How should we handle being attacked? maybe ignore attack like in COMBAT
     1032      "APPROACHING": {
     1033        "enter": function () {
     1034          this.SelectAnimation("move");
     1035          this.StartTimer(1000, 1000);
     1036        },
     1037
     1038        "leave": function() {
     1039          this.StopTimer();
     1040        },
     1041
     1042        "Timer": function(msg) {
     1043          if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
     1044          {
     1045            this.StopMoving();
     1046            this.FinishOrder();
     1047
     1048            // TODO stances?
     1049          }
     1050        },
     1051
     1052        "MoveCompleted": function() {
     1053          this.SetNextState("HEALING");
     1054        },
     1055
     1056//        "Attacked": function(msg) {
     1057          //Should we ignore close enemys?
     1058//        },
     1059      },
     1060
     1061      "HEALING": {
     1062        "enter": function() {
     1063          var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     1064          this.healTimers = cmpHeal.GetTimers();
     1065//          this.SelectAnimation("heal", false, 1.0, "heal"); // TODO needs animation
     1066//          this.SetAnimationSync(this.healTimers.prepare, this.healTimers.repeat);
     1067          this.StartTimer(this.healTimers.prepare, this.healTimers.repeat);
     1068          this.FaceTowardsTarget(this.order.data.target);
     1069        },
     1070
     1071        "leave": function() {
     1072          this.StopTimer();
     1073        },
     1074
     1075        "Timer": function(msg) {
     1076          var target = this.order.data.target;
     1077          // Check the target is still alive and healable
     1078          if (this.TargetIsAlive(target) && this.CanHeal(target))
     1079          {
     1080            // Check if we can still reach the target
     1081            if (this.CheckTargetRange(target, IID_Heal))
     1082            {
     1083              this.FaceTowardsTarget(target);
     1084              var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     1085              cmpHeal.PerformHeal(target);
     1086              return;
     1087            }
     1088            // Can't reach it - chase it
     1089            if (this.ShouldChaseTargetedEntity(target, this.order.data.force))
     1090            {
     1091              if (this.MoveToTargetRange(target, IID_Heal))
     1092              {
     1093                this.SetNextState("HEAL.CHASING");
     1094                return;
     1095              }
     1096            }
     1097          }
     1098          // Can't reach it, healed to max hp or doesn't exist any more - give up
     1099          if (this.FinishOrder())
     1100            return;
     1101
     1102          // TODO implement autoheal next (like in ATTACK FindNewTargets())
     1103          // needs FindNewHealTargets() and maybe a something like RespondToTargetedEntities() ; figure out how SortEntitiesByPriority is working
     1104//          if (this.FindNewHealTargets())
     1105//            return;
     1106
     1107          return;
     1108        },
     1109        // TODO "Attacked" - what behaviour?
     1110        // Should we ignore being attacked while healing?
     1111      },
     1112      "CHASING": {
     1113        "enter": function () {
     1114          this.SelectAnimation("move");
     1115          this.StartTimer(1000, 1000);
     1116        },
     1117
     1118        "leave": function () {
     1119          this.StopTimer();
     1120        },
     1121        "Timer": function(msg) {
     1122          if (this.ShouldAbandonChase(this.order.data.target, this.order.data.force))
     1123          {
     1124            this.StopMoving();
     1125            this.FinishOrder();
     1126
     1127          }
     1128        },
     1129        "MoveCompleted": function () {
     1130          this.SetNextState("HEALING");
     1131        },
     1132      }, 
     1133    },
     1134
    9981135        // Returning to dropsite
    9991136        "RETURNRESOURCE": {
    10001137            "APPROACHING": {
     
    17461883};
    17471884
    17481885/**
     1886 * Returns true if the target exists and the current hitpoints are at maximum.
     1887 */
     1888UnitAI.prototype.TargetIsAtMaxHitpoints = function(ent)
     1889{
     1890  var cmpHealth = Engine.QueryInterface(ent, IID_Health);
     1891  if(!cmpHealth)
     1892    return false;
     1893
     1894  return (cmpHealth.GetHitpoints() == cmpHealth.GetMaxHitpoints());
     1895};
     1896
     1897/**
    17491898 * Returns true if the target exists and needs to be killed before
    17501899 * beginning to gather resources from it.
    17511900 */
     
    23592508    this.AddOrder("GatherNearPosition", { "type": type, "x": position[0], "z": position[1] }, queued);
    23602509}
    23612510
     2511UnitAI.prototype.Heal = function(target, queued)
     2512{
     2513  if (!this.CanHeal(target))
     2514  {
     2515    // TODO should we really walk there if we can't heal
     2516    // If resource for healing is implemented check if we have insufficient
     2517    // resources and walk there.
     2518    this.WalkToTarget(target, queued);
     2519    return;
     2520  }
     2521  // We don't want to chase units that leave our visibility -> force = false
     2522  this.AddOrder("Heal", { "target": target, "force": false }, queued);
     2523};
     2524
    23622525UnitAI.prototype.ReturnResource = function(target, queued)
    23632526{
    23642527    if (!this.CanReturnResource(target, true))
     
    25552718    return true;
    25562719};
    25572720
     2721UnitAI.prototype.CanHeal = function(target)
     2722{
     2723  // Formation controllers should always respond to commands
     2724  // (then the individual units can make up their own minds)
     2725  if (this.IsFormationController())
     2726    return true;
     2727
     2728  // Verify that we're able to respond to Heal commands
     2729  var cmpHeal = Engine.QueryInterface(this.entity, IID_Heal);
     2730  if (!cmpHeal)
     2731    return false;
     2732
     2733  // Verify that the target is alive
     2734  if (!this.TargetIsAlive(target))
     2735    return false;
     2736
     2737  // Verify that the target is owned by the same player as the entity
     2738  // TODO or of an ally
     2739  var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     2740  if (!cmpOwnership || !IsOwnedByPlayer(cmpOwnership.GetOwner(), target))
     2741    return false;
     2742
     2743  // Verify that the target is a healable class
     2744  // We could also use cmpIdentity.GetClassesList but this way is cleaner
     2745  var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     2746  if (!cmpIdentity)
     2747    return false;
     2748  var healable = false;
     2749  for each (var healableClass in cmpHeal.GetHealableClasses())
     2750  {
     2751    if (cmpIdentity.HasClass(healableClass) != -1)
     2752    {
     2753      healable = true;
     2754    }
     2755  }
     2756  if (!healable)
     2757    return false;
     2758
     2759  // Check that the target is not at MaxHealth
     2760  if (this.TargetIsAtMaxHitpoints(target))
     2761    return false;
     2762
     2763  return true;
     2764};
     2765
    25582766UnitAI.prototype.CanReturnResource = function(target, checkCarriedResource)
    25592767{
    25602768    // Formation controllers should always respond to commands
  • 0ad/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         "<Speed>5</Speed>" +
     8         "<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" +
     9      "</a:example>" +
     10      "<element name='Range' a:help='Range (in metres) where healing is possible'>" +
     11        "<ref name='nonNegativeDecimal'/>" +
     12      "</element>" +
     13      "<element name='Speed' a:help='Hp healed per second'>" +
     14        "<ref name='nonNegativeDecimal'/>" +
     15      "</element>" +
     16      "<element name='HealableClasses'>" +
     17        "<attribute name='datatype'>" +
     18          "<value>tokens</value>" +
     19        "</attribute>" +
     20        "<text/>" +
     21      "</element>";
     22
     23Heal.prototype.Init = function()
     24{
     25};
     26
     27Heal.prototype.Serialize = null; // we have no dynamic state to save
     28
     29Heal.prototype.GetTimers = function(type)
     30{
     31        var prepare = 1000;
     32        var repeat = 1000;
     33        return { "prepare": prepare, "repeat": repeat };
     34}
     35
     36Heal.prototype.GetRange = function()
     37{
     38        var max = +this.template.Range;
     39        var min = 0;
     40        return { "max": max, "min": min };
     41};
     42
     43Heal.prototype.GetHealableClasses = function()
     44{
     45        var classes = this.template.HealableClasses._string;
     46        return classes.split(/\s+/);
     47};
     48
     49/**
     50 * Heal the target entity. This should only be called after a successful range
     51 * check, and should only be called after GetTimers().repeat msec has passed
     52 * since the last call to PerformHeal.
     53 */
     54Heal.prototype.PerformHeal = function(target)
     55{
     56          this.CauseHeal({"target": target});
     57};
     58
     59/**
     60 * Heal target
     61 */
     62Heal.prototype.CauseHeal = function(data)
     63{
     64        var cmpHealth = Engine.QueryInterface(data.target, IID_Health);
     65        if(!cmpHealth)
     66              return;
     67        var targetState = cmpHealth.Increase(Math.max(0,this.template.Speed));
     68        //TODO add some Engine.PostMessage? - shouldn't be needed as Increase already posts a message
     69        //TODO we need a sound file
     70//        PlaySound("heal_impact", this.entity);
     71};
     72
     73Engine.RegisterComponentType(IID_Heal, "Heal", Heal);
  • 0ad/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    <Speed>5</Speed>
     17    <HealableClasses datatype="tokens">Support Infantry</HealableClasses>
     18  </Heal>
    1419  <Cost>
    1520    <Resources>
    1621      <metal>120</metal>
  • 0ad/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%"
  • 0ad/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": //TODO add something like && targetState.needsheal ?
     260      if (isUnit(targetState) && playerOwned) //TODO fix for allies allyOwned
     261      {
     262        // TODO fix this for entState.Ability see unit_command.js and GuiInterface.js
     263        var healableClasses = entState.Healer.healableClasses;
     264        for each (var unitClass in targetState.identity.classes)
     265        {
     266          if (healableClasses.indexOf(unitClass) != -1)
     267          {
     268            return {"possible": true};
     269          }
     270        }
     271      }
     272      break;
    258273        case "gather":
    259274            if (targetState.resourceSupply && (playerOwned || gaiaOwned))
    260275            {
     
    350365            else
    351366                return {"type": "none", "cursor": "action-repair-disabled", "target": undefined};
    352367            break;
     368    case ACTION_HEAL: //TODO change disabled cursor
     369      if (getActionInfo("heal", target).possible){
     370        return {"type": "heal", "cursor": "action-heal", "target": target};
     371      }else
     372        return {"type": "none", "cursor": "action-repair-disabled", "target": undefined};
     373      break;
    353374        }
    354375    }
    355376    else if (Engine.HotkeyIsPressed("session.garrison"))
     
    9921013        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
    9931014        return true;
    9941015
     1016  case "heal":
     1017    Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
     1018    //TODO play sound
     1019//    Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
     1020    return true;
     1021
    9951022    case "build": // (same command as repair)
    9961023    case "repair":
    9971024        Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued});
     
    12041231                inputState = INPUT_PRESELECTEDACTION;
    12051232                preSelectedAction = ACTION_REPAIR;
    12061233                break;
     1234      case "heal":
     1235        inputState = INPUT_PRESELECTEDACTION;
     1236        preSelectedAction = ACTION_HEAL;
     1237        break;
    12071238            case "unload-all":
    12081239                unloadAll(entity);
    12091240                break;
  • 0ad/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: //TODO get some good tooltip
     279        var tooltip = "abilitytooltip_unit_command.js";
     280        break;
     281
    272282            case COMMAND:
    273283                // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
    274284                if (item.name == "unload-all")
     
    338348            icon.sprite = "stretched:session/icons/single/" + item.icon;
    339349
    340350        }
     351    else if (guiName == "Ability")
     352    {
     353      //TODO fix so that each ability has an icon with its name
     354      icon.sprite = "stretched:session/portraits/abilities/heal.png";
     355    }
    341356        else if (template.icon)
    342357        {
    343358            icon.sprite = "stretched:session/portraits/" + template.icon;
     
    511526            setupUnitBarterPanel(entState);
    512527        }
    513528
     529    // TODO change this to entState.Ability? to just have GuiInterface.js
     530    // build the Ability list and use entState.Ability instead of ["heal]
     531    if (entState.Healer)
     532    {
     533      setupUnitPanel("Ability", usedPanels, entState, ["heal"], function (item) { performCommand(entState.id, "heal"); });//item.name); });
     534    }
     535
    514536        if (entState.buildEntities && entState.buildEntities.length)
    515537        {
    516538            setupUnitPanel("Construction", usedPanels, entState, entState.buildEntities, startBuildingPlacement);
  • 0ad/binaries/data/mods/public/art/textures/cursors/action-heal.txt

     
     11 1
  • 0ad/binaries/data/mods/public/art/textures/ui/session/portraits/abilities/heal.png

    Cannot display: file marked as a binary type.
    svn:mime-type = image/png