Skip to content

Commit

Permalink
Merge pull request #17 from mmacy/adventure-save-load
Browse files Browse the repository at this point in the history
combat work part 1 of n
  • Loading branch information
mmacy committed Nov 8, 2023
2 parents 15b1d5c + 45f5d7b commit 5c36be5
Show file tree
Hide file tree
Showing 15 changed files with 687 additions and 122 deletions.
6 changes: 5 additions & 1 deletion osrlib/osrlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
LocationNotFoundError,
Direction,
)
from .enums import (
CharacterClassType,
)
from .game_manager import (
GameManager,
StorageType,
Expand Down Expand Up @@ -87,6 +90,7 @@
CharacterAlreadyInPartyError,
get_default_party,
)
from .player_character import PlayerCharacter
from .player_character import PlayerCharacter, Alignment
from .quest import Quest
from .saving_throws import saving_throws
from .utils import format_modifiers
2 changes: 1 addition & 1 deletion osrlib/osrlib/ability.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from abc import ABC, abstractmethod
from enum import Enum

from osrlib.character_classes import CharacterClassType
from osrlib.enums import CharacterClassType
from osrlib.combat import ModifierType


Expand Down
88 changes: 13 additions & 75 deletions osrlib/osrlib/character_classes.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
"""Defines character classes for player characters."""
from enum import Enum
from typing import List, Tuple, Union

from osrlib.combat import AttackType
from osrlib.dice_roller import DiceRoll, roll_dice


class CharacterClassType(Enum):
"""Enum representing the types of character classes."""

CLERIC = "Cleric"
DWARF = "Dwarf"
ELF = "Elf"
FIGHTER = "Fighter"
HALFLING = "Halfling"
MAGIC_USER = "Magic User"
THIEF = "Thief"
COMMONER = "Commoner"
from osrlib.enums import CharacterClassType
from osrlib.saving_throws import saving_throws


class ClassLevel:
Expand Down Expand Up @@ -66,7 +53,7 @@ class CharacterClass:
def __init__(self, character_class_type: CharacterClassType, level: int = 1, constitution_modifier: int = 0):
"""Initialize a CharacterClass instance."""
self.class_type = character_class_type
self.saving_throws = saving_throws[self.class_type]
self.class_saving_throws = saving_throws[self.class_type]
self.levels = class_levels[self.class_type]
self.current_level = self.levels[level]
self.hit_die = self.levels[1].hit_dice # hit die is always first-level (1dn)
Expand All @@ -83,6 +70,16 @@ def __str__(self) -> str:
"""Return a string representation of the CharacterClass instance."""
return self.class_type.value

@property
def saving_throws(self) -> List[int]:
"""Return the saving throws for the character at their current class level."""
for level_range in self.class_saving_throws:
if self.current_level in level_range:
return self.class_saving_throws[level_range]

raise ValueError(f"No saving throws available for {self.class_type.value} at level {self.current_level}")


def roll_hp(self, hp_modifier: int = 0) -> DiceRoll:
"""Roll hit points for the character.
Expand Down Expand Up @@ -286,65 +283,6 @@ def level_up(self, hp_modifier: int = 0) -> ClassLevel:
}


saving_throws = {
CharacterClassType.CLERIC: {
AttackType.DEATH_RAY_POISON: 11,
AttackType.MAGIC_WANDS: 12,
AttackType.PARALYSIS_TURN_TO_STONE: 14,
AttackType.DRAGON_BREATH: 16,
AttackType.RODS_STAVES_SPELLS: 15,
},
CharacterClassType.DWARF: {
AttackType.DEATH_RAY_POISON: 10,
AttackType.MAGIC_WANDS: 11,
AttackType.PARALYSIS_TURN_TO_STONE: 12,
AttackType.DRAGON_BREATH: 13,
AttackType.RODS_STAVES_SPELLS: 14,
},
CharacterClassType.ELF: {
AttackType.DEATH_RAY_POISON: 12,
AttackType.MAGIC_WANDS: 13,
AttackType.PARALYSIS_TURN_TO_STONE: 13,
AttackType.DRAGON_BREATH: 15,
AttackType.RODS_STAVES_SPELLS: 15,
},
CharacterClassType.FIGHTER: {
AttackType.DEATH_RAY_POISON: 12,
AttackType.MAGIC_WANDS: 13,
AttackType.PARALYSIS_TURN_TO_STONE: 14,
AttackType.DRAGON_BREATH: 15,
AttackType.RODS_STAVES_SPELLS: 16,
},
CharacterClassType.HALFLING: {
AttackType.DEATH_RAY_POISON: 10,
AttackType.MAGIC_WANDS: 11,
AttackType.PARALYSIS_TURN_TO_STONE: 12,
AttackType.DRAGON_BREATH: 13,
AttackType.RODS_STAVES_SPELLS: 14,
},
CharacterClassType.MAGIC_USER: {
AttackType.DEATH_RAY_POISON: 13,
AttackType.MAGIC_WANDS: 14,
AttackType.PARALYSIS_TURN_TO_STONE: 13,
AttackType.DRAGON_BREATH: 16,
AttackType.RODS_STAVES_SPELLS: 15,
},
CharacterClassType.THIEF: {
AttackType.DEATH_RAY_POISON: 13,
AttackType.MAGIC_WANDS: 14,
AttackType.PARALYSIS_TURN_TO_STONE: 13,
AttackType.DRAGON_BREATH: 16,
AttackType.RODS_STAVES_SPELLS: 15,
},
CharacterClassType.COMMONER: {
AttackType.DEATH_RAY_POISON: 20,
AttackType.MAGIC_WANDS: 20,
AttackType.PARALYSIS_TURN_TO_STONE: 20,
AttackType.DRAGON_BREATH: 20,
AttackType.RODS_STAVES_SPELLS: 20,
},
}


all_character_classes = [
CharacterClass(CharacterClassType.CLERIC),
Expand Down
8 changes: 5 additions & 3 deletions osrlib/osrlib/dungeon.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from random import randint, sample
from typing import List
from enum import Enum
import random
Expand Down Expand Up @@ -126,7 +125,9 @@ def __init__(
self.is_visited = is_visited

def __str__(self):
return f"Location ID: {self.id} Dimensions: {self.dimensions} Exits: {self.exits} Keywords: {self.keywords}"
exits_str = ", ".join(str(exit) for exit in self.exits)
return f"Location ID: {self.id} Dimensions: {self.dimensions} Exits: [{exits_str}] Keywords: {self.keywords}"


@property
def json(self):
Expand Down Expand Up @@ -329,7 +330,8 @@ def move(self, direction: Direction) -> Location:
exit = [exit for exit in self.current_location.exits if exit.direction == direction][0]
except IndexError:
gm.logger.debug(
f"No exit to the {direction.name} from {self.current_location}. The only exits are {self.current_location.exits}."
f"No exit to the {direction.name} from {self.current_location}. The only exits are: "
+ ", ".join(str(exit) for exit in self.current_location.exits) + "."
)
return None

Expand Down
13 changes: 13 additions & 0 deletions osrlib/osrlib/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from enum import Enum

class CharacterClassType(Enum):
"""Enum representing the types of character classes."""

CLERIC = "Cleric"
DWARF = "Dwarf"
ELF = "Elf"
FIGHTER = "Fighter"
HALFLING = "Halfling"
MAGIC_USER = "Magic User"
THIEF = "Thief"
COMMONER = "Commoner"
2 changes: 1 addition & 1 deletion osrlib/osrlib/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from enum import Enum
from typing import Optional, Set

from osrlib import CharacterClassType
from osrlib.enums import CharacterClassType


class ItemType(Enum):
Expand Down
67 changes: 64 additions & 3 deletions osrlib/osrlib/item_factories.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from osrlib import CharacterClassType, Armor, Item, ItemType, Weapon
from osrlib.item import Armor, Item, ItemType, Weapon
from osrlib.enums import CharacterClassType

_armor_combat_classes = {
CharacterClassType.FIGHTER,
Expand Down Expand Up @@ -26,7 +27,22 @@
"Leather Armor": {"ac": -2, "gp_value": 20, "usable_by": _armor_combat_classes | {CharacterClassType.THIEF}},
"Plate Mail": {"ac": -6, "gp_value": 60, "usable_by": _armor_combat_classes},
"Shield": {"ac": -1, "gp_value": 10, "usable_by": _armor_combat_classes},
"Robe": {"ac": 0, "gp_value": 1, "usable_by": {CharacterClassType.MAGIC_USER}},
"Robes": {"ac": 0, "gp_value": 1, "usable_by": {CharacterClassType.MAGIC_USER}},
}

magic_armor_data = {
"Chain Mail +1": {"ac": -5, "gp_value": 400, "usable_by": _armor_combat_classes},
"Chain Mail +2": {"ac": -6, "gp_value": 800, "usable_by": _armor_combat_classes},
"Chain Mail +3": {"ac": -7, "gp_value": 1600, "usable_by": _armor_combat_classes},
"Leather Armor +1": {"ac": -3, "gp_value": 200, "usable_by": _armor_combat_classes | {CharacterClassType.THIEF}},
"Leather Armor +2": {"ac": -4, "gp_value": 400, "usable_by": _armor_combat_classes | {CharacterClassType.THIEF}},
"Leather Armor +3": {"ac": -5, "gp_value": 800, "usable_by": _armor_combat_classes | {CharacterClassType.THIEF}},
"Plate Mail +1": {"ac": -7, "gp_value": 600, "usable_by": _armor_combat_classes},
"Plate Mail +2": {"ac": -8, "gp_value": 1200, "usable_by": _armor_combat_classes},
"Plate Mail +3": {"ac": -9, "gp_value": 2400, "usable_by": _armor_combat_classes},
"Shield +1": {"ac": -2, "gp_value": 100, "usable_by": _armor_combat_classes},
"Shield +2": {"ac": -3, "gp_value": 200, "usable_by": _armor_combat_classes},
"Shield +3": {"ac": -4, "gp_value": 400, "usable_by": _armor_combat_classes},
}

equipment_data = {
Expand Down Expand Up @@ -73,6 +89,51 @@
"War Hammer": {"damage": "1d6", "gp_value": 5, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}}
}

magic_weapon_data = {
"Battle Axe +1": {"damage": "1d8+1", "gp_value": 70, "usable_by": _weapon_combat_classes},
"Battle Axe +2": {"damage": "1d8+2", "gp_value": 140, "usable_by": _weapon_combat_classes},
"Battle Axe +3": {"damage": "1d8+3", "gp_value": 280, "usable_by": _weapon_combat_classes},
"Club +1": {"damage": "1d4+1", "gp_value": 30, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Club +2": {"damage": "1d4+2", "gp_value": 60, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Club +3": {"damage": "1d4+3", "gp_value": 120, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Crossbow +1": {"damage": "1d4+1", "gp_value": 130, "usable_by": _weapon_combat_classes, "range": 160},
"Crossbow +2": {"damage": "1d4+2", "gp_value": 260, "usable_by": _weapon_combat_classes, "range": 160},
"Crossbow +3": {"damage": "1d4+3", "gp_value": 520, "usable_by": _weapon_combat_classes, "range": 160},
"Dagger +1": {"damage": "1d4+1", "gp_value": 30, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Dagger +2": {"damage": "1d4+2", "gp_value": 60, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Dagger +3": {"damage": "1d4+3", "gp_value": 120, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Long Bow +1": {"damage": "1d6+1", "gp_value": 130, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}, "range": 140},
"Long Bow +2": {"damage": "1d6+2", "gp_value": 260, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}, "range": 140},
"Long Bow +3": {"damage": "1d6+3", "gp_value": 520, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}, "range": 140},
"Mace +1": {"damage": "1d6+1", "gp_value": 70, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Mace +2": {"damage": "1d6+2", "gp_value": 140, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Mace +3": {"damage": "1d6+3", "gp_value": 280, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"Pole Arm +1": {"damage": "1d10+1", "gp_value": 70, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF, CharacterClassType.DWARF}},
"Pole Arm +2": {"damage": "1d10+2", "gp_value": 140, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF, CharacterClassType.DWARF}},
"Pole Arm +3": {"damage": "1d10+3", "gp_value": 280, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF, CharacterClassType.DWARF}},
"Short Bow +1": {"damage": "1d6+1", "gp_value": 80, "usable_by": _weapon_combat_classes, "range": 100},
"Short Bow +2": {"damage": "1d6+2", "gp_value": 160, "usable_by": _weapon_combat_classes, "range": 100},
"Short Bow +3": {"damage": "1d6+3", "gp_value": 320, "usable_by": _weapon_combat_classes, "range": 100},
"Silver Dagger +1": {"damage": "1d4+1", "gp_value": 130, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Silver Dagger +2": {"damage": "1d4+2", "gp_value": 260, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Silver Dagger +3": {"damage": "1d4+3", "gp_value": 520, "usable_by": _weapon_combat_classes | {CharacterClassType.MAGIC_USER}, "range": 20},
"Sling +1": {"damage": "1d4+1", "gp_value": 130, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 80},
"Sling +2": {"damage": "1d4+2", "gp_value": 260, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 80},
"Sling +3": {"damage": "1d4+3", "gp_value": 520, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 80},
"Spear +1": {"damage": "1d6+1", "gp_value": 30, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 40},
"Spear +2": {"damage": "1d6+2", "gp_value": 60, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 40},
"Spear +3": {"damage": "1d6+3", "gp_value": 120, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}, "range": 40},
"Sword +1": {"damage": "1d8+1", "gp_value": 90, "usable_by": _weapon_combat_classes},
"Sword +2": {"damage": "1d8+2", "gp_value": 180, "usable_by": _weapon_combat_classes},
"Sword +3": {"damage": "1d8+3", "gp_value": 360, "usable_by": _weapon_combat_classes},
"Two-handed Sword +1": {"damage": "1d10+1", "gp_value": 100, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}},
"Two-handed Sword +2": {"damage": "1d10+2", "gp_value": 200, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}},
"Two-handed Sword +3": {"damage": "1d10+3", "gp_value": 400, "usable_by": {CharacterClassType.FIGHTER, CharacterClassType.ELF}},
"War Hammer +1": {"damage": "1d6+1", "gp_value": 70, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"War Hammer +2": {"damage": "1d6+2", "gp_value": 140, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
"War Hammer +3": {"damage": "1d6+3", "gp_value": 280, "usable_by": _weapon_combat_classes | {CharacterClassType.CLERIC}},
}

class ItemDataNotFoundError(Exception):
"""Raised when item data is not found."""

Expand Down Expand Up @@ -228,7 +289,7 @@ def equip_halfling(character: "PlayerCharacter"):
def equip_magic_user(character: "PlayerCharacter"):
"""Equip a Magic User character with starting gear."""
dagger = WeaponFactory.create_weapon("Dagger")
robe = ArmorFactory.create_armor("Robe")
robe = ArmorFactory.create_armor("Robes")
backpack = EquipmentFactory.create_item("Backpack")
spellbook = EquipmentFactory.create_item("Spell Book")

Expand Down
Loading

0 comments on commit 5c36be5

Please sign in to comment.