260 lines
5.5 KiB
Go

package towns
import (
"fmt"
"math"
"slices"
"sort"
"strconv"
"strings"
"dk/internal/helpers"
nigiri "git.sharkk.net/Sharkk/Nigiri"
)
// Town represents a town in the game
type Town struct {
ID int `json:"id"`
Name string `json:"name" db:"required,unique"`
X int `json:"x"`
Y int `json:"y"`
InnCost int `json:"inn_cost"`
MapCost int `json:"map_cost"`
TPCost int `json:"tp_cost"`
ShopList string `json:"shop_list"`
}
// Global store
var store *nigiri.BaseStore[Town]
var db *nigiri.Collection
// coordsKey creates a key for coordinate-based lookup
func coordsKey(x, y int) string {
return strconv.Itoa(x) + "," + strconv.Itoa(y)
}
// Init sets up the Nigiri store and indices
func Init(collection *nigiri.Collection) {
db = collection
store = nigiri.NewBaseStore[Town]()
// Register custom indices
store.RegisterIndex("byName", nigiri.BuildCaseInsensitiveLookupIndex(func(t *Town) string {
return t.Name
}))
store.RegisterIndex("byCoords", nigiri.BuildStringLookupIndex(func(t *Town) string {
return coordsKey(t.X, t.Y)
}))
store.RegisterIndex("byInnCost", nigiri.BuildIntGroupIndex(func(t *Town) int {
return t.InnCost
}))
store.RegisterIndex("byTPCost", nigiri.BuildIntGroupIndex(func(t *Town) int {
return t.TPCost
}))
store.RegisterIndex("allByID", nigiri.BuildSortedListIndex(func(a, b *Town) bool {
return a.ID < b.ID
}))
store.RebuildIndices()
}
// GetStore returns the towns store
func GetStore() *nigiri.BaseStore[Town] {
if store == nil {
panic("towns store not initialized - call Initialize first")
}
return store
}
// Creates a new Town with sensible defaults
func New() *Town {
return &Town{
Name: "",
X: 0, // Default coordinates
Y: 0,
InnCost: 50, // Default inn cost
MapCost: 100, // Default map cost
TPCost: 25, // Default teleport cost
ShopList: "", // No items by default
}
}
// Validate checks if town has valid values
func (t *Town) Validate() error {
if t.Name == "" {
return fmt.Errorf("town name cannot be empty")
}
if t.InnCost < 0 {
return fmt.Errorf("town InnCost cannot be negative")
}
if t.MapCost < 0 {
return fmt.Errorf("town MapCost cannot be negative")
}
if t.TPCost < 0 {
return fmt.Errorf("town TPCost cannot be negative")
}
return nil
}
// CRUD operations
func (t *Town) Save() error {
if t.ID == 0 {
id, err := store.Create(t)
if err != nil {
return err
}
t.ID = id
return nil
}
return store.Update(t.ID, t)
}
func (t *Town) Delete() error {
store.Remove(t.ID)
return nil
}
// Insert with ID assignment
func (t *Town) Insert() error {
id, err := store.Create(t)
if err != nil {
return err
}
t.ID = id
return nil
}
// Query functions
func Find(id int) (*Town, error) {
town, exists := store.Find(id)
if !exists {
return nil, fmt.Errorf("town with ID %d not found", id)
}
return town, nil
}
func All() ([]*Town, error) {
return store.AllSorted("allByID"), nil
}
func ByName(name string) (*Town, error) {
town, exists := store.LookupByIndex("byName", strings.ToLower(name))
if !exists {
return nil, fmt.Errorf("town with name '%s' not found", name)
}
return town, nil
}
func ByMaxInnCost(maxCost int) ([]*Town, error) {
return store.FilterByIndex("allByID", func(t *Town) bool {
return t.InnCost <= maxCost
}), nil
}
func ByMaxTPCost(maxCost int) ([]*Town, error) {
return store.FilterByIndex("allByID", func(t *Town) bool {
return t.TPCost <= maxCost
}), nil
}
func ByCoords(x, y int) (*Town, error) {
town, exists := store.LookupByIndex("byCoords", coordsKey(x, y))
if !exists {
return nil, nil // Return nil if not found (like original)
}
return town, nil
}
func ExistsAt(x, y int) bool {
_, exists := store.LookupByIndex("byCoords", coordsKey(x, y))
return exists
}
func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) {
maxDistance2 := float64(maxDistance * maxDistance)
result := store.FilterByIndex("allByID", func(t *Town) bool {
return t.DistanceFromSquared(fromX, fromY) <= maxDistance2
})
// Sort by distance, then by ID
sort.Slice(result, func(i, j int) bool {
distI := result[i].DistanceFromSquared(fromX, fromY)
distJ := result[j].DistanceFromSquared(fromX, fromY)
if distI == distJ {
return result[i].ID < result[j].ID
}
return distI < distJ
})
return result, nil
}
// Helper methods
func (t *Town) GetShopItems() []int {
return helpers.StringToInts(t.ShopList)
}
func (t *Town) SetShopItems(items []int) {
t.ShopList = helpers.IntsToString(items)
}
func (t *Town) HasShopItem(itemID int) bool {
return slices.Contains(t.GetShopItems(), itemID)
}
func (t *Town) DistanceFromSquared(x, y int) float64 {
dx := float64(t.X - x)
dy := float64(t.Y - y)
return dx*dx + dy*dy // Return squared distance for performance
}
func (t *Town) DistanceFrom(x, y int) float64 {
return math.Sqrt(t.DistanceFromSquared(x, y))
}
func (t *Town) IsStartingTown() bool {
return t.X == 0 && t.Y == 0
}
func (t *Town) CanAffordInn(gold int) bool {
return gold >= t.InnCost
}
func (t *Town) CanAffordMap(gold int) bool {
return gold >= t.MapCost
}
func (t *Town) CanAffordTeleport(gold int) bool {
return gold >= t.TPCost
}
func (t *Town) HasShop() bool {
return len(t.GetShopItems()) > 0
}
func (t *Town) GetPosition() (int, int) {
return t.X, t.Y
}
func (t *Town) SetPosition(x, y int) {
t.X = x
t.Y = y
}
// Legacy compatibility functions (will be removed later)
func LoadData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}
func SaveData(dataPath string) error {
// No longer needed - Nigiri handles this
return nil
}