package game import ( "fmt" "strconv" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" "thehouseoficarus/internal/ui" ) func (g *Game) doOption(sess *net.Session, input string) { p := sess.Player mode := g.colorMode(sess) if input == "" { hLabel := g.resolveConstant("table_header_label") hValue := g.resolveConstant("table_header_value") hDetail := g.resolveConstant("table_header_detail") hDesc := g.resolveConstant("table_header_desc") table := &ui.Table{ Columns: []string{ color.Render(mode, hLabel, "Option"), color.Render(mode, hValue, "Value"), color.Render(mode, hDetail, "Valid"), color.Render(mode, hDesc, "Description"), }, } for _, def := range player.OptionDefs { val := formatOptionValue(p, &def) val = truncateForTable(val, p.OptionBool("unicode")) valid := formatValidValues(&def) table.Rows = append(table.Rows, []string{ color.Render(mode, hLabel, def.Name), color.Render(mode, hValue, val), color.Render(mode, hDetail, valid), color.Render(mode, hDesc, def.Description), }) } for _, line := range table.Render(p.OptionBool("unicode"), p.OptionInt("wrap_width")) { sess.WriteLine(line) } return } parts := strings.Fields(input) name := strings.ToLower(parts[0]) def := player.GetOptionDef(name) if def == nil { sess.WriteLine(fmt.Sprintf("Unknown option: %s", name)) return } if len(parts) == 1 { val := formatOptionValue(p, def) valid := formatValidValues(def) sess.WriteLine(fmt.Sprintf("%s = %s [%s]", def.Name, val, valid)) sess.WriteLine(fmt.Sprintf(" %s", def.Description)) return } value := strings.ToLower(parts[1]) if def.Type == player.OptString && len(def.ValidValues) == 0 { // Free-text string options take the whole remaining input verbatim // ("option visual_tick_text Tick Tock" stores "Tick Tock"). value = strings.Join(parts[1:], " ") } if def.Name == "card_size" { switch value { case "1", "5", "7", "9": default: sess.WriteLine("Invalid value for card_size: use 1, 5, 7, or 9.") return } } parsed, ok := parseOptionValue(def, value) if !ok { valid := formatValidValues(def) sess.WriteLine(fmt.Sprintf("Invalid value for %s: %s [%s]", def.Name, value, valid)) return } if p.Options == nil { p.Options = make(map[string]any) } p.Options[def.Name] = parsed if def.Name == "unicode" && parsed == false { p.Options["card_size"] = 5 } // The account can be gone (purged or republished from another session) // while the player is still live: the option is applied to the player // either way, but the account update needs a valid account. if sess.Account != nil { if sess.Account.Options == nil { sess.Account.Options = make(map[string]any) } sess.Account.Options[def.Name] = parsed if def.Name == "unicode" && parsed == false { sess.Account.Options["card_size"] = 5 } if acc, err := g.AccountStore.LoadAccount(sess.Account.Name); err == nil { if acc.Options == nil { acc.Options = make(map[string]any) } acc.Options[def.Name] = parsed if def.Name == "unicode" && parsed == false { acc.Options["card_size"] = 5 } g.saveAccount(acc) } } sess.WriteLine(fmt.Sprintf("%s set to %s.", def.Name, formatOptionValue(p, def))) } func (g *Game) executeOption(sess *net.Session, args []string, rawInput string) { g.doOption(sess, strings.Join(rawArgsFrom(rawInput, "option"), " ")) } func formatOptionValue(p *player.Player, def *player.OptionDef) string { switch def.Type { case player.OptBool: if p.OptionBool(def.Name) { return "on" } return "off" case player.OptString: return p.OptionString(def.Name) case player.OptInt: return strconv.Itoa(p.OptionInt(def.Name)) } return "" } func formatValidValues(def *player.OptionDef) string { switch def.Type { case player.OptBool: return "on/off" case player.OptString: if len(def.ValidValues) > 0 { return strings.Join(def.ValidValues, "/") } return "string" case player.OptInt: if def.Name == "card_size" { return "1/5/7/9" } return "num" } return "" } func parseOptionValue(def *player.OptionDef, input string) (any, bool) { switch def.Type { case player.OptBool: switch input { case "on", "true", "yes", "1": return true, true case "off", "false", "no", "0": return false, true } return nil, false case player.OptString: if len(def.ValidValues) == 0 { return input, true } for _, v := range def.ValidValues { if strings.EqualFold(v, input) { return v, true } } return nil, false case player.OptInt: n, err := strconv.Atoi(input) if err != nil { return nil, false } return n, true } return nil, false } func truncateForTable(s string, unicode bool) string { ellipsis := color.Ellipsis(unicode) ellipLen := len([]rune(ellipsis)) if len([]rune(s)) <= 10 { return s } runes := []rune(s) return string(runes[:10-ellipLen]) + ellipsis }