289 lines
7.2 KiB
Go
289 lines
7.2 KiB
Go
package control
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"dk/internal/database"
|
|
"dk/internal/helpers/scanner"
|
|
|
|
"zombiezen.com/go/sqlite"
|
|
)
|
|
|
|
// Control represents the game control settings in the database
|
|
type Control struct {
|
|
ID int `db:"id" json:"id"`
|
|
WorldSize int `db:"world_size" json:"world_size"`
|
|
Open int `db:"open" json:"open"`
|
|
AdminEmail string `db:"admin_email" json:"admin_email"`
|
|
Class1Name string `db:"class_1_name" json:"class_1_name"`
|
|
Class2Name string `db:"class_2_name" json:"class_2_name"`
|
|
Class3Name string `db:"class_3_name" json:"class_3_name"`
|
|
}
|
|
|
|
// New 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",
|
|
}
|
|
}
|
|
|
|
var controlScanner = scanner.New[Control]()
|
|
|
|
// controlColumns returns the column list for control queries
|
|
func controlColumns() string {
|
|
return controlScanner.Columns()
|
|
}
|
|
|
|
// scanControl populates a Control struct using the fast scanner
|
|
func scanControl(stmt *sqlite.Stmt) *Control {
|
|
control := &Control{}
|
|
controlScanner.Scan(stmt, control)
|
|
return control
|
|
}
|
|
|
|
// Find retrieves the control record by ID (typically only ID 1 exists)
|
|
func Find(id int) (*Control, error) {
|
|
var control *Control
|
|
|
|
query := `SELECT ` + controlColumns() + ` FROM control WHERE id = ?`
|
|
|
|
err := database.Query(query, func(stmt *sqlite.Stmt) error {
|
|
control = scanControl(stmt)
|
|
return nil
|
|
}, id)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find control: %w", err)
|
|
}
|
|
|
|
if control == nil {
|
|
return nil, fmt.Errorf("control with ID %d not found", id)
|
|
}
|
|
|
|
return control, nil
|
|
}
|
|
|
|
// Get retrieves the main control record (ID 1)
|
|
func Get() (*Control, error) {
|
|
return Find(1)
|
|
}
|
|
|
|
// Save updates the control record in the database
|
|
func (c *Control) Save() error {
|
|
if c.ID == 0 {
|
|
return fmt.Errorf("cannot save control without ID")
|
|
}
|
|
|
|
query := `UPDATE control SET world_size = ?, open = ?, admin_email = ?, class_1_name = ?, class_2_name = ?, class_3_name = ? WHERE id = ?`
|
|
return database.Exec(query, c.WorldSize, c.Open, c.AdminEmail, c.Class1Name, c.Class2Name, c.Class3Name, c.ID)
|
|
}
|
|
|
|
// Insert saves a new control to the database and sets the ID
|
|
func (c *Control) Insert() error {
|
|
if c.ID != 0 {
|
|
return fmt.Errorf("control already has ID %d, use Save() to update", c.ID)
|
|
}
|
|
|
|
// Use a transaction to ensure we can get the ID
|
|
err := database.Transaction(func(tx *database.Tx) error {
|
|
query := `INSERT INTO control (world_size, open, admin_email, class_1_name, class_2_name, class_3_name) VALUES (?, ?, ?, ?, ?, ?)`
|
|
|
|
if err := tx.Exec(query, c.WorldSize, c.Open, c.AdminEmail, c.Class1Name, c.Class2Name, c.Class3Name); err != nil {
|
|
return fmt.Errorf("failed to insert control: %w", err)
|
|
}
|
|
|
|
// Get the last insert ID
|
|
var id int
|
|
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
|
|
id = stmt.ColumnInt(0)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get insert ID: %w", err)
|
|
}
|
|
|
|
c.ID = id
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// Delete removes the control record from the database
|
|
func (c *Control) Delete() error {
|
|
if c.ID == 0 {
|
|
return fmt.Errorf("cannot delete control without ID")
|
|
}
|
|
|
|
return database.Exec("DELETE FROM control WHERE id = ?", c.ID)
|
|
}
|
|
|
|
// 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 (c *Control) SetOpen(open bool) {
|
|
if open {
|
|
c.Open = 1
|
|
} else {
|
|
c.Open = 0
|
|
}
|
|
}
|
|
|
|
// Close closes the game world to new players
|
|
func (c *Control) Close() {
|
|
c.Open = 0
|
|
}
|
|
|
|
// OpenWorld opens the game world to new players
|
|
func (c *Control) OpenWorld() {
|
|
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
|
|
}
|
|
|
|
// SetClassNames 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]
|
|
}
|
|
}
|
|
|
|
// 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 ""
|
|
}
|
|
}
|
|
|
|
// SetClassName 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
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// ToMap converts the control to a map for efficient template rendering
|
|
func (c *Control) ToMap() map[string]any {
|
|
return map[string]any{
|
|
"ID": c.ID,
|
|
"WorldSize": c.WorldSize,
|
|
"Open": c.Open,
|
|
"AdminEmail": c.AdminEmail,
|
|
"Class1Name": c.Class1Name,
|
|
"Class2Name": c.Class2Name,
|
|
"Class3Name": c.Class3Name,
|
|
|
|
// Computed values
|
|
"IsOpen": c.IsOpen(),
|
|
"ClassNames": c.GetClassNames(),
|
|
"HasAdminEmail": c.HasAdminEmail(),
|
|
"IsWorldSizeValid": c.IsWorldSizeValid(),
|
|
"WorldRadius": c.GetWorldRadius(),
|
|
"WorldBounds": map[string]int{
|
|
"MinX": -c.GetWorldRadius(),
|
|
"MinY": -c.GetWorldRadius(),
|
|
"MaxX": c.GetWorldRadius(),
|
|
"MaxY": c.GetWorldRadius(),
|
|
},
|
|
}
|
|
}
|