Moved functions around. Moved Inventory and thus equipped items into its own class

This commit is contained in:
kansaigaijin
2025-07-13 16:10:31 +12:00
parent 54fef56615
commit d14e9e8e8b

856
player.py
View File

@@ -1,25 +1,165 @@
import function_list
from random import randint from random import randint
from enum_list import * from enum_list import DamageType, DamageMod, ItemType, Slots # Import specific enums
# Define Item classes here, as they are fundamental building blocks
class Item:
"""The base class for all items"""
def __init__(self, name, description, value, magical, itemType, attunement):
self.name = name
self.description = description
self.value = value
self.magical = magical
self.itemType = itemType
self.attunement = attunement
def __repr__(self):
return f"{self.name}\n=====\n{self.description}\nValue: {self.value}\n"
class Money(Item):
"""The currency item used in the world of Kanjin"""
def __init__(self, name, amt, magical, ItemType):
self.name = name
self.amt = amt
self.magical = magical
self.itemType = ItemType
super().__init__(name=self.name,
description=f"A small round coin made of {self.name.lower()} "
f"with the imperial city logo stamped on the face.",
value=self.amt,
magical=self.magical,
itemType=ItemType.Money,
attunement=False)
class Weapon(Item):
"""The base class for all weapons"""
def __init__(self, name, description, value, slot, damage1H, damage2H, versatiledmg,
dmgType, dmgMod, versatile, thrown, ItemType, magical, attunement):
self.slot = slot
self.damage1H = damage1H
self.damage2H = damage2H
self.versatiledmg = versatiledmg
self.dmgType = dmgType
self.dmgMod = dmgMod
self.versatile = versatile
self.thrown = thrown
self.itemType = ItemType
super().__init__(name, description, value, magical, ItemType, attunement)
def __str__(self):
if self.damage2H is None and self.damage1H is not None:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: One Handed - {self.damage1H}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
elif self.damage1H is None and self.damage2H is not None:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: Two Handed - {self.damage2H}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
elif self.versatile:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: Versatile - {self.versatiledmg}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
else:
return f"{self.name}\n=====" \
f"\n{self.description}\n" \
f"Value: {self.value}\n" \
f"Magical: {self.magical}\n" \
f"Attunement: {'Required' if self.attunement else 'Not Required'}"
class Armor(Item):
"""The base class for all armor"""
def __init__(self, name, description, value, slot, grade, ac, disadvantage, dmgRes, ItemType, magical, attunement):
self.slot = slot
self.grade = grade
self.ac = ac
self.stealthDis = disadvantage
self.dmgRes = dmgRes
self.itemType = ItemType
super().__init__(name, description, value, magical, ItemType, attunement)
def __repr__(self):
if self.stealthDis:
return f"{self.name}\n" \
f"=====\n" \
f"{self.description}\n" \
f"Value: {self.value}\n" \
f"AC: {self.ac}\n" \
f"Magical: {self.magical}"
elif not self.stealthDis:
return f"{self.name}\n=====" \
f"\n{self.description}\n" \
f"Value: {self.value}\n" \
f"AC: {self.ac}\n" \
f"Disadvantage on Stealth checks: {self.stealthDis}\n" \
f"Magical: {self.magical}\n"
return None
class Inventory: class Inventory:
def __init__(self): def __init__(self):
# Changed to a dictionary to store item objects as keys and their data (count, equipped, etc.) as values # Dictionary to store item objects as keys and their data (count) as values
self.items = {} self.items = {}
# Dictionary to store equipped items by slot
self.equipped_items = {
Slots.MainHand: None,
Slots.OffHand: None,
Slots.TwoHanded: None,
Slots.Helm: None,
Slots.Chest: None,
Slots.Wrists: None,
Slots.Feet: None,
Slots.Neck: None,
Slots.Cloak: None,
Slots.LeftRing: None,
Slots.RightRing: None,
Slots.Other: None, # For miscellaneous equipped items
Slots.Attunement: 0 # Counter for attuned items
}
def _get_slot_display_name(self, slot_val):
"""Helper to get a user-friendly string name for a slot, handling both Enum and string inputs."""
if isinstance(slot_val, Slots):
return slot_val.name.replace('TwoHanded', 'Two Handed').replace('MainHand',
'Main Hand').replace('OffHand',
'Off Hand').replace('LeftRing', 'Left Ring').replace(
'RightRing', 'Right Ring')
elif isinstance(slot_val, str):
# If it's already a string, assume it's the name and format it
return (slot_val.replace('TwoHanded', 'Two Handed').replace('MainHand', 'Main Hand')
.replace('OffHand','Off Hand').replace('LeftRing', 'Left Ring')
.replace('RightRing', 'Right Ring'))
return str(slot_val) # Fallback for unexpected types
def add_item(self, item, count=1): def add_item(self, item, count=1):
"""Adds an item to the inventory."""
if item in self.items: if item in self.items:
self.items[item]["Count"] += count self.items[item]["Count"] += count
print(f"Added {count} more {item.name}. Total: {self.items[item]['Count']}.") print(f"Added {count} more {item.name}. Total: {self.items[item]['Count']}.")
else: else:
self.items[item] = {"Count": count, "object": item.itemType, "equipped": False} self.items[item] = {"Count": count, "object": item.itemType}
print(f"Added {count} {item.name} to inventory.") print(f"Added {count} {item.name} to inventory.")
def remove_item(self, item, count=1): def remove_item(self, item, count=1):
"""Removes an item from the inventory."""
if item in self.items: if item in self.items:
if self.items[item]["Count"] <= count: if self.items[item]["Count"] <= count:
print(f"Removed all {self.items[item]['Count']} {item.name} from inventory.") print(f"Removed all {self.items[item]['Count']} {item.name} from inventory.")
del self.items[item] del self.items[item]
# If the item was equipped, unequip it
for slot, equipped_item in self.equipped_items.items():
if equipped_item == item:
self.unequip_item(slot)
else: else:
self.items[item]["Count"] -= count self.items[item]["Count"] -= count
print(f"Removed {count} {item.name} from inventory. Remaining: {self.items[item]['Count']}.") print(f"Removed {count} {item.name} from inventory. Remaining: {self.items[item]['Count']}.")
@@ -29,53 +169,207 @@ class Inventory:
def current_inventory(self): def current_inventory(self):
"""Prints a list of items in your backpack that aren't equipped to your person.""" """Prints a list of items in your backpack that aren't equipped to your person."""
print(f'In your rucksack you have:') print(f'In your rucksack you have:')
if not self.items: found_un_equipped = False
print(" Your rucksack is empty.")
return
for item_obj, item_data in self.items.items(): for item_obj, item_data in self.items.items():
# Assuming 'equipped' is a key in item_data and Equipment.inventory holds equipped items # Check if the item is in the backpack AND not currently equipped in any slot
# This logic needs careful alignment with how you manage equipped items. if item_obj not in self.equipped_items.values():
# For now, it checks if the item object itself is in any of the equipped slots. found_un_equipped = True
if not item_data["equipped"] and item_obj not in Equipment.inventory.values():
count = item_data["Count"] count = item_data["Count"]
print(f'{item_obj.name} x {count}\n' print(f'{item_obj.name} x {count}\n'
f' {item_obj.description}\n') f' {item_obj.description}\n')
if not found_un_equipped:
print(" Your rucksack is empty.")
@staticmethod def current_equipment(self):
def current_equipment():
"""Prints a list of items that you have equipped in your slots.""" """Prints a list of items that you have equipped in your slots."""
print(f'You are currently wearing:') print(f'You are currently wearing:')
equipment_found = False equipment_found = False
for slot, equipment in Equipment.inventory.items(): for slot, equipment in self.equipped_items.items():
if equipment is not None: if equipment is not None and slot != Slots.Attunement: # Don't print the attunement counter
equipment_found = True equipment_found = True
if slot == "Attunement1" or slot == "Attunement2" or slot == "Attunement3": # Assuming these are not to be printed slot_name = slot.name.replace("TwoHanded", "Two Handed").replace("MainHand", "Main Hand").replace("OffHand", "Off Hand").replace("LeftRing", "Left Ring").replace("RightRing", "Right Ring")
continue if equipment.itemType == ItemType.Weapon:
elif equipment.itemType == ItemType.Weapon:
if equipment.versatile: if equipment.versatile:
print(f'{slot}: {equipment.name} - {equipment.versatiledmg}\n' print(f'{slot_name}: {equipment.name} - {equipment.versatiledmg}\n'
f' {equipment.description}\n') f' {equipment.description}\n')
continue
elif equipment.damage1H is not None: elif equipment.damage1H is not None:
print(f'{slot}: {equipment.name} - {equipment.damage1H}\n' print(f'{slot_name}: {equipment.name} - {equipment.damage1H}\n'
f' {equipment.description}\n') f' {equipment.description}\n')
elif equipment.damage2H is not None: elif equipment.damage2H is not None:
print(f'{slot}: {equipment.name} - {equipment.damage2H}\n' print(f'{slot_name}: {equipment.name} - {equipment.damage2H}\n'
f' {equipment.description}\n') f' {equipment.description}\n')
elif equipment.itemType == ItemType.Armor: elif equipment.itemType == ItemType.Armor:
print(f'{slot}: {equipment.name} - AC {equipment.ac}\n' print(f'{slot_name}: {equipment.name} - AC {equipment.ac}\n'
f' {equipment.description}\n') f' {equipment.description}\n')
elif equipment.itemType == ItemType.Item: elif equipment.itemType == ItemType.Item:
print(f'{slot}: {equipment.name}\n' print(f'{slot_name}: {equipment.name}\n'
f' {equipment.description}\n') f' {equipment.description}\n')
if not equipment_found: if not equipment_found:
print(" Nothing equipped.") print(" Nothing equipped.")
print(f"Attunement slots used: {self.equipped_items[Slots.Attunement]}/3")
def equip_item(self, item_obj, slot: Slots):
"""Equips an item to a specific slot."""
if item_obj not in self.items or self.items[item_obj]["Count"] == 0:
print(f"You don't have {item_obj.name} to equip.")
return
# Check attunement limits before equipping (moved up for early exit)
if item_obj.attunement and self.equipped_items[Slots.Attunement] >= 3:
print(f"You cannot equip {item_obj.name}. You have reached your attunement limit (3/3).")
return
# Handle unequipping existing item in the target slot first
if self.equipped_items[slot] is not None:
self.unequip_item(slot)
# Now, handle equipping based on item type and specific properties
if item_obj.itemType == ItemType.Weapon:
if item_obj.slot == Slots.TwoHanded:
# If equipping a two-handed weapon, unequip anything in main/off-hand
if self.equipped_items[Slots.MainHand] is not None:
self.unequip_item(Slots.MainHand)
if self.equipped_items[Slots.OffHand] is not None:
self.unequip_item(Slots.OffHand)
self.equipped_items[Slots.TwoHanded] = item_obj
print(f"Equipped {item_obj.name} to {self._get_slot_display_name(Slots.TwoHanded)}.")
elif item_obj.versatile:
# If equipping a versatile weapon, it goes in MainHand, and potentially affects OffHand
if self.equipped_items[Slots.TwoHanded] is not None:
self.unequip_item(Slots.TwoHanded) # Unequip two-handed if present
self.equipped_items[Slots.MainHand] = item_obj
# A versatile weapon can be used two-handed, implying it might occupy the off-hand conceptually
# For simplicity, we'll just equip it to MainHand here. If you want it to occupy off-hand,
# you'd need more complex logic for 1H vs 2H use.
print(f"Equipped {item_obj.name} to {self._get_slot_display_name(Slots.MainHand)}.")
else: # Standard one-handed weapon (or other weapon types not specifically handled above)
self.equipped_items[slot] = item_obj # Equip to the specified slot (MainHand or OffHand)
print(f"Equipped {item_obj.name} to {self._get_slot_display_name(slot)}.")
elif item_obj.itemType == ItemType.Armor:
# For armor, ensure the target slot is appropriate for armor (e.g., Chest, Helm)
# The Armor class now has a 'slot' attribute, so we can use that for validation/assignment
if item_obj.slot == slot: # Ensure the item's intended slot matches the target slot
self.equipped_items[slot] = item_obj
print(f"Equipped {item_obj.name} to {self._get_slot_display_name(slot)}.")
else:
# Corrected this line to use _get_slot_display_name for both slot and item_obj.slot
print(f"Cannot equip {item_obj.name} to {self._get_slot_display_name(slot)}. It belongs in the {self._get_slot_display_name(item_obj.slot)} slot.")
return # Exit if slot mismatch
elif item_obj.itemType == ItemType.Item:
# For general items (rings, cloaks, etc.), equip to the specified slot
self.equipped_items[slot] = item_obj
print(f"Equipped {item_obj.name} to {self._get_slot_display_name(slot)}.")
else:
print(f"Cannot equip {item_obj.name}. Unknown item type or invalid slot for this item.")
return # Exit if item type is not recognized for equipping
self.update_attunement() # Update attunement after successful equip
def unequip_item(self, slot: Slots):
"""Unequips an item from a specific slot."""
if self.equipped_items[slot] is not None:
unequipped_item = self.equipped_items[slot]
self.equipped_items[slot] = None
print(f"Unequipped {unequipped_item.name} from {self._get_slot_display_name(slot)}.") # Use helper function
self.update_attunement()
else:
print(f"Nothing is equipped in {self._get_slot_display_name(slot)}.") # Use helper function
def update_attunement(self):
"""Recalculates the number of attuned items."""
attune_count = 0
for slot, item in self.equipped_items.items():
if slot != Slots.Attunement and item is not None and item.attunement:
attune_count += 1
self.equipped_items[Slots.Attunement] = attune_count
# --- Item Instances (Moved here for clarity, but still defined once) ---
# Different types of coins
GP1 = Money("Gold", 1, False, ItemType.Money)
SP1 = Money("Silver", 1, False, ItemType.Money)
CP1 = Money("Copper", 1, False, ItemType.Money)
# starting equip
rock = Weapon(
name="Rock",
description="A fist-sized rock, suitable for bludgeoning.",
value=None,
slot=Slots.MainHand,
damage1H='1d6',
damage2H=None,
versatiledmg=None,
dmgType=DamageType.Bludgeoning,
dmgMod=DamageMod.Strength,
versatile=False,
thrown=True,
magical=False,
ItemType=ItemType.Weapon,
attunement=False # Rocks typically aren't attuned
)
dagger = Weapon(
name="Dagger",
description="Pointy stabby-stab",
value=None,
slot=Slots.MainHand, # Can be main hand or off hand
damage1H='1d4', # Daggers are usually 1d4
damage2H=None,
versatiledmg=None, # Daggers are not versatile in the D&D sense (they are light, finesse)
dmgType=DamageType.Piercing, # Changed to piercing
dmgMod=DamageMod.Dexterity,
versatile=False, # Changed to False, as D&D versatile means 1H or 2H damage
thrown=True,
magical=False,
ItemType=ItemType.Weapon,
attunement=False
)
polearm = Weapon(
name="Polearm",
description="Long Pointy stabby-stab",
value=None,
slot=Slots.TwoHanded,
damage1H=None,
damage2H="1d10", # Polearms are typically 1d10
versatiledmg=None,
dmgType=DamageType.Piercing,
dmgMod=DamageMod.Strength,
versatile=False,
thrown=False, # Polearms are not typically thrown
magical=False,
ItemType=ItemType.Weapon,
attunement=False
)
tornRags = Armor(
name="Torn Rags",
description="A ripped and worn-out outfit.",
value=None,
slot=Slots.Chest,
grade="Light",
ac=10, # Base AC for light armor without proficiency is 10 + Dex mod
disadvantage=False, # Light armor usually doesn't give disadvantage
dmgRes="None",
magical=False,
ItemType=ItemType.Armor,
attunement=False
)
paper = Item(
name="Paper",
description="A blank piece of paper",
value=None,
magical=False,
itemType=ItemType.Item,
attunement=False
)
class Player: class Player:
"""Character Creation""" """Character Creation"""
# Default Character
def __init__(self, name, gender, age, race, job): def __init__(self, name, gender, age, race, job):
# Identity # Identity
self.name = name self.name = name
@@ -85,27 +379,29 @@ class Player:
self.job = job self.job = job
# Defence # Defence
self.ac = 0 self.ac = 0 # This will be calculated based on equipped armor
self.elemental_resistance = {DamageType.Fire: False, DamageType.Water: False, DamageType.Lightning: False} self.elemental_resistance = {DamageType.Fire: False,
self.physical_resistance = {DamageType.Slashing: False, DamageType.Crushing: False, DamageType.Piercing: False} DamageType.Cold: False,
DamageType.Lightning: False,
DamageType.Thunder: False,
DamageType.Poison: False,
DamageType.Acid: False,
DamageType.Necrotic: False,
DamageType.Radiant: False,
DamageType.Force: False,
DamageType.Psychic: False
}
self.physical_resistance = {DamageType.Slashing: False,
DamageType.Bludgeoning: False,
DamageType.Piercing: False}
# Levels # Levels
self.level = 1 self.level = 1
self.exp = 0 self.exp = 0
self.maxEXP = 100 self.maxEXP = 100
# Equipment # Equipment and Inventory
# Held items self.inventory = Inventory() # Each player gets their own inventory instance
self.inventory = Inventory()
self.weapon = rock
self.armor = tornRags
# Example of adding starting items to inventory
self.inventory.add_item(rock, 1)
self.inventory.add_item(tornRags, 1)
# Assuming you want to auto-equip these initially
self.equip_item(rock, "Main Hand")
self.equip_item(tornRags, "Chest")
# Ability Scores # Ability Scores
self.stats = {"Strength": 0, self.stats = {"Strength": 0,
@@ -122,6 +418,13 @@ class Player:
"Wisdom": 0, "Wisdom": 0,
"Charisma": 0} "Charisma": 0}
# Initial items and equipment
self.inventory.add_item(rock, 1)
self.inventory.add_item(tornRags, 1)
# Equip initial items (using the inventory's equip method)
self.inventory.equip_item(rock, Slots.MainHand)
self.inventory.equip_item(tornRags, Slots.Chest)
def getModifier(self): def getModifier(self):
"""Floor calculation to work out skill check modifiers""" """Floor calculation to work out skill check modifiers"""
@@ -133,7 +436,9 @@ class Player:
self.mods["Charisma"] = -5 + self.stats["Charisma"] // 2 self.mods["Charisma"] = -5 + self.stats["Charisma"] // 2
def set_stats_race(self): def set_stats_race(self):
if self.race.lower() == "human": """Applies racial ability score increases."""
# Access name attribute of Race object
if self.race.name.lower() == "human":
print("As a Human, two different ability scores of your choice increase by 1.") print("As a Human, two different ability scores of your choice increase by 1.")
selection1 = input("Choose the first ability score to increase by 1.\n" selection1 = input("Choose the first ability score to increase by 1.\n"
"1) Strength\n" "1) Strength\n"
@@ -207,162 +512,63 @@ class Player:
"6) Charisma\n >> ").lower() "6) Charisma\n >> ").lower()
continue continue
elif self.race.lower() == "elf": elif self.race.name.lower() == "elf":
print("As an Elf, your dexterity increases by 2.")
self.stats["Dexterity"] += 2 self.stats["Dexterity"] += 2
elif self.race.name.lower() == "dwarf":
elif self.race.lower() == "dwarf": print("As a Dwarf, your constitution increases by 2.")
self.stats["Constitution"] += 2 self.stats["Constitution"] += 2
def set_stats_job(self): def set_stats_job(self):
if self.job.lower() == "warrior": """Applies job-specific stats like hit points."""
self.hitDie = randint(1, 12) # Using self.job.name to access the job name from the Job object
self.maxHP = (12 + self.mods["Constitution"]) if self.job.name.lower() == "barbarian":
self.currentHP = (12 + self.mods["Constitution"]) self.hitDie = randint(self.job.hitdie[0], self.job.hitdie[1]) # Use job's hitdie range
self.maxHP = (self.job.hitdie[1] + self.mods["Constitution"]) # Use max hitdie value for initial HP
elif self.job.lower() == "cleric": self.currentHP = self.maxHP
self.hitDie = randint(1, 10) elif self.job.name.lower() == "cleric":
self.maxHP = (10 + self.mods["Constitution"]) self.hitDie = randint(self.job.hitdie[0], self.job.hitdie[1])
self.currentHP = (10 + self.mods["Constitution"]) self.maxHP = (self.job.hitdie[1] + self.mods["Constitution"])
self.currentHP = self.maxHP
elif self.job.lower() == "mage": elif self.job.name.lower() == "wizard":
self.hitDie = randint(1, 8) self.hitDie = randint(self.job.hitdie[0], self.job.hitdie[1])
self.maxHP = (8 + self.mods["Constitution"]) self.maxHP = (self.job.hitdie[1] + self.mods["Constitution"])
self.currentHP = (8 + self.mods["Constitution"]) self.currentHP = self.maxHP
def current_stats(self): def current_stats(self):
"""Prints a display of the user's current statistics.""" """Prints a display of the user's current statistics."""
print("Your current stats are:") print("Your current stats are:")
print(f'Hit Points: {self.currentHP}/{self.maxHP}') print(f'Hit Points: {self.currentHP}/{self.maxHP}')
print(f'Strength: {self.stats["Strength"]}') print(f'Strength: {self.stats["Strength"]} ({self.mods["Strength"]:+})')
print(f'Dexterity: {self.stats["Dexterity"]}') print(f'Dexterity: {self.stats["Dexterity"]} ({self.mods["Dexterity"]:+})')
print(f'Constitution: {self.stats["Constitution"]}') print(f'Constitution: {self.stats["Constitution"]} ({self.mods["Constitution"]:+})')
print(f'Intelligence: {self.stats["Intelligence"]}') print(f'Intelligence: {self.stats["Intelligence"]} ({self.mods["Intelligence"]:+})')
print(f'Wisdom: {self.stats["Wisdom"]}') print(f'Wisdom: {self.stats["Wisdom"]} ({self.mods["Wisdom"]:+})')
print(f'Charisma: {self.stats["Charisma"]}') print(f'Charisma: {self.stats["Charisma"]} ({self.mods["Charisma"]:+})')
def current_equip(self):
"""Prints a list of items currently equipped."""
print(f"You have equipped:\n {self.armor.name}\n"
f" {self.armor.description}"
f"\n{self.weapon.name}\n"
f" {self.weapon.description}")
def drop_item(self, item_name): def drop_item(self, item_name):
# Find the item object by name """Drops an item from the player's inventory."""
item_to_drop = None item_to_drop = None
for item_obj in self.inventory.items: for item_obj in self.inventory.items:
if item_obj.name.lower() == item_name.lower(): if item_obj.name.lower() == item_name.lower():
item_to_drop = item_obj item_to_drop = item_obj
break break
if item_to_drop: if item_to_drop:
self.inventory.remove_item(item_to_drop, count=1) # Remove one by default self.inventory.remove_item(item_to_drop, count=1)
else: else:
print(f"You don't have '{item_name}' in your inventory.") print(f"You don't have '{item_name}' in your inventory.")
def loot_object(self, object_name):
# This method assumes a specific structure for Map.scenes and lootable_items
for scene_name, room_data in Map.scenes.items():
if Engine.seed == scene_name:
found_item = False
for item_key_str, item_value_dict in room_data.get("lootable_items", {}).items():
# item_value_dict is like {Item_obj: count}
for item_obj, count in item_value_dict.items():
if object_name.lower() in item_obj.name.lower():
if count > 0:
self.inventory.add_item(item_obj, count)
room_data["lootable_items"][item_key_str][item_obj] = 0 # Set count to 0 in room
found_item = True
break
if found_item:
break
if not found_item:
print("You don't see one of those to loot.")
return # Exit after checking the current room
def add_inventory(self, object_name):
# This method assumes a specific structure for Map.scenes and available_items
for scene_name, room_data in Map.scenes.items():
if Engine.seed == scene_name:
found_item = False
for item_key_str, item_value_dict in room_data.get("available_items", {}).items():
# item_value_dict is like {Item_obj: count}
for item_obj, count_available in item_value_dict.items():
if object_name.lower() in item_obj.name.lower():
if count_available == 0:
print(f"There are no {item_obj.name} left here.")
found_item = True
break
response = input(f'There are {count_available} {item_obj.name} available.\n'
f'How many will you take? (enter "all" or a number)\n>> ').lower()
if response == "0" or response == "none":
print("You decide not to take any.")
found_item = True
break
elif response == "all":
self.inventory.add_item(item_obj, count_available)
room_data["available_items"][item_key_str][item_obj] = 0
found_item = True
break
elif response.isdigit():
num_to_take = int(response)
if num_to_take <= count_available:
self.inventory.add_item(item_obj, num_to_take)
room_data["available_items"][item_key_str][item_obj] -= num_to_take
found_item = True
break
else:
print("You can't take more than exist.")
found_item = True
break
else:
print("Invalid input. Please enter 'all' or a number.")
found_item = True
break
if found_item:
break
if not found_item:
print("You don't see any lying around.")
return # Exit after checking the current room
def equip_item(self, item_obj, slot):
"""Equips an item to a specific slot."""
if item_obj in self.inventory.items and self.inventory.items[item_obj]["Count"] > 0:
# Unequip existing item in that slot if any
if Equipment.inventory[slot] is not None:
unequipped_item = Equipment.inventory[slot]
print(f"Unequipping {unequipped_item.name} from {slot}.")
self.inventory.items[unequipped_item]["equipped"] = False # Mark as unequipped in inventory
Equipment.inventory[slot] = item_obj
self.inventory.items[item_obj]["equipped"] = True
print(f"Equipped {item_obj.name} to {slot}.")
else:
print(f"You don't have {item_obj.name} to equip.")
def unequip_item(self, slot):
"""Unequips an item from a specific slot."""
if Equipment.inventory[slot] is not None:
unequipped_item = Equipment.inventory[slot]
self.inventory.items[unequipped_item]["equipped"] = False
Equipment.inventory[slot] = None
print(f"Unequipped {unequipped_item.name} from {slot}.")
else:
print(f"Nothing is equipped in {slot}.")
def new_char(self): def new_char(self):
"""Outputs final stats, armour, and inventory""" """Outputs final stats, armour, and inventory"""
self.allocation() self.allocation() # 1. Sets base self.stats from rolls.
self.set_stats_race() self.set_stats_race() # 2. Applies racial bonuses to self.stats.
self.set_stats_job() self.getModifier() # 3. Calculates ALL self.mods based on the FINAL self.stats (base + racial).
self.getModifier() self.set_stats_job() # 4. Calculates maxHP using the now-accurate self.mods["Constitution"].
self.current_stats() self.current_stats() # 5. Displays the final stats.
print(' ') print(' ')
Inventory.current_equipment() # Call the static method of Inventory self.inventory.current_equipment() # Corrected to call instance method
print(' ') print(' ')
self.inventory.current_inventory() # Call the instance method of Player's inventory self.inventory.current_inventory()
print(' ') print(' ')
def health_check(self): def health_check(self):
@@ -378,61 +584,61 @@ class Player:
return damage return damage
def allocation(self): def allocation(self):
"""Handles the interactive allocation of rolled stats to attributes."""
# Mapping for shorthand attribute names
attribute_map = {
"strength": "Strength", "str": "Strength",
"dexterity": "Dexterity", "dex": "Dexterity",
"constitution": "Constitution", "con": "Constitution",
"intelligence": "Intelligence", "int": "Intelligence",
"wisdom": "Wisdom", "wis": "Wisdom",
"charisma": "Charisma", "cha": "Charisma"
}
while True: while True:
rolls = [] rolls = []
stats = [] stats = []
attributes = ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"] attributes = ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"]
while len(stats) != 6: while len(stats) != 6:
# roll 4d6 # Roll 4d6, drop lowest
for roll in range(4): four_d_six = sorted([randint(1, 6) for _ in range(4)])
r = randint(1, 6) val = sum(four_d_six[1:]) # Sum the top 3
# This rolling logic (if r < 6: r = randint(1, 6)) seems to aim for higher rolls.
# Standard D&D 4d6 drop lowest usually means you just roll 4 and drop the lowest.
# I'll keep your original logic but note it's non-standard.
if r < 6:
r = randint(1, 6)
rolls.append(r)
# Drop the lowest number and then add to single value
del rolls[rolls.index(min(rolls))]
val = sum(rolls)
stats.append(str(val)) stats.append(str(val))
rolls = [] rolls = [] # Reset for next roll
print(f"Please assign a stat to a selected attribute by entering the number then the attribute.\n" print(f"Please assign a stat to a selected attribute by entering the number then the attribute.\n"
f"For example, '10 strength' will assign 10 to your strength, if you have a 10 available.\n\n" f"For example, '10 strength' or '10 str' will assign 10 to your strength, if you have a 10 available.\n\n"
f"Please select a stat and an attribute.\n\n"
f"Your rolled stats are:\n {', '.join(stats)}\n\n" f"Your rolled stats are:\n {', '.join(stats)}\n\n"
f"Your attributes are:\n {', '.join(attributes)}\n") f"Your attributes are:\n {', '.join(attributes)}\n")
# Allocate current_attributes = list(attributes)
current_attributes = list(attributes) # Make a copy to modify current_stats_to_assign = list(stats)
current_stats_to_assign = list(stats) # Make a copy
while len(current_stats_to_assign) != 0: # Reset player stats for new allocation attempt
input_text = input(">> ").title() self.stats = {"Strength": 0, "Dexterity": 0, "Constitution": 0, "Intelligence": 0, "Wisdom": 0, "Charisma": 0}
while len(current_stats_to_assign) > 0:
input_text = input(">> ").lower().strip() # Convert to lower and strip whitespace
words = input_text.split() words = input_text.split()
if len(words) == 2:
try:
chosen_stat_str = words[0]
chosen_attribute = words[1]
if chosen_stat_str in current_stats_to_assign and chosen_attribute in current_attributes: if len(words) == 2:
self.stats[chosen_attribute] += int(chosen_stat_str) chosen_stat_str = words[0]
chosen_attribute_input = words[1]
# Resolve shorthand to full attribute name
chosen_attribute_full = attribute_map.get(chosen_attribute_input, None)
if chosen_stat_str in current_stats_to_assign and chosen_attribute_full in current_attributes:
self.stats[chosen_attribute_full] = int(chosen_stat_str) # Assign, not add
current_stats_to_assign.remove(chosen_stat_str) current_stats_to_assign.remove(chosen_stat_str)
current_attributes.remove(chosen_attribute) current_attributes.remove(chosen_attribute_full)
if len(current_stats_to_assign) == 1: if len(current_stats_to_assign) == 0:
self.stats[current_attributes[0]] += int(current_stats_to_assign[0]) break # All stats assigned
print("")
break
print(f"The remaining stats are:\n {', '.join(current_stats_to_assign)}\n\n" print(f"The remaining stats are:\n {', '.join(current_stats_to_assign)}\n\n"
f"The remaining attributes are:\n {', '.join(current_attributes)}") f"The remaining attributes are:\n {', '.join(current_attributes)}")
else: else:
print( print(
"Error. Either the number or attribute was not available/recognised. Please try again with 'Number' + 'Attribute'.") "Error. Either the number or attribute was not available/recognised. Please try again with 'Number' + 'Attribute'.")
except ValueError:
print("Error. Invalid number format. Please try again with 'Number' + 'Attribute'.")
except KeyError:
print("Error. Attribute not recognized. Please try again with 'Number' + 'Attribute'.")
else: else:
print("Error. Input was not recognised. Please try again with 'Number' + 'Attribute'.") print("Error. Input was not recognised. Please try again with 'Number' + 'Attribute'.")
@@ -448,237 +654,9 @@ class Player:
"WARNING: If you select 'N', your dice will be randomly rolled again. Proceed?\n" "WARNING: If you select 'N', your dice will be randomly rolled again. Proceed?\n"
" >> ").lower() " >> ").lower()
if select == "n": if select == "n":
# Reset stats for re-allocation continue # Loop back to re-roll and re-allocate
self.stats = {"Strength": 0, "Dexterity": 0, "Constitution": 0, "Intelligence": 0, "Wisdom": 0,
"Charisma": 0}
continue
elif select == 'y': elif select == 'y':
break break
else: else:
print("Error. Input was not recognised. Please select 'Y' or 'N'.") print("Error. Input was not recognised. Please select 'Y' or 'N'.")
# Loop back to ask again or handle as desired # Loop back to ask again or handle as desired
class Item:
"""The base class for all items"""
def __init__(self, name, description, value, magical, itemType, attunement):
self.name = name
self.description = description
self.value = value
self.magical = magical
self.itemType = itemType
self.attunement = attunement
def __repr__(self):
return f"{self.name}\n=====\n{self.description}\nValue: {self.value}\n"
class Money(Item):
"""The currency item used in the world of Kanjin"""
def __init__(self, name, amt, magical, ItemType):
self.name = name
self.amt = amt
self.magical = magical
self.itemType = ItemType
super().__init__(name=self.name,
description=f"A small round coin made of {self.name.lower()} "
f"with the imperial city logo stamped on the face.",
value=self.amt,
magical=self.magical,
ItemType=ItemType.Money,
attunement=False)
class Weapon(Item):
"""The base class for all weapons"""
def __init__(self, name, description, value, slot, damage1H, damage2H, versatiledmg,
dmgType, dmgMod, versatile, thrown, ItemType, magical, attunement):
self.slot = slot
self.damage1H = damage1H
self.damage2H = damage2H
self.versatiledmg = versatiledmg
self.dmgType = dmgType
self.dmgMod = dmgMod
self.versatile = versatile
self.thrown = thrown
self.itemType = ItemType
super().__init__(name, description, value, magical, ItemType, attunement)
def __str__(self):
if self.damage2H is None and self.damage1H is not None:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: One Handed - {self.damage1H}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
elif self.damage1H is None and self.damage2H is not None:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: Two Handed - {self.damage2H}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
elif self.versatile:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nDamage: Versatile - {self.versatiledmg}" \
f"\nDamage Type: {self.dmgType.name.title()}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
else:
return f"{self.name}\n=====" \
f"\n{self.description}" \
f"\nValue: {self.value}" \
f"\nMagical: {self.magical}" \
f"\nAttunement: {'Required' if self.attunement else 'Not Required'}"
class Armor(Item):
"""The base class for all armor"""
def __init__(self, name, description, value, grade, ac, disadvantage, dmgRes, ItemType, magical, attunement):
self.grade = grade
self.ac = ac
self.stealthDis = disadvantage
self.dmgRes = dmgRes
self.itemType = ItemType
super().__init__(name, description, value, magical, ItemType, attunement)
def __repr__(self):
if self.stealthDis:
return f"{self.name}\n" \
f"=====\n" \
f"{self.description}\n" \
f"Value: {self.value}\n" \
f"AC: {self.ac}\n" \
f"Magical: {self.magical}"
elif not self.stealthDis:
return f"{self.name}\n=====" \
f"\n{self.description}\n" \
f"Value: {self.value}\n" \
f"AC: {self.ac}\n" \
f"Disadvantage on Stealth checks: {self.stealthDis}\n" \
f"Magical: {self.magical}\n"
# Different types of coins
GP1 = Money("Gold", 1, False, ItemType.Money)
SP1 = Money("Silver", 1, False, ItemType.Money)
CP1 = Money("Copper", 1, False, ItemType.Money)
# starting equip
rock = Weapon(
name="Rock",
description="A fist-sized rock, suitable for bludgeoning.",
value=None,
slot="Main Hand",
damage1H='1d6',
damage2H=None,
versatiledmg=None,
dmgType=DamageType.Crushing,
dmgMod=DamageMod.Strength,
versatile=False,
thrown=True,
magical=False,
ItemType=ItemType.Weapon,
attunement=True
)
dagger = Weapon(
name="Dagger",
description="Pointy stabby-stab",
value=None,
slot="Main Hand",
damage1H=None,
damage2H=None,
versatiledmg='1d6',
dmgType=DamageType.Slashing,
dmgMod=DamageMod.Dexterity,
versatile=True,
thrown=True,
magical=False,
ItemType=ItemType.Weapon,
attunement=True
)
polearm = Weapon(
name="Polearm",
description="Long Pointy stabby-stab",
value=None,
slot="Two Handed",
damage1H=None,
damage2H="2d6",
versatiledmg=None,
dmgType=DamageType.Piercing,
dmgMod=DamageMod.Strength,
versatile=False,
thrown=True,
magical=False,
ItemType=ItemType.Weapon,
attunement=True
)
tornRags = Armor(
name="Torn Rags",
description="A ripped and worn-out outfit.",
value=None,
grade="Light",
ac=0,
disadvantage=True,
dmgRes="None",
magical=False,
ItemType=ItemType.Armor,
attunement=True
)
paper = Item(
name="Paper",
description="A blank piece of paper",
value=None,
magical=False,
ItemType=ItemType.Item,
attunement=True
)
Inventory = {
rock: {"Count": 1,
"object": Weapon,
},
dagger: {"Count": 1,
"object": Weapon,
},
polearm: {"Count": 1,
"object": Weapon
},
paper: {"Count": 1,
"object": Item
},
tornRags: {"Count": 1,
"object": Armor
},
GP1: {"Count": 10,
"object": Money
},
}
Equipment = {
"Main Hand": rock,
"Off Hand": None,
"Two Handed": None,
"Helm": None,
"Chest": tornRags,
"Wrists": None,
"Feet": None,
"Neck": None,
"Cloak": None,
"Left Ring": None,
"Right Ring": None,
"Other": paper,
"Attunement": 0
}