aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-11 20:41:06 -0400
committerhistoria <[not public]>2026-06-11 20:41:06 -0400
commitd6ab0e93a64525ce34f89e26d5272efbac513c32 (patch)
treed0ec59a72a32a15d0917d9c6c3bfa2f2a424c1f1 /internal
parent6b2b4bf470b655f01c5a21c5f558a6102402c214 (diff)
downloadthehouseoficarus-d6ab0e93a64525ce34f89e26d5272efbac513c32.tar.gz
feat: big map and options changes
Diffstat (limited to 'internal')
-rw-r--r--internal/game/action_gather.go2
-rw-r--r--internal/game/action_use.go2
-rw-r--r--internal/game/cmd_attack.go4
-rw-r--r--internal/game/cmd_look.go12
-rw-r--r--internal/game/cmd_map.go140
-rw-r--r--internal/game/cmd_move.go6
-rw-r--r--internal/game/cmd_option.go116
-rw-r--r--internal/game/cmd_toggle.go56
-rw-r--r--internal/game/game.go6
-rw-r--r--internal/game/map_test.go86
-rw-r--r--internal/game/tick.go4
-rw-r--r--internal/player/player.go127
12 files changed, 477 insertions, 84 deletions
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index e1e724e..62637c3 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -216,7 +216,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
if msg == "" {
msg = fmt.Sprintf("You manage to get some %s.", itemName)
}
- if xp > 0 && p.Toggles["xpdrops"] {
+ if xp > 0 && p.OptionBool("xpdrops") {
msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.SkillName(cfg.Skill)])
}
sess.WriteLine(msg)
diff --git a/internal/game/action_use.go b/internal/game/action_use.go
index c1a07ed..69d29c1 100644
--- a/internal/game/action_use.go
+++ b/internal/game/action_use.go
@@ -105,7 +105,7 @@ func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
itemName = def.Name
}
line := fmt.Sprintf("You make a %s.", itemName)
- if cfg.XP > 0 && p.Toggles["xpdrops"] {
+ if cfg.XP > 0 && p.OptionBool("xpdrops") {
line += fmt.Sprintf(" (+%dxp %s)", cfg.XP, player.SkillAbbr[player.SkillName(cfg.Skill)])
}
sess.WriteLine(line)
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index 4e2d2c0..f6ccf0c 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -210,7 +210,7 @@ func (g *Game) playerAttack(sess *net.Session, p *player.Player, mob *world.MobI
prefix := fmt.Sprintf(" You hit %s for %d damage.", mobName, dmg)
hpPart := fmt.Sprintf("[%d/%dhp]", mob.HP, mob.MaxHP)
line := fmt.Sprintf("%-*s %s", g.combatPadWidth, prefix, hpPart)
- if p.Toggles["xpdrops"] && len(gains) > 0 {
+ if p.OptionBool("xpdrops") && len(gains) > 0 {
var parts []string
for _, gain := range gains {
parts = append(parts, fmt.Sprintf("+%dxp %s", gain.XP, player.SkillAbbr[player.SkillName(gain.Skill)]))
@@ -453,7 +453,7 @@ func (g *Game) respawnMob(instanceID string) {
if g.Hub != nil {
for _, sess := range g.Hub.PlayersInRoom(homeRoom) {
- if p, ok := sess.Player.(*player.Player); ok && p.Toggles["mobspawn"] {
+ if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mobspawn") {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) spawns in the area.", mobDisplayName(inst, false), mobCombatLevel(inst)))
}
}
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index 0bec915..6d19023 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -25,7 +25,7 @@ func (g *Game) doLook(sess *net.Session) {
room.Name,
)
- if p.Toggles["tinymap"] && g.MapWidth > 0 {
+ if p.OptionBool("tinymap") && g.MapWidth > 0 {
descLines := wrapText(room.Description, g.MapWidth)
mapLines := buildTinyMap(g, p.RoomID)
if len(mapLines) == 7 {
@@ -112,7 +112,7 @@ func (g *Game) doLook(sess *net.Session) {
})
}
multi := len(instances) > 1
- showTimers := p.Toggles["depletion"]
+ showTimers := p.OptionBool("depletion")
var freshIdxs []int
var timed, depleted []instInfo
@@ -205,7 +205,7 @@ func (g *Game) doLook(sess *net.Session) {
}
reserved := ""
if info.ReservedFor != "" {
- if p.Toggles["reserve"] {
+ if p.OptionBool("reserve") {
reserved = fmt.Sprintf(" (reserved for %s for %d ticks)", info.ReservedFor, info.ReserveTimer)
} else {
reserved = " (reserved)"
@@ -225,7 +225,7 @@ func (g *Game) doLook(sess *net.Session) {
if len(room.Exits) > 0 {
sess.WriteLine("")
- if p.Toggles["exits"] {
+ if p.OptionBool("exits") {
sess.WriteLine("Exits:")
for _, dir := range world.ExitOrder {
exitDef, ok := room.Exits[dir]
@@ -345,7 +345,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
sess.WriteLine(fmt.Sprintf(" %s", desc))
}
- if p.Toggles["depletion"] {
+ if p.OptionBool("depletion") {
for _, ist := range instances {
if ist.Depleted {
if len(instances) > 1 {
@@ -525,7 +525,7 @@ func (g *Game) writeLookSideBySide(sess *net.Session, p *player.Player, descLine
if len(mapLines) > total {
total = len(mapLines)
}
- leftMap := p.Toggles["left-tinymap"]
+ leftMap := p.OptionBool("left-tinymap")
for i := 0; i < total; i++ {
desc := ""
if i < len(descLines) {
diff --git a/internal/game/cmd_map.go b/internal/game/cmd_map.go
new file mode 100644
index 0000000..7c506f9
--- /dev/null
+++ b/internal/game/cmd_map.go
@@ -0,0 +1,140 @@
+package game
+
+import (
+ "strings"
+
+ "thirdcollapse/internal/net"
+ "thirdcollapse/internal/player"
+ "thirdcollapse/internal/world"
+)
+
+func (g *Game) doMap(sess *net.Session) {
+ p := sess.Player.(*player.Player)
+ mapWidth := p.OptionInt("mapwidth")
+ mapHeight := p.OptionInt("mapheight")
+ if mapWidth < 5 {
+ mapWidth = 5
+ }
+ if mapHeight < 5 {
+ mapHeight = 5
+ }
+
+ lines := buildFullMap(g, p.RoomID, mapWidth, mapHeight)
+
+ mode := p.OptionString("mappadding")
+ if mode == "none" || mode == "x" {
+ lines = stripBlankRows(lines)
+ }
+ if mode == "none" || mode == "y" {
+ lines = leftTrimCommon(lines)
+ }
+
+ if len(lines) == 0 {
+ sess.WriteLine("\nNo map data to display.")
+ return
+ }
+ sess.WriteLine("")
+ for _, line := range lines {
+ sess.WriteLine(line)
+ }
+}
+
+func stripBlankRows(lines []string) []string {
+ var out []string
+ for _, line := range lines {
+ if strings.TrimSpace(line) != "" {
+ out = append(out, line)
+ }
+ }
+ return out
+}
+
+func leftTrimCommon(lines []string) []string {
+ min := -1
+ for _, line := range lines {
+ if strings.TrimSpace(line) == "" {
+ continue
+ }
+ n := 0
+ for _, r := range line {
+ if r == ' ' {
+ n++
+ } else {
+ break
+ }
+ }
+ if min < 0 || n < min {
+ min = n
+ }
+ }
+ if min <= 0 {
+ return lines
+ }
+ result := make([]string, len(lines))
+ for i, line := range lines {
+ if len(line) <= min {
+ result[i] = ""
+ } else {
+ result[i] = line[min:]
+ }
+ }
+ return result
+}
+
+func buildFullMap(g *Game, roomID, mapWidth, mapHeight int) []string {
+ mg := buildGraph(g, roomID)
+
+ grid := make([][]rune, mapHeight)
+ for i := range grid {
+ grid[i] = make([]rune, mapWidth)
+ for j := range grid[i] {
+ grid[i][j] = ' '
+ }
+ }
+
+ cx := mapWidth / 2
+ cy := mapHeight / 2
+
+ for pos, rid := range mg.posToRoom {
+ gr := cy + pos[1]*2
+ gc := cx + pos[0]*2
+ if gr < 0 || gr >= mapHeight || gc < 0 || gc >= mapWidth {
+ continue
+ }
+ if rid == roomID {
+ grid[gr][gc] = '@'
+ } else {
+ grid[gr][gc] = roomMapSymbol(g, rid)
+ }
+ }
+
+ for pos, rid := range mg.posToRoom {
+ x, y := pos[0], pos[1]
+
+ if rightID, ok := mg.posToRoom[[2]int{x + 1, y}]; ok {
+ if exitsConnect(g, rid, rightID, world.East, world.West) {
+ gr := cy + y*2
+ gc := cx + x*2 + 1
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ grid[gr][gc] = '─'
+ }
+ }
+ }
+
+ if bottomID, ok := mg.posToRoom[[2]int{x, y + 1}]; ok {
+ if exitsConnect(g, rid, bottomID, world.South, world.North) {
+ gr := cy + y*2 + 1
+ gc := cx + x*2
+ if gr >= 0 && gr < mapHeight && gc >= 0 && gc < mapWidth {
+ grid[gr][gc] = '│'
+ }
+ }
+ }
+ }
+
+ lines := make([]string, mapHeight)
+ for i := range grid {
+ lines[i] = string(grid[i])
+ }
+ return lines
+}
diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go
index 79ca45e..d62701d 100644
--- a/internal/game/cmd_move.go
+++ b/internal/game/cmd_move.go
@@ -79,7 +79,7 @@ func (g *Game) doMove(sess *net.Session, dir string) {
}
sess.WriteLine(fmt.Sprintf("\nYou walk %s.", exitDir))
- if p.Toggles["description"] {
+ if p.OptionBool("description") {
g.doLook(sess)
} else {
targetRoom, _ := g.World.LoadRoom(targetID)
@@ -88,6 +88,10 @@ func (g *Game) doMove(sess *net.Session, dir string) {
}
}
+ if p.OptionBool("automap") {
+ g.doMap(sess)
+ }
+
g.RunEnterSteps(sess, targetID)
}
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
new file mode 100644
index 0000000..49085db
--- /dev/null
+++ b/internal/game/cmd_option.go
@@ -0,0 +1,116 @@
+package game
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "thirdcollapse/internal/net"
+ "thirdcollapse/internal/player"
+)
+
+func (g *Game) doOption(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ if input == "" {
+ sess.WriteLine("")
+ sess.WriteLine(fmt.Sprintf(" %-14s %-10s %-22s %s", "Option", "Value", "Valid", "Description"))
+ for _, def := range player.OptionDefs {
+ val := formatOptionValue(p, &def)
+ valid := formatValidValues(&def)
+ sess.WriteLine(fmt.Sprintf(" %-14s %-10s %-22s %s", def.Name, val, valid, def.Description))
+ }
+ return
+ }
+
+ parts := strings.Fields(input)
+ name := strings.ToLower(parts[0])
+ def := player.GetOptionDef(name)
+ if def == nil {
+ sess.WriteLine(fmt.Sprintf("\nUnknown option: %s", name))
+ return
+ }
+
+ if len(parts) == 1 {
+ val := formatOptionValue(p, def)
+ valid := formatValidValues(def)
+ sess.WriteLine(fmt.Sprintf("\n%s = %s [%s]", def.Name, val, valid))
+ sess.WriteLine(fmt.Sprintf(" %s", def.Description))
+ return
+ }
+
+ value := strings.ToLower(parts[1])
+ parsed, ok := parseOptionValue(def, value)
+ if !ok {
+ valid := formatValidValues(def)
+ sess.WriteLine(fmt.Sprintf("\nInvalid 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
+ g.AccountStore.SaveCharacter(p)
+ sess.WriteLine(fmt.Sprintf("\n%s set to %s.", def.Name, formatOptionValue(p, def)))
+}
+
+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:
+ 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
+}
diff --git a/internal/game/cmd_toggle.go b/internal/game/cmd_toggle.go
deleted file mode 100644
index a399483..0000000
--- a/internal/game/cmd_toggle.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package game
-
-import (
- "fmt"
- "strings"
-
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
-)
-
-var toggles = []struct {
- Name string
- Description string
-}{
- {"description", "Long room descriptions"},
- {"tinymap", "Mini-map display"},
- {"left-tinymap", "Mini-map on left side of descriptions"},
- {"xpdrops", "XP drop messages"},
- {"exits", "Long exit display in look"},
- {"mobenter", "Messages when mobs enter the room"},
- {"mobleave", "Messages when mobs leave the room"},
- {"mobspawn", "Messages when mobs spawn in the area"},
- {"reserve", "Show full reserved item details"},
- {"depletion", "Show depletion and despawn timers on objects"},
-}
-
-func (g *Game) doToggle(sess *net.Session, input string) {
- p := sess.Player.(*player.Player)
-
- if input == "" {
- sess.WriteLine("")
- for _, t := range toggles {
- status := "Off"
- if p.Toggles[t.Name] {
- status = "On"
- }
- sess.WriteLine(fmt.Sprintf(" %-12s %-3s %s", t.Name, status, t.Description))
- }
- g.AccountStore.SaveCharacter(p)
- return
- }
-
- for _, t := range toggles {
- if strings.ToLower(input) == t.Name {
- p.Toggles[t.Name] = !p.Toggles[t.Name]
- status := "Off"
- if p.Toggles[t.Name] {
- status = "On"
- }
- sess.WriteLine(fmt.Sprintf("\n%s %s.", t.Description, status))
- g.AccountStore.SaveCharacter(p)
- return
- }
- }
- sess.WriteLine(fmt.Sprintf("\nUnknown toggle: %s", input))
-}
diff --git a/internal/game/game.go b/internal/game/game.go
index 0675a13..8151877 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -194,10 +194,12 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
return
case "description", "desc":
g.doDescription(sess)
- case "toggle":
- g.doToggle(sess, strings.Join(args, " "))
+ case "option", "options":
+ g.doOption(sess, strings.Join(args, " "))
case "exits":
g.doExits(sess)
+ case "map":
+ g.doMap(sess)
case "help":
if len(args) == 0 {
diff --git a/internal/game/map_test.go b/internal/game/map_test.go
index a50c2c2..adba595 100644
--- a/internal/game/map_test.go
+++ b/internal/game/map_test.go
@@ -92,3 +92,89 @@ func TestWrapText(t *testing.T) {
}
}
}
+
+func TestBuildFullMap(t *testing.T) {
+ g := &Game{
+ World: world.New("../../data"),
+ MapWidth: 70,
+ }
+
+ tests := []struct {
+ name string
+ roomID int
+ width int
+ height int
+ }{
+ {"room 1 30x20", 1, 30, 20},
+ {"room 1 20x10", 1, 20, 10},
+ {"room 25 30x20", 25, 30, 20},
+ {"min size 5x5", 1, 5, 5},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ lines := buildFullMap(g, tt.roomID, tt.width, tt.height)
+ if len(lines) != tt.height {
+ t.Errorf("expected %d lines, got %d", tt.height, len(lines))
+ }
+ for i, line := range lines {
+ if len([]rune(line)) != tt.width {
+ t.Errorf("line %d: expected %d runes, got %d in %q", i, tt.width, len([]rune(line)), line)
+ }
+ }
+ t.Logf("Room %d %dx%d map:\n%s", tt.roomID, tt.width, tt.height, strings.Join(lines, "\n"))
+ })
+ }
+}
+
+func TestStripBlankRows(t *testing.T) {
+ input := []string{" ", "hello", "", " world ", " "}
+ want := []string{"hello", " world "}
+ got := stripBlankRows(input)
+ if len(got) != len(want) {
+ t.Fatalf("expected %d lines, got %d", len(want), len(got))
+ }
+ for i := range want {
+ if got[i] != want[i] {
+ t.Errorf("line %d: want %q, got %q", i, want[i], got[i])
+ }
+ }
+}
+
+func TestLeftTrimCommon(t *testing.T) {
+ input := []string{" hello", " world", " ", " foo"}
+ got := leftTrimCommon(input)
+ // min leading spaces across non-blank lines: " hello"=4, " world"=4, " foo"=2 → min=2
+ // trim 2 from all lines
+ if got[0] != " hello" {
+ t.Errorf("line 0: want %q, got %q", " hello", got[0])
+ }
+ if got[1] != " world" {
+ t.Errorf("line 1: want %q, got %q", " world", got[1])
+ }
+ if got[2] != "" {
+ t.Errorf("line 2: want %q, got %q", "", got[2])
+ }
+ if got[3] != "foo" {
+ t.Errorf("line 3: want %q, got %q", "foo", got[3])
+ }
+}
+
+func TestLeftTrimCommonEmpty(t *testing.T) {
+ input := []string{" ", "", " "}
+ got := leftTrimCommon(input)
+ if len(got) != 3 {
+ t.Fatalf("expected 3 lines, got %d", len(got))
+ }
+}
+
+func TestLeftTrimCommonZero(t *testing.T) {
+ input := []string{"hello", "world"}
+ got := leftTrimCommon(input)
+ if got[0] != "hello" {
+ t.Errorf("line 0: want %q, got %q", "hello", got[0])
+ }
+ if got[1] != "world" {
+ t.Errorf("line 1: want %q, got %q", "world", got[1])
+ }
+}
diff --git a/internal/game/tick.go b/internal/game/tick.go
index 7eb63eb..3c3f1d3 100644
--- a/internal/game/tick.go
+++ b/internal/game/tick.go
@@ -126,14 +126,14 @@ func (g *Game) WanderTick() {
if !ok {
continue
}
- if p.RoomID == m.fromRoom && p.Toggles["mobleave"] {
+ if p.RoomID == m.fromRoom && p.OptionBool("mobleave") {
if m.level > 0 {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) leaves.", m.name, m.level))
} else {
sess.WriteLine(fmt.Sprintf("\n%s moves away.", m.name))
}
}
- if p.RoomID == m.toRoom && p.Toggles["mobenter"] {
+ if p.RoomID == m.toRoom && p.OptionBool("mobenter") {
if m.level > 0 {
sess.WriteLine(fmt.Sprintf("\n%s (level %d) enters.", m.name, m.level))
} else {
diff --git a/internal/player/player.go b/internal/player/player.go
index 56ab06c..97874e3 100644
--- a/internal/player/player.go
+++ b/internal/player/player.go
@@ -86,6 +86,53 @@ type InventorySlot struct {
Quantity int `yaml:"quantity"`
}
+type OptionType int
+
+const (
+ OptBool OptionType = iota
+ OptString
+ OptInt
+)
+
+type OptionDef struct {
+ Name string
+ Type OptionType
+ Default any
+ ValidValues []string
+ Description string
+}
+
+var OptionDefs = []OptionDef{
+ {"description", OptBool, true, nil, "Long room descriptions when moving"},
+ {"tinymap", OptBool, true, nil, "Mini-map display"},
+ {"left-tinymap", OptBool, false, nil, "Mini-map on left side of descriptions"},
+ {"xpdrops", OptBool, true, nil, "XP drop messages"},
+ {"exits", OptBool, true, nil, "Long exit display in look"},
+ {"mobenter", OptBool, true, nil, "Messages when mobs enter the room"},
+ {"mobleave", OptBool, true, nil, "Messages when mobs leave the room"},
+ {"mobspawn", OptBool, true, nil, "Messages when mobs spawn in the area"},
+ {"reserve", OptBool, true, nil, "Show full reserved item details"},
+ {"depletion", OptBool, false, nil, "Show depletion and despawn timers"},
+ {"color", OptString, "none", []string{"none", "ansi", "xterm256"}, "Color output mode"},
+ {"mapwidth", OptInt, 30, nil, "Map width for the map command"},
+ {"mapheight", OptInt, 20, nil, "Map height for the map command"},
+ {"mappadding", OptString, "none", []string{"none", "x", "y", "xy"}, "Map output padding mode"},
+ {"automap", OptBool, false, nil, "Show map automatically after moving"},
+}
+
+var optionByName map[string]*OptionDef
+
+func init() {
+ optionByName = make(map[string]*OptionDef, len(OptionDefs))
+ for i := range OptionDefs {
+ optionByName[OptionDefs[i].Name] = &OptionDefs[i]
+ }
+}
+
+func GetOptionDef(name string) *OptionDef {
+ return optionByName[name]
+}
+
type Player struct {
Name string `yaml:"name"`
Skills map[SkillName]int `yaml:"skills"`
@@ -96,12 +143,73 @@ type Player struct {
Credits int `yaml:"credits"`
Description string `yaml:"description"`
AttackStyle AttackStyle `yaml:"attack_style"`
- Toggles map[string]bool `yaml:"toggles"`
+ Options map[string]any `yaml:"options"`
Flags map[string]any `yaml:"flags"`
RegenerateTick int
Action *action.Action `yaml:"-"`
}
+func (p *Player) OptionBool(name string) bool {
+ def := GetOptionDef(name)
+ if def == nil || def.Type != OptBool {
+ return false
+ }
+ if p.Options == nil {
+ return def.Default.(bool)
+ }
+ val, ok := p.Options[name]
+ if !ok {
+ return def.Default.(bool)
+ }
+ b, ok := val.(bool)
+ if !ok {
+ return def.Default.(bool)
+ }
+ return b
+}
+
+func (p *Player) OptionString(name string) string {
+ def := GetOptionDef(name)
+ if def == nil || def.Type != OptString {
+ return ""
+ }
+ if p.Options == nil {
+ return def.Default.(string)
+ }
+ val, ok := p.Options[name]
+ if !ok {
+ return def.Default.(string)
+ }
+ s, ok := val.(string)
+ if !ok {
+ return def.Default.(string)
+ }
+ return s
+}
+
+func (p *Player) OptionInt(name string) int {
+ def := GetOptionDef(name)
+ if def == nil || def.Type != OptInt {
+ return 0
+ }
+ if p.Options == nil {
+ return def.Default.(int)
+ }
+ val, ok := p.Options[name]
+ if !ok {
+ return def.Default.(int)
+ }
+ switch v := val.(type) {
+ case int:
+ return v
+ case int64:
+ return int(v)
+ case float64:
+ return int(v)
+ }
+ return def.Default.(int)
+}
+
func (p *Player) InvSlot(i int) *InventorySlot {
if p.Inventory == nil {
return nil
@@ -140,18 +248,11 @@ func New(name string) *Player {
Skills: make(map[SkillName]int),
Equipment: make(map[object.EquipSlot]string),
AttackStyle: Accurate,
- Toggles: map[string]bool{
- "description": true,
- "tinymap": true,
- "left-tinymap": false,
- "xpdrops": true,
- "exits": true,
- "mobenter": true,
- "mobleave": true,
- "mobspawn": true,
- "reserve": true,
- },
- RoomID: 0,
+ Options: make(map[string]any),
+ RoomID: 0,
+ }
+ for _, def := range OptionDefs {
+ p.Options[def.Name] = def.Default
}
for _, s := range AllSkills {
p.Skills[s] = 0