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 }