lib/vhyl-rpg-lite-equipment.t

Reference index · View on GitHub

   1  /** Owned equipment with integer price and weight in hundredths of a pound. */
   2  class RpgLiteItem: object
   3      id = nil
   4      cost = 0
   5      weight = 0
   6      quantity = 1
   7      dr = 0
   8      db = 0
   9      worn = nil
  10      ready = nil
  11      weightHundredths() { return self.weight * self.quantity; }
  12  ;
  13  
  14  /** Mutable weapon instance with attack modes, ammunition, reserves and reload progress. */
  15  class RpgLiteWeapon: RpgLiteItem
  16      techLevel = 0
  17      modes = []
  18      ammunition = 0
  19      reserves = 0
  20      reloadProgress = 0
  21      weaponST = 10
  22      goatFoot = nil
  23      scopeBonus = 0
  24      bipod = nil
  25  ;
  26  
  27  /** One melee or ranged use of a weapon, including ST, damage, range and reload statistics. */
  28  class RpgLiteAttackMode: object
  29      skillId = nil
  30      specialty = nil
  31      base = 'thrust'
  32      dice = 0
  33      adds = 0
  34      damageType = 'cr'
  35      minimumST = 0
  36      hands = 1
  37      requiresTwoHands = nil
  38      unready = nil
  39      ranged = nil
  40      thrown = nil
  41      acc = 0
  42      halfRange = 0
  43      maximumRange = 0
  44      rangeScale = 1
  45      rof = 1
  46      pellets = 1
  47      capacity = 0
  48      reloadSeconds = 0
  49      individualReload = nil
  50      armorDivisor = 1
  51  ;
  52  
  53  /** Return [dice, adds] for ST 1–20 and thrust or swing. */
  54  rpgLiteDamageDice(strength, swing)
  55  {
  56      rpgInteger(strength, 1, 20);
  57      local thrust = [[1,-6],[1,-6],[1,-5],[1,-5],[1,-4],[1,-4],[1,-3],[1,-3],[1,-2],[1,-2],
  58          [1,-1],[1,-1],[1,0],[1,0],[1,1],[1,1],[1,2],[1,2],[2,-1],[2,-1]];
  59      local swings = [[1,-5],[1,-5],[1,-4],[1,-4],[1,-3],[1,-3],[1,-2],[1,-2],[1,-1],[1,0],
  60          [1,1],[1,2],[2,-1],[2,0],[2,1],[2,2],[3,-1],[3,0],[3,1],[3,2]];
  61      return swing ? swings[strength] : thrust[strength];
  62  }
  63  
  64  /** Roll basic damage with the crushing/non-crushing minimum, or take maximum damage. */
  65  rpgLiteRollDamage(dice, adds, damageType, maximum)
  66  {
  67      rpgInteger(dice, 1, 100); rpgInteger(adds, -1000, 1000);
  68      local damage = adds;
  69      for (local index = 0; index < dice; index += 1) damage += maximum ? 6 : rpgRandom.die();
  70      local minimum = damageType == 'cr' ? 0 : 1;
  71      return damage < minimum ? minimum : damage;
  72  }
  73  
  74  /** Apply half range, armor divisor, DR and wound multiplier in that order. */
  75  rpgLitePenetration(damage, dr, divisor, damageType, halfRange)
  76  {
  77      rpgInteger(damage, 0, 100000); rpgInteger(dr, 0, 10000); rpgInteger(divisor, 1, 100);
  78      if (!holds(['cr','cut','imp','burn','pi-','pi','pi+','tox','fat'], damageType)) throw new RpgError();
  79      if (halfRange) damage /= 2;
  80      local penetration = damage - dr / divisor;
  81      if (penetration <= 0) return 0;
  82      local amount = penetration;
  83      if (damageType == 'pi-') amount /= 2;
  84      if (damageType == 'cut' || damageType == 'pi+') amount = amount * 3 / 2;
  85      if (damageType == 'imp') amount *= 2;
  86      return amount < 1 ? 1 : amount;
  87  }
  88  
  89  /** Create an independent armor or shield item by catalogue ID. */
  90  rpgLiteMakeArmor(id)
  91  {
  92      foreach (local row in rpgLiteCatalogue.armor)
  93          if (row[1] == id)
  94          {
  95              local item = new RpgLiteItem(); item.id = id; item.dr = row[2];
  96              item.cost = row[3]; item.weight = row[4]; return item;
  97          }
  98      foreach (local row in rpgLiteCatalogue.shields)
  99          if (row[1] == id)
 100          {
 101              local item = new RpgLiteItem(); item.id = id; item.db = row[2];
 102              item.cost = row[3]; item.weight = row[4]; return item;
 103          }
 104      throw new RpgError();
 105  }
 106  
 107  /** Numeric weapon and attack-mode tables; no mutable ammunition is stored in these rows. */
 108  rpgLiteArmory: object
 109      weapons = [
 110          ['axe',0,50,400], ['mace',2,50,500], ['punch',0,0,0], ['kick',0,0,0],
 111          ['brass-knuckles',1,10,25], ['blackjack',1,20,100], ['broadsword',2,500,300], ['thrusting-broadsword',2,600,300],
 112          ['large-knife',0,40,100], ['poleaxe',3,120,1000], ['rapier',4,500,275],
 113          ['shortsword',2,400,200], ['spear',0,40,400], ['quarterstaff',0,10,400],
 114          ['thrusting-greatsword',3,900,700], ['longbow',0,200,300], ['short-bow',0,50,200],
 115          ['crossbow',2,150,600], ['throwing-axe',0,60,400], ['derringer',5,100,50],
 116          ['auto-pistol-9mm',6,350,240], ['revolver-357',7,500,300], ['auto-pistol-44m',8,750,450],
 117          ['lever-carbine',5,300,700], ['self-loading-rifle',6,600,1000],
 118          ['sniper-rifle',8,5600,1750], ['pump-shotgun',6,240,800]
 119      ]
 120      melee = [
 121          ['axe','axe-mace','swing',2,'cut',11,1,nil], ['mace','axe-mace','swing',3,'cr',12,1,nil],
 122          ['punch','brawling','thrust',-1,'cr',0,1,nil], ['kick','brawling','thrust',0,'cr',0,1,nil],
 123          ['brass-knuckles','brawling','thrust',0,'cr',0,1,nil],
 124          ['blackjack','brawling','thrust',0,'cr',7,1,nil],
 125          ['broadsword','broadsword','swing',1,'cut',10,1,nil], ['broadsword','broadsword','thrust',1,'cr',10,1,nil],
 126          ['thrusting-broadsword','broadsword','swing',1,'cut',10,1,nil], ['thrusting-broadsword','broadsword','thrust',2,'imp',10,1,nil],
 127          ['large-knife','knife','swing',-2,'cut',6,1,nil], ['large-knife','knife','thrust',0,'imp',6,1,nil],
 128          ['poleaxe','polearm','swing',4,'cut',12,2,true], ['poleaxe','polearm','swing',4,'cr',12,2,true],
 129          ['rapier','rapier','thrust',1,'imp',9,1,nil],
 130          ['shortsword','shortsword','swing',0,'cut',8,1,nil], ['shortsword','shortsword','thrust',0,'imp',8,1,nil],
 131          ['spear','spear','thrust',2,'imp',9,1,nil], ['spear','spear','thrust',3,'imp',9,2,nil],
 132          ['quarterstaff','staff','swing',2,'cr',7,2,nil], ['quarterstaff','staff','thrust',2,'cr',7,2,nil],
 133          ['quarterstaff','two-handed-sword','swing',2,'cr',9,2,nil], ['quarterstaff','two-handed-sword','thrust',1,'cr',9,2,nil],
 134          ['thrusting-greatsword','two-handed-sword','swing',3,'cut',12,2,nil], ['thrusting-greatsword','two-handed-sword','thrust',3,'imp',12,2,nil]
 135      ]
 136      ranged = [
 137          ['longbow','bow',nil,'thrust',0,2,'imp',3,150,200,10,11,1,1,2,nil,nil],
 138          ['short-bow','bow',nil,'thrust',0,0,'imp',1,100,150,10,7,1,1,2,nil,nil],
 139          ['crossbow','crossbow',nil,'thrust',0,4,'imp',4,200,250,10,7,1,1,4,nil,nil],
 140          ['throwing-axe','thrown-weapon','axe-mace','swing',0,2,'cut',2,10,15,10,11,1,1,1,nil,true],
 141          ['large-knife','thrown-weapon','knife','thrust',0,0,'imp',0,8,15,10,6,1,1,1,nil,true],
 142          ['spear','thrown-weapon','spear','thrust',0,3,'imp',2,10,15,10,9,1,1,1,nil,true],
 143          ['derringer','guns','pistol','fixed',1,0,'pi+',1,80,650,1,9,1,2,3,true,nil],
 144          ['auto-pistol-9mm','guns','pistol','fixed',2,2,'pi',2,150,1850,1,9,3,9,3,nil,nil],
 145          ['revolver-357','guns','pistol','fixed',3,-1,'pi',2,185,2000,1,10,3,6,3,true,nil],
 146          ['auto-pistol-44m','guns','pistol','fixed',3,0,'pi+',2,230,2500,1,12,3,10,3,nil,nil],
 147          ['lever-carbine','guns','rifle','fixed',5,0,'pi',4,450,3000,1,10,1,7,3,true,nil],
 148          ['self-loading-rifle','guns','rifle','fixed',7,0,'pi',5,1000,4200,1,10,3,8,3,nil,nil],
 149          ['sniper-rifle','guns','rifle','fixed',9,1,'pi',6,1500,5500,1,11,1,5,3,nil,nil],
 150          ['pump-shotgun','guns','shotgun','fixed',1,1,'pi-',3,50,125,1,10,2,5,3,true,nil]
 151      ]
 152  ;
 153  
 154  /** Create an unloaded weapon with independent modes and selected muscle-powered weapon ST. */
 155  rpgLiteMakeWeapon(id, weaponST)
 156  {
 157      local weapon = nil;
 158      foreach (local row in rpgLiteArmory.weapons)
 159          if (row[1] == id)
 160          {
 161              weapon = new RpgLiteWeapon(); weapon.id = id; weapon.techLevel = row[2];
 162              weapon.cost = row[3]; weapon.weight = row[4];
 163          }
 164      if (weapon == nil) throw new RpgError();
 165      rpgInteger(weaponST, 1, 20); weapon.weaponST = weaponST;
 166      foreach (local row in rpgLiteArmory.melee)
 167          if (row[1] == id)
 168          {
 169              local mode = new RpgLiteAttackMode(); mode.skillId = row[2]; mode.base = row[3];
 170              mode.adds = row[4]; mode.damageType = row[5]; mode.minimumST = row[6]; mode.hands = row[7];
 171              mode.unready = row[8]; weapon.modes = weapon.modes + [mode];
 172          }
 173      foreach (local row in rpgLiteArmory.ranged)
 174          if (row[1] == id)
 175          {
 176              local mode = new RpgLiteAttackMode(); mode.skillId = row[2]; mode.specialty = row[3];
 177              mode.base = row[4]; mode.dice = row[5]; mode.adds = row[6]; mode.damageType = row[7];
 178              mode.acc = row[8]; mode.halfRange = row[9]; mode.maximumRange = row[10]; mode.rangeScale = row[11];
 179              mode.minimumST = row[12]; mode.rof = row[13]; mode.capacity = row[14];
 180              mode.reloadSeconds = row[15]; mode.individualReload = row[16]; mode.thrown = row[17]; mode.ranged = true;
 181              if (!mode.thrown && mode.specialty != 'pistol') mode.hands = 2;
 182              mode.requiresTwoHands = holds(['longbow','short-bow','pump-shotgun'], id);
 183              if (id == 'pump-shotgun') mode.pellets = 9;
 184              weapon.modes = weapon.modes + [mode];
 185          }
 186      if (id == 'sniper-rifle') { weapon.scopeBonus = 3; weapon.bipod = true; }
 187      return weapon;
 188  }
 189  
 190  /** Query reload eligibility and duration without changing the weapon. */
 191  rpgLiteReloadSeconds(profile, weapon, mode)
 192  {
 193      if (!holds(profile.equipment, weapon) || !mode.ranged || mode.thrown || weapon.reserves < 1) return nil;
 194      local seconds = mode.reloadSeconds;
 195      if (weapon.id == 'crossbow')
 196      {
 197          local difference = weapon.weaponST - profile.st;
 198          if (difference >= 5 || difference >= 3 && (!weapon.goatFoot || profile.posture != 'standing')) return nil;
 199          seconds = difference <= 0 ? 4 : difference <= 2 ? 8 : 20;
 200      }
 201      if (weapon.ammunition >= mode.capacity) return nil;
 202      return seconds;
 203  }
 204  
 205  /** Advance one second of a legal reload; consume reserves only when loading completes. */
 206  rpgLiteReload(profile, weapon, mode)
 207  {
 208      local seconds = rpgLiteReloadSeconds(profile, weapon, mode);
 209      if (seconds == nil) return nil;
 210      weapon.ready = nil; weapon.reloadProgress += 1;
 211      if (weapon.reloadProgress >= seconds)
 212      {
 213          local amount = mode.individualReload ? 1 : mode.capacity - weapon.ammunition;
 214          if (amount > weapon.reserves) amount = weapon.reserves;
 215          weapon.ammunition += amount; weapon.reserves -= amount; weapon.reloadProgress = 0; weapon.ready = true;
 216      }
 217      return true;
 218  }
 219  
 220  /** Buy an affordable, available item once and transfer it to the character inventory. */
 221  rpgLiteBuy(profile, item)
 222  {
 223      if (holds(profile.equipment, item) || profile.money < item.cost
 224          || item.ofKind(RpgLiteWeapon) && item.techLevel > profile.techLevel) return nil;
 225      profile.money -= item.cost; profile.equipment = profile.equipment + [item]; return true;
 226  }
 227  
 228  /** Sum DR of the character's worn equipment. */
 229  rpgLiteArmor(profile)
 230  {
 231      local total = 0;
 232      foreach (local item in profile.equipment) if (item.worn) total += item.dr;
 233      return total;
 234  }