LCOV - code coverage report
Current view: top level - simulation/components - Heal.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 86 105 81.9 %
Date: 2023-04-02 12:52:40 Functions: 15 15 100.0 %

          Line data    Source code
       1             : function Heal() {}
       2             : 
       3           1 : Heal.prototype.Schema =
       4             :         "<a:help>Controls the healing abilities of the unit.</a:help>" +
       5             :         "<a:example>" +
       6             :                 "<Range>20</Range>" +
       7             :                 "<RangeOverlay>" +
       8             :                         "<LineTexture>heal_overlay_range.png</LineTexture>" +
       9             :                         "<LineTextureMask>heal_overlay_range_mask.png</LineTextureMask>" +
      10             :                         "<LineThickness>0.35</LineThickness>" +
      11             :                 "</RangeOverlay>" +
      12             :                 "<Health>5</Health>" +
      13             :                 "<Interval>2000</Interval>" +
      14             :                 "<UnhealableClasses datatype=\"tokens\">Cavalry</UnhealableClasses>" +
      15             :                 "<HealableClasses datatype=\"tokens\">Support Infantry</HealableClasses>" +
      16             :         "</a:example>" +
      17             :         "<element name='Range' a:help='Range (in metres) where healing is possible.'>" +
      18             :                 "<ref name='nonNegativeDecimal'/>" +
      19             :         "</element>" +
      20             :         "<optional>" +
      21             :                 "<element name='RangeOverlay'>" +
      22             :                         "<interleave>" +
      23             :                                 "<element name='LineTexture'><text/></element>" +
      24             :                                 "<element name='LineTextureMask'><text/></element>" +
      25             :                                 "<element name='LineThickness'><ref name='nonNegativeDecimal'/></element>" +
      26             :                         "</interleave>" +
      27             :                 "</element>" +
      28             :         "</optional>" +
      29             :         "<element name='Health' a:help='Health healed per Interval.'>" +
      30             :                 "<ref name='nonNegativeDecimal'/>" +
      31             :         "</element>" +
      32             :         "<element name='Interval' a:help='A heal is performed every Interval ms.'>" +
      33             :                 "<ref name='nonNegativeDecimal'/>" +
      34             :         "</element>" +
      35             :         "<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).'>" +
      36             :                 "<attribute name='datatype'>" +
      37             :                         "<value>tokens</value>" +
      38             :                 "</attribute>" +
      39             :                 "<text/>" +
      40             :         "</element>" +
      41             :         "<element name='HealableClasses' a:help='The target must have one of these classes to be healable.'>" +
      42             :                 "<attribute name='datatype'>" +
      43             :                         "<value>tokens</value>" +
      44             :                 "</attribute>" +
      45             :                 "<text/>" +
      46             :         "</element>";
      47             : 
      48           1 : Heal.prototype.Init = function()
      49             : {
      50             : };
      51             : 
      52           1 : Heal.prototype.GetTimers = function()
      53             : {
      54           5 :         return {
      55             :                 "prepare": 1000,
      56             :                 "repeat": this.GetInterval()
      57             :         };
      58             : };
      59             : 
      60           1 : Heal.prototype.GetHealth = function()
      61             : {
      62           5 :         return ApplyValueModificationsToEntity("Heal/Health", +this.template.Health, this.entity);
      63             : };
      64             : 
      65           1 : Heal.prototype.GetInterval = function()
      66             : {
      67           6 :         return ApplyValueModificationsToEntity("Heal/Interval", +this.template.Interval, this.entity);
      68             : };
      69             : 
      70           1 : Heal.prototype.GetRange = function()
      71             : {
      72           6 :         return {
      73             :                 "min": 0,
      74             :                 "max": ApplyValueModificationsToEntity("Heal/Range", +this.template.Range, this.entity)
      75             :         };
      76             : };
      77             : 
      78           1 : Heal.prototype.GetUnhealableClasses = function()
      79             : {
      80          14 :         return this.template.UnhealableClasses._string || "";
      81             : };
      82             : 
      83           1 : Heal.prototype.GetHealableClasses = function()
      84             : {
      85          12 :         return this.template.HealableClasses._string || "";
      86             : };
      87             : 
      88             : /**
      89             :  * Whether this entity can heal the target.
      90             :  *
      91             :  * @param {number} target - The target's entity ID.
      92             :  * @return {boolean} - Whether the target can be healed.
      93             :  */
      94           1 : Heal.prototype.CanHeal = function(target)
      95             : {
      96          16 :         let cmpHealth = Engine.QueryInterface(target, IID_Health);
      97          16 :         if (!cmpHealth || cmpHealth.IsUnhealable() || !cmpHealth.IsInjured())
      98           2 :                 return false;
      99             : 
     100          14 :         let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     101          14 :         if (!cmpOwnership || !IsOwnedByAllyOfPlayer(cmpOwnership.GetOwner(), target))
     102           1 :                 return false;
     103             : 
     104          13 :         let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
     105          13 :         if (!cmpIdentity)
     106           0 :                 return false;
     107             : 
     108          13 :         let targetClasses = cmpIdentity.GetClassesList();
     109          13 :         return !MatchesClassList(targetClasses, this.GetUnhealableClasses()) &&
     110             :                 MatchesClassList(targetClasses, this.GetHealableClasses());
     111             : };
     112             : 
     113           1 : Heal.prototype.GetRangeOverlays = function()
     114             : {
     115           1 :         if (!this.template.RangeOverlay)
     116           0 :                 return [];
     117             : 
     118           1 :         return [{
     119             :                 "radius": this.GetRange().max,
     120             :                 "texture": this.template.RangeOverlay.LineTexture,
     121             :                 "textureMask": this.template.RangeOverlay.LineTextureMask,
     122             :                 "thickness": +this.template.RangeOverlay.LineThickness
     123             :         }];
     124             : };
     125             : 
     126             : /**
     127             :  * @param {number} target - The target to heal.
     128             :  * @param {number} callerIID - The IID to notify on specific events.
     129             :  * @return {boolean} - Whether we started healing.
     130             :  */
     131           1 : Heal.prototype.StartHealing = function(target, callerIID)
     132             : {
     133           4 :         if (this.target)
     134           3 :                 this.StopHealing();
     135             : 
     136           4 :         if (!this.CanHeal(target))
     137           0 :                 return false;
     138             : 
     139           4 :         let timings = this.GetTimers();
     140           4 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     141             : 
     142             :         // If the repeat time since the last heal hasn't elapsed,
     143             :         // delay the action to avoid healing too fast.
     144           4 :         let prepare = timings.prepare;
     145           4 :         if (this.lastHealed)
     146             :         {
     147           3 :                 let repeatLeft = this.lastHealed + timings.repeat - cmpTimer.GetTime();
     148           3 :                 prepare = Math.max(prepare, repeatLeft);
     149             :         }
     150             : 
     151           4 :         let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     152           4 :         if (cmpVisual)
     153             :         {
     154           0 :                 cmpVisual.SelectAnimation("heal", false, 1.0);
     155           0 :                 cmpVisual.SetAnimationSyncRepeat(timings.repeat);
     156           0 :                 cmpVisual.SetAnimationSyncOffset(prepare);
     157             :         }
     158             : 
     159             :         // If using a non-default prepare time, re-sync the animation when the timer runs.
     160           4 :         this.resyncAnimation = prepare != timings.prepare;
     161           4 :         this.target = target;
     162           4 :         this.callerIID = callerIID;
     163           4 :         this.timer = cmpTimer.SetInterval(this.entity, IID_Heal, "PerformHeal", prepare, timings.repeat, null);
     164             : 
     165           4 :         return true;
     166             : };
     167             : 
     168             : /**
     169             :  * @param {string} reason - The reason why we stopped healing.
     170             :  */
     171           1 : Heal.prototype.StopHealing = function(reason)
     172             : {
     173           4 :         if (!this.target)
     174           0 :                 return;
     175             : 
     176           4 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     177           4 :         cmpTimer.CancelTimer(this.timer);
     178           4 :         delete this.timer;
     179             : 
     180           4 :         delete this.target;
     181             : 
     182           4 :         let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     183           4 :         if (cmpVisual)
     184           0 :                 cmpVisual.SelectAnimation("idle", false, 1.0);
     185             : 
     186             :         // The callerIID component may start again,
     187             :         // replacing the callerIID, hence save that.
     188           4 :         let callerIID = this.callerIID;
     189           4 :         delete this.callerIID;
     190             : 
     191           4 :         if (reason && callerIID)
     192             :         {
     193           0 :                 let component = Engine.QueryInterface(this.entity, callerIID);
     194           0 :                 if (component)
     195           0 :                         component.ProcessMessage(reason, null);
     196             :         }
     197             : };
     198             : 
     199             : /**
     200             :  * Heal our target entity.
     201             :  * @param data - Unused.
     202             :  * @param {number} lateness - The offset of the actual call and when it was expected.
     203             :  */
     204           1 : Heal.prototype.PerformHeal = function(data, lateness)
     205             : {
     206           5 :         if (!this.CanHeal(this.target))
     207             :         {
     208           1 :                 this.StopHealing("TargetInvalidated");
     209           1 :                 return;
     210             :         }
     211           4 :         if (!this.IsTargetInRange(this.target))
     212             :         {
     213           0 :                 this.StopHealing("OutOfRange");
     214           0 :                 return;
     215             :         }
     216             : 
     217             :         // ToDo: Enable entities to keep facing a target.
     218           4 :         Engine.QueryInterface(this.entity, IID_UnitAI)?.FaceTowardsTarget(this.target);
     219             : 
     220           4 :         let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
     221           4 :         this.lastHealed = cmpTimer.GetTime() - lateness;
     222             : 
     223           4 :         let cmpHealth = Engine.QueryInterface(this.target, IID_Health);
     224           4 :         let targetState = cmpHealth.Increase(this.GetHealth());
     225             : 
     226             :         // Add experience.
     227           4 :         let cmpLoot = Engine.QueryInterface(this.target, IID_Loot);
     228           4 :         let cmpPromotion = Engine.QueryInterface(this.entity, IID_Promotion);
     229           4 :         if (targetState !== undefined && cmpLoot && cmpPromotion)
     230             :                 // Health healed times experience per health.
     231           2 :                 cmpPromotion.IncreaseXp((targetState.new - targetState.old) / cmpHealth.GetMaxHitpoints() * cmpLoot.GetXp());
     232             : 
     233             :         // TODO we need a sound file.
     234             :         // PlaySound("heal_impact", this.entity);
     235             : 
     236           4 :         if (!cmpHealth.IsInjured())
     237             :         {
     238           0 :                 this.StopHealing("TargetInvalidated");
     239           0 :                 return;
     240             :         }
     241             : 
     242           4 :         if (this.resyncAnimation)
     243             :         {
     244           1 :                 let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     245           1 :                 if (cmpVisual)
     246             :                 {
     247           0 :                         let repeat = this.GetTimers().repeat;
     248           0 :                         cmpVisual.SetAnimationSyncRepeat(repeat);
     249           0 :                         cmpVisual.SetAnimationSyncOffset(repeat);
     250             :                 }
     251           1 :                 delete this.resyncAnimation;
     252             :         }
     253             : };
     254             : 
     255             : /**
     256             :  * @param {number} - The entity ID of the target to check.
     257             :  * @return {boolean} - Whether this entity is in range of its target.
     258             :  */
     259           1 : Heal.prototype.IsTargetInRange = function(target)
     260             : {
     261           4 :         let range = this.GetRange();
     262           4 :         let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
     263           4 :         return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false);
     264             : };
     265             : 
     266           1 : Heal.prototype.OnValueModification = function(msg)
     267             : {
     268           2 :         if (msg.component != "Heal" || msg.valueNames.indexOf("Heal/Range") === -1)
     269           1 :                 return;
     270             : 
     271           1 :         let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
     272           1 :         if (!cmpUnitAI)
     273           0 :                 return;
     274             : 
     275           1 :         cmpUnitAI.UpdateRangeQueries();
     276             : };
     277             : 
     278           1 : Engine.RegisterComponentType(IID_Heal, "Heal", Heal);

Generated by: LCOV version 1.14