From d6ecec8348ce8ddfb0cc94b03b01770d03c9bb85 Mon Sep 17 00:00:00 2001 From: Leo Martin Date: Tue, 22 Oct 2024 14:36:48 +0000 Subject: [PATCH] Add multiattack feature to monsters --- game/combat.py | 26 +++++++++++++++----------- game/dice.py | 11 +++++++---- game/monsters.py | 18 +++++++++--------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/game/combat.py b/game/combat.py index cf93a18..2eb63c2 100644 --- a/game/combat.py +++ b/game/combat.py @@ -20,7 +20,7 @@ class Stats: # hit dice (character level) hd: int # damage dice (unarmed strike) - base_dmg: tuple[int, int] = field(init=False) + base_dmg: list[tuple[int, int]] = field(init=False) # xp value (current experience) xp: int # strength @@ -49,7 +49,7 @@ def ac(self) -> int: @dataclass(eq=False, slots=True, kw_only=True) class Weapon: # weapon damage dice - base_dmg: tuple[int, int] = field(init=False) + base_dmg: list[tuple[int, int]] = field(init=False) # to hit bonus plus_hit: int = 0 # damage bonus @@ -64,8 +64,10 @@ def __post_init__(self, dmg_dice: str) -> None: def melee_attack(attacker: Actor, defender: Actor, level: Level, log: MessageLog) -> None: assert isinstance(attacker, Player) != isinstance(defender, Player) + damage_dice = attacker.stats.base_dmg to_hit_bonus, damage_bonus = strength_bonuses(attacker) if isinstance(attacker, Player) and attacker.inventory.weapon_slot: + damage_dice = attacker.inventory.weapon_slot.weapon.base_dmg to_hit_bonus += attacker.inventory.weapon_slot.weapon.plus_hit damage_bonus += attacker.inventory.weapon_slot.weapon.plus_dmg if defender.ai and defender.ai.is_helpless(): @@ -75,15 +77,17 @@ def melee_attack(attacker: Actor, defender: Actor, level: Level, log: MessageLog armor_class = defender.inventory.armor_slot.armor.ac if defender.ai: defender.ai.on_attacked(defender) - thac0 = 21 - attacker.stats.hd - hit = roll(1, d=20) + to_hit_bonus >= thac0 - armor_class - if hit: - damage_dice = attacker.stats.base_dmg - if isinstance(attacker, Player) and attacker.inventory.weapon_slot: - damage_dice = attacker.inventory.weapon_slot.weapon.base_dmg - damage = roll(*damage_dice) + damage_bonus - damage = max(0, damage) - defender.stats.hp = max(0, defender.stats.hp - damage) + action_hit = False + action_dmg = 0 + for n, d in damage_dice: + thac0 = 21 - attacker.stats.hd + attack_hit = roll(1, d=20) + to_hit_bonus >= thac0 - armor_class + if attack_hit: + attack_dmg = roll(n, d) + damage_bonus + action_dmg += max(0, attack_dmg) + action_hit = True + if action_hit: + defender.stats.hp = max(0, defender.stats.hp - action_dmg) log.append(hit_message(attacker, defender)) if defender.stats.hp == 0: level.entities.remove(defender) diff --git a/game/dice.py b/game/dice.py index 609bd7a..9077c65 100644 --- a/game/dice.py +++ b/game/dice.py @@ -16,7 +16,10 @@ def percent(p: int) -> bool: return random.randrange(100) < p -def parse_dice(dice: str) -> tuple[int, int]: - assert re.match(r"^\d+d\d+$", dice) - n, d = dice.split('d') - return int(n), int(d) +def parse_dice(expression: str) -> list[tuple[int, int]]: + assert re.match(r"^\d+d\d+(/\d+d\d+)*$", expression) + result = [] + for dice in expression.split('/'): + n, d = dice.split('d') + result.append((int(n), int(d))) + return result diff --git a/game/monsters.py b/game/monsters.py index 7c13e07..1cbb310 100644 --- a/game/monsters.py +++ b/game/monsters.py @@ -56,21 +56,21 @@ def spawn(self, x: int, y: int, extra_hd: int) -> Actor: MonsterType('Z', "zombie", hd=2, ac=8, dmg_dice='1d8', xp_value=6), MonsterType('G', "gnome", hd=1, ac=5, dmg_dice='1d6', xp_value=7, ai=IdleAI), MonsterType('L', "leprechaun", hd=3, ac=8, dmg_dice='1d1', xp_value=10, ai=IdleAI, generate=False), - MonsterType('C', "centaur", hd=4, ac=4, dmg_dice='2d6', xp_value=15, ai=IdleAI), - MonsterType('R', "rust monster", hd=5, ac=2, dmg_dice='0d0', xp_value=20, generate=False), - MonsterType('Q', "quasit", hd=3, ac=2, dmg_dice='1d10', xp_value=32), + MonsterType('C', "centaur", hd=4, ac=4, dmg_dice='1d6/1d6', xp_value=15, ai=IdleAI), + MonsterType('R', "rust monster", hd=5, ac=2, dmg_dice='0d0/0d0', xp_value=20, generate=False), + MonsterType('Q', "quasit", hd=3, ac=2, dmg_dice='1d2/1d2/1d4', xp_value=32), MonsterType('N', "nymph", hd=3, ac=9, dmg_dice='0d0', xp_value=37, ai=IdleAI, generate=False), - MonsterType('Y', "yeti", hd=4, ac=6, dmg_dice='2d6', xp_value=50, ai=IdleAI), - MonsterType('T', "troll", hd=6, ac=4, dmg_dice='4d7', xp_value=120), + MonsterType('Y', "yeti", hd=4, ac=6, dmg_dice='1d6/1d6', xp_value=50, ai=IdleAI), + MonsterType('T', "troll", hd=6, ac=4, dmg_dice='1d8/1d8/2d6', xp_value=120), MonsterType('W', "wraith", hd=5, ac=4, dmg_dice='1d6', xp_value=55, ai=IdleAI), MonsterType('F', "violet fungi", hd=8, ac=3, dmg_dice='0d0', xp_value=80, generate=False), MonsterType('I', "invisible stalker", hd=8, ac=3, dmg_dice='4d4', xp_value=120, ai=IdleAI, generate=False), - MonsterType('X', "xorn", hd=7, ac=-2, dmg_dice='5d7', xp_value=190), - MonsterType('U', "umber hulk", hd=8, ac=2, dmg_dice='7d5', xp_value=200), + MonsterType('X', "xorn", hd=7, ac=-2, dmg_dice='1d3/1d3/1d3/4d6', xp_value=190), + MonsterType('U', "umber hulk", hd=8, ac=2, dmg_dice='3d4/3d4/2d5', xp_value=200), MonsterType('M', "mimic", hd=7, ac=7, dmg_dice='3d4', xp_value=100, ai=IdleAI, generate=False), MonsterType('V', "vampire", hd=8, ac=1, dmg_dice='1d10', xp_value=350), - MonsterType('P', "purple worm", hd=15, ac=6, dmg_dice='4d8', xp_value=4000, ai=IdleAI), - MonsterType('D', "dragon", hd=10, ac=-1, dmg_dice='3d16', xp_value=6800), + MonsterType('P', "purple worm", hd=15, ac=6, dmg_dice='2d12/2d4', xp_value=4000, ai=IdleAI), + MonsterType('D', "dragon", hd=10, ac=-1, dmg_dice='1d8/1d8/3d10', xp_value=6800), ]