346 lines
7.7 KiB
Go
346 lines
7.7 KiB
Go
package control
|
|
|
|
import (
|
|
"dk/internal/store"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// 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"`
|
|
}
|
|
|
|
func (c *Control) Save() error {
|
|
controlStore := GetStore()
|
|
controlStore.UpdateControl(c)
|
|
return nil
|
|
}
|
|
|
|
func (c *Control) Delete() error {
|
|
controlStore := GetStore()
|
|
controlStore.RemoveControl(c.ID)
|
|
return nil
|
|
}
|
|
|
|
// Creates a new Control with sensible defaults
|
|
func New() *Control {
|
|
return &Control{
|
|
WorldSize: 200, // Default world size
|
|
Open: 1, // Default open for registration
|
|
AdminEmail: "", // No admin email by default
|
|
Class1Name: "Mage", // Default class names
|
|
Class2Name: "Warrior",
|
|
Class3Name: "Paladin",
|
|
}
|
|
}
|
|
|
|
// Validate checks if control has valid values
|
|
func (c *Control) Validate() error {
|
|
if c.WorldSize <= 0 || c.WorldSize > 10000 {
|
|
return fmt.Errorf("control WorldSize must be between 1 and 10000")
|
|
}
|
|
if c.Open != 0 && c.Open != 1 {
|
|
return fmt.Errorf("control Open must be 0 or 1")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ControlStore provides in-memory storage for control settings
|
|
type ControlStore struct {
|
|
*store.BaseStore[Control] // Embedded generic store
|
|
allByID []int // All IDs sorted by ID
|
|
mu sync.RWMutex // Protects indices
|
|
}
|
|
|
|
// Global in-memory store
|
|
var controlStore *ControlStore
|
|
var storeOnce sync.Once
|
|
|
|
// Initialize the in-memory store
|
|
func initStore() {
|
|
controlStore = &ControlStore{
|
|
BaseStore: store.NewBaseStore[Control](),
|
|
allByID: make([]int, 0),
|
|
}
|
|
}
|
|
|
|
// GetStore returns the global control store
|
|
func GetStore() *ControlStore {
|
|
storeOnce.Do(initStore)
|
|
return controlStore
|
|
}
|
|
|
|
// AddControl adds a control to the in-memory store and updates all indices
|
|
func (cs *ControlStore) AddControl(control *Control) {
|
|
cs.mu.Lock()
|
|
defer cs.mu.Unlock()
|
|
|
|
// Validate control
|
|
if err := control.Validate(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Add to base store
|
|
cs.Add(control.ID, control)
|
|
|
|
// Rebuild indices
|
|
cs.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// RemoveControl removes a control from the store and updates indices
|
|
func (cs *ControlStore) RemoveControl(id int) {
|
|
cs.mu.Lock()
|
|
defer cs.mu.Unlock()
|
|
|
|
// Remove from base store
|
|
cs.Remove(id)
|
|
|
|
// Rebuild indices
|
|
cs.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// UpdateControl updates a control efficiently
|
|
func (cs *ControlStore) UpdateControl(control *Control) {
|
|
cs.mu.Lock()
|
|
defer cs.mu.Unlock()
|
|
|
|
// Validate control
|
|
if err := control.Validate(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Update base store
|
|
cs.Add(control.ID, control)
|
|
|
|
// Rebuild indices
|
|
cs.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// LoadData loads control data from JSON file, or starts with empty store
|
|
func LoadData(dataPath string) error {
|
|
cs := GetStore()
|
|
|
|
// Load from base store, which handles JSON loading
|
|
if err := cs.BaseStore.LoadData(dataPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Rebuild indices from loaded data
|
|
cs.rebuildIndices()
|
|
return nil
|
|
}
|
|
|
|
// SaveData saves control data to JSON file
|
|
func SaveData(dataPath string) error {
|
|
cs := GetStore()
|
|
return cs.BaseStore.SaveData(dataPath)
|
|
}
|
|
|
|
// rebuildIndicesUnsafe rebuilds all indices from base store data (caller must hold lock)
|
|
func (cs *ControlStore) rebuildIndicesUnsafe() {
|
|
// Clear indices
|
|
cs.allByID = make([]int, 0)
|
|
|
|
// Collect all controls
|
|
allControls := cs.GetAll()
|
|
|
|
for id := range allControls {
|
|
cs.allByID = append(cs.allByID, id)
|
|
}
|
|
|
|
// Sort by ID (though typically only one control record exists)
|
|
for i := 0; i < len(cs.allByID); i++ {
|
|
for j := i + 1; j < len(cs.allByID); j++ {
|
|
if cs.allByID[i] > cs.allByID[j] {
|
|
cs.allByID[i], cs.allByID[j] = cs.allByID[j], cs.allByID[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rebuildIndices rebuilds all control-specific indices from base store data
|
|
func (cs *ControlStore) rebuildIndices() {
|
|
cs.mu.Lock()
|
|
defer cs.mu.Unlock()
|
|
cs.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// Retrieves the control record by ID (typically only ID 1 exists)
|
|
func Find(id int) (*Control, error) {
|
|
cs := GetStore()
|
|
control, exists := cs.GetByID(id)
|
|
if !exists {
|
|
return nil, fmt.Errorf("control with ID %d not found", id)
|
|
}
|
|
return control, nil
|
|
}
|
|
|
|
// Retrieves the main control record (ID 1)
|
|
func Get() (*Control, error) {
|
|
return Find(1)
|
|
}
|
|
|
|
// Saves a new control to the in-memory store and sets the ID
|
|
func (c *Control) Insert() error {
|
|
cs := GetStore()
|
|
|
|
// Validate before insertion
|
|
if err := c.Validate(); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
// Assign new ID if not set
|
|
if c.ID == 0 {
|
|
c.ID = cs.GetNextID()
|
|
}
|
|
|
|
// Add to store
|
|
cs.AddControl(c)
|
|
return nil
|
|
}
|
|
|
|
// Returns true if the game world is open for new players
|
|
func (c *Control) IsOpen() bool {
|
|
return c.Open == 1
|
|
}
|
|
|
|
// Sets whether the game world is open for new players
|
|
func (c *Control) SetOpen(open bool) {
|
|
if open {
|
|
c.Open = 1
|
|
} else {
|
|
c.Open = 0
|
|
}
|
|
}
|
|
|
|
// Closes the game world to new players
|
|
func (c *Control) Close() {
|
|
c.Open = 0
|
|
}
|
|
|
|
// Opens the game world to new players
|
|
func (c *Control) OpenWorld() {
|
|
c.Open = 1
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Sets all class names from a slice
|
|
func (c *Control) SetClassNames(classes []string) {
|
|
// Reset all class names
|
|
c.Class1Name = ""
|
|
c.Class2Name = ""
|
|
c.Class3Name = ""
|
|
|
|
// Set provided class names
|
|
if len(classes) > 0 {
|
|
c.Class1Name = classes[0]
|
|
}
|
|
if len(classes) > 1 {
|
|
c.Class2Name = classes[1]
|
|
}
|
|
if len(classes) > 2 {
|
|
c.Class3Name = classes[2]
|
|
}
|
|
}
|
|
|
|
// 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 ""
|
|
}
|
|
}
|
|
|
|
// Sets the name of a specific class (1-3)
|
|
func (c *Control) SetClassName(classNum int, name string) bool {
|
|
switch classNum {
|
|
case 1:
|
|
c.Class1Name = name
|
|
return true
|
|
case 2:
|
|
c.Class2Name = name
|
|
return true
|
|
case 3:
|
|
c.Class3Name = name
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Returns true if an admin email is configured
|
|
func (c *Control) HasAdminEmail() bool {
|
|
return c.AdminEmail != ""
|
|
}
|
|
|
|
// Returns true if the world size is within reasonable bounds
|
|
func (c *Control) IsWorldSizeValid() bool {
|
|
return c.WorldSize > 0 && c.WorldSize <= 10000
|
|
}
|
|
|
|
// Returns the world radius (half the world size)
|
|
func (c *Control) GetWorldRadius() int {
|
|
return c.WorldSize / 2
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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
|
|
}
|