package control import ( "encoding/json" "fmt" "os" "sync" ) var ( global *Control configPath string mu sync.RWMutex ) func init() { global = New() } // Control represents the game control settings type Control struct { 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"` } // New creates a new Control with sensible defaults func New() *Control { return &Control{ WorldSize: 200, Open: 1, AdminEmail: "", Class1Name: "Mage", Class2Name: "Warrior", Class3Name: "Paladin", } } // Load loads control settings from JSON file into global instance func Load(filename string) error { mu.Lock() configPath = filename mu.Unlock() data, err := os.ReadFile(filename) if err != nil { if os.IsNotExist(err) { return nil // Keep defaults } return fmt.Errorf("failed to read config file: %w", err) } if len(data) == 0 { return nil } control := &Control{} if err := json.Unmarshal(data, control); err != nil { return fmt.Errorf("failed to parse config: %w", err) } // Apply defaults for any missing fields defaults := New() if control.WorldSize == 0 { control.WorldSize = defaults.WorldSize } if control.Class1Name == "" { control.Class1Name = defaults.Class1Name } if control.Class2Name == "" { control.Class2Name = defaults.Class2Name } if control.Class3Name == "" { control.Class3Name = defaults.Class3Name } mu.Lock() global = control mu.Unlock() return nil } // Save saves global control settings to the loaded path func Save() error { mu.RLock() defer mu.RUnlock() if configPath == "" { return fmt.Errorf("no config path set, call Load() first") } data, err := json.MarshalIndent(global, "", "\t") if err != nil { return fmt.Errorf("failed to marshal config: %w", err) } if err := os.WriteFile(configPath, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } return nil } // Get returns the global control instance (thread-safe) func Get() *Control { mu.RLock() defer mu.RUnlock() return global } // Set updates the global control instance (thread-safe) func Set(control *Control) { mu.Lock() defer mu.Unlock() global = control } 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") } return nil } // IsOpen returns true if the game world is open for new players func (c *Control) IsOpen() bool { return c.Open == 1 } // 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 }