Split commands and skills into separate listings

This commit is contained in:
Jared Miller 2026-02-08 13:15:04 -05:00
parent a8917da59e
commit 529320acb5
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 79 additions and 56 deletions

View file

@ -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",
)
)

View file

@ -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