aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/color/color.go475
-rw-r--r--internal/config/config.go69
-rw-r--r--internal/game/action.go12
-rw-r--r--internal/game/action_burn.go66
-rw-r--r--internal/game/action_cook.go8
-rw-r--r--internal/game/action_gather.go15
-rw-r--r--internal/game/action_room.go4
-rw-r--r--internal/game/action_search.go19
-rw-r--r--internal/game/action_talk.go12
-rw-r--r--internal/game/action_toggle.go8
-rw-r--r--internal/game/action_use.go19
-rw-r--r--internal/game/cmd_alias.go2
-rw-r--r--internal/game/cmd_attack.go22
-rw-r--r--internal/game/cmd_color.go39
-rw-r--r--internal/game/cmd_colortable.go151
-rw-r--r--internal/game/cmd_cook.go6
-rw-r--r--internal/game/cmd_description.go4
-rw-r--r--internal/game/cmd_drop.go12
-rw-r--r--internal/game/cmd_eat.go10
-rw-r--r--internal/game/cmd_equipment.go4
-rw-r--r--internal/game/cmd_get.go26
-rw-r--r--internal/game/cmd_inventory.go9
-rw-r--r--internal/game/cmd_look.go24
-rw-r--r--internal/game/cmd_map.go4
-rw-r--r--internal/game/cmd_move.go6
-rw-r--r--internal/game/cmd_option.go35
-rw-r--r--internal/game/cmd_prompt.go33
-rw-r--r--internal/game/cmd_queued.go4
-rw-r--r--internal/game/cmd_quit.go8
-rw-r--r--internal/game/cmd_remove.go11
-rw-r--r--internal/game/cmd_say.go8
-rw-r--r--internal/game/cmd_score.go26
-rw-r--r--internal/game/cmd_search.go4
-rw-r--r--internal/game/cmd_style.go4
-rw-r--r--internal/game/cmd_use.go6
-rw-r--r--internal/game/cmd_walk.go6
-rw-r--r--internal/game/cmd_wear.go12
-rw-r--r--internal/game/color.go24
-rw-r--r--internal/game/game.go36
-rw-r--r--internal/game/help.go8
-rw-r--r--internal/game/login_account.go31
-rw-r--r--internal/game/login_char.go11
-rw-r--r--internal/game/map.go2
-rw-r--r--internal/game/map_test.go2
-rw-r--r--internal/game/prompt.go49
-rw-r--r--internal/game/table.go18
-rw-r--r--internal/game/tick.go30
-rw-r--r--internal/game/types.go2
-rw-r--r--internal/game/utils.go6
-rw-r--r--internal/net/server.go137
-rw-r--r--internal/net/terminal.html2
-rw-r--r--internal/object/item.go2
-rw-r--r--internal/player/account.go1
-rw-r--r--internal/player/player.go15
-rw-r--r--internal/player/store.go2
-rw-r--r--internal/world/mob.go2
-rw-r--r--internal/world/room.go2
57 files changed, 1085 insertions, 480 deletions
diff --git a/internal/color/color.go b/internal/color/color.go
index 4ef66b8..9496a1d 100644
--- a/internal/color/color.go
+++ b/internal/color/color.go
@@ -2,185 +2,420 @@ package color
import (
"fmt"
+ "math"
+ "regexp"
+ "strconv"
"strings"
)
const Reset = "\033[0m"
-var ansiFg = map[string]int{
- "black": 30,
- "red": 31,
- "green": 32,
- "yellow": 33,
- "blue": 34,
- "magenta": 35,
- "cyan": 36,
- "white": 37,
-}
-
-var ansiBg = map[string]int{
- "black": 40,
- "red": 41,
- "green": 42,
- "yellow": 43,
- "blue": 44,
- "magenta": 45,
- "cyan": 46,
- "white": 47,
-}
-
-var ansiBrightFg = map[string]int{
- "bright_black": 90,
- "bright_red": 91,
- "bright_green": 92,
- "bright_yellow": 93,
- "bright_blue": 94,
- "bright_magenta": 95,
- "bright_cyan": 96,
- "bright_white": 97,
-}
-
-var ansiBrightBg = map[string]int{
- "bright_black": 100,
- "bright_red": 101,
- "bright_green": 102,
- "bright_yellow": 103,
- "bright_blue": 104,
- "bright_magenta": 105,
- "bright_cyan": 106,
- "bright_white": 107,
-}
-
-var xtermFg = map[string]int{
- "black": 0,
- "red": 1,
- "green": 2,
- "yellow": 3,
- "blue": 4,
- "magenta": 5,
- "cyan": 6,
- "white": 7,
- "bright_black": 8,
- "bright_red": 9,
- "bright_green": 10,
- "bright_yellow": 11,
- "bright_blue": 12,
- "bright_magenta": 13,
- "bright_cyan": 14,
- "bright_white": 15,
-}
-
-var ansiStyles = map[string]string{
- "bold": "\033[1m",
- "dim": "\033[2m",
- "italic": "\033[3m",
- "underline": "\033[4m",
-}
-
-func Fg(mode, name string) string {
- if mode == "none" || mode == "" {
- return ""
+var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
+
+var ansi16RGB = [16][3]int{
+ {0, 0, 0}, // 0 black
+ {170, 0, 0}, // 1 red
+ {0, 170, 0}, // 2 green
+ {170, 170, 0}, // 3 yellow
+ {0, 0, 170}, // 4 blue
+ {170, 0, 170}, // 5 magenta
+ {0, 170, 170}, // 6 cyan
+ {170, 170, 170}, // 7 white
+ {85, 85, 85}, // 8 bright black
+ {255, 85, 85}, // 9 bright red
+ {85, 255, 85}, // 10 bright green
+ {255, 255, 85}, // 11 bright yellow
+ {85, 85, 255}, // 12 bright blue
+ {255, 85, 255}, // 13 bright magenta
+ {85, 255, 255}, // 14 bright cyan
+ {255, 255, 255}, // 15 bright white
+}
+
+var cubeValues = [6]int{0, 95, 135, 175, 215, 255}
+
+func Xterm256ToRGB(index int) (int, int, int) {
+ if index < 16 {
+ return ansi16RGB[index][0], ansi16RGB[index][1], ansi16RGB[index][2]
}
- if mode == "xterm256" {
- if c, ok := xtermFg[name]; ok {
- return fmt.Sprintf("\033[38;5;%dm", c)
+ if index < 232 {
+ i := index - 16
+ b := cubeValues[i%6]
+ g := cubeValues[(i/6)%6]
+ r := cubeValues[(i/36)%6]
+ return r, g, b
+ }
+ v := 8 + (index-232)*10
+ return v, v, v
+}
+
+func colorDist(r1, g1, b1, r2, g2, b2 int) int {
+ dr := r1 - r2
+ dg := g1 - g2
+ db := b1 - b2
+ return dr*dr + dg*dg + db*db
+}
+
+func nearestANSI(index int) int {
+ if index < 16 {
+ if index < 8 {
+ return 30 + index
}
- return ""
+ return 90 + (index - 8)
}
- if c, ok := ansiFg[name]; ok {
- return fmt.Sprintf("\033[%dm", c)
+ r, g, b := Xterm256ToRGB(index)
+ bestDist := math.MaxInt
+ bestCode := 37
+ for i := 0; i < 16; i++ {
+ dist := colorDist(r, g, b, ansi16RGB[i][0], ansi16RGB[i][1], ansi16RGB[i][2])
+ if dist < bestDist {
+ bestDist = dist
+ if i < 8 {
+ bestCode = 30 + i
+ } else {
+ bestCode = 90 + (i - 8)
+ }
+ }
}
- if c, ok := ansiBrightFg[name]; ok {
- return fmt.Sprintf("\033[%dm", c)
+ return bestCode
+}
+
+func nearestANSIBg(index int) int {
+ fg := nearestANSI(index)
+ if fg >= 90 {
+ return fg - 90 + 100
}
- return ""
+ return fg - 30 + 40
+}
+
+func nearestCubeComponent(v int) int {
+ if v < 48 {
+ return 0
+ }
+ if v < 115 {
+ return 1
+ }
+ if v < 155 {
+ return 2
+ }
+ if v < 195 {
+ return 3
+ }
+ if v < 235 {
+ return 4
+ }
+ return 5
}
-func Bg(mode, name string) string {
- if mode == "none" || mode == "" {
+func NearestXterm256(r, g, b int) int {
+ ri := nearestCubeComponent(r)
+ gi := nearestCubeComponent(g)
+ bi := nearestCubeComponent(b)
+ cubeIdx := 16 + ri*36 + gi*6 + bi
+ cubeDist := colorDist(r, g, b, cubeValues[ri], cubeValues[gi], cubeValues[bi])
+
+ avg := (r + g + b) / 3
+ grayStep := (avg - 8 + 5) / 10
+ if grayStep < 0 {
+ grayStep = 0
+ }
+ if grayStep > 23 {
+ grayStep = 23
+ }
+ grayIdx := 232 + grayStep
+ gv := 8 + grayStep*10
+ grayDist := colorDist(r, g, b, gv, gv, gv)
+
+ bestIdx := cubeIdx
+ bestDist := cubeDist
+ if grayDist < bestDist {
+ bestIdx = grayIdx
+ bestDist = grayDist
+ }
+
+ for i := 0; i < 16; i++ {
+ d := colorDist(r, g, b, ansi16RGB[i][0], ansi16RGB[i][1], ansi16RGB[i][2])
+ if d < bestDist {
+ bestDist = d
+ bestIdx = i
+ }
+ }
+
+ return bestIdx
+}
+
+func FgCode(mode string, index int) string {
+ if mode == "none" || mode == "" || index < 0 || index > 255 {
return ""
}
if mode == "xterm256" {
- if c, ok := xtermFg[name]; ok {
- return fmt.Sprintf("\033[48;5;%dm", c)
- }
+ return fmt.Sprintf("\033[38;5;%dm", index)
+ }
+ return fmt.Sprintf("\033[%dm", nearestANSI(index))
+}
+
+func BgCode(mode string, index int) string {
+ if mode == "none" || mode == "" || index < 0 || index > 255 {
return ""
}
- if c, ok := ansiBg[name]; ok {
- return fmt.Sprintf("\033[%dm", c)
+ if mode == "xterm256" {
+ return fmt.Sprintf("\033[48;5;%dm", index)
}
- if c, ok := ansiBrightBg[name]; ok {
- return fmt.Sprintf("\033[%dm", c)
+ return fmt.Sprintf("\033[%dm", nearestANSIBg(index))
+}
+
+func StyleCode(name string) string {
+ switch name {
+ case "bold":
+ return "\033[1m"
+ case "dim":
+ return "\033[2m"
+ case "underline":
+ return "\033[4m"
}
return ""
}
-func Style(name string) string {
- if s, ok := ansiStyles[name]; ok {
- return s
+func VisibleLen(s string) int {
+ return len(ansiRe.ReplaceAllString(s, ""))
+}
+
+func ContrastFg(mode string, bgIndex int) string {
+ r, g, b := Xterm256ToRGB(bgIndex)
+ if r+g+b > 384 {
+ return FgCode(mode, 0)
}
- return ""
+ return FgCode(mode, 15)
}
type ColorSpec struct {
- Fg string
- Bg string
+ Fg int
+ Bg int
Bold bool
Dim bool
- Italic bool
Underline bool
+ Gradient []int
+}
+
+func NoColor() ColorSpec {
+ return ColorSpec{Fg: -1, Bg: -1}
}
func Parse(input string) ColorSpec {
- var spec ColorSpec
+ spec := ColorSpec{Fg: -1, Bg: -1}
for _, token := range strings.Fields(input) {
switch {
case token == "bold":
spec.Bold = true
case token == "dim":
spec.Dim = true
- case token == "italic":
- spec.Italic = true
case token == "underline":
spec.Underline = true
- case strings.HasPrefix(token, "fg="):
- spec.Fg = strings.TrimPrefix(token, "fg=")
- case strings.HasPrefix(token, "bg="):
- spec.Bg = strings.TrimPrefix(token, "bg=")
+ case strings.HasPrefix(token, "bg:"):
+ if n, err := strconv.Atoi(strings.TrimPrefix(token, "bg:")); err == nil && n >= 0 && n <= 255 {
+ spec.Bg = n
+ }
+ case strings.HasPrefix(token, "g:"):
+ parts := strings.Split(token[2:], ",")
+ var stops []int
+ for _, p := range parts {
+ if n, err := strconv.Atoi(strings.TrimSpace(p)); err == nil && n >= 0 && n <= 255 {
+ stops = append(stops, n)
+ }
+ }
+ if len(stops) >= 2 {
+ spec.Gradient = stops
+ }
+ default:
+ if n, err := strconv.Atoi(token); err == nil && n >= 0 && n <= 255 {
+ spec.Fg = n
+ }
}
}
return spec
}
func (s ColorSpec) Empty() bool {
- return s.Fg == "" && s.Bg == "" && !s.Bold && !s.Dim && !s.Italic && !s.Underline
+ return s.Fg < 0 && s.Bg < 0 && !s.Bold && !s.Dim && !s.Underline && len(s.Gradient) == 0
}
-func Render(mode string, spec ColorSpec, text string) string {
- if mode == "none" || mode == "" || spec.Empty() {
- return text
- }
+func stylePrefix(spec ColorSpec) string {
var codes []string
if spec.Bold {
- codes = append(codes, Style("bold"))
+ codes = append(codes, StyleCode("bold"))
}
if spec.Dim {
- codes = append(codes, Style("dim"))
- }
- if spec.Italic {
- codes = append(codes, Style("italic"))
+ codes = append(codes, StyleCode("dim"))
}
if spec.Underline {
- codes = append(codes, Style("underline"))
+ codes = append(codes, StyleCode("underline"))
+ }
+ return strings.Join(codes, "")
+}
+
+func Render(mode string, spec ColorSpec, text string) string {
+ if mode == "none" || mode == "" || spec.Empty() {
+ return text
+ }
+ if len(spec.Gradient) >= 2 {
+ return renderGradient(mode, spec, text)
}
- if spec.Fg != "" {
- codes = append(codes, Fg(mode, spec.Fg))
+ var codes []string
+ prefix := stylePrefix(spec)
+ if prefix != "" {
+ codes = append(codes, prefix)
+ }
+ if spec.Fg >= 0 {
+ codes = append(codes, FgCode(mode, spec.Fg))
}
- if spec.Bg != "" {
- codes = append(codes, Bg(mode, spec.Bg))
+ if spec.Bg >= 0 {
+ codes = append(codes, BgCode(mode, spec.Bg))
}
if len(codes) == 0 {
return text
}
return strings.Join(codes, "") + text + Reset
}
+
+func interpolateGradient(stops []int, t float64) int {
+ if t <= 0 {
+ return stops[0]
+ }
+ if t >= 1 {
+ return stops[len(stops)-1]
+ }
+ seg := t * float64(len(stops)-1)
+ i := int(seg)
+ if i >= len(stops)-1 {
+ i = len(stops) - 2
+ }
+ frac := seg - float64(i)
+ r1, g1, b1 := Xterm256ToRGB(stops[i])
+ r2, g2, b2 := Xterm256ToRGB(stops[i+1])
+ r := r1 + int(frac*float64(r2-r1))
+ g := g1 + int(frac*float64(g2-g1))
+ b := b1 + int(frac*float64(b2-b1))
+ return NearestXterm256(r, g, b)
+}
+
+func renderGradient(mode string, spec ColorSpec, text string) string {
+ runes := []rune(text)
+ n := len(runes)
+ if n == 0 {
+ return ""
+ }
+ var sb strings.Builder
+ prefix := stylePrefix(spec)
+ if spec.Bg >= 0 {
+ sb.WriteString(BgCode(mode, spec.Bg))
+ }
+ for i, r := range runes {
+ t := 0.0
+ if n > 1 {
+ t = float64(i) / float64(n-1)
+ }
+ idx := interpolateGradient(spec.Gradient, t)
+ if prefix != "" {
+ sb.WriteString(prefix)
+ }
+ sb.WriteString(FgCode(mode, idx))
+ sb.WriteRune(r)
+ }
+ sb.WriteString(Reset)
+ return sb.String()
+}
+
+func ExpandTags(mode string, text string) string {
+ return ExpandTagsWithDefault(mode, NoColor(), text)
+}
+
+func ExpandTagsWithDefault(mode string, def ColorSpec, text string) string {
+ if !strings.Contains(text, "{") {
+ if !def.Empty() {
+ return Render(mode, def, text)
+ }
+ return text
+ }
+
+ var sb strings.Builder
+ pos := 0
+ for pos < len(text) {
+ start := strings.Index(text[pos:], "{")
+ if start < 0 {
+ remaining := text[pos:]
+ if remaining != "" {
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, remaining))
+ } else {
+ sb.WriteString(remaining)
+ }
+ }
+ break
+ }
+ start += pos
+
+ end := strings.Index(text[start+1:], "}")
+ if end < 0 {
+ remaining := text[pos:]
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, remaining))
+ } else {
+ sb.WriteString(remaining)
+ }
+ break
+ }
+ end += start + 1
+
+ specStr := text[start+1 : end]
+
+ if specStr == "" || specStr == "/" {
+ before := text[pos : start+end-start+1]
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, before))
+ } else {
+ sb.WriteString(before)
+ }
+ pos = end + 1
+ continue
+ }
+
+ spec := Parse(specStr)
+ if spec.Empty() {
+ before := text[pos : end+1]
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, before))
+ } else {
+ sb.WriteString(before)
+ }
+ pos = end + 1
+ continue
+ }
+
+ closeStart := strings.Index(text[end+1:], "{/}")
+ if closeStart < 0 {
+ remaining := text[pos:]
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, remaining))
+ } else {
+ sb.WriteString(remaining)
+ }
+ break
+ }
+ closeStart += end + 1
+
+ before := text[pos:start]
+ if before != "" {
+ if !def.Empty() {
+ sb.WriteString(Render(mode, def, before))
+ } else {
+ sb.WriteString(before)
+ }
+ }
+
+ inner := text[end+1 : closeStart]
+ sb.WriteString(Render(mode, spec, inner))
+
+ pos = closeStart + 3
+ }
+
+ return sb.String()
+}
diff --git a/internal/config/config.go b/internal/config/config.go
index 48f1a69..d2065d2 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -7,37 +7,45 @@ import (
)
type Config struct {
- Game GameConfig `yaml:"game"`
- Colors ColorsConfig `yaml:"colors"`
- Telnet TelnetConfig `yaml:"telnet"`
- HTTP HTTPConfig `yaml:"http"`
- HTTPS HTTPSConfig `yaml:"https"`
+ Game GameConfig `yaml:"game"`
+ Colors ColorsConfig `yaml:"colors"`
+ Telnet TelnetConfig `yaml:"telnet"`
+ TelnetTLS TelnetTLSConfig `yaml:"telnet_tls"`
+ HTTP HTTPConfig `yaml:"http"`
+ HTTPS HTTPSConfig `yaml:"https"`
}
type ColorsConfig map[string]string
func DefaultColors() ColorsConfig {
return ColorsConfig{
- "room_name": "fg=cyan bold",
- "room_number": "dim",
- "room_desc": "fg=white",
- "direction": "fg=cyan",
- "exit_direction": "fg=cyan",
- "exit_name": "fg=green",
- "mob_name": "fg=bright_red",
- "friendly_npc": "fg=green",
- "hostile_npc": "fg=bright_red",
- "damage": "fg=red",
- "enemy_hp": "fg=red",
- "character_hp": "fg=green",
- "xp": "fg=yellow",
- "level_up": "fg=bright_yellow bold",
- "item": "fg=green",
- "player_name": "fg=bright_white",
- "death": "fg=red bold",
- "victory": "fg=green",
- "miss": "dim",
- "error": "fg=red",
+ "room_name": "81 bold",
+ "room_number": "240",
+ "room_desc": "252",
+ "direction": "75",
+ "exit_direction": "75",
+ "exit_name": "114",
+ "mob_name": "203",
+ "friendly_npc": "120",
+ "hostile_npc": "203",
+ "damage": "196",
+ "enemy_hp": "167",
+ "character_hp": "84",
+ "xp": "222",
+ "level_up": "226 bold",
+ "item": "223",
+ "player_name": "189",
+ "death": "196 bold",
+ "victory": "83",
+ "miss": "243",
+ "error": "209",
+ "say": "230",
+ "dialog": "117",
+ "broadcast": "215",
+ "fire": "208",
+ "eat_food": "156",
+ "drop_message": "186",
+ "credits_pickup": "220",
}
}
@@ -50,6 +58,13 @@ type TelnetConfig struct {
Port int `yaml:"port"`
}
+type TelnetTLSConfig struct {
+ Enabled bool `yaml:"enabled"`
+ Port int `yaml:"port"`
+ CertFile string `yaml:"cert_file"`
+ KeyFile string `yaml:"key_file"`
+}
+
type HTTPConfig struct {
Enabled bool `yaml:"enabled"`
Port int `yaml:"port"`
@@ -72,6 +87,10 @@ func Default() *Config {
Enabled: true,
Port: 4000,
},
+ TelnetTLS: TelnetTLSConfig{
+ Enabled: false,
+ Port: 4001,
+ },
HTTP: HTTPConfig{
Enabled: false,
Port: 8080,
diff --git a/internal/game/action.go b/internal/game/action.go
index 3857030..04e2b0a 100644
--- a/internal/game/action.go
+++ b/internal/game/action.go
@@ -6,12 +6,12 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
var verbAliases = map[string]string{
diff --git a/internal/game/action_burn.go b/internal/game/action_burn.go
index 5c19510..01bf385 100644
--- a/internal/game/action_burn.go
+++ b/internal/game/action_burn.go
@@ -4,11 +4,11 @@ import (
"fmt"
"math/rand"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doBurn(sess *net.Session, p *player.Player, input string) {
@@ -70,13 +70,13 @@ func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fro
}
if logDef.BurnTicks <= 0 {
- sess.WriteLine(fmt.Sprintf("You can't burn the %s.", logDef.Name))
+ sess.WriteLine(fmt.Sprintf("You can't burn the %s.", g.itemColorize(sess, logDef, logDef.Name)))
return
}
skillLevel := p.Level(player.Firemaking)
if logDef.FireLevel > 0 && skillLevel < logDef.FireLevel {
- sess.WriteLine(fmt.Sprintf("You need level %d firemaking to burn %s.", logDef.FireLevel, logDef.Name))
+ sess.WriteLine(fmt.Sprintf("You need level %d firemaking to burn %s.", logDef.FireLevel, g.itemColorize(sess, logDef, logDef.Name)))
return
}
@@ -87,13 +87,13 @@ func (g *Game) startBurn(sess *net.Session, p *player.Player, itemID string, fro
if !fromGround {
if !p.HasItem(itemID) {
- sess.WriteLine(fmt.Sprintf("You don't have any %s.", logDef.Name))
+ sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, logDef, logDef.Name)))
return
}
} else {
ground := g.World.GroundItems(p.RoomID)
if ground[itemID] <= 0 {
- sess.WriteLine(fmt.Sprintf("There are no %s on the ground here.", logDef.Name))
+ sess.WriteLine(fmt.Sprintf("There are no %s on the ground here.", g.itemColorize(sess, logDef, logDef.Name)))
return
}
}
@@ -135,7 +135,7 @@ func (g *Game) startStoke(sess *net.Session, p *player.Player, itemID string) {
}
if !p.HasItem(itemID) {
- sess.WriteLine(fmt.Sprintf("You don't have any %s.", logDef.Name))
+ sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, logDef, logDef.Name)))
return
}
@@ -171,7 +171,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
if phase == 0 {
if !p.HasItem(itemID) {
- sess.WriteLine(fmt.Sprintf("You're out of %s.", logDef.Name))
+ sess.WriteLine(fmt.Sprintf("You're out of %s.", g.itemColorize(sess, logDef, logDef.Name)))
g.CancelAction(p)
return
}
@@ -179,7 +179,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
g.World.AddGroundItem(p.RoomID, itemID, 1)
g.AccountStore.SaveCharacter(p)
- sess.WriteLine(fmt.Sprintf("You drop the %s and begin to make a fire...", logDef.Name))
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You drop the %s and begin to make a fire...", g.itemColorize(sess, logDef, logDef.Name))))
g.broadcastDrop(p, logDef.Name)
data["phase"] = 1
@@ -195,7 +195,7 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
}
if phase == 1 {
- sess.WriteLine(fmt.Sprintf("You try burning the %s on the ground...", logDef.Name))
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You try burning the %s on the ground...", g.itemColorize(sess, logDef, logDef.Name))))
data["phase"] = 2
p.Action.WaitLeft = engine.ToTicks(toolSpeed)
return
@@ -216,26 +216,26 @@ func (g *Game) advanceBurn(sess *net.Session, p *player.Player) {
g.World.AddObjInstance(p.RoomID, "fire", logDef.BurnTicks)
xp := logDef.FireXP
- if xp > 0 {
- if newLevel := p.AddSkillXP(player.Firemaking, xp); newLevel > 0 {
- sess.WriteLine(fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel))
- }
+ if xp > 0 {
+ if newLevel := p.AddSkillXP(player.Firemaking, xp); newLevel > 0 {
+ sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel)))
}
+ }
- g.AccountStore.SaveCharacter(p)
- g.CancelAction(p)
+ g.AccountStore.SaveCharacter(p)
+ g.CancelAction(p)
- msg := fmt.Sprintf("You manage to get a fire going!")
- if xp > 0 && p.OptionBool("xp_drops") {
- msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking])
- }
- sess.WriteLine(msg)
+ msg := g.colorize(sess, "fire", "You manage to get a fire going!")
+ if xp > 0 && p.OptionBool("xp_drops") {
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking]))
+ }
+ sess.WriteLine(msg)
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other != sess {
- other.WriteLine(fmt.Sprintf("\n%s started a fire!", p.Name))
- }
+ for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
+ if other != sess {
+ other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s started a fire!", p.Name)))
}
+ }
return
}
@@ -266,7 +266,7 @@ func (g *Game) advanceStoke(sess *net.Session, p *player.Player) {
}
if !p.HasItem(itemID) {
- sess.WriteLine(fmt.Sprintf("You're out of %s and the fire is roaring.", name))
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You're out of %s and the fire is roaring.", g.itemColorize(sess, logDef, name))))
g.CancelAction(p)
return
}
@@ -276,20 +276,20 @@ func (g *Game) advanceStoke(sess *net.Session, p *player.Player) {
if xp > 0 {
if newLevel := p.AddSkillXP(player.Firemaking, xp); newLevel > 0 {
- sess.WriteLine(fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel))
+ sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d firemaking! ***", newLevel)))
}
}
g.AccountStore.SaveCharacter(p)
- msg := fmt.Sprintf("You throw %s onto the fire.", name)
+ msg := g.colorize(sess, "fire", fmt.Sprintf("You throw %s onto the fire.", name))
if xp > 0 && p.OptionBool("xp_drops") {
- msg += fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking])
+ msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.Firemaking]))
}
sess.WriteLine(msg)
if !p.HasItem(itemID) {
- sess.WriteLine(fmt.Sprintf("You're out of %s and the fire is roaring.", name))
+ sess.WriteLine(g.colorize(sess, "fire", fmt.Sprintf("You're out of %s and the fire is roaring.", g.itemColorize(sess, logDef, name))))
g.CancelAction(p)
return
}
diff --git a/internal/game/action_cook.go b/internal/game/action_cook.go
index e4d2597..a5bda41 100644
--- a/internal/game/action_cook.go
+++ b/internal/game/action_cook.go
@@ -4,10 +4,10 @@ import (
"fmt"
"math/rand"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) startCook(sess *net.Session, p *player.Player, recipe *action.RecipeDef, stationName string) {
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index e3ea105..045c51b 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -5,12 +5,13 @@ import (
"math/rand"
"strings"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.ObjectDef, st *world.ObjState) {
@@ -263,6 +264,8 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
msg := drop.Message
if msg == "" {
msg = fmt.Sprintf("You manage to get some %s.", g.itemColorize(sess, itemDef, itemName))
+ } else {
+ msg = color.ExpandTags(g.colorMode(sess), msg)
}
if xp > 0 && p.OptionBool("xp_drops") {
msg += g.colorize(sess, "xp", fmt.Sprintf(" (+%dxp %s)", xp, player.SkillAbbr[player.SkillName(cfg.Skill)]))
diff --git a/internal/game/action_room.go b/internal/game/action_room.go
index 426b512..603ac7e 100644
--- a/internal/game/action_room.go
+++ b/internal/game/action_room.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
+ "thehouseoficarus/internal/net"
)
func (g *Game) RunEnterSteps(sess *net.Session, roomID int) {
@@ -40,7 +40,7 @@ func (g *Game) BroadcastRespawns() {
msg := strings.ReplaceAll(cfg.RespawnBroadcast, "{name}", name)
if g.Hub != nil {
for _, sess := range g.Hub.PlayersInRoom(st.RoomID) {
- sess.WriteLine(fmt.Sprintf("\n%s", msg))
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s", msg)))
}
}
}
diff --git a/internal/game/action_search.go b/internal/game/action_search.go
index e9d3d46..d45066d 100644
--- a/internal/game/action_search.go
+++ b/internal/game/action_search.go
@@ -3,10 +3,10 @@ package game
import (
"fmt"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) startSearch(sess *net.Session, p *player.Player, itemID string, slotIdx int) {
@@ -101,23 +101,24 @@ func (g *Game) giveSearchLoot(sess *net.Session, p *player.Player, drop *action.
}
name := drop.ItemID
- if def, err := g.ItemStore.Load(drop.ItemID); err == nil {
- name = def.Name
+ lootDef, _ := g.ItemStore.Load(drop.ItemID)
+ if lootDef != nil {
+ name = lootDef.Name
}
if drop.ItemID == "credits" {
p.Credits += qty
- sess.WriteLine(fmt.Sprintf("You find %d credits.", qty))
+ sess.WriteLine(g.colorize(sess, "credits_pickup", fmt.Sprintf("You find %d credits.", qty)))
return
}
freeSlot := p.FirstFreeSlot()
if freeSlot == -1 {
g.World.AddGroundItem(p.RoomID, drop.ItemID, qty)
- sess.WriteLine(fmt.Sprintf("You find %s. It falls to the ground.", name))
+ sess.WriteLine(fmt.Sprintf("You find %s. It falls to the ground.", g.itemColorize(sess, lootDef, name)))
return
}
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: drop.ItemID, Quantity: qty})
- sess.WriteLine(fmt.Sprintf("You find %s.", name))
+ sess.WriteLine(fmt.Sprintf("You find %s.", g.itemColorize(sess, lootDef, name)))
}
diff --git a/internal/game/action_talk.go b/internal/game/action_talk.go
index 9e90f93..30bf206 100644
--- a/internal/game/action_talk.go
+++ b/internal/game/action_talk.go
@@ -4,11 +4,11 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) startMobTalk(sess *net.Session, p *player.Player, mob *world.MobInstance) {
@@ -54,7 +54,7 @@ func (g *Game) startTalk(sess *net.Session, p *player.Player, obj *object.Object
}
func (g *Game) showTalkNode(sess *net.Session, node action.TalkNode) {
- sess.WriteLine(fmt.Sprintf("\n%s", node.Message))
+ sess.WriteLine(fmt.Sprintf("\n%s", g.colorize(sess, "dialog", node.Message)))
if node.Action != nil {
g.applyNodeAction(sess, node.Action)
diff --git a/internal/game/action_toggle.go b/internal/game/action_toggle.go
index f20d03c..db7a199 100644
--- a/internal/game/action_toggle.go
+++ b/internal/game/action_toggle.go
@@ -3,9 +3,9 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) startToggle(sess *net.Session, p *player.Player, obj *object.ObjectDef) {
@@ -41,7 +41,7 @@ func (g *Game) startToggle(sess *net.Session, p *player.Player, obj *object.Obje
if g.Hub != nil {
for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
if other != sess && other.Player != nil {
- other.WriteLine(fmt.Sprintf("\n%s uses the %s.", p.Name, obj.Name))
+ other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s uses the %s.", p.Name, obj.Name)))
}
}
}
diff --git a/internal/game/action_use.go b/internal/game/action_use.go
index d953521..b71ec17 100644
--- a/internal/game/action_use.go
+++ b/internal/game/action_use.go
@@ -4,11 +4,11 @@ import (
"fmt"
"math/rand"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) startUse(sess *net.Session, p *player.Player, obj *object.ObjectDef) {
@@ -99,16 +99,17 @@ func (g *Game) advanceUse(sess *net.Session, p *player.Player) {
p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: cfg.Reward.ItemID, Quantity: qty})
if cfg.XP > 0 {
if newLevel := p.AddSkillXP(player.SkillName(cfg.Skill), cfg.XP); newLevel > 0 {
- sess.WriteLine(fmt.Sprintf("*** You are now level %d %s! ***", newLevel, cfg.Skill))
+ sess.WriteLine(g.colorize(sess, "level_up", fmt.Sprintf("*** You are now level %d %s! ***", newLevel, cfg.Skill)))
}
}
g.AccountStore.SaveCharacter(p)
itemName := cfg.Reward.ItemID
- if def, err := g.ItemStore.Load(cfg.Reward.ItemID); err == nil {
- itemName = def.Name
+ rewardDef, _ := g.ItemStore.Load(cfg.Reward.ItemID)
+ if rewardDef != nil {
+ itemName = rewardDef.Name
}
- line := fmt.Sprintf("You make a %s.", itemName)
+ line := fmt.Sprintf("You make a %s.", g.itemColorize(sess, rewardDef, itemName))
if cfg.XP > 0 && p.OptionBool("xp_drops") {
line += fmt.Sprintf(" (+%dxp %s)", cfg.XP, player.SkillAbbr[player.SkillName(cfg.Skill)])
}
diff --git a/internal/game/cmd_alias.go b/internal/game/cmd_alias.go
index cb0537e..e7e4e12 100644
--- a/internal/game/cmd_alias.go
+++ b/internal/game/cmd_alias.go
@@ -5,7 +5,7 @@ import (
"sort"
"strings"
- "thirdcollapse/internal/net"
+ "thehouseoficarus/internal/net"
)
func (g *Game) doAlias(sess *net.Session, args []string) {
diff --git a/internal/game/cmd_attack.go b/internal/game/cmd_attack.go
index 2e15419..359e0ac 100644
--- a/internal/game/cmd_attack.go
+++ b/internal/game/cmd_attack.go
@@ -6,12 +6,12 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) doAttack(sess *net.Session, input string) {
@@ -308,7 +308,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
op := other.Player.(*player.Player)
mobLvl := mobCombatLevel(mob)
levelStr := g.levelColorize(other, op.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl))
- other.WriteLine(fmt.Sprintf("\n%s has slain %s %s!", p.Name, mobDisplayName(mob, false), levelStr))
+ other.WriteLine(g.colorize(other, "broadcast", fmt.Sprintf("\n%s has slain %s %s!", p.Name, mobDisplayName(mob, false), levelStr)))
}
}
}
@@ -324,7 +324,7 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
if !mob.Unique {
dropper = "The " + mob.Name
}
- sess.WriteLine(fmt.Sprintf(" %s drops: %s", dropper, name))
+ sess.WriteLine(g.colorize(sess, "drop_message", fmt.Sprintf(" %s drops: %s", dropper, name)))
}
if len(mob.Drops.Loot) > 0 {
@@ -345,9 +345,9 @@ func (g *Game) endCombat(sess *net.Session, p *player.Player, mob *world.MobInst
dropper = "The " + mob.Name
}
if qty > 1 {
- sess.WriteLine(fmt.Sprintf(" %s drops: %d x %s", dropper, qty, name))
+ sess.WriteLine(g.colorize(sess, "drop_message", fmt.Sprintf(" %s drops: %d x %s", dropper, qty, name)))
} else {
- sess.WriteLine(fmt.Sprintf(" %s drops: %s", dropper, name))
+ sess.WriteLine(g.colorize(sess, "drop_message", fmt.Sprintf(" %s drops: %s", dropper, name)))
}
}
}
@@ -476,7 +476,7 @@ func (g *Game) respawnMob(instanceID string) {
if p, ok := sess.Player.(*player.Player); ok && p.OptionBool("mob_spawn") {
mobLvl := mobCombatLevel(inst)
levelStr := g.levelColorize(sess, p.CombatLevel(), mobLvl, fmt.Sprintf("(level %d)", mobLvl))
- sess.WriteLine(fmt.Sprintf("\n%s %s spawns in the area.", mobDisplayName(inst, false), levelStr))
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s %s spawns in the area.", mobDisplayName(inst, false), levelStr)))
}
}
}
diff --git a/internal/game/cmd_color.go b/internal/game/cmd_color.go
index 7a208a2..1dd14fa 100644
--- a/internal/game/cmd_color.go
+++ b/internal/game/cmd_color.go
@@ -4,10 +4,10 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/color"
- "thirdcollapse/internal/config"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/config"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doColor(sess *net.Session, input string) {
@@ -57,8 +57,8 @@ func (g *Game) doColor(sess *net.Session, input string) {
spec := color.Parse(value)
if spec.Empty() && value != "" {
sess.WriteLine(fmt.Sprintf("\nInvalid color string: %s", value))
- sess.WriteLine("Format: fg=<color> bg=<color> bold dim italic underline")
- sess.WriteLine("Example: fg=green bg=black bold")
+ sess.WriteLine("Format: <0-255> [bg:<0-255>] [bold] [dim] [underline]")
+ sess.WriteLine("Example: 208 bold")
return
}
@@ -150,17 +150,40 @@ var colorCategoryOrder = []string{
"victory",
"miss",
"error",
+ "say",
+ "dialog",
+ "broadcast",
+ "fire",
+ "eat_food",
+ "drop_message",
+ "credits_pickup",
}
func showColorTable(g *Game, sess *net.Session) {
p := sess.Player.(*player.Player)
+ mode := g.colorMode(sess)
table := &Table{
- Columns: []string{"Target", "Color", "Source"},
+ Columns: []string{
+ color.Render(mode, color.Parse("75"), "Target"),
+ "Color",
+ color.Render(mode, color.Parse("243"), "Source"),
+ },
}
for _, cat := range colorCategoryOrder {
val := g.getCurrentColor(sess, cat)
source := g.colorSource(sess, cat)
- table.Rows = append(table.Rows, []string{cat, val, source})
+ coloredVal := val
+ if val != "off" && val != "" {
+ spec := color.Parse(val)
+ if !spec.Empty() {
+ coloredVal = color.Render(mode, spec, val)
+ }
+ }
+ table.Rows = append(table.Rows, []string{
+ color.Render(mode, color.Parse("75"), cat),
+ coloredVal,
+ color.Render(mode, color.Parse("243"), source),
+ })
}
for _, line := range table.Render(p.OptionBool("unicode")) {
sess.WriteLine(line)
diff --git a/internal/game/cmd_colortable.go b/internal/game/cmd_colortable.go
new file mode 100644
index 0000000..90ae8c4
--- /dev/null
+++ b/internal/game/cmd_colortable.go
@@ -0,0 +1,151 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) doColortable(sess *net.Session) {
+ mode := g.colorMode(sess)
+ if mode == "none" || mode == "" {
+ sess.WriteLine("\nEnable colors first: option color ansi (or xterm256)")
+ return
+ }
+
+ p := sess.Player.(*player.Player)
+ unicode := p.OptionBool("unicode")
+
+ sess.WriteLine(fmt.Sprintf("\nColor mode: %s", mode))
+
+ sess.WriteLine("")
+ writeSectionHeader(sess, "Foreground Colors", unicode)
+ writeColorBlock(sess, mode, true)
+
+ sess.WriteLine("")
+ writeSectionHeader(sess, "Background Colors", unicode)
+ writeColorBlock(sess, mode, false)
+
+ sess.WriteLine("")
+ writeSectionHeader(sess, "Combinations", unicode)
+ writeCombinations(sess, mode)
+
+ sess.WriteLine("")
+ writeSectionHeader(sess, "ANSI Mode (16 colors)", unicode)
+ writeANSITable(sess, mode)
+}
+
+func writeSectionHeader(sess *net.Session, title string, unicode bool) {
+ if unicode {
+ sess.WriteLine(fmt.Sprintf("── %s ──", title))
+ } else {
+ sess.WriteLine(fmt.Sprintf("-- %s --", title))
+ }
+}
+
+func writeColorBlock(sess *net.Session, mode string, fg bool) {
+ sess.WriteLine("Standard:")
+ writeColorRow(sess, mode, 0, 8, fg)
+ sess.WriteLine("Bright:")
+ writeColorRow(sess, mode, 8, 16, fg)
+
+ sess.WriteLine("Color cube:")
+ for blockRow := 0; blockRow < 2; blockRow++ {
+ for row := 0; row < 6; row++ {
+ var sb strings.Builder
+ for block := 0; block < 3; block++ {
+ base := 16 + (blockRow*3+block)*36 + row*6
+ for col := 0; col < 6; col++ {
+ idx := base + col
+ if idx > 231 {
+ break
+ }
+ if fg {
+ sb.WriteString(color.FgCode(mode, idx))
+ } else {
+ sb.WriteString(color.ContrastFg(mode, idx))
+ sb.WriteString(color.BgCode(mode, idx))
+ }
+ sb.WriteString(fmt.Sprintf("%4d", idx))
+ sb.WriteString(color.Reset)
+ }
+ if block < 2 {
+ sb.WriteString(" ")
+ }
+ }
+ sess.WriteLine(sb.String())
+ }
+ if blockRow == 0 {
+ sess.WriteLine("")
+ }
+ }
+
+ sess.WriteLine("Grayscale:")
+ writeColorRow(sess, mode, 232, 244, fg)
+ writeColorRow(sess, mode, 244, 256, fg)
+}
+
+func writeColorRow(sess *net.Session, mode string, start, end int, fg bool) {
+ var sb strings.Builder
+ for i := start; i < end && i < 256; i++ {
+ if fg {
+ sb.WriteString(color.FgCode(mode, i))
+ } else {
+ sb.WriteString(color.ContrastFg(mode, i))
+ sb.WriteString(color.BgCode(mode, i))
+ }
+ sb.WriteString(fmt.Sprintf("%4d", i))
+ sb.WriteString(color.Reset)
+ }
+ sess.WriteLine(sb.String())
+}
+
+func writeCombinations(sess *net.Session, mode string) {
+ type combo struct {
+ fg int
+ bg int
+ desc string
+ }
+ combos := []combo{
+ {15, 0, "white on black"},
+ {208, 0, "orange on black"},
+ {0, 178, "black on gold"},
+ {15, 24, "white on dark blue"},
+ {0, 15, "black on white"},
+ {15, 88, "white on dark red"},
+ {178, 52, "gold on brown"},
+ {14, 0, "bright cyan on black"},
+ {0, 228, "black on pale yellow"},
+ {15, 22, "white on dark green"},
+ {9, 0, "bright red on black"},
+ {11, 17, "bright yellow on navy"},
+ }
+ for _, c := range combos {
+ spec := color.ColorSpec{Fg: c.fg, Bg: c.bg}
+ sample := color.Render(mode, spec, fmt.Sprintf(" %3d on %3d ", c.fg, c.bg))
+ sess.WriteLine(fmt.Sprintf(" %s %s", sample, c.desc))
+ }
+}
+
+func writeANSITable(sess *net.Session, mode string) {
+ names := [16]string{
+ "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white",
+ "brt black", "brt red", "brt green", "brt yellow",
+ "brt blue", "brt magenta", "brt cyan", "brt white",
+ }
+ for row := 0; row < 2; row++ {
+ var sb strings.Builder
+ for col := 0; col < 8; col++ {
+ idx := row*8 + col
+ sb.WriteString(color.FgCode(mode, idx))
+ sb.WriteString(fmt.Sprintf("%3d", idx))
+ sb.WriteString(color.Reset)
+ sb.WriteString(fmt.Sprintf(" %-12s", names[idx]))
+ }
+ sess.WriteLine(sb.String())
+ }
+}
diff --git a/internal/game/cmd_cook.go b/internal/game/cmd_cook.go
index be5fa3a..054a4b0 100644
--- a/internal/game/cmd_cook.go
+++ b/internal/game/cmd_cook.go
@@ -5,9 +5,9 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
type cookableEntry struct {
diff --git a/internal/game/cmd_description.go b/internal/game/cmd_description.go
index 56ce441..20ada37 100644
--- a/internal/game/cmd_description.go
+++ b/internal/game/cmd_description.go
@@ -3,8 +3,8 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doDescription(sess *net.Session) {
diff --git a/internal/game/cmd_drop.go b/internal/game/cmd_drop.go
index b08eadf..702d1db 100644
--- a/internal/game/cmd_drop.go
+++ b/internal/game/cmd_drop.go
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doDropAll(sess *net.Session) {
@@ -179,11 +179,11 @@ func (g *Game) doDrop(sess *net.Session, input string) {
dropped := qty - remaining
g.AccountStore.SaveCharacter(p)
if dropped == 0 {
- sess.WriteLine(fmt.Sprintf("You don't have any %s.", name))
+ sess.WriteLine(fmt.Sprintf("You don't have any %s.", g.itemColorize(sess, def, name)))
} else if dropped == 1 {
- sess.WriteLine(fmt.Sprintf("You drop a %s.", name))
+ sess.WriteLine(fmt.Sprintf("You drop a %s.", g.itemColorize(sess, def, name)))
} else {
- sess.WriteLine(fmt.Sprintf("You drop %d x %s.", dropped, name))
+ sess.WriteLine(fmt.Sprintf("You drop %d x %s.", dropped, g.itemColorize(sess, def, name)))
}
}
@@ -215,7 +215,7 @@ func (g *Game) handleDropAllConfirm(sess *net.Session, input string) {
if def != nil {
name = def.Name
}
- dropped = append(dropped, name)
+ dropped = append(dropped, g.itemColorize(sess, def, name))
p.SetInvSlot(i, nil)
}
diff --git a/internal/game/cmd_eat.go b/internal/game/cmd_eat.go
index 5d6292b..9d4bfd0 100644
--- a/internal/game/cmd_eat.go
+++ b/internal/game/cmd_eat.go
@@ -4,9 +4,9 @@ import (
"fmt"
"time"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doEat(sess *net.Session, input string) {
@@ -65,7 +65,7 @@ func (g *Game) doEat(sess *net.Session, input string) {
}
if !p.OptionBool("queue_silently") {
- sess.WriteLine(fmt.Sprintf("\nYou prepare to eat %s.", def.Name))
+ sess.WriteLine(fmt.Sprintf("\nYou prepare to eat %s.", g.itemColorize(sess, def, def.Name)))
}
}
@@ -98,7 +98,7 @@ func (g *Game) doEatNow(sess *net.Session, p *player.Player, itemID string, def
p.ActionState = &ActionState{Type: ActionEating, TargetName: def.Name}
- sess.WriteLine(def.EatMessage)
+ sess.WriteLine(g.colorize(sess, "eat_food", def.EatMessage))
if p.HP <= 0 {
g.endCombat(sess, p, nil)
diff --git a/internal/game/cmd_equipment.go b/internal/game/cmd_equipment.go
index 4947d2b..7113880 100644
--- a/internal/game/cmd_equipment.go
+++ b/internal/game/cmd_equipment.go
@@ -3,8 +3,8 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doEquipment(sess *net.Session) {
diff --git a/internal/game/cmd_get.go b/internal/game/cmd_get.go
index e75c59e..8237ec9 100644
--- a/internal/game/cmd_get.go
+++ b/internal/game/cmd_get.go
@@ -3,8 +3,8 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doGet(sess *net.Session, input string) {
@@ -125,17 +125,9 @@ func (g *Game) doGet(sess *net.Session, input string) {
if picked == 0 {
sess.WriteLine("Your inventory is full.")
} else if picked == 1 {
- name := itemID
- if def != nil {
- name = def.Name
- }
- sess.WriteLine(fmt.Sprintf("You pick up a %s.", name))
+ sess.WriteLine(fmt.Sprintf("You pick up a %s.", g.itemColorize(sess, def, name)))
} else {
- name := itemID
- if def != nil {
- name = def.Name
- }
- sess.WriteLine(fmt.Sprintf("You pick up %d x %s.", picked, name))
+ sess.WriteLine(fmt.Sprintf("You pick up %d x %s.", picked, g.itemColorize(sess, def, name)))
}
}
@@ -306,7 +298,7 @@ func (g *Game) doGetAll(sess *net.Session) {
break
}
slot.Quantity += qty
- picked = append(picked, fmt.Sprintf("%s (now %d)", def.Name, slot.Quantity))
+ picked = append(picked, fmt.Sprintf("%s (now %d)", g.itemColorize(sess, def, def.Name), slot.Quantity))
stacked = true
qty = 0
break
@@ -335,7 +327,7 @@ func (g *Game) doGetAll(sess *net.Session) {
if def != nil {
name = def.Name
}
- picked = append(picked, name)
+ picked = append(picked, g.itemColorize(sess, def, name))
break
}
@@ -361,7 +353,7 @@ func (g *Game) doGetAll(sess *net.Session) {
if def != nil {
name = def.Name
}
- picked = append(picked, name)
+ picked = append(picked, g.itemColorize(sess, def, name))
qty -= take
}
}
@@ -388,9 +380,9 @@ func (g *Game) pickupCredits(sess *net.Session, p *player.Player, itemID string)
p.Credits += qty
g.AccountStore.SaveCharacter(p)
if qty == 1 {
- sess.WriteLine("You pick up 1 credit.")
+ sess.WriteLine(g.colorize(sess, "credits_pickup", "You pick up 1 credit."))
} else {
- sess.WriteLine(fmt.Sprintf("You pick up %d credits. (total: %d)", qty, p.Credits))
+ sess.WriteLine(g.colorize(sess, "credits_pickup", fmt.Sprintf("You pick up %d credits. (total: %d)", qty, p.Credits)))
}
}
diff --git a/internal/game/cmd_inventory.go b/internal/game/cmd_inventory.go
index 7017b18..8684fcd 100644
--- a/internal/game/cmd_inventory.go
+++ b/internal/game/cmd_inventory.go
@@ -3,21 +3,19 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doInventory(sess *net.Session) {
p := sess.Player.(*player.Player)
sess.WriteLine("")
- sess.WriteLine("Inventory (28 slots):")
+ sess.WriteLine(fmt.Sprintf("Inventory (%d free):", p.FreeSlots()))
- empty := 0
num := 0
for i := 0; i < 28; i++ {
slot := p.InvSlot(i)
if slot == nil {
- empty++
continue
}
num++
@@ -33,5 +31,4 @@ func (g *Game) doInventory(sess *net.Session) {
sess.WriteLine(fmt.Sprintf(" %2d) %s", num, coloredName))
}
}
- sess.WriteLine(fmt.Sprintf(" (%d empty slots)", empty))
}
diff --git a/internal/game/cmd_look.go b/internal/game/cmd_look.go
index bd738be..8cadfd3 100644
--- a/internal/game/cmd_look.go
+++ b/internal/game/cmd_look.go
@@ -2,16 +2,16 @@ package game
import (
"fmt"
- "regexp"
"sort"
"strconv"
"strings"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) doLook(sess *net.Session) {
@@ -32,9 +32,11 @@ func (g *Game) doLook(sess *net.Session) {
descWidth = 70
}
rawLines := wrapText(room.Description, descWidth)
+ mode := g.colorMode(sess)
+ roomDescSpec := g.resolveColor(sess, "room_desc")
descLines := make([]string, len(rawLines))
for i, l := range rawLines {
- descLines[i] = g.colorize(sess, "room_desc", l)
+ descLines[i] = color.ExpandTagsWithDefault(mode, roomDescSpec, l)
}
wroteDesc := false
if p.OptionString("tiny_map") != "off" {
@@ -331,7 +333,7 @@ func (g *Game) doLook(sess *net.Session) {
for _, other := range others {
if other != sess && other.Player != nil {
op := other.Player.(*player.Player)
- line := fmt.Sprintf("\n%s is here", op.Name)
+ line := fmt.Sprintf("\n%s is here", g.colorize(sess, "player_name", op.Name))
if cs := combat.GetCombat(op.Name); cs != nil {
if mob := g.MobStore.GetInstance(cs.MobID); mob != nil && mob.HP > 0 {
name := mobDisplayName(mob, false)
@@ -476,7 +478,7 @@ func (g *Game) doLookTarget(sess *net.Session, input string) {
}
lines := []string{
"",
- def.Name,
+ g.itemColorize(sess, def, def.Name),
fmt.Sprintf(" %s", def.Description),
fmt.Sprintf(" Value: %d credits", def.Value),
}
@@ -606,10 +608,8 @@ func mobInstanceIdx(mob *world.MobInstance, roomMobs []*world.MobInstance) int {
return 0
}
-var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
-
func visibleLen(s string) int {
- return len(ansiRe.ReplaceAllString(s, ""))
+ return color.VisibleLen(s)
}
func wrapText(text string, width int) []string {
diff --git a/internal/game/cmd_map.go b/internal/game/cmd_map.go
index f4ec11c..1c78be3 100644
--- a/internal/game/cmd_map.go
+++ b/internal/game/cmd_map.go
@@ -1,8 +1,8 @@
package game
import (
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doMap(sess *net.Session) {
diff --git a/internal/game/cmd_move.go b/internal/game/cmd_move.go
index 11aa17e..a140003 100644
--- a/internal/game/cmd_move.go
+++ b/internal/game/cmd_move.go
@@ -3,9 +3,9 @@ package game
import (
"fmt"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doMove(sess *net.Session, dir string) {
diff --git a/internal/game/cmd_option.go b/internal/game/cmd_option.go
index 9302baa..3d4d420 100644
--- a/internal/game/cmd_option.go
+++ b/internal/game/cmd_option.go
@@ -5,22 +5,34 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doOption(sess *net.Session, input string) {
p := sess.Player.(*player.Player)
+ mode := g.colorMode(sess)
if input == "" {
sess.WriteLine("")
table := &Table{
- Columns: []string{"Option", "Value", "Valid", "Description"},
+ Columns: []string{
+ color.Render(mode, color.Parse("75"), "Option"),
+ color.Render(mode, color.Parse("230"), "Value"),
+ color.Render(mode, color.Parse("243"), "Valid"),
+ color.Render(mode, color.Parse("252"), "Description"),
+ },
}
for _, def := range player.OptionDefs {
val := formatOptionValue(p, &def)
valid := formatValidValues(&def)
- table.Rows = append(table.Rows, []string{def.Name, val, valid, def.Description})
+ table.Rows = append(table.Rows, []string{
+ color.Render(mode, color.Parse("75"), def.Name),
+ color.Render(mode, color.Parse("230"), val),
+ color.Render(mode, color.Parse("243"), valid),
+ color.Render(mode, color.Parse("252"), def.Description),
+ })
}
for _, line := range table.Render(p.OptionBool("unicode")) {
sess.WriteLine(line)
@@ -56,7 +68,20 @@ func (g *Game) doOption(sess *net.Session, input string) {
p.Options = make(map[string]any)
}
p.Options[def.Name] = parsed
- g.AccountStore.SaveCharacter(p)
+
+ if sess.Account.Options == nil {
+ sess.Account.Options = make(map[string]any)
+ }
+ sess.Account.Options[def.Name] = parsed
+
+ acc, err := g.AccountStore.LoadAccount(sess.Account.Name)
+ if err == nil {
+ if acc.Options == nil {
+ acc.Options = make(map[string]any)
+ }
+ acc.Options[def.Name] = parsed
+ g.AccountStore.SaveAccount(acc)
+ }
sess.WriteLine(fmt.Sprintf("\n%s set to %s.", def.Name, formatOptionValue(p, def)))
}
diff --git a/internal/game/cmd_prompt.go b/internal/game/cmd_prompt.go
new file mode 100644
index 0000000..81c3409
--- /dev/null
+++ b/internal/game/cmd_prompt.go
@@ -0,0 +1,33 @@
+package game
+
+import (
+ "fmt"
+
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+)
+
+func (g *Game) doPrompt(sess *net.Session, input string) {
+ p := sess.Player.(*player.Player)
+
+ if input == "" {
+ prompt := p.Prompt
+ if prompt == "" {
+ prompt = "> "
+ }
+ sess.WriteLine(fmt.Sprintf("\nPrompt: %s", prompt))
+ sess.WriteLine("Use 'prompt <value>' to change. Use 'prompt reset' to restore default.")
+ return
+ }
+
+ if input == "reset" {
+ p.Prompt = "> "
+ g.AccountStore.SaveCharacter(p)
+ sess.WriteLine("\nPrompt reset to default.")
+ return
+ }
+
+ p.Prompt = input
+ g.AccountStore.SaveCharacter(p)
+ sess.WriteLine(fmt.Sprintf("\nPrompt set to: %s", input))
+}
diff --git a/internal/game/cmd_queued.go b/internal/game/cmd_queued.go
index 040ff31..d9e92d5 100644
--- a/internal/game/cmd_queued.go
+++ b/internal/game/cmd_queued.go
@@ -4,8 +4,8 @@ import (
"fmt"
"sort"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doQueued(sess *net.Session) {
diff --git a/internal/game/cmd_quit.go b/internal/game/cmd_quit.go
index 446305b..89b6e7f 100644
--- a/internal/game/cmd_quit.go
+++ b/internal/game/cmd_quit.go
@@ -1,10 +1,10 @@
package game
import (
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doQuit(sess *net.Session) {
diff --git a/internal/game/cmd_remove.go b/internal/game/cmd_remove.go
index f511ec4..61fdc29 100644
--- a/internal/game/cmd_remove.go
+++ b/internal/game/cmd_remove.go
@@ -4,9 +4,9 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doRemove(sess *net.Session, input string) {
@@ -68,8 +68,9 @@ func (g *Game) doRemove(sess *net.Session, input string) {
g.AccountStore.SaveCharacter(p)
name := foundItemID
- if def, err := g.ItemStore.Load(foundItemID); err == nil {
+ def, _ := g.ItemStore.Load(foundItemID)
+ if def != nil {
name = def.Name
}
- sess.WriteLine(fmt.Sprintf("You remove %s.", name))
+ sess.WriteLine(fmt.Sprintf("You remove %s.", g.itemColorize(sess, def, name)))
}
diff --git a/internal/game/cmd_say.go b/internal/game/cmd_say.go
index 35ec5fa..67d8165 100644
--- a/internal/game/cmd_say.go
+++ b/internal/game/cmd_say.go
@@ -3,8 +3,8 @@ package game
import (
"fmt"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doSay(sess *net.Session, msg string) {
@@ -13,9 +13,9 @@ func (g *Game) doSay(sess *net.Session, msg string) {
for _, other := range g.Hub.PlayersInRoom(roomID) {
if other == sess {
- other.WriteLine(fmt.Sprintf("You say: %s", msg))
+ other.WriteLine(fmt.Sprintf("You say: %s", g.colorize(sess, "say", msg)))
} else {
- other.WriteLine(fmt.Sprintf("\n%s says: %s", p.Name, msg))
+ other.WriteLine(fmt.Sprintf("\n%s says: %s", g.colorize(other, "player_name", p.Name), g.colorize(other, "say", msg)))
}
}
}
diff --git a/internal/game/cmd_score.go b/internal/game/cmd_score.go
index 2c62839..40d3007 100644
--- a/internal/game/cmd_score.go
+++ b/internal/game/cmd_score.go
@@ -4,29 +4,35 @@ import (
"fmt"
"strconv"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doScore(sess *net.Session) {
p := sess.Player.(*player.Player)
+ mode := g.colorMode(sess)
sess.WriteLines(
"",
- fmt.Sprintf("Name: %s", p.Name),
- fmt.Sprintf("Combat Level: %d", p.CombatLevel()),
- fmt.Sprintf("HP: %d/%d", p.HP, p.MaxHP()),
- fmt.Sprintf("Credits: %d", p.Credits),
+ fmt.Sprintf("Name: %s", g.colorize(sess, "player_name", p.Name)),
+ fmt.Sprintf("Combat Level: %s", color.Render(mode, color.Parse("230"), fmt.Sprint(p.CombatLevel()))),
+ fmt.Sprintf("HP: %s/%s", g.colorize(sess, "character_hp", fmt.Sprint(p.HP)), color.Render(mode, color.Parse("230"), fmt.Sprint(p.MaxHP()))),
+ fmt.Sprintf("Credits: %s", g.colorize(sess, "credits_pickup", fmt.Sprint(p.Credits))),
)
- t := &Table{Title: "Skills", Columns: []string{"Skill", "Level", "XP"}}
+ t := &Table{Title: "Skills", Columns: []string{
+ color.Render(mode, color.Parse("75"), "Skill"),
+ color.Render(mode, color.Parse("230"), "Level"),
+ color.Render(mode, color.Parse("222"), "XP"),
+ }}
for _, s := range player.AllSkills {
level := p.Level(s)
xp := p.Skills[s]
next := player.XPForNextLevel(xp)
t.Rows = append(t.Rows, []string{
- string(s),
- strconv.Itoa(level),
- fmt.Sprintf("%d / %d XP", xp, xp+next),
+ color.Render(mode, color.Parse("75"), string(s)),
+ color.Render(mode, color.Parse("230"), strconv.Itoa(level)),
+ color.Render(mode, color.Parse("222"), fmt.Sprintf("%d / %d XP", xp, xp+next)),
})
}
for _, line := range t.Render(p.OptionBool("unicode")) {
diff --git a/internal/game/cmd_search.go b/internal/game/cmd_search.go
index d9a5b1e..d4d2e72 100644
--- a/internal/game/cmd_search.go
+++ b/internal/game/cmd_search.go
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doSearch(sess *net.Session, input string) {
diff --git a/internal/game/cmd_style.go b/internal/game/cmd_style.go
index d86920c..901d02d 100644
--- a/internal/game/cmd_style.go
+++ b/internal/game/cmd_style.go
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doStyle(sess *net.Session, input string) {
diff --git a/internal/game/cmd_use.go b/internal/game/cmd_use.go
index ef36e12..56aa5a1 100644
--- a/internal/game/cmd_use.go
+++ b/internal/game/cmd_use.go
@@ -4,9 +4,9 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doUse(sess *net.Session, input string) {
diff --git a/internal/game/cmd_walk.go b/internal/game/cmd_walk.go
index 31f9e18..4ff6187 100644
--- a/internal/game/cmd_walk.go
+++ b/internal/game/cmd_walk.go
@@ -5,9 +5,9 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) doWalk(sess *net.Session, args []string) {
diff --git a/internal/game/cmd_wear.go b/internal/game/cmd_wear.go
index 7c3515f..cbc2ff3 100644
--- a/internal/game/cmd_wear.go
+++ b/internal/game/cmd_wear.go
@@ -4,9 +4,9 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) doWear(sess *net.Session, input string) {
@@ -39,7 +39,7 @@ func (g *Game) doWear(sess *net.Session, input string) {
def, err := g.ItemStore.Load(slot.ItemID)
if err != nil || def.EquipSlot == "" {
- sess.WriteLine(fmt.Sprintf("You can't wear %s.", match.Name))
+ sess.WriteLine(fmt.Sprintf("You can't wear %s.", g.itemColorize(sess, def, match.Name)))
return
}
@@ -76,7 +76,7 @@ func (g *Game) doWear(sess *net.Session, input string) {
if def.WeaponType != "" {
verb = "wield"
}
- sess.WriteLine(fmt.Sprintf("You %s %s.", verb, match.Name))
+ sess.WriteLine(fmt.Sprintf("You %s %s.", verb, g.itemColorize(sess, def, match.Name)))
}
func (g *Game) doWearAll(sess *net.Session) {
@@ -147,7 +147,7 @@ func (g *Game) doWearAll(sess *net.Session) {
if def != nil {
name = def.Name
}
- equipped = append(equipped, name)
+ equipped = append(equipped, g.itemColorize(sess, def, name))
}
if len(equipped) == 0 {
diff --git a/internal/game/color.go b/internal/game/color.go
index f93f097..4fd8000 100644
--- a/internal/game/color.go
+++ b/internal/game/color.go
@@ -1,11 +1,11 @@
package game
import (
- "thirdcollapse/internal/color"
- "thirdcollapse/internal/config"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/config"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
)
func (g *Game) colorMode(sess *net.Session) string {
@@ -19,7 +19,7 @@ func (g *Game) resolveColor(sess *net.Session, category string) color.ColorSpec
if sess.Account != nil && sess.Account.Colors != nil {
if val, ok := sess.Account.Colors[category]; ok {
if val == "off" {
- return color.ColorSpec{}
+ return color.NoColor()
}
if val != "" {
return color.Parse(val)
@@ -34,7 +34,7 @@ func (g *Game) resolveColor(sess *net.Session, category string) color.ColorSpec
if val, ok := config.DefaultColors()[category]; ok && val != "" {
return color.Parse(val)
}
- return color.ColorSpec{}
+ return color.NoColor()
}
func (g *Game) colorize(sess *net.Session, category, text string) string {
@@ -46,15 +46,15 @@ func levelColorSpec(myLevel, theirLevel int) color.ColorSpec {
diff := theirLevel - myLevel
switch {
case diff == 0:
- return color.Parse("fg=white")
+ return color.Parse("7")
case diff > 0 && diff < 5:
- return color.Parse("fg=bright_yellow")
+ return color.Parse("11")
case diff >= 5:
- return color.Parse("fg=bright_red")
+ return color.Parse("9")
case diff < 0 && diff > -5:
- return color.Parse("fg=yellow")
+ return color.Parse("3")
default:
- return color.Parse("fg=green")
+ return color.Parse("2")
}
}
diff --git a/internal/game/game.go b/internal/game/game.go
index 33dd0d1..9d07ead 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -7,14 +7,14 @@ import (
"sync"
"time"
- "thirdcollapse/internal/action"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/config"
- "thirdcollapse/internal/engine"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/object"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/action"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/config"
+ "thehouseoficarus/internal/engine"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/object"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
type CommandClass int
@@ -124,6 +124,8 @@ func (g *Game) HandleSession(sess *net.Session, input string) {
g.handleDropAllConfirm(sess, input)
case net.StateCookRecipe:
g.handleCookRecipe(sess, input)
+ case net.StateColorChoice:
+ g.handleColorChoice(sess, input)
}
}
@@ -132,9 +134,10 @@ func classifyCommand(cmd string) CommandClass {
case "say", "score", "sc", "inventory", "i", "inv",
"equipment", "eq", "look", "l", "exits", "help",
"map", "option", "options", "alias", "unalias",
- "description", "desc", "queued", "color", "colors":
+ "description", "desc", "queued", "color", "colors",
+ "colortable", "prompt", "style":
return ClassInstant
- case "wear", "wield", "remove", "unwear", "unwield", "style":
+ case "wear", "wield", "remove", "unwear", "unwield":
return ClassFree
case "get", "take", "grab", "pick", "drop",
"attack", "kill",
@@ -310,6 +313,19 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
g.doOption(sess, strings.Join(args, " "))
case "color", "colors":
g.doColor(sess, strings.Join(args, " "))
+ case "colortable":
+ g.doColortable(sess)
+ case "prompt":
+ if len(args) == 0 {
+ g.doPrompt(sess, "")
+ } else {
+ msgStart := strings.Index(strings.ToLower(rawInput), "prompt ") + 7
+ if msgStart >= 7 && msgStart < len(rawInput) {
+ g.doPrompt(sess, rawInput[msgStart:])
+ } else {
+ g.doPrompt(sess, strings.Join(args, " "))
+ }
+ }
case "exits":
g.doExits(sess)
case "map":
diff --git a/internal/game/help.go b/internal/game/help.go
index d3f34c3..f9d822c 100644
--- a/internal/game/help.go
+++ b/internal/game/help.go
@@ -6,8 +6,8 @@ import (
"path/filepath"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
"gopkg.in/yaml.v3"
)
@@ -29,6 +29,7 @@ var commandList = []cmdEntry{
{"burn", "Active", "Start a fire"},
{"chop / cut", "Active", "Chop trees (Woodcutting)"},
{"color / colors", "Instant", "Customize display colors"},
+ {"colortable", "Instant", "Display color reference chart"},
{"cook", "Active", "Cook raw food on a fire or range"},
{"description / desc", "Instant", "Set your character description"},
{"drop", "Active", "Drop items to the ground"},
@@ -44,6 +45,7 @@ var commandList = []cmdEntry{
{"mine", "Active", "Mine rocks (Mining)"},
{"north / south / east / west / up / down", "Active", "Move in a direction"},
{"option / options", "Instant", "View or change settings"},
+ {"prompt", "Instant", "Set custom command prompt"},
{"pull / push", "Active", "Toggle levers and switches"},
{"queued", "Instant", "Show pending tick actions"},
{"quit", "Active", "Rest and disconnect"},
@@ -52,7 +54,7 @@ var commandList = []cmdEntry{
{"score / sc", "Instant", "View your stats and skills"},
{"search", "Active", "Search items for loot"},
{"stoke", "Active", "Add logs to a fire"},
- {"style", "Free", "Change combat style"},
+ {"style", "Instant", "Change combat style"},
{"talk / speak / ask", "Active", "Talk to NPCs"},
{"unalias", "Instant", "Remove command shortcuts"},
{"use", "Active", "Use an object (crafting)"},
diff --git a/internal/game/login_account.go b/internal/game/login_account.go
index 3ad1fee..176a1ee 100644
--- a/internal/game/login_account.go
+++ b/internal/game/login_account.go
@@ -5,8 +5,8 @@ import (
"os"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) handleAccountName(sess *net.Session, input string) {
@@ -69,6 +69,7 @@ func (g *Game) handlePassword(sess *net.Session, input string) {
Characters: acc.Characters,
Aliases: acc.Aliases,
Colors: acc.Colors,
+ Options: acc.Options,
}
if sess.Account.Aliases == nil {
sess.Account.Aliases = make(map[string]string)
@@ -76,6 +77,9 @@ func (g *Game) handlePassword(sess *net.Session, input string) {
if sess.Account.Colors == nil {
sess.Account.Colors = make(map[string]string)
}
+ if sess.Account.Options == nil {
+ sess.Account.Options = make(map[string]any)
+ }
g.showMenu(sess)
}
@@ -144,8 +148,27 @@ func (g *Game) handleNewAccountConfirm(sess *net.Session, input string) {
Name: acc.Name,
PasswordHash: acc.PasswordHash,
Aliases: make(map[string]string),
+ Colors: make(map[string]string),
+ Options: make(map[string]any),
}
sess.PendingPass = ""
+ sess.State = net.StateColorChoice
+ sess.Write("\nShould I disable color? [y/N] ")
+}
+
+func (g *Game) handleColorChoice(sess *net.Session, input string) {
+ input = strings.ToLower(strings.TrimSpace(input))
+ if input == "y" || input == "yes" {
+ sess.Account.Options["color"] = "none"
+ acc, err := g.AccountStore.LoadAccount(sess.Account.Name)
+ if err == nil {
+ if acc.Options == nil {
+ acc.Options = make(map[string]any)
+ }
+ acc.Options["color"] = "none"
+ g.AccountStore.SaveAccount(acc)
+ }
+ }
g.showMenu(sess)
}
@@ -158,7 +181,7 @@ func (g *Game) showMenu(sess *net.Session) {
}
if len(sess.Account.Characters) > 0 {
lines = append(lines,
- " (C)onnect character to TC",
+ " (C)onnect character to THOI",
"",
" (L)ist characters",
" (R)ename character",
@@ -171,9 +194,9 @@ func (g *Game) showMenu(sess *net.Session) {
" (A)ccount rename",
" (Q)uit",
"",
- "> ",
)
sess.WriteLines(lines...)
+ sess.Write("> ")
}
func (g *Game) handleMenu(sess *net.Session, input string) {
diff --git a/internal/game/login_char.go b/internal/game/login_char.go
index ba034bb..82065d3 100644
--- a/internal/game/login_char.go
+++ b/internal/game/login_char.go
@@ -5,8 +5,8 @@ import (
"os"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) handleNewCharName(sess *net.Session, input string) {
@@ -82,6 +82,13 @@ func (g *Game) connectCharacter(sess *net.Session, name string) {
g.loggedInChars[name] = sess
g.charsMu.Unlock()
+ if sess.Account != nil && sess.Account.Options != nil {
+ p.Options = make(map[string]any)
+ for k, v := range sess.Account.Options {
+ p.Options[k] = v
+ }
+ }
+
g.World.SeedGroundItems(p.RoomID)
g.seedRoomMobs(p.RoomID)
g.seedRoomObjects(p.RoomID)
diff --git a/internal/game/map.go b/internal/game/map.go
index 8b14e25..32d3d7c 100644
--- a/internal/game/map.go
+++ b/internal/game/map.go
@@ -3,7 +3,7 @@ package game
import (
"strings"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/world"
)
type mapGlyphs struct {
diff --git a/internal/game/map_test.go b/internal/game/map_test.go
index 8c82f4a..a695c1b 100644
--- a/internal/game/map_test.go
+++ b/internal/game/map_test.go
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/world"
)
func TestBuildTinyMap(t *testing.T) {
diff --git a/internal/game/prompt.go b/internal/game/prompt.go
index 2fc36ac..de88bc6 100644
--- a/internal/game/prompt.go
+++ b/internal/game/prompt.go
@@ -4,17 +4,17 @@ import (
"fmt"
"strings"
- "thirdcollapse/internal/color"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
)
func (g *Game) promptStr(sess *net.Session) string {
prompt := "> "
if p, ok := sess.Player.(*player.Player); ok && p != nil {
- if custom := p.OptionString("prompt"); custom != "" {
- prompt = custom
+ if p.Prompt != "" {
+ prompt = p.Prompt
}
}
@@ -24,42 +24,7 @@ func (g *Game) promptStr(sess *net.Session) string {
}
func (g *Game) expandPromptColors(sess *net.Session, text string) string {
- mode := g.colorMode(sess)
- for {
- start := strings.Index(text, "{")
- if start < 0 {
- break
- }
-
- end := strings.Index(text[start+1:], "}")
- if end < 0 {
- break
- }
- end += start + 1
- specStr := text[start+1 : end]
-
- if specStr == "" || specStr == "/" {
- break
- }
-
- spec := color.Parse(specStr)
- if spec.Empty() {
- break
- }
-
- closeStart := strings.Index(text[end+1:], "{/}")
- if closeStart < 0 {
- break
- }
- closeStart += end + 1
- closeEnd := closeStart + 3
-
- inner := text[end+1 : closeStart]
- colored := color.Render(mode, spec, inner)
-
- text = text[:start] + colored + text[closeEnd:]
- }
- return text
+ return color.ExpandTags(g.colorMode(sess), text)
}
func (g *Game) expandPromptVars(sess *net.Session, text string) string {
diff --git a/internal/game/table.go b/internal/game/table.go
index 56c0079..b96dca1 100644
--- a/internal/game/table.go
+++ b/internal/game/table.go
@@ -1,8 +1,9 @@
package game
import (
- "fmt"
"strings"
+
+ "thehouseoficarus/internal/color"
)
type tableGlyphs struct {
@@ -50,15 +51,16 @@ func (t *Table) Render(unicode bool) []string {
colWidths := make([]int, nCols)
for i, h := range t.Columns {
- colWidths[i] = len(h)
+ colWidths[i] = color.VisibleLen(h)
}
for _, row := range t.Rows {
for i, cell := range row {
if i >= nCols {
break
}
- if len(cell) > colWidths[i] {
- colWidths[i] = len(cell)
+ vl := color.VisibleLen(cell)
+ if vl > colWidths[i] {
+ colWidths[i] = vl
}
}
}
@@ -87,7 +89,11 @@ func (t *Table) Render(unicode bool) []string {
if i < len(cells) {
cell = cells[i]
}
- b.WriteString(fmt.Sprintf(" %-*s ", colWidths[i], cell))
+ pad := colWidths[i] - color.VisibleLen(cell)
+ if pad < 0 {
+ pad = 0
+ }
+ b.WriteString(" " + cell + strings.Repeat(" ", pad) + " ")
}
b.WriteRune(g.side)
return b.String()
@@ -115,7 +121,7 @@ func (t *Table) Render(unicode bool) []string {
tr.WriteRune(g.side)
tr.WriteString(" ")
tr.WriteString(t.Title)
- padding := innerWidth - 2 - len(t.Title)
+ padding := innerWidth - 2 - color.VisibleLen(t.Title)
if padding < 0 {
padding = 0
}
diff --git a/internal/game/tick.go b/internal/game/tick.go
index 7567ca7..ab82254 100644
--- a/internal/game/tick.go
+++ b/internal/game/tick.go
@@ -4,9 +4,9 @@ import (
"fmt"
"math/rand"
- "thirdcollapse/internal/combat"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/combat"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func (g *Game) DisconnectTick() {
@@ -127,21 +127,21 @@ func (g *Game) WanderTick() {
if !ok {
continue
}
- if p.RoomID == m.fromRoom && p.OptionBool("mob_leave") {
- 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.fromRoom && p.OptionBool("mob_leave") {
+ if m.level > 0 {
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s (level %d) leaves.", m.name, m.level)))
+ } else {
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s moves away.", m.name)))
}
- if p.RoomID == m.toRoom && p.OptionBool("mob_enter") {
- if m.level > 0 {
- sess.WriteLine(fmt.Sprintf("\n%s (level %d) enters.", m.name, m.level))
- } else {
- sess.WriteLine(fmt.Sprintf("\n%s drifts in.", m.name))
- }
+ }
+ if p.RoomID == m.toRoom && p.OptionBool("mob_enter") {
+ if m.level > 0 {
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s (level %d) enters.", m.name, m.level)))
+ } else {
+ sess.WriteLine(g.colorize(sess, "broadcast", fmt.Sprintf("\n%s drifts in.", m.name)))
}
}
+ }
}
}
diff --git a/internal/game/types.go b/internal/game/types.go
index faed9e1..d41b18a 100644
--- a/internal/game/types.go
+++ b/internal/game/types.go
@@ -1,6 +1,6 @@
package game
-import "thirdcollapse/internal/object"
+import "thehouseoficarus/internal/object"
var EquipSlots = []object.EquipSlot{
object.SlotHead, object.SlotNeck, object.SlotTorso, object.SlotLegs,
diff --git a/internal/game/utils.go b/internal/game/utils.go
index 729ea97..2379fc2 100644
--- a/internal/game/utils.go
+++ b/internal/game/utils.go
@@ -6,9 +6,9 @@ import (
"strconv"
"strings"
- "thirdcollapse/internal/net"
- "thirdcollapse/internal/player"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func plural(n int) string {
diff --git a/internal/net/server.go b/internal/net/server.go
index 1b6c99b..203539c 100644
--- a/internal/net/server.go
+++ b/internal/net/server.go
@@ -6,8 +6,10 @@ import (
"log"
"net"
"net/http"
+ "strings"
- "thirdcollapse/internal/config"
+ "thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/config"
)
type SessionState int
@@ -30,6 +32,7 @@ const (
StateTalk
StateDropAllConfirm
StateCookRecipe
+ StateColorChoice
)
type Session struct {
@@ -51,11 +54,13 @@ type AccountEntry struct {
Characters []string
Aliases map[string]string
Colors map[string]string
+ Options map[string]any
}
type Server struct {
config *config.Config
telnetLn net.Listener
+ telnetTLSLn net.Listener
httpServer *http.Server
httpsServer *http.Server
hub *Hub
@@ -149,6 +154,26 @@ func NewServer(cfg *config.Config) (*Server, error) {
s.telnetLn = ln
}
+ if cfg.TelnetTLS.Enabled {
+ if cfg.TelnetTLS.CertFile == "" || cfg.TelnetTLS.KeyFile == "" {
+ return nil, fmt.Errorf("telnet_tls enabled but cert_file and key_file are required")
+ }
+ cert, err := tls.LoadX509KeyPair(cfg.TelnetTLS.CertFile, cfg.TelnetTLS.KeyFile)
+ if err != nil {
+ return nil, fmt.Errorf("telnet_tls cert: %w", err)
+ }
+ tlsCfg := &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MinVersion: tls.VersionTLS12,
+ }
+ addr := fmt.Sprintf(":%d", cfg.TelnetTLS.Port)
+ ln, err := net.Listen("tcp", addr)
+ if err != nil {
+ return nil, fmt.Errorf("telnet_tls listen: %w", err)
+ }
+ s.telnetTLSLn = tls.NewListener(ln, tlsCfg)
+ }
+
if cfg.HTTP.Enabled {
s.httpServer = &http.Server{
Addr: fmt.Sprintf(":%d", cfg.HTTP.Port),
@@ -181,6 +206,10 @@ func (s *Server) ListenAndServe(handler func(*Session, string)) error {
go s.serveTelnet()
}
+ if s.telnetTLSLn != nil {
+ go s.serveTelnetTLS()
+ }
+
if s.httpServer != nil {
go s.serveHTTP()
}
@@ -189,7 +218,7 @@ func (s *Server) ListenAndServe(handler func(*Session, string)) error {
go s.serveHTTPS()
}
- if s.telnetLn == nil && s.httpServer == nil && s.httpsServer == nil {
+ if s.telnetLn == nil && s.telnetTLSLn == nil && s.httpServer == nil && s.httpsServer == nil {
return fmt.Errorf("no listeners configured")
}
@@ -211,6 +240,21 @@ func (s *Server) serveTelnet() {
}
}
+func (s *Server) serveTelnetTLS() {
+ for {
+ conn, err := s.telnetTLSLn.Accept()
+ if err != nil {
+ return
+ }
+ sess := &Session{
+ Conn: newTCPConn(conn),
+ State: StateAccountName,
+ }
+ s.hub.Add(sess)
+ go s.handleSession(sess, s.handler)
+ }
+}
+
func (s *Server) serveHTTP() {
err := s.httpServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
@@ -225,6 +269,53 @@ func (s *Server) serveHTTPS() {
}
}
+func artColor(r rune) string {
+ switch r {
+ case '@':
+ return color.FgCode("ansi", 4)
+ case '!', ':', '.':
+ return color.StyleCode("dim") + color.FgCode("ansi", 4)
+ case ',':
+ return color.FgCode("ansi", 5)
+ case '*':
+ return color.FgCode("ansi", 5)
+ case '+', '|', '-':
+ return color.FgCode("ansi", 3)
+ }
+ return ""
+}
+
+func colorizeRunes(s string) string {
+ var sb strings.Builder
+ current := ""
+ for _, r := range s {
+ want := artColor(r)
+ if want != current {
+ if current != "" {
+ sb.WriteString(color.Reset)
+ }
+ if want != "" {
+ sb.WriteString(want)
+ }
+ current = want
+ }
+ sb.WriteRune(r)
+ }
+ if current != "" {
+ sb.WriteString(color.Reset)
+ }
+ return sb.String()
+}
+
+func colorizeArt(art string) string {
+ welcomeText := "welcome to the house of icarus"
+ before, after, found := strings.Cut(art, welcomeText)
+ if found {
+ return colorizeRunes(before) + color.FgCode("ansi", 7) + welcomeText + color.Reset + colorizeRunes(after)
+ }
+ return colorizeRunes(art)
+}
+
func (s *Server) handleSession(sess *Session, handler func(*Session, string)) {
defer func() {
if r := recover(); r != nil {
@@ -236,22 +327,32 @@ func (s *Server) handleSession(sess *Session, handler func(*Session, string)) {
sess.Conn.Write([]byte("\033[2J\033[H"))
- welcomeart := `
- , 3333333 333 333 333 3333333 3333333
- | 33! 33! 333 33! 33! 333 33! 333 ':. +
- -+- * 3!! 3!3!3!3! !!3 3!3!!3! 3!3 !3! '::._ *
- | !!: !!: !!! !!: !!: :!! !!: !!! '._)
- : : : : : : : : :: : : ,
- . * ,
- , welcome to third collapse . *
-
- 3333333 333333 333 , 333 333333 3333333 333333 33333333
- !33 33! 333 33! 33! 33! 333 33! 333 !33 33!
- !3! 3!3 !3! 3!! 3!! + 3!3!3!3! 3!33!3! !33!! 3!!!:!
- :!! !!: !!! !!: !!: !!: !!! !!: !:! !!:
- :: :: : : :. : : ::.: : : ::.: : : : : : ::.: : : :: :::
-
-`
+ welcomeart := colorizeArt(`
+ | *
+ * + -+-
+ . , |
+. , . ,
+ *
+ @@@@@@@ @@@ @@@ @@@@@@@@ @@@ @@@ @@@@@@ @@@ @@@ @@@@@@ @@@@@@@@ +
+ @@! @@! @@@ @@! + @@! @@@ @@! @@@ @@! @@@ !@@ @@!
+ @!! @!@!@!@! @!!!:! @!@!@!@! @!@ !@! @!@ !@! !@@!! @!!!:! *
+ !!: !!: !!! !!: . !!: !!! !!: !!! !!: !!! !:! !!:
+ : : : : : :: ::: : : : : :. : :.:: : ::.: : : :: ::: .
+ . +
+ * welcome to the house of icarus , ,
+ , .
+ @@@@@@ @@@@@@@@ @@@ @@@@@@@ @@@@@@ @@@@@@@ @@@ @@@ @@@@@@
+ @@! @@@ @@! , @@! !@@ @@! @@@ @@! @@@ @@! @@@ !@@
+ | @!@ !@! @!!!:! !!@ !@! @!@!@!@! @!@!!@! @!@ !@! !@@!!
+ -+- !!: !!! !!: !!: :!! !!: !!! !!: :!! !!: !!! !:!
+ | : :. : : . : :: :: : : : : : : : :.:: : ::.: :
+ ,
+ . + , .
+ . ,
+ * . , *
+ +
+`)
+
sess.Write(welcomeart)
sess.Write("What's your account name? ")
diff --git a/internal/net/terminal.html b/internal/net/terminal.html
index 2d98178..3e2506a 100644
--- a/internal/net/terminal.html
+++ b/internal/net/terminal.html
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
-<title>Third Collapse</title>
+<title>The House of Icarus</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
diff --git a/internal/object/item.go b/internal/object/item.go
index 7b2a107..6078712 100644
--- a/internal/object/item.go
+++ b/internal/object/item.go
@@ -3,7 +3,7 @@ package object
import (
"strings"
- "thirdcollapse/internal/world"
+ "thehouseoficarus/internal/world"
)
type EquipSlot string
diff --git a/internal/player/account.go b/internal/player/account.go
index 36fab43..99a052c 100644
--- a/internal/player/account.go
+++ b/internal/player/account.go
@@ -6,4 +6,5 @@ type Account struct {
Characters []string `yaml:"characters"`
Aliases map[string]string `yaml:"aliases"`
Colors map[string]string `yaml:"colors"`
+ Options map[string]any `yaml:"options"`
}
diff --git a/internal/player/player.go b/internal/player/player.go
index e0c7ab9..1cd8677 100644
--- a/internal/player/player.go
+++ b/internal/player/player.go
@@ -1,7 +1,7 @@
package player
-import "thirdcollapse/internal/action"
-import "thirdcollapse/internal/object"
+import "thehouseoficarus/internal/action"
+import "thehouseoficarus/internal/object"
type SkillName string
@@ -115,7 +115,7 @@ var OptionDefs = []OptionDef{
{"reserve", OptBool, true, nil, "Show full reserved item details"},
{"depletion", OptBool, false, nil, "Show depletion and despawn timers"},
{"despawn", OptBool, false, nil, "Show ground item despawn timers"},
- {"color", OptString, "ansi", []string{"none", "ansi", "xterm256"}, "Color output mode"},
+ {"color", OptString, "xterm256", []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"},
@@ -123,7 +123,6 @@ var OptionDefs = []OptionDef{
{"queue_silently", OptBool, true, nil, "Suppress messages for queued tick actions"},
{"room_desc_width", OptInt, 70, nil, "Maximum width for room descriptions"},
{"unicode", OptBool, true, nil, "Unicode box-drawing characters"},
- {"prompt", OptString, "> ", nil, "Custom command prompt"},
}
var optionByName map[string]*OptionDef
@@ -150,7 +149,8 @@ type Player struct {
Credits int `yaml:"credits"`
Description string `yaml:"description"`
AttackStyle AttackStyle `yaml:"attack_style"`
- Options map[string]any `yaml:"options"`
+ Prompt string `yaml:"prompt"`
+ Options map[string]any `yaml:"-"`
Flags map[string]any `yaml:"flags"`
RegenerateTick int
Action *action.Action `yaml:"-"`
@@ -258,12 +258,9 @@ func New(name string) *Player {
Skills: make(map[SkillName]int),
Equipment: make(map[object.EquipSlot]string),
AttackStyle: Accurate,
- Options: make(map[string]any),
+ Prompt: "> ",
RoomID: 0,
}
- for _, def := range OptionDefs {
- p.Options[def.Name] = def.Default
- }
for _, s := range AllSkills {
p.Skills[s] = 0
}
diff --git a/internal/player/store.go b/internal/player/store.go
index c19d9fb..04af594 100644
--- a/internal/player/store.go
+++ b/internal/player/store.go
@@ -6,7 +6,7 @@ import (
"path/filepath"
"strings"
- "thirdcollapse/internal/object"
+ "thehouseoficarus/internal/object"
"gopkg.in/yaml.v3"
)
diff --git a/internal/world/mob.go b/internal/world/mob.go
index c161e10..d801b59 100644
--- a/internal/world/mob.go
+++ b/internal/world/mob.go
@@ -8,7 +8,7 @@ import (
"strings"
"sync"
- "thirdcollapse/internal/action"
+ "thehouseoficarus/internal/action"
"gopkg.in/yaml.v3"
)
diff --git a/internal/world/room.go b/internal/world/room.go
index 16fd3fd..c0f531e 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -2,7 +2,7 @@ package world
import (
"gopkg.in/yaml.v3"
- "thirdcollapse/internal/action"
+ "thehouseoficarus/internal/action"
)
type ExitDir string