"""Look command for viewing the world.""" from mudlib.commands import CommandDefinition, register from mudlib.commands.things import _format_thing_name from mudlib.effects import get_effects_at from mudlib.entity import Entity from mudlib.player import Player from mudlib.render.ansi import RESET, colorize_terrain from mudlib.thing import Thing from mudlib.zone import Zone # Viewport dimensions VIEWPORT_WIDTH = 21 VIEWPORT_HEIGHT = 11 async def cmd_look(player: Player, args: str) -> None: """Render the current viewport to the player. Args: player: The player executing the command args: Command arguments (unused for now) """ zone = player.location if zone is None or not isinstance(zone, Zone): player.writer.write("You are nowhere.\r\n") await player.writer.drain() return # Get the viewport from the zone viewport = zone.get_viewport(player.x, player.y, VIEWPORT_WIDTH, VIEWPORT_HEIGHT) # Calculate center position center_x = VIEWPORT_WIDTH // 2 center_y = VIEWPORT_HEIGHT // 2 # Get nearby entities (players and mobs) from the zone # Viewport half-diagonal distance for range viewport_range = VIEWPORT_WIDTH // 2 + VIEWPORT_HEIGHT // 2 nearby = zone.contents_near(player.x, player.y, viewport_range) # Build a list of (relative_x, relative_y) for other entities entity_positions = [] for obj in nearby: # Only show entities (players/mobs), not the current player if not isinstance(obj, Entity) or obj is player: continue # Skip dead mobs if hasattr(obj, "alive") and not obj.alive: continue # Calculate relative position (shortest path wrapping) dx = obj.x - player.x dy = obj.y - player.y if zone.toroidal: if dx > zone.width // 2: dx -= zone.width elif dx < -(zone.width // 2): dx += zone.width if dy > zone.height // 2: dy -= zone.height elif dy < -(zone.height // 2): dy += zone.height rel_x = dx + center_x rel_y = dy + center_y # Check if within viewport bounds if 0 <= rel_x < VIEWPORT_WIDTH and 0 <= rel_y < VIEWPORT_HEIGHT: entity_positions.append((rel_x, rel_y)) # Build the output with ANSI coloring # priority: player @ > other players * > mobs * > effects > terrain half_width = VIEWPORT_WIDTH // 2 half_height = VIEWPORT_HEIGHT // 2 output_lines = [] for y, row in enumerate(viewport): line = [] for x, tile in enumerate(row): # Check if this is the player's position if x == center_x and y == center_y: line.append(colorize_terrain("@", player.color_depth)) # Check if this is another entity's position elif (x, y) in entity_positions: line.append(colorize_terrain("*", player.color_depth)) else: # Check for active effects at this world position world_x, world_y = zone.wrap( player.x - half_width + x, player.y - half_height + y, ) effects = get_effects_at(world_x, world_y) if effects: # use the most recent effect e = effects[-1] line.append(f"{e.color}{e.char}{RESET}") else: line.append(colorize_terrain(tile, player.color_depth)) output_lines.append("".join(line)) # Send to player player.writer.write("\r\n".join(output_lines) + "\r\n") # Show items on the ground at player's position from mudlib.portal import Portal contents_here = zone.contents_at(player.x, player.y) ground_items = [ obj for obj in contents_here if isinstance(obj, Thing) and not isinstance(obj, Portal) ] portals = [obj for obj in contents_here if isinstance(obj, Portal)] if ground_items: names = ", ".join(_format_thing_name(item) for item in ground_items) player.writer.write(f"On the ground: {names}\r\n") if portals: names = ", ".join(p.name for p in portals) player.writer.write(f"Portals: {names}\r\n") await player.writer.drain() # Register the look command with its alias register(CommandDefinition("look", cmd_look, aliases=["l"]))