lib/vhyl-rpg-lite-combat.t¶
Reference index ยท View on GitHub
1 /** Persistent encounter participant, ready weapons, shield, defenses and aim/concentration. */ 2 class RpgLiteFighter: object 3 profile = nil 4 weapon = nil 5 modeIndex = 1 6 offWeapon = nil 7 offModeIndex = 1 8 shield = nil 9 shieldSkill = nil 10 unarmedSkill = nil 11 maneuver = 'nothing' 12 defenseOption = 'dodge' 13 blocks = 0 14 parries = 0 15 offParries = 0 16 attacked = nil 17 participated = nil 18 aimSeconds = 0 19 aimTarget = nil 20 aimWeapon = nil 21 concentration = 0 22 initiativeTie = 0 23 freeHands = 2 24 mode() { return self.weapon.modes[self.modeIndex]; } 25 ; 26 27 /** Authored tactical facts, availability and consequence hooks for one combat turn. */ 28 class RpgLiteManeuver: object 29 kind = 'nothing' 30 option = nil 31 target = nil 32 distance = 1 33 speed = 0 34 sizeModifier = 0 35 rangeModifier = nil 36 movement = 0 37 posture = nil 38 offHand = nil 39 hands = 2 40 braced = nil 41 boots = nil 42 aware = true 43 fromBehind = nil 44 shieldSide = true 45 visible = true 46 immobilized = nil 47 inReach = true 48 targetThreatens = true 49 humanTarget = true 50 deadly = nil 51 faceVisible = true 52 preflight = nil 53 attackModifier = 0 54 defenseModifier = 0 55 darkness = 0 56 someLight = true 57 defense = 'dodge' 58 secondDefense = 'parry' 59 available(fighter) { return nil; } 60 apply(fighter, result) { return nil; } 61 reroll(profile, kind, roll) { return nil; } 62 ; 63 64 /** Attack, defense, damage and injury receipt; blocked results include a reason. */ 65 class RpgLiteCombatResult: object 66 status = 'blocked' 67 reason = nil 68 attack = nil 69 defenses = [] 70 damage = 0 71 injury = 0 72 strikes = [] 73 outcome = nil 74 counterInjury = 0 75 ; 76 77 /** Look up the printed Lite range table through 100 yards; no missing Basic Set rows are inferred. */ 78 rpgLiteRangeModifier(yards) 79 { 80 rpgInteger(yards, 0, 100); 81 local table = [[2,0],[3,-1],[5,-2],[10,-4],[15,-5],[20,-6],[30,-7],[50,-8],[70,-9],[100,-10]]; 82 foreach (local row in table) if (yards <= row[1]) return row[2]; 83 throw new RpgError(); 84 } 85 86 /** Return the melee attack or defense adjustment for a named posture. */ 87 rpgLitePostureModifier(posture, defense) 88 { 89 if (posture == 'standing') return 0; 90 if (posture == 'crouching') return defense ? 0 : -2; 91 if (posture == 'sitting' || posture == 'kneeling') return -2; 92 if (posture == 'lying' || posture == 'crawling') return defense ? -3 : -4; 93 throw new RpgError(); 94 } 95 96 /** Roll an active defense, preserving automatic success on 3 or 4 even below score 3. */ 97 rpgLiteDefenseCheck(score) 98 { 99 rpgInteger(score, -1000, 1000); 100 local roll = rpgRandom.roll(); 101 local value = rpgCheck(score < 3 ? 3 : score, roll); 102 value.score = score; value.margin = score - roll; 103 value.achieved = roll <= 4 || roll < 17 && roll <= score; 104 value.outcome = value.achieved ? 'success' : 'setback'; return value; 105 } 106 107 /** Find the skill identity used by the fighter's selected attack mode. */ 108 rpgLiteCombatSkill(fighter, mode) 109 { 110 local profile = fighter.profile; 111 local techLevel = mode.skillId == 'guns' || mode.skillId == 'gunner' ? fighter.weapon.techLevel : nil; 112 return rpgLiteTraining(profile, mode.skillId, techLevel, mode.specialty); 113 } 114 115 /** Return the highest applicable DX, Brawling or Karate rating. */ 116 rpgLiteUnarmedScore(fighter) 117 { 118 local score = fighter.profile.dx; 119 foreach (local id in ['brawling', 'karate']) 120 { 121 local skill = rpgLiteTraining(fighter.profile, id, nil, nil); 122 local value = fighter.profile.skillScore(skill); 123 if (value != nil && value > score) score = value; 124 } 125 return score; 126 } 127 128 /** Query defense eligibility and score without rolling or consuming a defense. */ 129 rpgLiteDefenseScore(fighter, attacker, request, kind) 130 { 131 local profile = fighter.profile; local incoming = attacker.mode(); 132 if (!request.aware || request.fromBehind || request.immobilized || profile.dead || profile.unconscious 133 || fighter.maneuver == 'all-out-attack') return nil; 134 local score = 0; 135 if (kind == 'dodge') 136 { 137 score = profile.basicSpeedQuarters() / 4 + 3 - profile.encumbrance(); 138 if (score < 1) score = 1; 139 score += profile.level('enhanced-dodge'); 140 if (profile.hp() * 3 < profile.maxHP) score = (score + 1) / 2; 141 if (profile.fp() * 3 < profile.maxEnergy) score = (score + 1) / 2; 142 } 143 else if (kind == 'block') 144 { 145 if (fighter.blocks > 0 || fighter.shield == nil || !fighter.shield.ready 146 || incoming.ranged && incoming.base == 'fixed') return nil; 147 local skill = fighter.shieldSkill == nil ? rpgLiteTraining(profile, 'shield', nil, nil) : fighter.shieldSkill; 148 local rating = profile.skillScore(skill); 149 if (rating == nil) return nil; 150 score = 3 + rating / 2 + profile.level('enhanced-block'); 151 if (incoming.skillId == 'flail') score -= 2; 152 } 153 else if (kind == 'parry' || kind == 'parry-other') 154 { 155 local otherHand = kind == 'parry-other'; 156 local parryWeapon = otherHand ? fighter.offWeapon : fighter.weapon; 157 local parryMode = parryWeapon == nil ? nil : parryWeapon.modes[otherHand ? fighter.offModeIndex : fighter.modeIndex]; 158 if ((otherHand ? fighter.offParries : fighter.parries) > 0 || fighter.maneuver == 'move-attack' 159 || incoming.ranged && !incoming.thrown && !request.inReach) return nil; 160 local unarmed = parryWeapon == nil || parryWeapon.id == 'punch' || parryWeapon.id == 'kick'; 161 if (unarmed) 162 { 163 if (fighter.freeHands < (otherHand && fighter.weapon == nil ? 2 : 1)) return nil; 164 score = 3 + rpgLiteUnarmedScore(fighter) / 2; 165 local karate = profile.skillScore(rpgLiteTraining(profile, 'karate', nil, nil)); 166 if (attacker.weapon.id != 'punch' && attacker.weapon.id != 'kick' 167 && incoming.base != 'thrust' && karate == nil) score -= 3; 168 if (karate != nil && karate >= rpgLiteUnarmedScore(fighter)) score -= profile.encumbrance(); 169 } 170 else 171 { 172 if (!parryWeapon.ready || parryMode.ranged 173 || parryMode.skillId == 'axe-mace' && fighter.attacked) return nil; 174 local rating = profile.skillScore(rpgLiteTraining(profile, parryMode.skillId, nil, parryMode.specialty)); 175 if (rating == nil) return nil; 176 if (otherHand && profile.level('ambidexterity') == 0) rating -= 4; 177 score = 3 + rating / 2; 178 if (parryWeapon.id == 'quarterstaff') score += 2; 179 if (parryMode.skillId == 'knife') score -= 1; 180 } 181 score += profile.level('enhanced-parry-all'); 182 local enhanced = profile.trait('enhanced-parry'); 183 if (enhanced != nil && enhanced.scope == (unarmed ? 'unarmed' : parryMode.skillId)) score += 1; 184 if (incoming.thrown) score -= attacker.weapon.weight <= 100 ? 2 : 1; 185 if (incoming.skillId == 'flail') score -= 4; 186 } 187 else throw new RpgError(); 188 score += profile.level('combat-reflexes') + request.defenseModifier; 189 if (fighter.shield != nil && fighter.shield.ready && request.shieldSide) score += fighter.shield.db; 190 score += rpgLitePostureModifier(profile.posture, true); 191 if (profile.stunned || profile.mentalStun) score -= 4; 192 if (fighter.maneuver == 'all-out-defense' && (fighter.defenseOption == kind 193 || kind == 'parry-other' && fighter.defenseOption == 'parry')) score += 2; 194 if (!request.visible) score -= 4; 195 return score; 196 } 197 198 /** Lose aim and check whether ongoing concentration survives distraction. */ 199 rpgLiteDistract(fighter) 200 { 201 fighter.aimSeconds = 0; 202 if (fighter.concentration > 0 && !rpgLiteAttributeCheck(fighter.profile, rpgWill, 'concentration', -3).achieved) 203 fighter.concentration = 0; 204 return nil; 205 } 206 207 /** Resolve one legal strike; normally invoked by the battle turn scheduler. */ 208 rpgLiteStrike(attacker, defender, request) 209 { 210 local result = new RpgLiteCombatResult(); 211 local profile = attacker.profile; local weapon = attacker.weapon; 212 if (weapon == nil || !weapon.ready) { result.reason = 'weapon-not-ready'; return result; } 213 local mode = attacker.mode(); 214 if (profile.stunned || profile.mentalStun || profile.unconscious || profile.dead) 215 { result.reason = 'unable-to-attack'; return result; } 216 if (!mode.ranged && !request.inReach) { result.reason = 'out-of-reach'; return result; } 217 if (request.deadly && !request.targetThreatens && profile.level('cannot-harm-innocents') > 0) 218 { result.reason = 'cannot-harm-innocents'; return result; } 219 if (mode.hands == 2 && request.hands < 2 && (mode.requiresTwoHands || profile.st * 2 < mode.minimumST * 3) 220 || request.hands == 2 && attacker.shield != nil && attacker.shield.ready) 221 { result.reason = 'hands-unavailable'; return result; } 222 local strength = weapon.id == 'crossbow' ? weapon.weaponST : profile.st; 223 local maximum = mode.rangeScale == 10 ? mode.maximumRange * strength / 10 : mode.maximumRange; 224 local half = mode.rangeScale == 10 ? mode.halfRange * strength / 10 : mode.halfRange; 225 if (mode.ranged && request.distance > maximum) { result.reason = 'out-of-range'; return result; } 226 if (mode.ranged && !mode.thrown && weapon.ammunition < 1) { result.reason = 'empty'; return result; } 227 local context = new RpgLiteContext(); context.kind = 'attack'; 228 context.skill = rpgLiteCombatSkill(attacker, mode); context.offHand = request.offHand; 229 context.darkness = request.darkness; context.someLight = request.someLight; 230 context.modifier = request.attackModifier; 231 local unarmed = holds(['punch','kick','brass-knuckles'], weapon.id); 232 if (unarmed || weapon.id == 'blackjack') 233 { 234 context.skill = nil; context.attribute = rpgDX; 235 local rating = weapon.id == 'blackjack' ? profile.skillScore(rpgLiteTraining(profile, 'brawling', nil, nil)) : rpgLiteUnarmedScore(attacker); 236 context.modifier += rating == nil || rating < profile.dx ? 0 : rating - profile.dx; 237 } 238 if (weapon.id == 'kick') context.modifier -= 2; 239 local effectiveST = profile.fp() * 3 < profile.maxEnergy ? (profile.st + 1) / 2 : profile.st; 240 local minimumST = weapon.bipod && profile.posture == 'lying' ? (mode.minimumST * 2 + 2) / 3 : mode.minimumST; 241 if (effectiveST < minimumST) context.modifier -= minimumST - effectiveST; 242 if (mode.ranged) 243 { 244 context.modifier += request.sizeModifier + (request.rangeModifier == nil 245 ? rpgLiteRangeModifier(request.distance + request.speed) : request.rangeModifier); 246 if (defender.profile.posture != 'standing') context.modifier -= 2; 247 if (attacker.aimSeconds > 0 && attacker.aimTarget == defender && attacker.aimWeapon == weapon 248 && request.kind != 'move-attack') 249 { 250 context.modifier += mode.acc + (attacker.aimSeconds >= 3 ? 2 : attacker.aimSeconds == 2 ? 1 : 0); 251 context.modifier += attacker.aimSeconds < weapon.scopeBonus ? attacker.aimSeconds : weapon.scopeBonus; 252 if ((request.braced || weapon.bipod && profile.posture == 'lying' || mode.hands == 1 && request.hands == 2) 253 && (mode.base == 'fixed' || weapon.id == 'crossbow')) context.modifier += 1; 254 } 255 } 256 else 257 { 258 context.modifier += rpgLitePostureModifier(profile.posture, nil); 259 if (attacker.shield != nil && attacker.shield.ready && attacker.shield.db == 3) context.modifier -= 2; 260 } 261 if (request.kind == 'all-out-attack' && (request.option == 'determined' || mode.ranged)) context.modifier += mode.ranged ? 1 : 4; 262 if (request.kind == 'move-attack') context.modifier -= 4; 263 if (profile.level('reluctant-killer') > 0 && request.deadly && request.humanTarget) 264 context.modifier -= request.faceVisible ? 4 : 2; 265 local score = rpgLiteScore(profile, context); 266 if (score == nil) { result.reason = 'training-required'; return result; } 267 if (request.kind == 'move-attack' && !mode.ranged && score > 9) score = 9; 268 if (score < 3) { result.reason = 'too-difficult'; return result; } 269 if (request.preflight) { result.status = 'ready'; return result; } 270 result.status = 'resolved'; result.attack = rpgCheck(score, rpgRandom.roll()); 271 if (request.reroll(profile, 'attack', result.attack.roll)) 272 result.attack = rpgCheck(score, rpgLiteReroll(profile, result.attack.roll, 3, nil)); 273 else if (request.reroll(defender.profile, 'enemy-attack', result.attack.roll)) 274 result.attack = rpgCheck(score, rpgLiteReroll(defender.profile, result.attack.roll, 3, true)); 275 attacker.attacked = true; attacker.participated = true; 276 if (mode.ranged && !mode.thrown) weapon.ammunition -= 1; 277 if (mode.thrown || mode.unready && profile.st * 2 < mode.minimumST * 3 278 || mode.hands == 2 && request.hands == 1 && profile.st < mode.minimumST * (mode.unready ? 3 : 2)) weapon.ready = nil; 279 attacker.aimSeconds = 0; 280 if (!result.attack.achieved) 281 { 282 result.outcome = 'miss'; 283 if (weapon.id == 'kick' && !rpgLiteAttributeCheck(profile, rpgDX, 'keep-feet', 0).achieved) profile.posture = 'lying'; 284 return result; 285 } 286 if (result.attack.critical != 'success') 287 { 288 local choices = [request.defense]; 289 if (defender.maneuver == 'all-out-defense' && defender.defenseOption == 'double' 290 && request.secondDefense != request.defense) choices = choices + [request.secondDefense]; 291 foreach (local kind in choices) 292 { 293 local defense = rpgLiteDefenseScore(defender, attacker, request, kind); 294 if (defense == nil) continue; 295 if (!request.visible && kind != 'dodge' 296 && !rpgLiteAttributeCheck(defender.profile, rpgPerception, 'hearing', -2).achieved) continue; 297 if (defender.maneuver == 'nothing' && defender.profile.hp() <= 0 && !rpgLiteCanAct(defender.profile, nil)) break; 298 if (kind == 'block') defender.blocks += 1; 299 if (kind == 'parry') defender.parries += 1; 300 if (kind == 'parry-other') defender.offParries += 1; 301 defender.participated = true; rpgLiteDistract(defender); 302 local check = rpgLiteDefenseCheck(defense); 303 if (request.reroll(defender.profile, 'defense', check.roll)) 304 { 305 local rolled = rpgLiteReroll(defender.profile, check.roll, 3, nil); 306 check = rpgCheck(defense < 3 ? 3 : defense, rolled); 307 check.score = defense; check.margin = defense - rolled; 308 check.achieved = rolled <= 4 || rolled < 17 && rolled <= defense; 309 } 310 result.defenses = result.defenses + [check]; 311 if (check.achieved) 312 { 313 result.outcome = 'defended'; 314 if (kind == 'parry' && unarmed && defender.weapon != nil 315 && !holds(['punch','kick','brass-knuckles'], defender.weapon.id)) 316 { 317 local counter = new RpgLiteContext(); counter.kind = 'attack'; 318 counter.skill = rpgLiteCombatSkill(defender, defender.mode()); 319 if (profile.skillScore(rpgLiteTraining(profile, 'karate', nil, nil)) != nil) counter.modifier = -4; 320 if (rpgLiteCheck(defender.profile, counter).achieved) 321 { 322 local retaliation = defender.mode(); 323 local damage = rpgLiteDamageDice(defender.profile.st, retaliation.base == 'swing'); 324 local rolled = rpgLiteRollDamage(damage[1], damage[2] + retaliation.adds, retaliation.damageType, nil); 325 result.counterInjury = rpgLiteWound(profile, rpgLitePenetration(rolled, rpgLiteArmor(profile), 1, retaliation.damageType, nil)).injury; 326 } 327 } 328 return result; 329 } 330 } 331 } 332 local dice = mode.dice; local adds = mode.adds; 333 if (mode.base != 'fixed') 334 { 335 if (mode.minimumST > 0 && !mode.ranged && strength > mode.minimumST * 3) strength = mode.minimumST * 3; 336 local damage = rpgLiteDamageDice(strength, mode.base == 'swing'); dice = damage[1]; adds += damage[2]; 337 if (unarmed) 338 { 339 local karate = profile.skillScore(rpgLiteTraining(profile, 'karate', nil, nil)); 340 if (karate != nil && karate >= profile.dx) adds += dice * (karate > profile.dx ? 2 : 1); 341 if (weapon.id == 'kick' && request.boots) adds += 1; 342 } 343 if (request.kind == 'all-out-attack' && request.option == 'strong' && !mode.ranged) adds += dice > 2 ? dice : 2; 344 } 345 local rawDamage = 0; 346 for (local index = 0; index < dice; index += 1) rawDamage += result.attack.roll == 3 ? 6 : rpgRandom.die(); 347 if (result.attack.roll != 3 && request.reroll(profile, 'damage', rawDamage)) 348 rawDamage = rpgLiteReroll(profile, rawDamage, dice, true); 349 result.damage = rawDamage + adds; 350 if (result.damage < (mode.damageType == 'cr' ? 0 : 1)) result.damage = mode.damageType == 'cr' ? 0 : 1; 351 result.injury = rpgLitePenetration(result.damage, rpgLiteArmor(defender.profile), mode.armorDivisor, 352 mode.damageType, mode.ranged && half > 0 && request.distance >= half); 353 rpgLiteWound(defender.profile, result.injury); 354 if (result.injury > 0) rpgLiteDistract(defender); 355 result.outcome = 'hit'; return result; 356 } 357 358 /** One-second turn scheduler with stable initiative, maneuvers and end-of-battle fatigue. */ 359 class RpgLiteBattle: object 360 fighters = [] 361 order = [] 362 index = 1 363 seconds = 0 364 active = nil 365 begin() 366 { 367 if (self.active || self.order.length() > 0 || self.fighters.length() < 2) throw new RpgError(); 368 foreach (local fighter in self.fighters) 369 { 370 if (holds(self.order, fighter)) throw new RpgError(); 371 fighter.initiativeTie = rpgRandom.roll(); 372 local updated = []; local inserted = nil; 373 foreach (local other in self.order) 374 { 375 local ownSpeed = fighter.profile.basicSpeedQuarters(); local otherSpeed = other.profile.basicSpeedQuarters(); 376 if (!inserted && (ownSpeed > otherSpeed || ownSpeed == otherSpeed && 377 (fighter.profile.dx > other.profile.dx || fighter.profile.dx == other.profile.dx 378 && fighter.initiativeTie > other.initiativeTie))) 379 { updated = updated + [fighter]; inserted = true; } 380 updated = updated + [other]; 381 } 382 if (!inserted) updated = updated + [fighter]; 383 self.order = updated; 384 } 385 self.active = true; return self.order[1]; 386 } 387 current() { return self.active ? self.order[self.index] : nil; } 388 step(fighter, request) 389 { 390 local result = new RpgLiteCombatResult(); 391 if (fighter != self.current()) { result.reason = 'not-your-turn'; return result; } 392 if (!holds(['nothing','move','posture','aim','attack','all-out-attack','move-attack', 393 'all-out-defense','concentrate','ready'], request.kind)) throw new RpgError(); 394 local limit = holds(['move','move-attack','all-out-defense'], request.kind) ? fighter.profile.move() 395 : request.kind == 'all-out-attack' ? fighter.profile.move() / 2 : 1; 396 if (request.movement < 0 || request.movement > limit 397 || request.kind == 'all-out-defense' && request.option != 'dodge' && request.movement > 1) 398 { result.reason = 'movement-limit'; return result; } 399 result.reason = request.available(fighter); if (result.reason != nil) return result; 400 if (request.target != nil && !holds(self.fighters, request.target)) throw new RpgError(); 401 if ((fighter.profile.stunned || fighter.profile.mentalStun) && request.kind != 'nothing') 402 { result.reason = 'stunned'; return result; } 403 if (request.kind == 'posture' && (request.posture == nil 404 || fighter.profile.posture == 'lying' && request.posture == 'standing')) 405 { result.reason = 'invalid-posture-change'; return result; } 406 if (request.kind == 'posture') rpgLitePostureModifier(request.posture, nil); 407 if (holds(['attack','all-out-attack','move-attack','aim'], request.kind) && request.target == nil) 408 throw new RpgError(); 409 if (request.kind == 'all-out-attack' && !holds(['determined','double','strong'], request.option) 410 || request.kind == 'all-out-defense' && !holds(['dodge','block','parry','double'], request.option)) 411 throw new RpgError(); 412 if (request.kind == 'aim' && (fighter.weapon == nil || !fighter.weapon.ready || !fighter.mode().ranged || !request.visible)) 413 { result.reason = 'cannot-aim'; return result; } 414 if (request.kind == 'ready' && fighter.weapon == nil) 415 { result.reason = 'no-weapon'; return result; } 416 if (request.kind == 'ready' && request.option == 'reload' 417 && rpgLiteReloadSeconds(fighter.profile, fighter.weapon, fighter.mode()) == nil) 418 { result.reason = 'cannot-reload'; return result; } 419 if (holds(['attack','all-out-attack','move-attack'], request.kind)) 420 { 421 request.preflight = true; result = rpgLiteStrike(fighter, request.target, request); request.preflight = nil; 422 if (result.status != 'ready') return result; 423 } 424 if (!rpgLiteCanAct(fighter.profile, request.kind == 'nothing')) 425 { result.status = 'resolved'; result.outcome = 'incapacitated'; self.advance(); return result; } 426 fighter.blocks = 0; fighter.parries = 0; fighter.offParries = 0; fighter.attacked = nil; 427 if (request.kind != 'concentrate') fighter.concentration = 0; 428 fighter.maneuver = request.kind; fighter.defenseOption = request.option; 429 if (holds(['attack','all-out-attack','move-attack'], request.kind)) 430 { 431 result = rpgLiteStrike(fighter, request.target, request); 432 if (result.status != 'resolved') return result; 433 if (request.kind == 'all-out-attack' && request.option == 'double' && !fighter.mode().ranged 434 && (fighter.weapon.ready || fighter.offWeapon != nil && fighter.offWeapon.ready) 435 && !fighter.profile.stunned && !fighter.profile.unconscious && !request.target.profile.dead) 436 { 437 local primary = fighter.weapon; local modeIndex = fighter.modeIndex; local offHand = request.offHand; 438 if (fighter.offWeapon != nil && fighter.offWeapon.ready) 439 { fighter.weapon = fighter.offWeapon; fighter.modeIndex = fighter.offModeIndex; request.offHand = true; } 440 result.strikes = result.strikes + [rpgLiteStrike(fighter, request.target, request)]; 441 fighter.weapon = primary; fighter.modeIndex = modeIndex; request.offHand = offHand; 442 } 443 } 444 else 445 { 446 result.status = 'resolved'; result.outcome = request.kind; 447 if (request.kind == 'aim') 448 { 449 if (fighter.aimTarget != request.target || fighter.aimWeapon != fighter.weapon) fighter.aimSeconds = 0; 450 fighter.aimTarget = request.target; fighter.aimWeapon = fighter.weapon; fighter.aimSeconds += 1; 451 } 452 else fighter.aimSeconds = 0; 453 if (request.kind == 'posture') fighter.profile.posture = request.posture; 454 if (request.kind == 'concentrate') fighter.concentration += 1; 455 if (request.kind == 'ready') 456 { 457 if (request.option == 'reload') rpgLiteReload(fighter.profile, fighter.weapon, fighter.mode()); 458 else fighter.weapon.ready = true; 459 } 460 if (request.kind == 'nothing' && fighter.profile.fearSeconds == 0 && (fighter.profile.stunned || fighter.profile.mentalStun)) 461 rpgLiteRecoverStun(fighter.profile); 462 } 463 fighter.profile.shock = fighter.profile.level('high-pain-threshold') > 0 ? 0 464 : result.counterInjury > 4 ? 4 : result.counterInjury; 465 request.apply(fighter, result); self.advance(); return result; 466 } 467 advance() 468 { 469 self.index += 1; 470 if (self.index > self.order.length()) 471 { 472 self.index = 1; self.seconds += 1; rpgSpendTime(1); 473 foreach (local fighter in self.order) rpgLiteHealthTime(fighter.profile, 1); 474 } 475 return nil; 476 } 477 finish() 478 { 479 if (!self.active) return nil; 480 self.active = nil; 481 if (self.seconds > 10) 482 foreach (local fighter in self.fighters) 483 if (fighter.participated) 484 { 485 local cost = 1 + fighter.profile.encumbrance(); 486 if (fighter.weapon != nil && fighter.mode().minimumST > fighter.profile.st) cost += 1; 487 rpgLiteFatigue(fighter.profile, cost, 'battle'); 488 } 489 return true; 490 } 491 ;