aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/config/config.go32
-rw-r--r--internal/game/cmd_consume.go4
-rw-r--r--internal/game/cmd_verbs.go22
-rw-r--r--internal/game/combat_mob.go2
-rw-r--r--internal/game/core_login_char.go4
-rw-r--r--internal/game/game.go8
-rw-r--r--internal/net/server.go106
-rw-r--r--internal/player/player.go2
8 files changed, 124 insertions, 56 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 91d5986..6393816 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -7,12 +7,14 @@ import (
)
type Config struct {
- 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"`
+ TickLength int `yaml:"tick_length"`
+ StartingRoom int `yaml:"starting_room"`
+ StartupValidation ValidationConfig `yaml:"startup_validation"`
+ DefaultColors ColorsConfig `yaml:"default_colors"`
+ Telnet TelnetConfig `yaml:"telnet"`
+ TelnetTLS TelnetTLSConfig `yaml:"telnet_tls"`
+ HTTP HTTPConfig `yaml:"http"`
+ HTTPS HTTPSConfig `yaml:"https"`
}
type ColorsConfig map[string]string
@@ -63,11 +65,6 @@ func DefaultColors() ColorsConfig {
}
}
-type GameConfig struct {
- TickLength int `yaml:"tick_length"`
- StartupValidation ValidationConfig `yaml:"startup_validation"`
-}
-
type ValidationConfig struct {
RootRooms []int `yaml:"root_rooms"`
IgnoreUnreachable []int `yaml:"ignore_unreachable"`
@@ -99,14 +96,13 @@ type HTTPSConfig struct {
func Default() *Config {
return &Config{
- Game: GameConfig{
- TickLength: 600,
- StartupValidation: ValidationConfig{
- RootRooms: []int{1},
- IgnoreUnreachable: []int{},
- },
+ TickLength: 600,
+ StartingRoom: 1001,
+ StartupValidation: ValidationConfig{
+ RootRooms: []int{1},
+ IgnoreUnreachable: []int{},
},
- Colors: DefaultColors(),
+ DefaultColors: DefaultColors(),
Telnet: TelnetConfig{
Enabled: true,
Port: 4000,
diff --git a/internal/game/cmd_consume.go b/internal/game/cmd_consume.go
index 423cddb..aec33c5 100644
--- a/internal/game/cmd_consume.go
+++ b/internal/game/cmd_consume.go
@@ -71,7 +71,7 @@ func (g *Game) doEat(sess *net.Session, input string) {
Timestamp: time.Now(),
})
- if !p.OptionBool("queue_silently") {
+ if p.OptionBool("show_queued_cmds") {
sess.WriteLine(fmt.Sprintf("You prepare to eat %s.", g.itemColorize(sess, def, def.Name)))
}
}
@@ -206,7 +206,7 @@ func (g *Game) doDrink(sess *net.Session, args []string) {
Timestamp: time.Now(),
})
- if !p.OptionBool("queue_silently") {
+ if p.OptionBool("show_queued_cmds") {
sess.WriteLine(fmt.Sprintf("You prepare to drink the %s.", g.itemColorize(sess, def, def.Name)))
}
}
diff --git a/internal/game/cmd_verbs.go b/internal/game/cmd_verbs.go
index d07ef2f..70b2fb2 100644
--- a/internal/game/cmd_verbs.go
+++ b/internal/game/cmd_verbs.go
@@ -9,7 +9,7 @@ import (
)
var alwaysAvailable = []string{
- "look", "l",
+ "look",
"say",
"global",
"who",
@@ -17,30 +17,30 @@ var alwaysAvailable = []string{
"map",
"walk",
"score", "stats",
- "inventory", "inv", "i",
- "equipment", "eq", "equip", "wear", "wield",
- "remove", "unwear", "unwield",
+ "inventory",
+ "equip",
+ "remove",
"style",
- "tech", "t",
- "option", "options",
- "color", "colors", "colortable",
+ "tech",
+ "option",
+ "color", "colortable",
"prompt",
"alias", "unalias",
"help",
"queued",
"stop",
"aps",
- "autotrigger", "auto",
+ "autotrigger",
"trigger",
"sneak",
- "mods", "modules", "modlist", "mod", "module",
- "id", "identify",
+ "mods",
+ "identify",
"eat", "drink",
"fletch",
"clean",
"burn", "stoke",
"search",
- "get", "take", "grab", "pick",
+ "get",
"drop",
}
diff --git a/internal/game/combat_mob.go b/internal/game/combat_mob.go
index a4bf435..4cdd27b 100644
--- a/internal/game/combat_mob.go
+++ b/internal/game/combat_mob.go
@@ -270,7 +270,7 @@ func (g *Game) killPlayer(sess *net.Session, p *player.Player, mob *world.MobIns
}
g.dropItemsOnDeath(p)
p.HP = p.MaxHP()
- p.RoomID = 1001
+ p.RoomID = g.StartingRoom
g.AccountStore.SaveCharacter(p)
if g.Hub != nil {
g.Hub.EnterRoom(sess, p.RoomID)
diff --git a/internal/game/core_login_char.go b/internal/game/core_login_char.go
index d705684..b1d3d26 100644
--- a/internal/game/core_login_char.go
+++ b/internal/game/core_login_char.go
@@ -41,8 +41,8 @@ func (g *Game) handleNewCharName(sess *net.Session, input string) {
}
p := player.New(name)
- p.RoomID = 1001
- p.Stats.RecordRoomVisit(1001)
+ p.RoomID = g.StartingRoom
+ p.Stats.RecordRoomVisit(g.StartingRoom)
if err := g.AccountStore.SaveCharacter(p); err != nil {
sess.WriteLine(fmt.Sprintf("Error creating character: %v", err))
diff --git a/internal/game/game.go b/internal/game/game.go
index bed75b0..c30f9aa 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -56,6 +56,7 @@ type Game struct {
queue *CommandQueue
safespot *SafespotManager
ValidationConfig config.ValidationConfig
+ StartingRoom int
// Per-tick / per-session runtime bookkeeping.
charsMu sync.Mutex
@@ -73,7 +74,7 @@ type Game struct {
shutdownMu sync.Mutex
}
-func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.ValidationConfig) *Game {
+func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.ValidationConfig, startingRoom int) *Game {
g := &Game{
Deps: Deps{
World: world.New(dataDir),
@@ -93,6 +94,7 @@ func New(dataDir string, colorConfig *config.ColorsConfig, valConfig config.Vali
queue: NewCommandQueue(),
safespot: NewSafespotManager(),
ValidationConfig: valConfig,
+ StartingRoom: startingRoom,
loggedInChars: make(map[string]*net.Session),
restTimers: make(map[string]uint64),
guardWatchTimers: make(map[string]int),
@@ -251,7 +253,7 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
Args: strings.Join(parts[1:], " "),
Timestamp: time.Now(),
})
- if !p.OptionBool("queue_silently") {
+ if p.OptionBool("show_queued_cmds") {
sess.WriteLine(fmt.Sprintf("You prepare to %s.", cmd))
}
return
@@ -270,7 +272,7 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) {
Timestamp: time.Now(),
})
g.cancelRest(p.Name)
- if !p.OptionBool("queue_silently") {
+ if p.OptionBool("show_queued_cmds") {
sess.WriteLine(fmt.Sprintf("You prepare to %s.", cmd))
}
}
diff --git a/internal/net/server.go b/internal/net/server.go
index 851a4b5..345122e 100644
--- a/internal/net/server.go
+++ b/internal/net/server.go
@@ -1,12 +1,18 @@
package net
import (
+ "crypto/rand"
+ "crypto/rsa"
"crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
"fmt"
"log"
+ "math/big"
"net"
"net/http"
"sync"
+ "time"
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/color"
@@ -70,13 +76,14 @@ type Session struct {
}
type Server struct {
- config *config.Config
- telnetLn net.Listener
- telnetTLSLn net.Listener
- httpServer *http.Server
- httpsServer *http.Server
- hub *Hub
- handler func(*Session, string)
+ config *config.Config
+ telnetLn net.Listener
+ telnetTLSLn net.Listener
+ httpServer *http.Server
+ httpsServer *http.Server
+ httpsTLSConfig *tls.Config
+ hub *Hub
+ handler func(*Session, string)
}
type Hub struct {
@@ -192,12 +199,20 @@ func NewServer(cfg *config.Config) (*Server, error) {
}
if cfg.TelnetTLS.Enabled {
+ var cert tls.Certificate
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)
+ var err error
+ cert, err = generateSelfSignedCert()
+ if err != nil {
+ return nil, fmt.Errorf("telnet_tls self-signed cert: %w", err)
+ }
+ log.Printf("telnet_tls: using self-signed certificate (no cert_file/key_file configured)")
+ } else {
+ var err error
+ 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},
@@ -219,19 +234,68 @@ func NewServer(cfg *config.Config) (*Server, error) {
}
if cfg.HTTPS.Enabled {
+ var cert tls.Certificate
if cfg.HTTPS.CertFile == "" || cfg.HTTPS.KeyFile == "" {
- return nil, fmt.Errorf("https enabled but cert_file and key_file are required")
+ var err error
+ cert, err = generateSelfSignedCert()
+ if err != nil {
+ return nil, fmt.Errorf("https self-signed cert: %w", err)
+ }
+ log.Printf("https: using self-signed certificate (no cert_file/key_file configured)")
+ } else {
+ var err error
+ cert, err = tls.LoadX509KeyPair(cfg.HTTPS.CertFile, cfg.HTTPS.KeyFile)
+ if err != nil {
+ return nil, fmt.Errorf("https cert: %w", err)
+ }
+ }
+ s.httpsTLSConfig = &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ MinVersion: tls.VersionTLS12,
}
s.httpsServer = &http.Server{
- Addr: fmt.Sprintf(":%d", cfg.HTTPS.Port),
- Handler: newHTTPMux(s),
- TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12},
+ Addr: fmt.Sprintf(":%d", cfg.HTTPS.Port),
+ Handler: newHTTPMux(s),
}
}
return s, nil
}
+func generateSelfSignedCert() (tls.Certificate, error) {
+ key, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return tls.Certificate{}, fmt.Errorf("rsa key generation: %w", err)
+ }
+ serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
+ if err != nil {
+ return tls.Certificate{}, fmt.Errorf("serial number: %w", err)
+ }
+ now := time.Now()
+ template := &x509.Certificate{
+ SerialNumber: serial,
+ Subject: pkix.Name{
+ CommonName: "THOI Self-Signed Certificate",
+ },
+ NotBefore: now.Add(-1 * time.Hour),
+ NotAfter: now.Add(365 * 24 * time.Hour),
+ KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
+ ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
+ BasicConstraintsValid: true,
+ IsCA: true,
+ DNSNames: []string{"localhost"},
+ IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
+ }
+ certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
+ if err != nil {
+ return tls.Certificate{}, fmt.Errorf("x509 creation: %w", err)
+ }
+ return tls.Certificate{
+ Certificate: [][]byte{certDER},
+ PrivateKey: key,
+ }, nil
+}
+
func (s *Server) Hub() *Hub {
return s.hub
}
@@ -300,8 +364,14 @@ func (s *Server) serveHTTP() {
}
func (s *Server) serveHTTPS() {
- err := s.httpsServer.ListenAndServeTLS(s.config.HTTPS.CertFile, s.config.HTTPS.KeyFile)
- if err != nil && err != http.ErrServerClosed {
+ addr := fmt.Sprintf(":%d", s.config.HTTPS.Port)
+ ln, err := net.Listen("tcp", addr)
+ if err != nil {
+ log.Printf("https server error: %v", err)
+ return
+ }
+ tlsLn := tls.NewListener(ln, s.httpsTLSConfig)
+ if err := s.httpsServer.Serve(tlsLn); err != nil && err != http.ErrServerClosed {
log.Printf("https server error: %v", err)
}
}
diff --git a/internal/player/player.go b/internal/player/player.go
index 7d9b397..845dbb0 100644
--- a/internal/player/player.go
+++ b/internal/player/player.go
@@ -124,7 +124,7 @@ var OptionDefs = []OptionDef{
{"map_height", OptInt, 20, nil, "Map height for the map command"},
{"map_padding", OptString, "none", []string{"none", "x", "y", "xy"}, "Map output padding mode"},
{"automap", OptBool, false, nil, "Show map automatically after moving"},
- {"queue_silently", OptBool, true, nil, "Suppress messages for queued tick actions"},
+ {"show_queued_cmds", OptBool, false, nil, "Show confirmation messages for queued tick actions"},
{"room_desc_width", OptInt, 70, nil, "Maximum width for room descriptions"},
{"wrap_width", OptInt, 120, nil, "Wrap all output to this many columns (minimum 80)"},
{"unicode", OptBool, true, nil, "Unicode box-drawing characters"},