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 }