package control import ( "encoding/json" "fmt" "os" "sync" ) var ( global *Control mu sync.RWMutex filename string ) // Control represents the game control settings type Control struct { WorldSize int `json:"world_size"` Open int `json:"open"` AdminEmail string `json:"admin_email"` EmailMode string `json:"email_mode"` // "file" or "smtp" EmailFilePath string `json:"email_file_path"` // path for file mode SMTPHost string `json:"smtp_host"` SMTPPort string `json:"smtp_port"` SMTPUsername string `json:"smtp_username"` SMTPPassword string `json:"smtp_password"` } // Init loads control settings from the specified JSON file func Init(jsonFile string) error { mu.Lock() defer mu.Unlock() filename = jsonFile // Try to load from file if data, err := os.ReadFile(filename); err == nil { var ctrl Control if err := json.Unmarshal(data, &ctrl); err != nil { return fmt.Errorf("failed to parse JSON: %w", err) } // Apply defaults for any missing fields defaults := New() if ctrl.WorldSize == 0 { ctrl.WorldSize = defaults.WorldSize } if ctrl.EmailMode == "" { ctrl.EmailMode = defaults.EmailMode } if ctrl.EmailFilePath == "" { ctrl.EmailFilePath = defaults.EmailFilePath } global = &ctrl } else { // Create default control settings if file doesn't exist global = New() if err := save(); err != nil { return fmt.Errorf("failed to create default config file: %w", err) } } return global.Validate() } // save writes the current control settings to the JSON file (internal use) func save() error { if filename == "" { return fmt.Errorf("no filename set") } data, err := json.MarshalIndent(global, "", " ") if err != nil { return fmt.Errorf("failed to marshal JSON: %w", err) } return os.WriteFile(filename, data, 0644) } // Save writes the current control settings to the JSON file (public) func Save() error { mu.RLock() defer mu.RUnlock() if global == nil { return fmt.Errorf("control not initialized") } return save() } // New creates a new Control with sensible defaults func New() *Control { return &Control{ WorldSize: 200, Open: 1, AdminEmail: "", EmailMode: "file", EmailFilePath: "emails.txt", SMTPHost: "", SMTPPort: "587", SMTPUsername: "", SMTPPassword: "", } } // Get returns the global control instance (thread-safe) func Get() *Control { mu.RLock() defer mu.RUnlock() if global == nil { panic("control not initialized - call Init first") } return global } // Set updates the global control instance and saves to file (thread-safe) func Set(control *Control) error { mu.Lock() defer mu.Unlock() if err := control.Validate(); err != nil { return err } global = control return save() } // Update updates specific fields of the control settings and saves to file func Update(updater func(*Control)) error { mu.Lock() defer mu.Unlock() // Create a copy to work with updated := *global updater(&updated) if err := updated.Validate(); err != nil { return err } global = &updated return save() } // UpdateNoSave updates specific fields without saving (useful for batch updates) func UpdateNoSave(updater func(*Control)) error { mu.Lock() defer mu.Unlock() // Create a copy to work with updated := *global updater(&updated) if err := updated.Validate(); err != nil { return err } global = &updated return nil } // Validate checks if control settings have valid values func (c *Control) Validate() error { if c.WorldSize <= 0 || c.WorldSize > 10000 { return fmt.Errorf("WorldSize must be between 1 and 10000") } if c.Open != 0 && c.Open != 1 { return fmt.Errorf("Open must be 0 or 1") } if c.EmailMode != "file" && c.EmailMode != "smtp" { return fmt.Errorf("EmailMode must be 'file' or 'smtp'") } if c.EmailMode == "smtp" { if c.SMTPHost == "" { return fmt.Errorf("SMTPHost required when EmailMode is 'smtp'") } if c.SMTPUsername == "" { return fmt.Errorf("SMTPUsername required when EmailMode is 'smtp'") } if c.SMTPPassword == "" { return fmt.Errorf("SMTPPassword required when EmailMode is 'smtp'") } } return nil } // IsOpen returns true if the game world is open for new players func (c *Control) IsOpen() bool { return c.Open == 1 } // SetOpen sets whether the game world is open for new players func SetOpen(open bool) error { return Update(func(c *Control) { if open { c.Open = 1 } else { c.Open = 0 } }) } // HasAdminEmail returns true if an admin email is configured func (c *Control) HasAdminEmail() bool { return c.AdminEmail != "" } // IsEmailConfigured returns true if email is properly configured func (c *Control) IsEmailConfigured() bool { if c.EmailMode == "file" { return c.EmailFilePath != "" } return c.SMTPHost != "" && c.SMTPUsername != "" && c.SMTPPassword != "" } // IsWorldSizeValid returns true if the world size is within reasonable bounds func (c *Control) IsWorldSizeValid() bool { return c.WorldSize > 0 && c.WorldSize <= 10000 } // GetWorldRadius returns the world radius (half the world size) func (c *Control) GetWorldRadius() int { return c.WorldSize / 2 } // IsWithinWorldBounds returns true if the given coordinates are within world bounds func (c *Control) IsWithinWorldBounds(x, y int) bool { radius := c.GetWorldRadius() return x >= -radius && x <= radius && y >= -radius && y <= radius } // GetWorldBounds returns the minimum and maximum coordinates for the world func (c *Control) GetWorldBounds() (minX, minY, maxX, maxY int) { radius := c.GetWorldRadius() return -radius, -radius, radius, radius }