Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Armour #13

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions data/l10n/en/main.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ item-is-in-inventory = You already picked up this item.
pick-up-failed-message = You can't pick up this item.
go-failed-message = You can't go there.
equip-item-message = You equipped { INTER($item) }.
unequip-item-message = You took of { INTER($item)} and took it in your inventory.
cannot-equip = You can't equip { INTER($weapon) }.
attack-character-message = { INTER($source) } attacks { INTER($target) }{ EXISTS($weapon) ->
[true] { "" } with { $weapon }
Expand Down
6 changes: 4 additions & 2 deletions data/worlds/chaosdorf/areas/cave.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ name = "tv remote"
description = "the batteries seem to have died. Can you point it at the door?"

[[contents]]
kind = "item"
description = "an old-school fedora out of fine black fabric"
kind = "armour"
name = "black hat"
description = "an old-school fedora out of fine black fabric"
armour_type = "head"
defense = 5

[[contents]]
kind = "gateway"
Expand Down
6 changes: 4 additions & 2 deletions data/worlds/chaosdorf/areas/hackcenter.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ name = "printer"
description = "a big paper disgorging device"

[[contents]]
kind = "item"
kind = "armour"
name = "grey hat"
description = "a grey cap with some strange stickers on it"
description = "a grey cap with some strange stickers on it"
armour_type = "head"
defense = 5
4 changes: 3 additions & 1 deletion data/worlds/chaosdorf/areas/kitchen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ target = "hackcenter"
obvious = true

[[contents]]
kind = "item"
kind = "armour"
name = "white hat"
description = "a western like hat made out of white leather"
armour_type = "head"
defense = 5

[[contents]]
kind = "enemy"
Expand Down
5 changes: 5 additions & 0 deletions src/fantasy_forge/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ def from_dict(world: World, area_dict: dict) -> Area:
from fantasy_forge.weapon import Weapon

contents_list.append(Weapon(world, entity_dict))
case "armour":
from fantasy_forge.armour import Armour

contents_list.append(Armour(world, entity_dict))

case default:
contents_list.append(Entity(world, entity_dict))
contents = {entity.name: entity for entity in contents_list}
Expand Down
46 changes: 46 additions & 0 deletions src/fantasy_forge/armour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

from typing import Self

from fantasy_forge.item import Item
from fantasy_forge.world import World

# Armour types:
# 'head': for caps, hats, helmets
# 'torso': for shirts, hoodies
# 'legs': for short, trousers, cargos
# 'feet': for shoes
ARMOUR_TYPES = ("head", "torso", "legs", "feet")


class Armour(Item):
armour_type: str
defense: int

__important_attributes__ = ("name", "armour_type", "defense")

def __init__(self, world, config_dict):
a_type: str = config_dict.pop("armour_type")
assert a_type in ARMOUR_TYPES
self.armour_type = a_type
self.defense = config_dict.pop("defense")
super().__init__(world, config_dict)

def to_dict(self: Self) -> dict:
armour_dict: dict = super().to_dict()
armour_dict["armour_type"] = self.armour_type
armour_dict["defense"] = self.defense
return armour_dict

@staticmethod
def from_dict(world: World, armour_dict: dict) -> Armour:
armour_type: str = armour_dict.get("armour_type", "torso")
defense: int = armour_dict.get("defense", 0)
armour: Armour = Armour(
world,
armour_dict["name"],
armour_dict.get("description", ""),
armour_type,
defense,
)
return armour
1 change: 0 additions & 1 deletion src/fantasy_forge/enemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from fantasy_forge.character import Character
from fantasy_forge.item import Item
from fantasy_forge.weapon import Weapon
from fantasy_forge.world import World

BASE_DAMAGE = 1
Expand Down
89 changes: 74 additions & 15 deletions src/fantasy_forge/player.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Self

from fantasy_forge.area import Area
from fantasy_forge.armour import ARMOUR_TYPES, Armour
from fantasy_forge.character import Character
from fantasy_forge.enemy import BASE_DAMAGE
from fantasy_forge.entity import Entity
Expand Down Expand Up @@ -29,6 +30,7 @@ class Player(Character):

area: Area # the area we are currently in
seen_entities: dict[str, Entity]
armour_slots: dict[str, Armour]

def __init__(self: Self, world: World, name: str, health: int = BASE_PLAYER_HEALTH):
super().__init__(
Expand All @@ -46,6 +48,20 @@ def __init__(self: Self, world: World, name: str, health: int = BASE_PLAYER_HEAL
self.area.contents[self.name] = self
self.seen_entities = {}

# define armour slots
self.armour_slots: dict[str, Armour] = dict()
for armour_type in ARMOUR_TYPES:
self.armour_slots[armour_type] = None

@property
def defense(self) -> int:
defense_sum: int = 0
armour_item: Armour
for armour_item in self.armour_slots.keys():
if armour_item is not None:
defense_sum += armour_item.defense
return defense_sum

def look_around(self):
"""Player looks around the current area."""
# clear seen items, but re-add inventory items
Expand Down Expand Up @@ -122,40 +138,57 @@ def pick_up(self, item_name: str):
else:
print(self.world.l10n.format_value("pick-up-failed-message"))

def equip(self, weapon_name: str):
"""Puts an item in the main hand."""
weapon = self.seen_entities.get(weapon_name)
if weapon is None:
def equip(self, item_name: str):
"""Equips item."""
item = self.seen_entities.get(item_name)
# check if item was already seen
if item is None:
print(
self.world.l10n.format_value(
"entity-does-not-exist",
{"entity": weapon_name},
{"entity": item_name},
)
)
return
if not isinstance(weapon, Weapon):
print(self.world.l10n.format_value("cannot-equip", {"weapon": weapon_name}))
return
# item must be in the area or the inventory to be equiped
if (
weapon_name not in self.area.contents
and weapon_name not in self.inventory.contents
item_name not in self.area.contents
and item_name not in self.inventory.contents
):
print(self.world.l10n.format_value("item-vanished"))
self.seen_entities.pop(weapon_name)
self.seen_entities.pop(item_name)
return
if weapon not in self.inventory.contents.values():

# by equipping the item is implicitly picked up
if item not in self.inventory.contents.values():
# if it's not already in the inventory, place it there
self.inventory.add(weapon)
self.inventory.add(item)
# picking up items keeps them in seen_entities
self.area.contents.pop(weapon_name)
self.area.contents.pop(item_name)
print(
self.world.l10n.format_value(
"pick-up-item-message",
{
"item": weapon.name,
"item": item.name,
},
)
)
if isinstance(item, Weapon):
self.equip_weapon(item)
elif isinstance(item, Armour):
self.equip_armour(item)
else:
print(
self.world.l10n.format_value(
"cannot-equip",
{
"weapon": item.name,
},
)
)

def equip_weapon(self, weapon: Weapon):
"""Equips weapon."""
self.main_hand = weapon
print(
self.world.l10n.format_value(
Expand All @@ -167,6 +200,32 @@ def equip(self, weapon_name: str):
)
)

def equip_armour(self, armour: Armour) -> None:
"""Equips armour piece."""
current_armour: Armour = self.armour_slots.pop(armour.armour_type)
# check if armour slot is already filled
if current_armour is not None:
print(
self.world.l10n.format_value(
"unequip-item-message",
{
"player": self.name,
"item": armour.name,
},
)
)

self.armour_slots[armour.armour_type] = armour
print(
self.world.l10n.format_value(
"equip-item-message",
{
"player": self.name,
"item": armour.name,
},
)
)

# TODO: Refactor
def attack(self, target_name: str) -> None:
"""Player attacks character using their main hand."""
Expand Down
19 changes: 15 additions & 4 deletions src/fantasy_forge/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from cmd import Cmd
from typing import TYPE_CHECKING

from fantasy_forge.armour import Armour
from fantasy_forge.character import Character
from fantasy_forge.gateway import Gateway
from fantasy_forge.item import Item
Expand Down Expand Up @@ -165,6 +166,16 @@ def do_inventory(self, arg: str):
"""shows the contents of the players inventory"""
print(self.player.inventory.on_look())

def do_armour(self, arg: str):
"""shows the players armour"""
for armour_type, armour_item in self.player.armour_slots.items():
if armour_item is None:
print(f"{armour_type}: None")
else:
print(
f"{armour_type}: {armour_item.name} ({armour_item.defense} defense)"
)

def do_use(self, arg: str):
"""
use <subject> [with <other>]
Expand Down Expand Up @@ -249,7 +260,7 @@ def complete_attack(
return completions

def do_equip(self, arg: str) -> None:
"""Take an item out of the inventory and place it firmly in your hand."""
"""Take an item out of the inventory and put it on."""
self.player.equip(arg)

def complete_equip(
Expand All @@ -259,11 +270,11 @@ def complete_equip(
begidx: int,
endidx: int,
) -> list[str]:
weapon = line.removeprefix("equip ").strip()
item = line.removeprefix("equip ").strip()
completions = [
text + name.removeprefix(weapon).strip() + " "
text + name.removeprefix(item).strip() + " "
for name, entity in self.player.seen_entities.items()
if name.startswith(weapon) and isinstance(entity, Weapon)
if name.startswith(item) and isinstance(entity, (Weapon, Armour))
]
if " " in completions:
completions.remove(" ")
Expand Down