Split commands and skills into separate listings
This commit is contained in:
parent
a8917da59e
commit
529320acb5
2 changed files with 79 additions and 56 deletions
|
|
@ -175,29 +175,21 @@ async def cmd_commands(player: Player, args: str) -> None:
|
||||||
seen.add(defn_id)
|
seen.add(defn_id)
|
||||||
unique_commands.append(defn)
|
unique_commands.append(defn)
|
||||||
|
|
||||||
# Group commands by type
|
# Group commands by type (excluding combat moves)
|
||||||
movement: list[CommandDefinition] = []
|
movement: list[CommandDefinition] = []
|
||||||
combat_attack: list[CommandDefinition] = []
|
|
||||||
combat_defend: list[CommandDefinition] = []
|
|
||||||
other: list[CommandDefinition] = []
|
other: list[CommandDefinition] = []
|
||||||
|
|
||||||
for defn in unique_commands:
|
for defn in unique_commands:
|
||||||
# Check if it's a movement command
|
# Check if it's a movement command
|
||||||
if defn.name in DIRECTIONS:
|
if defn.name in DIRECTIONS:
|
||||||
movement.append(defn)
|
movement.append(defn)
|
||||||
# Check if it's a combat attack
|
# Skip combat attacks and defenses
|
||||||
elif defn.help.startswith("Attack with"):
|
elif defn.help.startswith("Attack with") or defn.help.startswith("Defend with"):
|
||||||
combat_attack.append(defn)
|
continue
|
||||||
# Check if it's a combat defense
|
|
||||||
elif defn.help.startswith("Defend with"):
|
|
||||||
combat_defend.append(defn)
|
|
||||||
# Everything else
|
# Everything else
|
||||||
else:
|
else:
|
||||||
other.append(defn)
|
other.append(defn)
|
||||||
|
|
||||||
# Format output
|
|
||||||
output_lines = []
|
|
||||||
|
|
||||||
def format_command(defn: CommandDefinition) -> str:
|
def format_command(defn: CommandDefinition) -> str:
|
||||||
"""Format a command with its aliases in parens."""
|
"""Format a command with its aliases in parens."""
|
||||||
if defn.aliases:
|
if defn.aliases:
|
||||||
|
|
@ -205,27 +197,60 @@ async def cmd_commands(player: Player, args: str) -> None:
|
||||||
return f"{defn.name}({aliases_str})"
|
return f"{defn.name}({aliases_str})"
|
||||||
return defn.name
|
return defn.name
|
||||||
|
|
||||||
if movement:
|
all_commands = movement + other
|
||||||
movement.sort(key=lambda d: d.name)
|
all_commands.sort(key=lambda d: d.name)
|
||||||
movement_str = ", ".join(format_command(d) for d in movement)
|
names = [format_command(d) for d in all_commands]
|
||||||
output_lines.append(f"Movement: {movement_str}")
|
|
||||||
|
|
||||||
if combat_attack:
|
await player.send(" ".join(names) + "\r\n")
|
||||||
combat_attack.sort(key=lambda d: d.name)
|
|
||||||
attack_str = ", ".join(format_command(d) for d in combat_attack)
|
|
||||||
output_lines.append(f"Combat (attack): {attack_str}")
|
|
||||||
|
|
||||||
if combat_defend:
|
|
||||||
combat_defend.sort(key=lambda d: d.name)
|
|
||||||
defend_str = ", ".join(format_command(d) for d in combat_defend)
|
|
||||||
output_lines.append(f"Combat (defend): {defend_str}")
|
|
||||||
|
|
||||||
if other:
|
async def cmd_skills(player: Player, args: str) -> None:
|
||||||
other.sort(key=lambda d: d.name)
|
"""List all combat skills (attacks and defenses), or show detail for one.
|
||||||
other_str = ", ".join(format_command(d) for d in other)
|
|
||||||
output_lines.append(f"Other: {other_str}")
|
|
||||||
|
|
||||||
await player.send("\r\n".join(output_lines) + "\r\n")
|
Args:
|
||||||
|
player: The player executing the command
|
||||||
|
args: Command arguments (skill name for detail view, empty for list)
|
||||||
|
"""
|
||||||
|
# If args provided, show detail view for that skill
|
||||||
|
if args.strip():
|
||||||
|
await _show_command_detail(player, args.strip())
|
||||||
|
return
|
||||||
|
|
||||||
|
# Otherwise, show full list
|
||||||
|
# Collect unique commands by CommandDefinition id (avoids alias duplication)
|
||||||
|
seen: set[int] = set()
|
||||||
|
unique_commands: list[CommandDefinition] = []
|
||||||
|
|
||||||
|
for defn in _registry.values():
|
||||||
|
defn_id = id(defn)
|
||||||
|
if defn_id not in seen and not defn.hidden:
|
||||||
|
seen.add(defn_id)
|
||||||
|
unique_commands.append(defn)
|
||||||
|
|
||||||
|
# Group combat moves
|
||||||
|
attacks: list[CommandDefinition] = []
|
||||||
|
defenses: list[CommandDefinition] = []
|
||||||
|
|
||||||
|
for defn in unique_commands:
|
||||||
|
# Check if it's a combat attack
|
||||||
|
if defn.help.startswith("Attack with"):
|
||||||
|
attacks.append(defn)
|
||||||
|
# Check if it's a combat defense
|
||||||
|
elif defn.help.startswith("Defend with"):
|
||||||
|
defenses.append(defn)
|
||||||
|
|
||||||
|
def format_command(defn: CommandDefinition) -> str:
|
||||||
|
"""Format a command with its aliases in parens."""
|
||||||
|
if defn.aliases:
|
||||||
|
aliases_str = ", ".join(defn.aliases)
|
||||||
|
return f"{defn.name}({aliases_str})"
|
||||||
|
return defn.name
|
||||||
|
|
||||||
|
all_skills = attacks + defenses
|
||||||
|
all_skills.sort(key=lambda d: d.name)
|
||||||
|
names = [format_command(d) for d in all_skills]
|
||||||
|
|
||||||
|
await player.send(" ".join(names) + "\r\n")
|
||||||
|
|
||||||
|
|
||||||
# Register the commands command
|
# Register the commands command
|
||||||
|
|
@ -238,3 +263,14 @@ register(
|
||||||
help="list available commands",
|
help="list available commands",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Register the skills command
|
||||||
|
register(
|
||||||
|
CommandDefinition(
|
||||||
|
"skills",
|
||||||
|
cmd_skills,
|
||||||
|
aliases=[],
|
||||||
|
mode="*",
|
||||||
|
help="list combat skills",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -84,16 +84,16 @@ async def test_commands_lists_all_commands(player):
|
||||||
# Collect all output
|
# Collect all output
|
||||||
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
# Should contain at least movement and other groups
|
|
||||||
assert "Movement:" in output
|
|
||||||
assert "Other:" in output
|
|
||||||
|
|
||||||
# Should show some specific commands with their primary names
|
# Should show some specific commands with their primary names
|
||||||
assert "north" in output
|
assert "north" in output
|
||||||
assert "look" in output
|
assert "look" in output
|
||||||
assert "quit" in output
|
assert "quit" in output
|
||||||
assert "commands" in output
|
assert "commands" in output
|
||||||
|
|
||||||
|
# Should not contain combat moves
|
||||||
|
assert "punch" not in output
|
||||||
|
assert "roundhouse" not in output
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_commands_shows_aliases(player):
|
async def test_commands_shows_aliases(player):
|
||||||
|
|
@ -126,29 +126,16 @@ async def test_commands_deduplicates_entries(player):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_commands_groups_correctly(player):
|
async def test_commands_sorted_alphabetically(player):
|
||||||
"""Test that commands are grouped by type."""
|
"""Test that commands are sorted alphabetically."""
|
||||||
await commands.dispatch(player, "commands")
|
await commands.dispatch(player, "commands")
|
||||||
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
# Find the groups
|
# All commands should appear in a single sorted list
|
||||||
movement_pos = output.find("Movement:")
|
assert "north" in output
|
||||||
other_pos = output.find("Other:")
|
assert "east" in output
|
||||||
|
assert "quit" in output
|
||||||
# At minimum, movement and other should exist
|
assert "look" in output
|
||||||
assert movement_pos != -1
|
|
||||||
assert other_pos != -1
|
|
||||||
|
|
||||||
# Check that movement commands appear in the movement section
|
|
||||||
# (before the "Other:" section)
|
|
||||||
movement_section = output[movement_pos:other_pos]
|
|
||||||
assert "north" in movement_section
|
|
||||||
assert "east" in movement_section
|
|
||||||
|
|
||||||
# Check that non-movement commands appear in other section
|
|
||||||
other_section = output[other_pos:]
|
|
||||||
assert "quit" in other_section
|
|
||||||
assert "look" in other_section
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -157,9 +144,9 @@ async def test_commands_via_alias(player):
|
||||||
await commands.dispatch(player, "cmds")
|
await commands.dispatch(player, "cmds")
|
||||||
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
||||||
|
|
||||||
# Should produce the same output structure
|
# Should produce the same output as commands
|
||||||
assert "Movement:" in output
|
assert "north" in output
|
||||||
assert "Other:" in output
|
assert "look" in output
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue