Implement Elf Ranged Combat and Pinned Mechanic
- Added 'Shoot Bow' action for Elf with Ballistic Skill mechanics (1995 rules). - Implemented strict Line of Sight (LOS) raycasting (Amanatides & Woo) with UI feedback for blockers. - Added 'Pinned' status: Heroes adjacent to monsters (without intervening walls) cannot move. - Enhanced UI with visual indicators for blocked shots (red circles) and temporary modals. - Polished 'End Phase' button layout and hidden it during Monster phase.
This commit is contained in:
@@ -100,6 +100,73 @@ export class CombatMechanics {
|
||||
return log;
|
||||
}
|
||||
|
||||
static resolveRangedAttack(attacker, defender, gameEngine = null) {
|
||||
const log = {
|
||||
attackerId: attacker.id,
|
||||
defenderId: defender.id,
|
||||
hitSuccess: false,
|
||||
damageTotal: 0,
|
||||
woundsCaused: 0,
|
||||
defenderDied: false,
|
||||
message: ''
|
||||
};
|
||||
|
||||
// 1. Roll To Hit (BS vs WS)
|
||||
// Use attacker BS or default to WS if missing (fallback).
|
||||
const attackerBS = attacker.stats.bs || attacker.stats.ws;
|
||||
const defenderWS = defender.stats.ws;
|
||||
|
||||
const toHitTarget = this.getToHitTarget(attackerBS, defenderWS);
|
||||
const hitRoll = this.rollD6();
|
||||
log.hitRoll = hitRoll;
|
||||
log.toHitTarget = toHitTarget;
|
||||
|
||||
if (hitRoll === 1) {
|
||||
log.hitSuccess = false;
|
||||
log.message = `${attacker.name} dispara y falla (1 es fallo automático)`;
|
||||
return log;
|
||||
}
|
||||
|
||||
if (hitRoll < toHitTarget) {
|
||||
log.hitSuccess = false;
|
||||
log.message = `${attacker.name} dispara y falla (${hitRoll} < ${toHitTarget})`;
|
||||
return log;
|
||||
}
|
||||
|
||||
log.hitSuccess = true;
|
||||
|
||||
// 2. Roll Damage
|
||||
// Elf Bow Strength = 3
|
||||
const weaponStrength = 3;
|
||||
const damageRoll = this.rollD6();
|
||||
const damageTotal = weaponStrength + damageRoll;
|
||||
log.damageRoll = damageRoll;
|
||||
log.damageTotal = damageTotal;
|
||||
|
||||
// 3. Compare vs Toughness
|
||||
const defTough = defender.stats.toughness || 1;
|
||||
const wounds = Math.max(0, damageTotal - defTough);
|
||||
|
||||
log.woundsCaused = wounds;
|
||||
|
||||
// 4. Build Message
|
||||
if (wounds > 0) {
|
||||
log.message = `${attacker.name} acierta con el arco y causa ${wounds} heridas! (Daño ${log.damageTotal} - Res ${defTough})`;
|
||||
} else {
|
||||
log.message = `${attacker.name} acierta pero la flecha rebota. (Daño ${log.damageTotal} vs Res ${defTough})`;
|
||||
}
|
||||
|
||||
// 5. Apply Damage
|
||||
this.applyDamage(defender, wounds, gameEngine);
|
||||
|
||||
if (defender.isDead) {
|
||||
log.defenderDied = true;
|
||||
log.message += ` ¡${defender.name} ha muerto!`;
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
static getToHitTarget(attackerWS, defenderWS) {
|
||||
// Adjust for 0-index array
|
||||
const row = attackerWS - 1;
|
||||
|
||||
Reference in New Issue
Block a user