package game import ( "fmt" "strings" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/world" ) func (g *Game) promptStr(sess *net.Session) string { prompt := "> " if p := sess.Player; p != nil { prompt = p.Prompt } prompt = g.expandPromptColors(sess, prompt) prompt = g.expandPromptVars(sess, prompt) return prompt } func (g *Game) expandPromptColors(sess *net.Session, text string) string { return g.expandTags(sess, text) } func (g *Game) expandPromptVars(sess *net.Session, text string) string { p := sess.Player if p == nil { return text } text = strings.ReplaceAll(text, "%%", "\x00") text = strings.ReplaceAll(text, "%h", fmt.Sprint(p.HP)) text = strings.ReplaceAll(text, "%H", fmt.Sprint(p.MaxHP())) text = strings.ReplaceAll(text, "%b", fmt.Sprintf("%.1f", p.Battery)) text = strings.ReplaceAll(text, "%B", fmt.Sprintf("%.0f", p.MaxBattery())) text = strings.ReplaceAll(text, "%r", fmt.Sprint(p.RunEnergy)) text = strings.ReplaceAll(text, "%R", fmt.Sprint(p.MaxRunEnergy())) text = strings.ReplaceAll(text, "%c", fmt.Sprint(p.Credits)) text = strings.ReplaceAll(text, "%k", fmt.Sprint(countChips(p))) text = strings.ReplaceAll(text, "%i", fmt.Sprint(p.FreeSlots())) text = strings.ReplaceAll(text, "%s", attackStyleShort(p.AttackStyle)) text = strings.ReplaceAll(text, "%S", attackStyleLong(p.AttackStyle)) mobHP, mobMaxHP := "", "" if cs := g.Combat.Get(p.Name); cs != nil { mob := g.MobStore.GetInstance(cs.MobID) if mob != nil && mob.HP > 0 { mobHP = fmt.Sprint(mob.HP) mobMaxHP = fmt.Sprint(mob.MaxHP) } } text = strings.ReplaceAll(text, "%m", mobHP) text = strings.ReplaceAll(text, "%M", mobMaxHP) allExits, openExits := "", "" if room, err := g.World.LoadRoom(p.RoomID); err == nil { for _, dir := range world.ExitOrder { exitDef, ok := room.Exits[dir] if !ok { continue } letter := exitPromptLetter(dir) allExits += letter if exitDef.Condition != nil && !g.checkCondition(sess, exitDef.Condition) { continue } openExits += letter } } text = strings.ReplaceAll(text, "%E", allExits) text = strings.ReplaceAll(text, "%e", openExits) for _, sk := range player.AllSkills { tag := string(sk) text = strings.ReplaceAll(text, "%X_"+tag, fmt.Sprint(p.Skills[sk])) text = strings.ReplaceAll(text, "%x_"+tag, fmt.Sprint(xpToLevel(p, sk))) } text = strings.ReplaceAll(text, "\x00", "%") return text } func attackStyleShort(s player.AttackStyle) string { switch s { case player.Accurate: return "acc" case player.Aggressive: return "agg" case player.Defensive: return "def" case player.Balanced: return "bal" } return "acc" } func attackStyleLong(s player.AttackStyle) string { switch s { case player.Accurate: return "accurate" case player.Aggressive: return "aggressive" case player.Defensive: return "defensive" case player.Balanced: return "balanced" } return "accurate" } func xpToLevel(p *player.Player, sk player.SkillName) int { currentXP := p.Skills[sk] currentLevel := p.Level(sk) nextLevelXP := player.XPForLevel(currentLevel + 1) return nextLevelXP - currentXP } // Cardinal exits get uppercase single letters; intercardinal exits get lowercase // two-letter strings. This visual distinction helps players scan the prompt at a glance. func exitPromptLetter(dir world.ExitDir) string { switch dir { case world.North: return "N" case world.South: return "S" case world.East: return "E" case world.West: return "W" case world.Northeast: return "ne" case world.Northwest: return "nw" case world.Southeast: return "se" case world.Southwest: return "sw" case world.Up: return "U" case world.Down: return "D" } return "?" }