package game import ( "fmt" "path/filepath" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/object" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) showRoomName(sess *net.Session, p *player.Player, room *world.Room) { roomIDStr := fmt.Sprintf("#%d", room.ID) if area := g.getRoomArea(room.ID); area != "" { roomIDStr = fmt.Sprintf("%s, #%d", area, room.ID) } title := fmt.Sprintf("%s (%s)", g.colorize(sess, "room_name", room.Name), g.colorize(sess, "room_number", roomIDStr)) if bracket := g.roomSymbolBracket(sess, p, room); bracket != "" { title += " " + bracket } sess.WriteLines("", title) } func (g *Game) getRoomArea(roomID int) string { path, ok := g.World.GetRoomPath(roomID) if !ok { return "" } rel, err := filepath.Rel(filepath.Join(g.DataDir, "rooms"), filepath.Dir(path)) if err != nil || rel == "." { return "" } return strings.ReplaceAll(rel, "_", " ") } // roomSymbolBracket renders a dim-bracketed colored symbol (e.g. "[D]") when the // player has set a custom symbol for this room, else "". func (g *Game) roomSymbolBracket(sess *net.Session, p *player.Player, room *world.Room) string { if p == nil { return "" } if _, ok := p.MapSymbols[room.ID]; !ok { return "" } ch, spec := roomMapSymbol(g, sess, room.ID, false) mode := g.colorMode(sess) dimSpec := g.resolveColor(sess, "dim") return color.Render(mode, dimSpec, "[") + color.Render(mode, spec, string(ch)) + color.Render(mode, dimSpec, "]") } func (g *Game) showRoomDescription(sess *net.Session, p *player.Player, room *world.Room, descWidth int) []string { if descWidth <= 0 { descWidth = 70 } rawLines := wrapText(g.roomDescription(sess, room), descWidth) roomDescSpec := g.resolveColor(sess, "room_desc") lines := make([]string, len(rawLines)) for i, l := range rawLines { lines[i] = g.expandTagsDefault(sess, roomDescSpec, l) } return lines } // roomDescription resolves the room's effective description for this player, // picking the first conditional variant whose condition passes. func (g *Game) roomDescription(sess *net.Session, room *world.Room) string { for _, d := range room.Description { if d.Condition == nil || g.checkCondition(sess, d.Condition) { return d.Text } } return "" } // resolveObjDesc resolves the description an object shows to this player. // The first variant whose condition passes wins; if none pass the object is // reported as not present (false), so look falls through as if it were not there. func (g *Game) resolveObjDesc(sess *net.Session, def *object.ObjectDef) (string, bool) { for _, d := range def.Description { if d.Condition == nil || g.checkCondition(sess, d.Condition) { return d.Text, true } } return "", false } // formatExitLines builds the verbose " dir - target [tags]" exit listing, // applying hidden-exit filtering and blocked/HIDDEN tags via exitDisplayState. // Shared by showRoomExits (the `look` exit section) and the standalone `exits` // command so they always agree on what is listed and how it is tagged. func (g *Game) formatExitLines(sess *net.Session, room *world.Room) []string { type exitLine struct { dir string targetName string } var lines []exitLine maxDirLen := 0 for _, dir := range world.ExitOrder { exitDef, ok := room.Exits[dir] if !ok { continue } ed := g.exitDisplayState(sess, room.ID, dir, exitDef) if !ed.Visible { continue } coloredDir := g.colorize(sess, "exit_direction", string(dir)) targetRoom, err := g.World.LoadRoom(exitDef.Room) targetName := g.colorize(sess, "room_number", fmt.Sprintf("#%d", exitDef.Room)) if err == nil { targetName = g.colorize(sess, "exit_name", targetRoom.Name) } if ed.Blocked { targetName += " (blocked)" } if ed.TagHidden { targetName += " (HIDDEN)" } lines = append(lines, exitLine{coloredDir, targetName}) if visibleLen(coloredDir) > maxDirLen { maxDirLen = visibleLen(coloredDir) } } out := make([]string, len(lines)) for i, l := range lines { pad := maxDirLen + (len(l.dir) - visibleLen(l.dir)) out[i] = fmt.Sprintf(" %-*s - %s", pad, l.dir, l.targetName) } return out } func (g *Game) showRoomExits(sess *net.Session, p *player.Player, room *world.Room) { if len(room.Exits) == 0 { return } sess.WriteLine("") if p.OptionBool("exits") { lines := g.formatExitLines(sess, room) if len(lines) > 0 { sess.WriteLine("Exits:") for _, l := range lines { sess.WriteLine(l) } } return } sess.Write("Exits: ") first := true for _, dir := range world.ExitOrder { exitDef, ok := room.Exits[dir] if !ok { continue } if !g.exitDisplayState(sess, room.ID, dir, exitDef).Visible { continue } if !first { sess.Write(", ") } sess.Write(g.colorize(sess, "exit_direction", string(dir))) first = false } sess.WriteLine("") }