Skip to content

Commit

Permalink
Merge pull request #22 from mmacy/exploration-work
Browse files Browse the repository at this point in the history
rudimentary explore-dungeon-and-fight-stuff functionality
  • Loading branch information
mmacy committed Nov 19, 2023
2 parents 967a793 + 71a1f14 commit 92cc355
Show file tree
Hide file tree
Showing 16 changed files with 822 additions and 319 deletions.
61 changes: 0 additions & 61 deletions osrgame/osrgame/example_adventure.py

This file was deleted.

57 changes: 18 additions & 39 deletions osrgame/osrgame/osrgame.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from textual.app import App, ComposeResult
from screen import CharacterScreen, WelcomeScreen, ExploreScreen

from osrlib import CharacterClassType, PlayerCharacter, Armor, Item, Weapon, Party, Adventure, Dungeon, DungeonMaster, Location, Exit, Direction
from osrlib.adventure import Adventure
from osrlib.dungeon import Dungeon
from osrlib.party import get_default_party
from screen import CharacterScreen, WelcomeScreen, ExploreScreen


class OSRConsole(App):
Expand All @@ -14,43 +15,6 @@ class OSRConsole(App):
("q", "quit", "Quit"),
]

# Set up a test party
default_party = get_default_party()
player_character = default_party.get_character_by_index(0)
armor = Armor("Chain Mail", -5, usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 2, gp_value = 40)
shield = Armor("Shield", -1, usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 2, gp_value = 10)
sword = Weapon("Sword", "1d8", usable_by_classes = {CharacterClassType.FIGHTER}, max_equipped = 1, gp_value = 10)
backpack = Item("Backpack", gp_value = 5)
wineskin = Item("Wineskin", gp_value = 1)
iron_rations = Item("Iron Rations", gp_value = 15)
torch = Item("Torch", gp_value = 1)
player_character.inventory.add_item(armor)
player_character.inventory.add_item(shield)
player_character.inventory.add_item(sword)
player_character.inventory.add_item(backpack)
player_character.inventory.add_item(wineskin)
player_character.inventory.add_item(iron_rations)
player_character.inventory.add_item(torch)
player_character.inventory.equip_item(armor)
player_character.inventory.equip_item(shield)
player_character.inventory.equip_item(sword)

# Set up a test dungeon
loc0 = Location(9999, 10, 10, [Exit(Direction.NORTH, 1)], ["Randoville", "tavern", "The Beer and Whine"], None)
loc1 = Location(1, 40, 30, [Exit(Direction.SOUTH, 0), Exit(Direction.NORTH, 2)], ["just outside", "dwarven mines", "ancient", "entrance", "cold wind", "dragon rumors"], None)
loc2 = Location(2, 10, 10, [Exit(Direction.SOUTH, 1), Exit(Direction.NORTH, 3)], ["vestibule", "large entry doors", "dirty stone", "carvings"], None)
loc3 = Location(3, 5, 5, [Exit(Direction.SOUTH, 2)], ["guard station", "broken weapons", "bat droppings"], None)
dungeon = Dungeon("Mine of Deepness - 1", "An ancient lost dwarven mine.", [loc0, loc1, loc2, loc3], 9999)

# Set up a test adventure
adventure = Adventure("Sparky's Not-So-Secret Secret")
adventure.introduction = "Deep in the heart of the Tall Icy Mountains lies the long abandoned Mine of Deepness, a once-thriving dwarven operation now reduced to whispered rumors and tavern tales. Legend holds that a fearsome dragon named Sparky has claimed the labyrinthine tunnels as its lair, its fiery breath illuminating the darkened corridors once chiseled with dwarven precision. The beleaguered town of Randoville has suffered a series of devastating raids, livestock and treasure vanishing in plumes of smoke and flame. Mayor Swiggins Chuggery, desperate and out of options, has commissioned your party to delve into the mine, confirm the presence of the mythical beast, and eliminate the threat that hangs like a dark cloud over the land."
adventure.add_dungeon(dungeon)
adventure.set_active_dungeon(dungeon)

# Initialize the Dungeon Master
#dungeon_master = DungeonMaster(adventure)

SCREENS = {"scr_character": CharacterScreen(),
"scr_welcome": WelcomeScreen(),
"scr_explore": ExploreScreen()}
Expand All @@ -74,6 +38,21 @@ def action_quit(self) -> None:
"""Quit the application."""
self.exit()

adventure = Adventure("Whispers in the Mystic Forest")
adventure.introduction = "In the heart of the cursed Mystic Forest, a tale as old as time stirs once again. Legends speak of Glofarnux, an ancient wizard lich whose thirst for arcane knowledge knew no bounds. His lair, known as the Dungeon of the Mad Mage, has remained hidden for centuries, its entrance concealed within a secluded glade, untouched by the ravages of time. Brave adventurers are called to unravel the mysteries lying in the depths of this forgotten dungeon. Within its walls, echoes of the past mingle with the shadows of the present, challenging all who dare to uncover the secrets of Glofarnux and the arcane magic that pulsates through the very stones of the dungeon. Your journey begins at the edge of the Mystic Forest, where the line between myth and reality blurs, and the adventure of a lifetime awaits."

dungeon = Dungeon.get_random_dungeon("Dungeon of the Mad Mage",
"The first level of the home of the ancient wizard lich Glofarnux, its "
"entrance hidden in a forgotten glade deep in the cursed Mystic Forest.",
num_locations=50)
dungeon.set_start_location(1)

if dungeon.validate_location_connections():
print("Dungeon location connection graph is valid.")

adventure.add_dungeon(dungeon)
adventure.set_active_dungeon(dungeon)
adventure.set_active_party(get_default_party())

app = OSRConsole()
if __name__ == "__main__":
Expand Down
75 changes: 54 additions & 21 deletions osrgame/osrgame/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from textual.screen import Screen
from textual.widgets import Button, Header, Footer, Log, Placeholder

from osrlib import DungeonMaster
from osrlib.dungeon_master import DungeonMaster
from osrlib.dungeon import Direction
from osrlib.utils import wrap_text
from example_adventure import adventure
from widgets import CharacterStats, AbilityTable, ItemTable, SavingThrows, CharacterScreenButtons


Expand Down Expand Up @@ -64,6 +63,8 @@ class CharacterScreen(Screen):
BINDINGS = [
("k", "clear_log", "Clear log"),
("escape", "app.pop_screen", "Pop screen"),
("n", "next_character", "Next character"),
("ctrl+n", "new_character", "New character"),
]

def compose(self) -> ComposeResult:
Expand All @@ -79,35 +80,42 @@ def compose(self) -> ComposeResult:
def on_mount(self) -> None:
"""Perform actions when the widget is mounted."""
self.query_one(Log).border_subtitle = "LOG"
self.query_one(CharacterStats).pc_name = self.app.player_character.name
self.query_one(CharacterStats).pc_class = self.app.player_character.character_class
self.query_one(CharacterStats).pc_level = self.app.player_character.character_class.current_level
self.query_one(CharacterStats).pc_hp = self.app.player_character.character_class.hp
self.query_one(CharacterStats).pc_ac = self.app.player_character.armor_class
self.query_one(CharacterStats).pc_name = self.app.adventure.active_party.active_character.name #.player_character.name
self.query_one(CharacterStats).pc_class = self.app.adventure.active_party.active_character.character_class
self.query_one(CharacterStats).pc_level = self.app.adventure.active_party.active_character.character_class.current_level
self.query_one(CharacterStats).pc_hp = self.app.adventure.active_party.active_character.character_class.hp
self.query_one(CharacterStats).pc_ac = self.app.adventure.active_party.active_character.armor_class
self.query_one(AbilityTable).update_table()
self.query_one(SavingThrows).update_table()
self.query_one(ItemTable).items = self.app.player_character.inventory.all_items
self.query_one(ItemTable).items = self.app.adventure.active_party.active_character.inventory.all_items

def on_button_pressed(self, event: Button.Pressed) -> None:
pc = self.app.player_character
pc = self.app.adventure.active_party.active_character
if event.button.id == "btn_roll_abilities":
self.reroll()
self.query_one(CharacterStats).pc_ac = pc.armor_class

elif event.button.id == "btn_roll_hp":
hp_roll = pc.roll_hp()
pc.character_class.hp = max(hp_roll.total_with_modifier, 1)
pc.character_class.max_hp = max(hp_roll.total_with_modifier, 1)
pc.character_class.hp = pc.character_class.max_hp
roll_string = hp_roll.pretty_print()
self.query_one(Log).write_line(roll_string)
self.query_one(CharacterStats).pc_hp = pc.character_class.hp
self.query_one(CharacterStats).pc_hp = pc.character_class.max_hp

def action_clear_log(self) -> None:
"""An action to clear the log."""
self.query_one(Log).clear()

def action_next_character(self) -> None:
"""An action to switch to the next character in the party."""
self.app.adventure.active_party.set_next_character_as_active()
self.on_mount()


def reroll(self):
"""Rolls the ability scores of the active character."""
self.app.player_character.roll_abilities()
self.app.adventure.active_party.active_character.roll_abilities()
self.query_one(AbilityTable).update_table()


Expand Down Expand Up @@ -172,7 +180,7 @@ class ExploreScreen(Screen):
("?", "summarize", "Summarize session"),
]

dungeon_master = DungeonMaster(adventure)
dungeon_master = None

def compose(self) -> ComposeResult:
yield Header(show_clock=True)
Expand All @@ -188,29 +196,54 @@ def on_mount(self) -> None:
def action_start_session(self) -> None:
"""Start a new session."""

player_log = self.query_one("#player_log")
dm_log = self.query_one("#dm_log")

self.dungeon_master = DungeonMaster(self.app.adventure)
dm_response = self.dungeon_master.start_session()

# TODO: Need to do this automatically (move the party to the first "real" location)
dm_response = self.dungeon_master.move_party(Direction.NORTH)
# Move the party to the first location
first_exit = self.dungeon_master.adventure.active_dungeon.get_location_by_id(1).exits[0]
dm_response = self.dungeon_master.move_party(first_exit.direction)

self.query_one("#player_log").clear()
self.query_one("#player_log").write_line("The party stands ready.")
self.query_one("#player_log").write_line("---")
self.query_one("#dm_log").write_line(wrap_text(dm_response))
self.query_one("#dm_log").write_line("---")
player_log.clear()
player_log.write_line("The party stands ready.")
player_log.write_line("---")
dm_log.write_line("> " + str(self.dungeon_master.adventure.active_dungeon.current_location))
dm_log.write_line(wrap_text(dm_response))
dm_log.write_line("---")

def action_quit(self) -> None:
"""Quit the application."""
self.app.exit()

def perform_move_action(self, direction: Direction, log_message: str) -> None:
"""Perform a move action in a given direction."""
"""Move the party in the specified direction, execute battle (if any), and log the results."""

self.query_one("#player_log").write_line(log_message)
self.query_one("#player_log").write_line("---")
dm_response = self.dungeon_master.move_party(direction)
self.query_one("#dm_log").write_line("> " + str(self.dungeon_master.adventure.active_dungeon.current_location))
self.query_one("#dm_log").write_line(wrap_text(dm_response))
self.check_for_encounter()
self.query_one("#dm_log").write_line("---")

def check_for_encounter(self) -> None:
"""Check for an encounter and execute battle if there are monsters in the encounter."""
if self.dungeon_master.adventure.active_dungeon.current_location.encounter and not self.dungeon_master.adventure.active_dungeon.current_location.encounter.is_ended:

encounter = self.dungeon_master.adventure.active_dungeon.current_location.encounter
encounter.start_encounter(self.dungeon_master.adventure.active_party)
encounter_log = encounter.get_encounter_log()

self.query_one("#dm_log").write_line("---")
dm_response = self.dungeon_master.summarize_battle(encounter_log)
self.query_one("#dm_log").write_line(wrap_text(dm_response))

self.query_one("#player_log").write_line("Encounter!")
self.query_one("#player_log").write_line(str(self.dungeon_master.adventure.active_party))
self.query_one("#player_log").write_line("---")

def action_move_north(self) -> None:
"""Move the party north."""
self.perform_move_action(Direction.NORTH, "Moving north...")
Expand Down
2 changes: 1 addition & 1 deletion osrgame/osrgame/screen.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ CharacterScreen {
ExploreScreen {
layout: grid;
grid-size: 2;
grid-columns: 0.5fr 1.5fr;
grid-columns: 0.75fr 1.25fr;
grid-rows: 100%;
background: $surface;
}
Expand Down
4 changes: 2 additions & 2 deletions osrgame/osrgame/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def on_mount(self) -> None:
table.add_columns("Ability", score_column, "Modifiers")

def update_table(self):
pc = self.app.player_character
pc = self.app.adventure.active_party.active_character
table = self.query_one(DataTable)
table.clear()
for k, v in pc.abilities.items():
Expand All @@ -90,7 +90,7 @@ def on_mount(self) -> None:
table.add_columns("Saving Throw", score_column)

def update_table(self):
pc = self.app.player_character
pc = self.app.adventure.active_party.active_character
table = self.query_one(DataTable)
table.clear()
for k, v in pc.character_class.saving_throws.items():
Expand Down
Loading

0 comments on commit 92cc355

Please sign in to comment.