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 }