Add render_prompt with modal templates
This commit is contained in:
parent
9729e853e1
commit
780501ceed
5 changed files with 503 additions and 6 deletions
|
|
@ -60,7 +60,7 @@ async def cmd_edit(player: Player, args: str) -> None:
|
||||||
player.editor = Editor(
|
player.editor = Editor(
|
||||||
save_callback=save_callback_fn,
|
save_callback=save_callback_fn,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
color_depth=player.color_depth,
|
color_depth=player.color_depth or "16",
|
||||||
initial_content=initial_content,
|
initial_content=initial_content,
|
||||||
)
|
)
|
||||||
player.mode_stack.append("editor")
|
player.mode_stack.append("editor")
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,10 @@ async def cmd_look(player: Player, args: str) -> None:
|
||||||
for x, tile in enumerate(row):
|
for x, tile in enumerate(row):
|
||||||
# Check if this is the player's position
|
# Check if this is the player's position
|
||||||
if x == center_x and y == center_y:
|
if x == center_x and y == center_y:
|
||||||
line.append(colorize_terrain("@", player.color_depth))
|
line.append(colorize_terrain("@", player.color_depth or "16"))
|
||||||
# Check if this is another entity's position
|
# Check if this is another entity's position
|
||||||
elif (x, y) in entity_positions:
|
elif (x, y) in entity_positions:
|
||||||
line.append(colorize_terrain("*", player.color_depth))
|
line.append(colorize_terrain("*", player.color_depth or "16"))
|
||||||
else:
|
else:
|
||||||
# Check for active effects at this world position
|
# Check for active effects at this world position
|
||||||
world_x, world_y = zone.wrap(
|
world_x, world_y = zone.wrap(
|
||||||
|
|
@ -109,7 +109,7 @@ async def cmd_look(player: Player, args: str) -> None:
|
||||||
e = effects[-1]
|
e = effects[-1]
|
||||||
line.append(f"{e.color}{e.char}{RESET}")
|
line.append(f"{e.color}{e.char}{RESET}")
|
||||||
else:
|
else:
|
||||||
line.append(colorize_terrain(tile, player.color_depth))
|
line.append(colorize_terrain(tile, player.color_depth or "16"))
|
||||||
output_lines.append("".join(line))
|
output_lines.append("".join(line))
|
||||||
|
|
||||||
# Build structured output
|
# Build structured output
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class Player(Entity):
|
||||||
paint_mode: bool = False
|
paint_mode: bool = False
|
||||||
painting: bool = False
|
painting: bool = False
|
||||||
paint_brush: str = "."
|
paint_brush: str = "."
|
||||||
|
prompt_template: str | None = None
|
||||||
_last_msdp: dict = field(default_factory=dict, repr=False)
|
_last_msdp: dict = field(default_factory=dict, repr=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -38,8 +39,10 @@ class Player(Entity):
|
||||||
return self.mode_stack[-1]
|
return self.mode_stack[-1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def color_depth(self) -> str:
|
def color_depth(self) -> str | None:
|
||||||
"""Best available color mode: truecolor, 256, or 16."""
|
"""Best available color mode: truecolor, 256, 16, or None if no ANSI."""
|
||||||
|
if not self.caps.ansi:
|
||||||
|
return None
|
||||||
return self.caps.color_depth
|
return self.caps.color_depth
|
||||||
|
|
||||||
async def send(self, message: str) -> None:
|
async def send(self, message: str) -> None:
|
||||||
|
|
|
||||||
113
src/mudlib/prompt.py
Normal file
113
src/mudlib/prompt.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""Pure functions for prompt rendering."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from mudlib.render.colors import colorize
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from mudlib.player import Player
|
||||||
|
|
||||||
|
# Default prompt templates by mode
|
||||||
|
DEFAULT_TEMPLATES: dict[str, str] = {
|
||||||
|
"normal": "{stamina_gauge} {pl} > ",
|
||||||
|
"combat": "{stamina_gauge} {pl} vs {opponent} > ",
|
||||||
|
"editor": "editor> ",
|
||||||
|
"if": "> ",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def render_prompt(player: Player) -> str:
|
||||||
|
"""Render the prompt string based on player state and mode.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player: The player whose prompt to render
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted prompt string with ANSI codes based on player color support
|
||||||
|
"""
|
||||||
|
# Get template from player override or mode default
|
||||||
|
template = player.prompt_template or DEFAULT_TEMPLATES.get(
|
||||||
|
player.mode, "{stamina_gauge} {pl} > "
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build variable dictionary
|
||||||
|
stamina_pct = (
|
||||||
|
int((player.stamina / player.max_stamina) * 100)
|
||||||
|
if player.max_stamina > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute stamina gauge with conditional coloring
|
||||||
|
if stamina_pct >= 60:
|
||||||
|
stamina_gauge = f"{{green}}<{stamina_pct}%>{{/}}"
|
||||||
|
elif stamina_pct >= 30:
|
||||||
|
stamina_gauge = f"{{yellow}}<{stamina_pct}%>{{/}}"
|
||||||
|
else:
|
||||||
|
stamina_gauge = f"{{red}}<{stamina_pct}%>{{/}}"
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"stamina_pct": str(stamina_pct),
|
||||||
|
"stamina": str(round(player.stamina)),
|
||||||
|
"max_stamina": str(round(player.max_stamina)),
|
||||||
|
"stamina_gauge": stamina_gauge,
|
||||||
|
"pl": str(round(player.pl)),
|
||||||
|
"max_pl": str(round(player.max_pl)),
|
||||||
|
"opponent": _get_opponent_name(player),
|
||||||
|
"move": "", # TODO: current attack/defense move name
|
||||||
|
"name": player.name,
|
||||||
|
"mode": player.mode,
|
||||||
|
"x": str(player.x),
|
||||||
|
"y": str(player.y),
|
||||||
|
"combat_state": _get_combat_state(player),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Substitute variables in template
|
||||||
|
result = template
|
||||||
|
for key, value in variables.items():
|
||||||
|
result = result.replace(f"{{{key}}}", value)
|
||||||
|
|
||||||
|
# Process color tags based on player's color support
|
||||||
|
result = colorize(result, player.color_depth)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _get_opponent_name(player: Player) -> str:
|
||||||
|
"""Get the name of the player's combat opponent if any.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player: The player to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Opponent name if in combat, empty string otherwise
|
||||||
|
"""
|
||||||
|
from mudlib.combat.engine import get_encounter
|
||||||
|
|
||||||
|
encounter = get_encounter(player)
|
||||||
|
if encounter is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Determine who the opponent is (the other entity in the encounter)
|
||||||
|
if encounter.attacker is player:
|
||||||
|
return encounter.defender.name
|
||||||
|
return encounter.attacker.name
|
||||||
|
|
||||||
|
|
||||||
|
def _get_combat_state(player: Player) -> str:
|
||||||
|
"""Get the current combat state for the player.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player: The player to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Combat state string: "idle", "telegraph", "window", or "resolve"
|
||||||
|
"""
|
||||||
|
from mudlib.combat.engine import get_encounter
|
||||||
|
|
||||||
|
encounter = get_encounter(player)
|
||||||
|
if encounter is None:
|
||||||
|
return "idle"
|
||||||
|
|
||||||
|
return encounter.state.value
|
||||||
381
tests/test_prompt.py
Normal file
381
tests/test_prompt.py
Normal file
|
|
@ -0,0 +1,381 @@
|
||||||
|
"""Tests for prompt rendering system."""
|
||||||
|
|
||||||
|
from mudlib.caps import ClientCaps
|
||||||
|
from mudlib.combat.encounter import CombatEncounter
|
||||||
|
from mudlib.combat.engine import active_encounters
|
||||||
|
from mudlib.entity import Entity
|
||||||
|
from mudlib.player import Player
|
||||||
|
from mudlib.prompt import render_prompt
|
||||||
|
|
||||||
|
|
||||||
|
def setup_function():
|
||||||
|
"""Clear global state before each test."""
|
||||||
|
active_encounters.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_function():
|
||||||
|
"""Clear global state after each test."""
|
||||||
|
active_encounters.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def test_normal_mode_prompt():
|
||||||
|
"""Normal mode shows stamina gauge and power level."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 50% is in 30-59% range, should be yellow
|
||||||
|
assert result == "\033[33m<50%>\033[0m 200 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_combat_mode_with_opponent():
|
||||||
|
"""Combat mode includes opponent name."""
|
||||||
|
player = Player(
|
||||||
|
name="Goku",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal", "combat"],
|
||||||
|
)
|
||||||
|
opponent = Entity(name="Vegeta", pl=150.0)
|
||||||
|
|
||||||
|
encounter = CombatEncounter(attacker=player, defender=opponent)
|
||||||
|
active_encounters.append(encounter)
|
||||||
|
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 50% is in 30-59% range, should be yellow
|
||||||
|
assert result == "\033[33m<50%>\033[0m 200 vs Vegeta > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_combat_mode_as_defender():
|
||||||
|
"""Combat mode works when player is the defender."""
|
||||||
|
player = Player(
|
||||||
|
name="Goku",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal", "combat"],
|
||||||
|
)
|
||||||
|
opponent = Entity(name="Vegeta", pl=150.0)
|
||||||
|
|
||||||
|
encounter = CombatEncounter(attacker=opponent, defender=player)
|
||||||
|
active_encounters.append(encounter)
|
||||||
|
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 50% is in 30-59% range, should be yellow
|
||||||
|
assert result == "\033[33m<50%>\033[0m 200 vs Vegeta > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_editor_mode_static_prompt():
|
||||||
|
"""Editor mode returns static prompt."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal", "editor"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "editor> "
|
||||||
|
|
||||||
|
|
||||||
|
def test_if_mode_static_prompt():
|
||||||
|
"""IF mode returns minimal static prompt."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal", "if"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "> "
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_stamina():
|
||||||
|
"""Zero stamina renders as 0% in red."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=0.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 0% is < 30%, should be red
|
||||||
|
assert result == "\033[31m<0%>\033[0m 200 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_max_stamina():
|
||||||
|
"""Full stamina renders as 100% in green."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=100.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 100% is >= 60%, should be green
|
||||||
|
assert result == "\033[32m<100%>\033[0m 200 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_percentage():
|
||||||
|
"""Stamina percentage rounds to integer."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=33.3,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 33% is in 30-59% range, should be yellow
|
||||||
|
assert result == "\033[33m<33%>\033[0m 200 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_pl():
|
||||||
|
"""Power level rounds to integer."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=199.7,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# 50% is in 30-59% range, should be yellow
|
||||||
|
assert result == "\033[33m<50%>\033[0m 200 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_template_overrides_default():
|
||||||
|
"""Player can set custom prompt template."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="[{pl}] > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "[200] > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_template_all_variables():
|
||||||
|
"""Custom template can use all supported variables."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
max_pl=250.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="{stamina}/{stamina_pct}% {pl}/{max_pl} > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# No color tags in this custom template, so no ANSI codes
|
||||||
|
assert result == "50/50% 200/250 > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_variable_left_as_is():
|
||||||
|
"""Unknown variables in template are not substituted."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="{pl} {unknown} > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "200 {unknown} > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_opponent_var_in_normal_mode_empty():
|
||||||
|
"""Opponent variable is empty string when not in combat."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="{pl} vs {opponent} > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "200 vs > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_color_support():
|
||||||
|
"""Players with no ANSI support get tags stripped."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
caps=ClientCaps(ansi=False),
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have no ANSI codes, just text
|
||||||
|
assert result == "<50%> 200 > "
|
||||||
|
assert "\033[" not in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_green():
|
||||||
|
"""Stamina >= 60% renders green gauge."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=80.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have green color code
|
||||||
|
assert "\033[32m<80%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_yellow():
|
||||||
|
"""Stamina 30-59% renders yellow gauge."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=45.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have yellow color code
|
||||||
|
assert "\033[33m<45%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_red():
|
||||||
|
"""Stamina < 30% renders red gauge."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=20.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have red color code
|
||||||
|
assert "\033[31m<20%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_boundary_60():
|
||||||
|
"""Stamina exactly 60% renders green."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=60.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have green color code
|
||||||
|
assert "\033[32m<60%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_boundary_30():
|
||||||
|
"""Stamina exactly 30% renders yellow."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=30.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have yellow color code
|
||||||
|
assert "\033[33m<30%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_stamina_gauge_boundary_29():
|
||||||
|
"""Stamina at 29% renders red."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=29.5,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
# Should have red color code
|
||||||
|
assert "\033[31m<29%>\033[0m" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_max_stamina():
|
||||||
|
"""Zero max_stamina doesn't crash."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=0.0,
|
||||||
|
max_stamina=0.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert "0%" in result
|
||||||
|
|
||||||
|
|
||||||
|
def test_name_variable():
|
||||||
|
"""Name variable substitutes player name."""
|
||||||
|
player = Player(
|
||||||
|
name="Goku",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="{name} > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "Goku > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_mode_variable():
|
||||||
|
"""Mode variable substitutes current mode."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal", "combat"],
|
||||||
|
prompt_template="[{mode}] > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "[combat] > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_coordinates_variables():
|
||||||
|
"""X and Y variables substitute coordinates."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
x=42,
|
||||||
|
y=13,
|
||||||
|
prompt_template="({x},{y}) > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "(42,13) > "
|
||||||
|
|
||||||
|
|
||||||
|
def test_combat_state_idle():
|
||||||
|
"""Combat state is 'idle' when not in combat."""
|
||||||
|
player = Player(
|
||||||
|
name="Test",
|
||||||
|
stamina=50.0,
|
||||||
|
max_stamina=100.0,
|
||||||
|
pl=200.0,
|
||||||
|
mode_stack=["normal"],
|
||||||
|
prompt_template="[{combat_state}] > ",
|
||||||
|
)
|
||||||
|
result = render_prompt(player)
|
||||||
|
assert result == "[idle] > "
|
||||||
Loading…
Reference in a new issue