lib/vhyl-rpg-lite-health.t

Reference index ยท View on GitHub

   1  /** Structured injury, healing, fatigue and elapsed-time result with any resistance checks. */
   2  class RpgLiteHealthResult: object
   3      status = 'resolved'
   4      injury = 0
   5      healed = 0
   6      fatigue = 0
   7      check = nil
   8      checks = []
   9      seconds = 0
  10  ;
  11  
  12  /** Apply injury, shock, major wounds, death thresholds and mortal wounds. */
  13  rpgLiteWound(profile, amount)
  14  {
  15      rpgInteger(amount, 0, 100000);
  16      local result = new RpgLiteHealthResult();
  17      if (profile.dead || amount == 0) return result;
  18      local oldHP = profile.hp(); profile.injury += amount; result.injury = amount;
  19      profile.woundSerial += 1;
  20      profile.aidUsed = nil;
  21      if (profile.level('high-pain-threshold') == 0)
  22      { profile.shock += amount; if (profile.shock > 4) profile.shock = 4; }
  23      if (amount * 2 > profile.maxHP && !profile.unconscious)
  24      {
  25          result.check = rpgLiteAttributeCheck(profile, rpgHT, 'major-wound', 0);
  26          result.checks = result.checks + [result.check];
  27          if (!result.check.achieved)
  28          {
  29              profile.stunned = true; profile.posture = 'lying';
  30              foreach (local item in profile.equipment) if (!item.worn) item.ready = nil;
  31              if (result.check.margin <= -5 || result.check.critical == 'failure') profile.unconscious = true;
  32          }
  33      }
  34      for (local multiple = 1; multiple <= 4 && !profile.dead; multiple += 1)
  35          if (oldHP > -multiple * profile.maxHP && profile.hp() <= -multiple * profile.maxHP)
  36          {
  37              result.check = rpgLiteAttributeCheck(profile, rpgHT, 'death', 0);
  38              result.checks = result.checks + [result.check];
  39              if (!result.check.achieved)
  40              {
  41                  if (profile.mortal || result.check.margin < -2) profile.dead = true;
  42                  else { profile.mortal = true; profile.unconscious = true; }
  43              }
  44              else if (result.check.roll > profile.ht)
  45              { profile.apparentDeath = true; profile.unconscious = true; }
  46          }
  47      if (profile.hp() <= -5 * profile.maxHP) profile.dead = true;
  48      if (profile.dead) profile.unconscious = true;
  49      return result;
  50  }
  51  
  52  /** Apply fatigue and overflow injury, preserving recovery restrictions for named causes. */
  53  rpgLiteFatigue(profile, amount, cause)
  54  {
  55      rpgInteger(amount, 0, 10000);
  56      local result = new RpgLiteHealthResult();
  57      if (profile.dead) return result;
  58      if (holds(['exertion','battle','thirst'], cause) && amount > 0) amount += profile.heatExtra;
  59      local remaining = amount;
  60      while (remaining > 0)
  61      {
  62          if (profile.fp() <= 0) result.injury += rpgLiteWound(profile, 1).injury;
  63          if (profile.fp() > -profile.maxEnergy)
  64          {
  65              profile.fatigue += 1; result.fatigue += 1;
  66              if (cause == 'hunger') profile.hunger += 1;
  67              if (cause == 'thirst') profile.thirst += 1;
  68              if (cause == 'sleep') profile.sleepDebt += 1;
  69          }
  70          remaining -= 1;
  71      }
  72      if (profile.fp() <= -profile.maxEnergy) { profile.unconscious = true; profile.fatigueCollapse = true; }
  73      return result;
  74  }
  75  
  76  /** Check consciousness and effort when necessary; a failed check may incapacitate the character. */
  77  rpgLiteCanAct(profile, doNothing)
  78  {
  79      if (profile.dead || profile.unconscious || profile.mortal) return nil;
  80      if (doNothing) return true;
  81      if (profile.fp() <= 0)
  82      {
  83          local check = rpgLiteAttributeCheck(profile, rpgWill, 'effort', 0);
  84          if (!check.achieved) { profile.unconscious = true; profile.fatigueCollapse = true; return nil; }
  85      }
  86      if (profile.hp() <= 0)
  87      {
  88          local check = rpgLiteAttributeCheck(profile, rpgHT, 'consciousness', -(-profile.hp() / profile.maxHP));
  89          if (!check.achieved) { profile.unconscious = true; return nil; }
  90      }
  91      return true;
  92  }
  93  
  94  /** Apply direct healing and the associated mortal-wound and awakening checks. */
  95  rpgLiteHeal(profile, amount)
  96  {
  97      rpgInteger(amount, 0, 10000);
  98      if (profile.dead) return 0;
  99      if (amount > profile.injury) amount = profile.injury;
 100      profile.injury -= amount;
 101      if (amount > 0 && profile.mortal)
 102      {
 103          local check = rpgLiteAttributeCheck(profile, rpgHT, 'mortal-recovery', 0);
 104          if (check.achieved)
 105          {
 106              profile.mortal = nil;
 107              if (!rpgLiteAttributeCheck(profile, rpgHT, 'permanent-health', 0).achieved && profile.ht > 1) profile.ht -= 1;
 108          }
 109      }
 110      if (amount > 0 && profile.unconscious && !profile.mortal && profile.fp() > 0
 111          && rpgLiteAttributeCheck(profile, rpgHT, 'wake', 0).achieved)
 112      { profile.unconscious = nil; profile.apparentDeath = nil; profile.needsMedicalHelp = nil; }
 113      return amount;
 114  }
 115  
 116  /** Persistent first-aid session with supplies, bandaging and TL-dependent treatment time. */
 117  class RpgLiteTreatment: object
 118      patient = nil
 119      healer = nil
 120      skill = nil
 121      techLevel = 3
 122      elapsed = 0
 123      bandaged = nil
 124      completed = nil
 125      modifier = 0
 126      supplies = 1
 127      bandageHealing = 0
 128      advance(seconds)
 129      {
 130          rpgInteger(seconds, 1, 86400);
 131          local result = new RpgLiteHealthResult();
 132          if (self.completed || self.patient.aidUsed || self.patient.dead
 133              || self.healer.dead || self.healer.unconscious || self.healer.stunned || self.supplies < 1)
 134          { result.status = 'blocked'; return result; }
 135          rpgInteger(self.techLevel, 0, 12);
 136          local extra = self.techLevel <= 4 ? 1800 : self.techLevel <= 7 ? 1200 : 600;
 137          local remaining = extra + 60 - self.elapsed;
 138          if (seconds > remaining) seconds = remaining;
 139          self.elapsed += seconds; rpgSpendTime(seconds); result.seconds = seconds;
 140          if (self.elapsed >= 60 && !self.bandaged)
 141          {
 142              self.bandaged = true;
 143              if (self.patient.bandagedSerial != self.patient.woundSerial)
 144              {
 145                  self.bandageHealing = rpgLiteHeal(self.patient, 1); result.healed += self.bandageHealing;
 146                  self.patient.bandagedSerial = self.patient.woundSerial;
 147              }
 148          }
 149          if (self.elapsed < extra + 60) return result;
 150          local context = new RpgLiteContext();
 151          context.skill = self.skill == nil ? rpgLiteTraining(self.healer, 'first-aid', self.techLevel, nil) : self.skill;
 152          context.modifier = self.modifier;
 153          result.check = rpgLiteCheck(self.healer, context); self.completed = true; self.supplies -= 1;
 154          if (result.check.critical == 'failure') result.injury = rpgLiteWound(self.patient, 2 + self.bandageHealing).injury;
 155          else if (result.check.achieved)
 156          {
 157              local bonus = self.techLevel <= 1 ? -4 : self.techLevel <= 3 ? -3
 158                  : self.techLevel <= 5 ? -2 : self.techLevel <= 7 ? -1 : self.techLevel == 8 ? 0 : 1;
 159              local total = (result.check.critical == 'success' ? 6 : rpgRandom.die()) + bonus;
 160              if (total > 1) result.healed += rpgLiteHeal(self.patient, total - 1);
 161          }
 162          self.patient.aidUsed = true; return result;
 163      }
 164  ;
 165  
 166  /** Attempt end-of-turn stun recovery; fixed Fright durations cannot be shortened. */
 167  rpgLiteRecoverStun(profile)
 168  {
 169      if ((!profile.stunned && !profile.mentalStun) || profile.unconscious || profile.dead || profile.fearSeconds > 0) return nil;
 170      local check = rpgLiteAttributeCheck(profile, profile.mentalStun ? rpgIQ : rpgHT,
 171          profile.mentalStun ? 'mental-stun' : 'stun', 0);
 172      if (check.achieved) { profile.stunned = nil; profile.mentalStun = nil; }
 173      return check;
 174  }
 175  
 176  /** Advance quiet rest or sleep and eligible recovery; consumes the stated game time. */
 177  rpgLiteRest(profile, seconds, food, water, sleeping, healingAllowed)
 178  {
 179      rpgInteger(seconds, 1, 86400);
 180      local result = new RpgLiteHealthResult(); result.seconds = seconds;
 181      if (profile.dead) { result.status = 'blocked'; return result; }
 182      rpgSpendTime(seconds);
 183      for (local elapsed = 0; elapsed < seconds && (profile.stunned || profile.mentalStun)
 184          && !profile.unconscious && profile.fearSeconds == 0; elapsed += 1) rpgLiteRecoverStun(profile);
 185      profile.shock = 0;
 186      if (food) profile.hunger = 0;
 187      if (water)
 188      {
 189          profile.waterRestClock += seconds;
 190          if (profile.waterRestClock >= 86400)
 191          { profile.fatigue -= profile.thirst; result.fatigue -= profile.thirst; profile.thirst = 0; profile.waterRestClock = 0; }
 192      }
 193      else profile.waterRestClock = 0;
 194      if (sleeping)
 195      {
 196          profile.sleepClock += seconds;
 197          if (profile.sleepClock >= 28800) { profile.awakeClock = 0; profile.sleepCheckClock = 0; profile.sleepPenalty = nil; }
 198          while (profile.sleepDebt > 0 && profile.sleepClock >= (profile.sleepRecovered ? 3600 : 28800))
 199          {
 200              profile.sleepClock -= profile.sleepRecovered ? 3600 : 28800;
 201              profile.sleepRecovered = true; profile.sleepDebt -= 1; profile.fatigue -= 1;
 202          }
 203      }
 204      else { profile.sleepClock = 0; profile.sleepRecovered = nil; }
 205      profile.restClock += seconds;
 206      local locked = profile.hunger + profile.thirst + profile.sleepDebt + profile.temperatureFatigue;
 207      while (profile.restClock >= 600)
 208      {
 209          profile.restClock -= 600;
 210          if (profile.fatigue > locked) { profile.fatigue -= 1; result.fatigue -= 1; }
 211      }
 212      if (profile.fatigue == locked) profile.restClock = 0;
 213      if (food && healingAllowed)
 214      {
 215          profile.healingClock += seconds;
 216          while (profile.healingClock >= 86400)
 217          {
 218              profile.healingClock -= 86400;
 219              result.check = rpgLiteAttributeCheck(profile, rpgHT, 'healing', 0);
 220              if (result.check.achieved && profile.injury > profile.diseaseInjury + profile.temperatureInjury)
 221                  result.healed += rpgLiteHeal(profile, 1);
 222          }
 223      }
 224      rpgLiteHealthTime(profile, seconds);
 225      return result;
 226  }
 227  
 228  /** Advance condition clocks without charging game time; compose with one activity clock. */
 229  rpgLiteHealthTime(profile, seconds)
 230  {
 231      if (profile.dead) return nil;
 232      profile.conditionTime(seconds);
 233      if (profile.fatigueCollapse && profile.fp() > 0 && !profile.mortal)
 234      { profile.unconscious = nil; profile.fatigueCollapse = nil; }
 235      if (profile.fearSeconds > 0)
 236      {
 237          profile.fearSeconds -= seconds;
 238          if (profile.fearSeconds <= 0)
 239          { profile.fearSeconds = 0; profile.stunned = nil; if (profile.hp() > 0 && profile.fp() > 0) profile.unconscious = nil; }
 240      }
 241      if (profile.mortal)
 242      {
 243          profile.mortalClock += seconds;
 244          while (profile.mortalClock >= 1800 && !profile.dead && profile.mortal)
 245          {
 246              profile.mortalClock -= 1800;
 247              local check = rpgLiteAttributeCheck(profile, rpgHT, 'mortal-wound', 0);
 248              if (!check.achieved) profile.dead = true;
 249              else if (check.critical == 'success')
 250              {
 251                  profile.mortal = nil;
 252                  if (!rpgLiteAttributeCheck(profile, rpgHT, 'permanent-health', 0).achieved && profile.ht > 1) profile.ht -= 1;
 253              }
 254          }
 255      }
 256      if (profile.unconscious && !profile.mortal && !profile.dead && profile.fearSeconds == 0)
 257      {
 258          profile.unconsciousClock += seconds;
 259          if (profile.fp() > 0 && profile.hp() > 0 && profile.unconsciousClock >= 900)
 260          { profile.unconscious = nil; profile.apparentDeath = nil; }
 261          else if (profile.fp() > 0 && profile.hp() <= 0)
 262          {
 263              local interval = profile.hp() > -profile.maxHP ? 3600 : 43200;
 264              while (profile.unconsciousClock >= interval && profile.unconscious && !profile.dead)
 265              {
 266                  profile.unconsciousClock -= interval;
 267                  local check = rpgLiteAttributeCheck(profile, rpgHT, 'wake', 0);
 268                  if (check.achieved && !profile.needsMedicalHelp) profile.unconscious = nil;
 269                  else if (profile.hp() <= -profile.maxHP)
 270                  {
 271                      if (profile.needsMedicalHelp && !check.achieved) profile.dead = true;
 272                      profile.needsMedicalHelp = true;
 273                  }
 274              }
 275          }
 276      }
 277      if (!profile.unconscious) profile.unconsciousClock = 0;
 278      return nil;
 279  }
 280  
 281  /** Resolve fear and set the Lite stun or fainting duration on failure. */
 282  rpgLiteFright(profile, modifier)
 283  {
 284      local result = rpgLiteAttributeCheck(profile, rpgWill, 'fright', modifier);
 285      if (!result.achieved)
 286      {
 287          if (result.critical == 'failure')
 288          { profile.fearSeconds = (-result.margin + rpgRandom.die()) * 60; profile.unconscious = true; }
 289          else { profile.fearSeconds = -result.margin + rpgRandom.die() + rpgRandom.die(); profile.stunned = true; }
 290      }
 291      return result;
 292  }
 293  
 294  modify RpgLiteProfile
 295      sleepClock = 0
 296      sleepRecovered = nil
 297      needsMedicalHelp = nil
 298      fatigueCollapse = nil
 299      woundSerial = 0
 300      bandagedSerial = -1
 301      waterRestClock = 0
 302      awakeClock = 0
 303      sleepCheckClock = 0
 304      sleepPenalty = nil
 305      burningPenalty = 0
 306      diseaseInjury = 0
 307      temperatureInjury = 0
 308      temperatureFatigue = 0
 309      heatExtra = 0
 310  ;
 311  
 312  /** Authored poison or disease parameters: resistance, delay, cycle, damage and recovery modifier. */
 313  class RpgLiteHazard: object
 314      kind = 'poison'
 315      resistanceModifier = 0
 316      delay = 0
 317      interval = 1
 318      cycles = 1
 319      dice = 1
 320      adds = 0
 321      fatigueDamage = nil
 322      resistible = true
 323      recoveryModifier = 0
 324  ;
 325  
 326  /** Persistent exposure timeline; partial intervals and disease recovery locks survive saves. */
 327  class RpgLiteExposure: object
 328      profile = nil
 329      hazard = nil
 330      elapsed = 0
 331      remaining = 0
 332      started = nil
 333      finished = nil
 334      lockedInjury = 0
 335      advance(seconds)
 336      {
 337          rpgInteger(seconds, 0, 86400);
 338          local result = new RpgLiteHealthResult();
 339          if (self.finished || self.profile.dead) return result;
 340          rpgInteger(self.hazard.interval, 1, 31536000); rpgInteger(self.hazard.cycles, 1, 10000);
 341          rpgInteger(self.hazard.delay, 0, 31536000);
 342          if (!self.started)
 343          {
 344              self.remaining = self.hazard.cycles; self.started = true;
 345              if (self.hazard.kind == 'disease' && self.hazard.resistible)
 346              {
 347                  result.check = rpgLiteAttributeCheck(self.profile, rpgHT, 'disease', self.hazard.resistanceModifier);
 348                  if (result.check.achieved) { self.finished = true; return result; }
 349              }
 350          }
 351          self.elapsed += seconds;
 352          local due = self.hazardDelayDone ? self.hazard.interval : self.hazard.delay;
 353          while (self.elapsed >= due && !self.finished)
 354          {
 355              self.elapsed -= due;
 356              result.check = self.hazard.resistible && (self.hazard.kind != 'disease' || self.hazardDelayDone)
 357                  ? rpgLiteAttributeCheck(self.profile, rpgHT, self.hazard.kind,
 358                      self.hazard.resistanceModifier + (self.hazard.kind == 'disease' ? self.hazard.recoveryModifier : 0)) : nil;
 359              if (self.hazard.kind == 'disease' && result.check != nil && result.check.achieved)
 360              { self.finished = true; break; }
 361              if (result.check == nil || !result.check.achieved)
 362              {
 363                  local amount = rpgLiteRollDamage(self.hazard.dice, self.hazard.adds, 'cr', nil);
 364                  if (self.hazard.fatigueDamage) result.fatigue += rpgLiteFatigue(self.profile, amount, self.hazard.kind).fatigue;
 365                  else
 366                  {
 367                      local injury = rpgLiteWound(self.profile, amount).injury; result.injury += injury;
 368                      if (self.hazard.kind == 'disease')
 369                      { self.lockedInjury += injury; self.profile.diseaseInjury += injury; }
 370                  }
 371              }
 372              self.remaining -= 1;
 373              if (self.remaining == 0 || self.profile.dead) self.finished = true;
 374              due = self.hazard.interval;
 375              self.hazardDelayDone = true;
 376          }
 377          if (self.finished && self.lockedInjury > 0)
 378          { self.profile.diseaseInjury -= self.lockedInjury; self.lockedInjury = 0; }
 379          return result;
 380      }
 381      hazardDelayDone = nil
 382  ;
 383  
 384  /** Apply seconds without air, or resume clean air and check lasting injury. */
 385  rpgLiteSuffocate(profile, seconds, cleanAir)
 386  {
 387      rpgInteger(seconds, 0, 86400);
 388      if (cleanAir)
 389      {
 390          if (profile.airClock > 120 && !rpgLiteAttributeCheck(profile, rpgHT, 'brain-injury', 0).achieved && profile.iq > 1)
 391              profile.iq -= 1;
 392          profile.airClock = 0; return nil;
 393      }
 394      for (local elapsed = 0; elapsed < seconds && !profile.dead; elapsed += 1)
 395      {
 396          profile.airClock += 1; rpgLiteFatigue(profile, 1, 'air');
 397          if (profile.fp() <= 0 && !profile.unconscious
 398              && !rpgLiteAttributeCheck(profile, rpgWill, 'air', 0).achieved)
 399          { profile.unconscious = true; profile.fatigueCollapse = true; }
 400          if (profile.airClock >= 240) { profile.dead = true; profile.unconscious = true; }
 401      }
 402      return nil;
 403  }
 404  
 405  /** Persistent heat/cold exposure and shelter-dependent recovery restrictions. */
 406  class RpgLiteClimate: object
 407      profile = nil
 408      fahrenheit = 70
 409      wind = 0
 410      clothing = 0
 411      wet = nil
 412      active = true
 413      sheltered = nil
 414      elapsed = 0
 415      advance(seconds)
 416      {
 417          rpgInteger(seconds, 0, 86400);
 418          self.profile.heatExtra = self.fahrenheit >= 121 ? 2 : self.fahrenheit >= 91 ? 1 : 0;
 419          if (self.sheltered || self.fahrenheit >= 35 && self.fahrenheit <= 80)
 420          {
 421              self.elapsed = 0; self.profile.temperatureFatigue = 0; self.profile.temperatureInjury = 0;
 422              if (self.sheltered) self.profile.heatExtra = 0;
 423              return 0;
 424          }
 425          if (self.fahrenheit > 80 && !self.active) return 0;
 426          local interval = self.fahrenheit < 35 ? (self.wind >= 30 ? 600 : self.wind >= 10 ? 900 : 1800) : 1800;
 427          local modifier = self.fahrenheit < 35 ? self.clothing - (self.wet ? 5 : 0)
 428              - (self.fahrenheit < 0 ? -self.fahrenheit / 10 : 0) : 0;
 429          local total = 0; self.elapsed += seconds;
 430          while (self.elapsed >= interval)
 431          {
 432              self.elapsed -= interval;
 433              local check = rpgLiteAttributeCheck(self.profile, rpgHT, 'temperature', modifier);
 434              if (!check.achieved)
 435              {
 436                  local amount = self.fahrenheit > 80 && check.critical == 'failure' ? rpgRandom.die() : 1;
 437                  local damage = rpgLiteFatigue(self.profile, amount, 'temperature'); total += damage.fatigue;
 438                  self.profile.temperatureFatigue += damage.fatigue; self.profile.temperatureInjury += damage.injury;
 439              }
 440          }
 441          return total;
 442      }
 443  ;
 444  
 445  /** Advance food, water and waking-time needs using the day's supplied meals and quarts. */
 446  class RpgLiteNeeds: object
 447      profile = nil
 448      waterQuarts = 2
 449      requiredQuarts = 2
 450      meals = 3
 451      active = true
 452      elapsed = 0
 453      advance(seconds)
 454      {
 455          rpgInteger(seconds, 0, 86400); rpgInteger(self.meals, 0, 3);
 456          rpgInteger(self.waterQuarts, 0, 100); rpgInteger(self.requiredQuarts, 1, 100);
 457          for (local index = 0; index < seconds && !self.profile.dead; index += 1)
 458          {
 459              self.elapsed += 1; self.profile.awakeClock += 1;
 460              if (self.elapsed % 28800 == 0 && self.waterQuarts < self.requiredQuarts)
 461                  rpgLiteFatigue(self.profile, 1, 'thirst');
 462              if (self.elapsed >= 86400)
 463              {
 464                  self.elapsed = 0; rpgLiteFatigue(self.profile, 3 - self.meals, 'hunger');
 465                  if (self.waterQuarts < 1)
 466                  { rpgLiteFatigue(self.profile, 1, 'thirst'); rpgLiteWound(self.profile, 1); }
 467              }
 468              if (self.profile.awakeClock >= 57600 && (self.profile.awakeClock - 57600) % 14400 == 0)
 469                  rpgLiteFatigue(self.profile, 1, 'sleep');
 470              local interval = self.profile.sleepDebt > 0 && self.profile.fp() * 3 < self.profile.maxEnergy
 471                  ? (self.active ? 7200 : 1800) : self.profile.sleepDebt * 2 >= self.profile.maxEnergy && !self.active ? 7200 : 0;
 472              if (interval > 0)
 473              {
 474                  self.profile.sleepCheckClock += 1;
 475                  if (self.profile.sleepCheckClock >= interval)
 476                  {
 477                      self.profile.sleepCheckClock = 0;
 478                      if (!rpgLiteAttributeCheck(self.profile, rpgWill, 'sleep', 0).achieved)
 479                      { self.profile.unconscious = true; return nil; }
 480                      self.profile.sleepPenalty = true;
 481                  }
 482              }
 483              else self.profile.sleepCheckClock = 0;
 484          }
 485          return nil;
 486      }
 487  ;
 488  
 489  /** Track burning clothes, distraction, damage and extinguishing maneuvers. */
 490  class RpgLiteFire: object
 491      profile = nil
 492      burning = 0
 493      extinguishProgress = 0
 494      ignite(basicDamage)
 495      {
 496          if (basicDamage >= 10) self.burning = 2;
 497          else if (basicDamage >= 3 && self.burning == 0) self.burning = 1;
 498          self.profile.burningPenalty = self.burning == 2 ? 3 : self.burning == 1 ? 2 : 0;
 499          return self.burning;
 500      }
 501      tick(seconds)
 502      {
 503          rpgInteger(seconds, 0, 86400);
 504          for (local elapsed = 0; elapsed < seconds && self.burning > 0 && !self.profile.dead; elapsed += 1)
 505          {
 506              local damage = rpgLiteRollDamage(1, self.burning == 1 ? -4 : -1, 'burn', nil);
 507              self.profile.burningPenalty = damage > rpgLiteArmor(self.profile) ? (self.burning == 1 ? 2 : 3) : 0;
 508              rpgLiteWound(self.profile, rpgLitePenetration(damage, rpgLiteArmor(self.profile), 1, 'burn', nil));
 509          }
 510          return nil;
 511      }
 512      extinguish(water)
 513      {
 514          if (water) { self.burning = 0; self.extinguishProgress = 0; self.profile.burningPenalty = 0; return true; }
 515          self.extinguishProgress += 1;
 516          if (self.extinguishProgress < (self.burning == 2 ? 3 : 1)) return nil;
 517          self.extinguishProgress = 0;
 518          if (rpgLiteAttributeCheck(self.profile, rpgDX, 'extinguish', 0).achieved) self.burning = 0;
 519          if (self.burning == 0) self.profile.burningPenalty = 0;
 520          return self.burning == 0;
 521      }
 522  ;