279 lines
6.2 KiB
Go
279 lines
6.2 KiB
Go
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 {
|
|
ID int `json:"id"`
|
|
WorldSize int `json:"world_size"`
|
|
Open int `json:"open"`
|
|
AdminEmail string `json:"admin_email"`
|
|
Class1Name string `json:"class_1_name"`
|
|
Class2Name string `json:"class_2_name"`
|
|
Class3Name string `json:"class_3_name"`
|
|
}
|
|
|
|
// 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.Class1Name == "" {
|
|
ctrl.Class1Name = defaults.Class1Name
|
|
}
|
|
if ctrl.Class2Name == "" {
|
|
ctrl.Class2Name = defaults.Class2Name
|
|
}
|
|
if ctrl.Class3Name == "" {
|
|
ctrl.Class3Name = defaults.Class3Name
|
|
}
|
|
|
|
ctrl.ID = 1 // Ensure singleton ID
|
|
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{
|
|
ID: 1, // Singleton
|
|
WorldSize: 200,
|
|
Open: 1,
|
|
AdminEmail: "",
|
|
Class1Name: "Mage",
|
|
Class2Name: "Warrior",
|
|
Class3Name: "Paladin",
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
control.ID = 1 // Ensure it's always ID 1 (singleton)
|
|
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.Class1Name == "" {
|
|
return fmt.Errorf("Class1Name cannot be empty")
|
|
}
|
|
if c.Class2Name == "" {
|
|
return fmt.Errorf("Class2Name cannot be empty")
|
|
}
|
|
if c.Class3Name == "" {
|
|
return fmt.Errorf("Class3Name cannot be empty")
|
|
}
|
|
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
|
|
}
|
|
})
|
|
}
|
|
|
|
// GetClassNames returns all class names as a slice
|
|
func (c *Control) GetClassNames() []string {
|
|
classes := make([]string, 0, 3)
|
|
if c.Class1Name != "" {
|
|
classes = append(classes, c.Class1Name)
|
|
}
|
|
if c.Class2Name != "" {
|
|
classes = append(classes, c.Class2Name)
|
|
}
|
|
if c.Class3Name != "" {
|
|
classes = append(classes, c.Class3Name)
|
|
}
|
|
return classes
|
|
}
|
|
|
|
// GetClassName returns the name of a specific class (1-3)
|
|
func (c *Control) GetClassName(classNum int) string {
|
|
switch classNum {
|
|
case 1:
|
|
return c.Class1Name
|
|
case 2:
|
|
return c.Class2Name
|
|
case 3:
|
|
return c.Class3Name
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// IsValidClassName returns true if the given name matches one of the configured classes
|
|
func (c *Control) IsValidClassName(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
return name == c.Class1Name || name == c.Class2Name || name == c.Class3Name
|
|
}
|
|
|
|
// GetClassNumber returns the class number (1-3) for a given class name, or 0 if not found
|
|
func (c *Control) GetClassNumber(name string) int {
|
|
if name == c.Class1Name && name != "" {
|
|
return 1
|
|
}
|
|
if name == c.Class2Name && name != "" {
|
|
return 2
|
|
}
|
|
if name == c.Class3Name && name != "" {
|
|
return 3
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// HasAdminEmail returns true if an admin email is configured
|
|
func (c *Control) HasAdminEmail() bool {
|
|
return c.AdminEmail != ""
|
|
}
|
|
|
|
// 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
|
|
}
|