From 7685d572864502eedf68d8c31a4c6eebe94d488e Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 27 Mar 2025 13:21:08 +1100 Subject: [PATCH] HP Bar works, msg logs work, WIP part 7 --- source/actions.py | 23 ++++++++++++++--- source/colors.py | 15 +++++++++++ source/components/fighter.py | 9 ++++--- source/engine.py | 24 +++++++++++++++--- source/entity_types.py | 4 +-- source/main.py | 7 +++++- source/message_log.py | 49 ++++++++++++++++++++++++++++++++++++ source/render_functions.py | 15 +++++++++++ 8 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 source/colors.py create mode 100644 source/message_log.py create mode 100644 source/render_functions.py diff --git a/source/actions.py b/source/actions.py index cce0c16..6e07a10 100644 --- a/source/actions.py +++ b/source/actions.py @@ -1,5 +1,7 @@ from typing import Any +import colors + class BaseAction: entity: Any @@ -57,14 +59,27 @@ class MeleeAction(DirectionAction): if not target: return False - + + #apply damage damage = self.entity.fighter.attack - target.fighter.defense + target.fighter.current_hp -= damage + + #calculate output + engine = self.entity.floor_map.engine + msg_text = f"{self.entity.name} attacked {target.name}" + msg_color = colors.white + + if self.entity is engine.player: + msg_color = colors.player_atk + else: + msg_color = colors.enemy_atk if damage > 0: - print(f"{self.entity.name} attacked {target.name} for {damage} damage") - target.fighter.current_hp -= damage + msg_text += f" for {damage} damage" else: - print(f"{self.entity.name} attacked {target.name} but was ineffective") + msg_text += f" but was ineffective" + + engine.message_log.add_message(text = msg_text, fg=msg_color) return True diff --git a/source/colors.py b/source/colors.py new file mode 100644 index 0000000..d446790 --- /dev/null +++ b/source/colors.py @@ -0,0 +1,15 @@ +#copy/pasted, because reasons +white = (0xFF, 0xFF, 0xFF) +black = (0x0, 0x0, 0x0) + +player_atk = (0xE0, 0xE0, 0xE0) +enemy_atk = (0xFF, 0xC0, 0xC0) + +player_die = (0xFF, 0x30, 0x30) +enemy_die = (0xFF, 0xA0, 0x30) + +welcome_text = (0x20, 0xA0, 0xFF) + +bar_text = white +bar_filled = (0x0, 0x60, 0x0) +bar_empty = (0x40, 0x10, 0x10) diff --git a/source/components/fighter.py b/source/components/fighter.py index a18b6f4..cdb80ea 100644 --- a/source/components/fighter.py +++ b/source/components/fighter.py @@ -2,6 +2,7 @@ from __future__ import annotations from typing import Any +import colors from components.base_component import BaseComponent class Fighter(BaseComponent): @@ -29,13 +30,15 @@ class Fighter(BaseComponent): def die_and_despawn(self) -> None: - if self.entity is self.entity.floor_map.engine.player and self.entity.ai: + engine = self.entity.floor_map.engine + + if self.entity is engine.player and self.entity.ai: from event_handler import GameOverEventHandler self.entity.floor_map.engine.event_handler = GameOverEventHandler(self.entity.floor_map.engine) - print("You died") + engine.message_log.add_message("You died.", colors.player_die) else: - print(f"The {self.entity.name} died") + engine.message_log.add_message(f"The {self.entity.name} died", colors.enemy_die) self.entity.char = "%" self.entity.color = (191, 0, 0) diff --git a/source/engine.py b/source/engine.py index 3a9ee07..6a977a7 100644 --- a/source/engine.py +++ b/source/engine.py @@ -2,16 +2,23 @@ from tcod.context import Context from tcod.console import Console from tcod.map import compute_fov +from message_log import Message, MessageLog +from render_functions import render_hp_bar + import entity_types from floor_map import FloorMap #TODO: replace with "DungeonMap" class Engine: - def __init__(self, floor_map: FloorMap): + def __init__(self, floor_map: FloorMap, intro_msg: Message = None): from event_handler import InGameEventHandler self.event_handler = InGameEventHandler(self) self.floor_map = floor_map self.floor_map.engine = self #references everywhere! + self.message_log = MessageLog() + if intro_msg: + self.message_log.push_message(intro_msg) + #grab the player object self.player = self.floor_map.player @@ -38,9 +45,18 @@ class Engine: self.floor_map.render(console) #UI - console.print( - x=1, y=47, - string=f"HP: {self.player.fighter.current_hp}/{self.player.fighter.current_hp}", + render_hp_bar( + console = console, + current_value = self.player.fighter.current_hp, + max_value = self.player.fighter.maximum_hp, + total_width = 20 + ) + self.message_log.render( + console=console, + x=21, + y=45 - 5, + width = 40, + height = 5 ) #send to the screen diff --git a/source/entity_types.py b/source/entity_types.py index 96a7af4..18c7d61 100644 --- a/source/entity_types.py +++ b/source/entity_types.py @@ -8,7 +8,7 @@ player = Actor( name = "Player", walkable = False, ai_class = BaseAI, - fighter = Fighter(hp = 10, attack = 2, defense = 0), + fighter = Fighter(hp = 10, attack = 2, defense = 1), ) #gobbos @@ -18,7 +18,7 @@ gobbo = Actor( name = "Gobbo", walkable = False, ai_class = AttackOnSight, - fighter = Fighter(hp = 5, attack = 2, defense = 0), + fighter = Fighter(hp = 5, attack = 1, defense = 0), ) gobbo_red = Actor( diff --git a/source/main.py b/source/main.py index 396fed5..2b46ef0 100755 --- a/source/main.py +++ b/source/main.py @@ -3,6 +3,8 @@ import tcod from engine import Engine from procgen import generate_floor_map +from message_log import Message +import colors def main() -> None: #tcod stuff @@ -16,6 +18,8 @@ def main() -> None: w, h = context.recommended_console_size(min_columns=10, min_rows=10) + print (w,h) + console = tcod.console.Console( width = w, height = h + 5, @@ -24,7 +28,8 @@ def main() -> None: engine = Engine( #is created externally, because - floor_map = generate_floor_map(80, 45, 10, 10) + floor_map = generate_floor_map(80, 45, 10, 10), + intro_msg = Message("Welcome to the Cave of Gobbos!", colors.welcome_text) ) #game loop that never returns diff --git a/source/message_log.py b/source/message_log.py new file mode 100644 index 0000000..3aefa0f --- /dev/null +++ b/source/message_log.py @@ -0,0 +1,49 @@ +from typing import List, Reversible, Tuple +from textwrap import TextWrapper + +from tcod.console import Console + +import colors + + +class Message: + def __init__(self, text: str, fg: Tuple[int, int, int] = colors.white, count: int = 1): + self.raw_text = text + self.fg = fg + self.count = count + + @property + def full_text(self) -> str: + if self.count > 1: + return f"{self.raw_text} (x{self.count})" + return self.raw_text + + +class MessageLog: + def __init__(self): + self.messages: List[Message] = [] + + def add_message(self, text: str, fg: Tuple[int, int, int] = colors.white, *, stack: bool = True) -> None: + if stack and self.messages and text == self.messages[-1].raw_text: + self.messages[-1].count += 1 + else: + self.messages.append(Message(text, fg)) + + def push_message(self, msg: Message) -> None: + self.messages.append(msg) + + def render(self, console: Console, x: int, y: int, width: int, height: int) -> None: + self.render_messages(console, x, y, width, height, self.messages) + + @staticmethod + def render_messages(console: Console, x: int, y: int, width: int, height: int, messages: Reversible[Message]) -> None: + y_offset = height - 1 + + wrapper = TextWrapper(width=width, subsequent_indent = " ") + + for message in reversed(messages): + for line in reversed(wrapper.wrap(message.full_text)): #oh, neat + console.print(x=x,y=y + y_offset,string=line,fg=message.fg) + y_offset -= 1 + if y_offset < 0: + return \ No newline at end of file diff --git a/source/render_functions.py b/source/render_functions.py new file mode 100644 index 0000000..352fbd8 --- /dev/null +++ b/source/render_functions.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import colors + +from tcod.console import Console + +def render_hp_bar(console: Console, current_value: int, max_value: int, total_width: int) -> None: + bar_width = int(float(current_value) / max_value * total_width) + + console.draw_rect(x=0, y=45, width=total_width, height=1, ch=1, bg=colors.bar_empty) + + if bar_width > 0: + console.draw_rect(x=0, y=45, width=bar_width, height=1, ch=1, bg=colors.bar_filled) + + console.print(x=1, y=45, string=f"HP: {current_value}/{max_value}", fg=colors.bar_text) \ No newline at end of file