Skip to content

Commit

Permalink
Implement melee weapons
Browse files Browse the repository at this point in the history
Add all melee weapons and handle dice expressions for damage. Generate
enchanted and cursed weapons. Add string representation for weapons.
Handle wielded and cursed weapons in actions.
  • Loading branch information
leomartius committed Sep 26, 2024
1 parent d3fdb80 commit fd0f240
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 14 deletions.
3 changes: 3 additions & 0 deletions game/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ def __init__(self, item: Item):

def perform(self, actor: Actor, level: Level, log: MessageLog) -> bool:
assert isinstance(actor, Player)
if self.item == actor.inventory.weapon_slot:
log.append("That's already in use.")
return False
actor.inventory.weapon_slot = self.item
log.append(f"You are now wielding {self.item}.")
return True
Expand Down
23 changes: 15 additions & 8 deletions game/combat.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from __future__ import annotations

from dataclasses import dataclass, field
from dataclasses import InitVar, dataclass, field

from game.dice import roll
from game.dice import parse_dice, roll
from game.entity import Actor, Player
from game.level import Level
from game.messages import MessageLog
Expand Down Expand Up @@ -41,15 +41,21 @@ def ac(self) -> int:
return self.base_ac - self.plus_ac


@dataclass(frozen=True, slots=True)
@dataclass(eq=False, slots=True, kw_only=True)
class Weapon:
# weapon damage die
damage: int
# weapon damage dice
base_dmg: tuple[int, int] = field(init=False)
# to hit bonus
plus_hit: int = 0
# damage bonus
plus_dmg: int = 0

# damage dice expression
dmg_dice: InitVar[str]

def __post_init__(self, dmg_dice: str) -> None:
self.base_dmg = parse_dice(dmg_dice)


def melee_attack(attacker: Actor, defender: Actor, level: Level, log: MessageLog) -> None:
assert isinstance(attacker, Player) != isinstance(defender, Player)
Expand All @@ -63,10 +69,11 @@ def melee_attack(attacker: Actor, defender: Actor, level: Level, log: MessageLog
thac0 = 21 - attacker.stats.hd
hit = roll(1, d=20) + to_hit_bonus >= thac0 - armor_class
if hit:
damage_die = attacker.stats.dmg
damage_dice = 1, attacker.stats.dmg
if isinstance(attacker, Player) and attacker.inventory.weapon_slot:
damage_die = attacker.inventory.weapon_slot.weapon.damage
damage = roll(1, d=damage_die) + damage_bonus
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)
log.append(f"You hit the {defender.name}." if isinstance(attacker, Player)
else f"The {attacker.name} hits you.")
Expand Down
7 changes: 7 additions & 0 deletions game/dice.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import random
import re


def roll(n: int, d: int) -> int:
Expand All @@ -13,3 +14,9 @@ def roll(n: int, d: int) -> int:

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)
6 changes: 6 additions & 0 deletions game/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ def __str__(self) -> str:
class WeaponItem(Item):
weapon: Weapon

def __str__(self) -> str:
if self.identified:
return f"a {self.weapon.plus_hit:+d},{self.weapon.plus_dmg:+d} {self.name}"
else:
return f"{article(self.name)} {self.name}"


def article(noun: str) -> str:
return "an" if noun[0] in 'aeiou' else "a"
10 changes: 8 additions & 2 deletions game/game_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ def new_game() -> tuple[Player, Level, MessageLog]:
)
inventory.add_item(armor)
inventory.armor_slot = armor
weapon = WeaponItem(x=0, y=0, glyph=Glyph.WEAPON, name='+1,+1 mace',
weapon=Weapon(plus_hit=1, plus_dmg=1, damage=9))
weapon = WeaponItem(
x=0,
y=0,
glyph=Glyph.WEAPON,
name="mace",
weapon=Weapon(plus_hit=+1, plus_dmg=+1, dmg_dice='2d4'),
identified=True,
)
inventory.add_item(weapon)
inventory.weapon_slot = weapon
player = Player(
Expand Down
9 changes: 6 additions & 3 deletions game/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ def spawn(self, x: int, y: int) -> Item:
]

weapon_types = [
ItemType(1, Glyph.WEAPON, 'dagger', Weapon(damage=6)),
ItemType(1, Glyph.WEAPON, 'long sword', Weapon(damage=10)),
ItemType(1, Glyph.WEAPON, "mace", Weapon(dmg_dice='2d4')),
ItemType(1, Glyph.WEAPON, "longsword", Weapon(dmg_dice='3d4')),
ItemType(1, Glyph.WEAPON, "dagger", Weapon(dmg_dice='1d6')),
ItemType(1, Glyph.WEAPON, "two-handed sword", Weapon(dmg_dice='4d4')),
ItemType(1, Glyph.WEAPON, "spear", Weapon(dmg_dice='2d3')),
]


Expand All @@ -68,7 +71,7 @@ def get_item_types(self) -> tuple[list[ItemType], list[int]]:
ItemCategory(27, potion_types),
ItemCategory(27, scroll_types),
ItemCategory(8, armor_types),
ItemCategory(9, weapon_types),
ItemCategory(8, weapon_types),
]


Expand Down
9 changes: 8 additions & 1 deletion game/procgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import random

from game.constants import Glyph, Tile
from game.entity import ArmorItem, Item
from game.entity import ArmorItem, Item, WeaponItem
from game.items import get_item_categories
from game.level import Level
from game.monsters import eligible_monsters
Expand Down Expand Up @@ -266,6 +266,13 @@ def place_item(all_rooms: list[Room | Junction], level: Level) -> None:
item.armor.plus_ac = -rng.randint(1, 3)
elif r < 0.28:
item.armor.plus_ac = +rng.randint(1, 3)
elif isinstance(item, WeaponItem):
r = rng.random()
if r < 0.10:
item.cursed = True
item.weapon.plus_hit = -rng.randint(1, 3)
elif r < 0.15:
item.weapon.plus_hit = +rng.randint(1, 3)
level.entities.add(item)


Expand Down
3 changes: 3 additions & 0 deletions game/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def event(self, event: tcod.event.Event, player: Player, level: Level, log: Mess
case Command.READ:
return UseItem(glyph=Glyph.SCROLL)
case Command.WIELD:
if player.inventory.weapon_slot and player.inventory.weapon_slot.cursed:
log.append("You can't. It appears to be cursed.")
return Play()
return UseItem(glyph=Glyph.WEAPON)
case Command.WEAR:
if player.inventory.armor_slot:
Expand Down

0 comments on commit fd0f240

Please sign in to comment.