262 lines
5.6 KiB
Go
262 lines
5.6 KiB
Go
package towns
|
|
|
|
import (
|
|
"dk/internal/store"
|
|
"fmt"
|
|
"math"
|
|
"slices"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"dk/internal/helpers"
|
|
)
|
|
|
|
// Town represents a town in the game
|
|
type Town struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
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"`
|
|
}
|
|
|
|
func (t *Town) Save() error {
|
|
return GetStore().UpdateWithRebuild(t.ID, t)
|
|
}
|
|
|
|
func (t *Town) Delete() error {
|
|
GetStore().RemoveWithRebuild(t.ID)
|
|
return nil
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// coordsKey creates a key for coordinate-based lookup
|
|
func coordsKey(x, y int) string {
|
|
return strconv.Itoa(x) + "," + strconv.Itoa(y)
|
|
}
|
|
|
|
// TownStore with enhanced BaseStore
|
|
type TownStore struct {
|
|
*store.BaseStore[Town]
|
|
}
|
|
|
|
// Global store with singleton pattern
|
|
var GetStore = store.NewSingleton(func() *TownStore {
|
|
ts := &TownStore{BaseStore: store.NewBaseStore[Town]()}
|
|
|
|
// Register indices
|
|
ts.RegisterIndex("byName", store.BuildCaseInsensitiveLookupIndex(func(t *Town) string {
|
|
return t.Name
|
|
}))
|
|
|
|
ts.RegisterIndex("byCoords", store.BuildStringLookupIndex(func(t *Town) string {
|
|
return coordsKey(t.X, t.Y)
|
|
}))
|
|
|
|
ts.RegisterIndex("byInnCost", store.BuildIntGroupIndex(func(t *Town) int {
|
|
return t.InnCost
|
|
}))
|
|
|
|
ts.RegisterIndex("byTPCost", store.BuildIntGroupIndex(func(t *Town) int {
|
|
return t.TPCost
|
|
}))
|
|
|
|
ts.RegisterIndex("allByID", store.BuildSortedListIndex(func(a, b *Town) bool {
|
|
return a.ID < b.ID
|
|
}))
|
|
|
|
return ts
|
|
})
|
|
|
|
// Enhanced CRUD operations
|
|
func (ts *TownStore) AddTown(town *Town) error {
|
|
return ts.AddWithRebuild(town.ID, town)
|
|
}
|
|
|
|
func (ts *TownStore) RemoveTown(id int) {
|
|
ts.RemoveWithRebuild(id)
|
|
}
|
|
|
|
func (ts *TownStore) UpdateTown(town *Town) error {
|
|
return ts.UpdateWithRebuild(town.ID, town)
|
|
}
|
|
|
|
// Data persistence
|
|
func LoadData(dataPath string) error {
|
|
ts := GetStore()
|
|
return ts.BaseStore.LoadData(dataPath)
|
|
}
|
|
|
|
func SaveData(dataPath string) error {
|
|
ts := GetStore()
|
|
return ts.BaseStore.SaveData(dataPath)
|
|
}
|
|
|
|
// Query functions using enhanced store
|
|
func Find(id int) (*Town, error) {
|
|
ts := GetStore()
|
|
town, exists := ts.Find(id)
|
|
if !exists {
|
|
return nil, fmt.Errorf("town with ID %d not found", id)
|
|
}
|
|
return town, nil
|
|
}
|
|
|
|
func All() ([]*Town, error) {
|
|
ts := GetStore()
|
|
return ts.AllSorted("allByID"), nil
|
|
}
|
|
|
|
func ByName(name string) (*Town, error) {
|
|
ts := GetStore()
|
|
town, exists := ts.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) {
|
|
ts := GetStore()
|
|
return ts.FilterByIndex("allByID", func(t *Town) bool {
|
|
return t.InnCost <= maxCost
|
|
}), nil
|
|
}
|
|
|
|
func ByMaxTPCost(maxCost int) ([]*Town, error) {
|
|
ts := GetStore()
|
|
return ts.FilterByIndex("allByID", func(t *Town) bool {
|
|
return t.TPCost <= maxCost
|
|
}), nil
|
|
}
|
|
|
|
func ByCoords(x, y int) (*Town, error) {
|
|
ts := GetStore()
|
|
town, exists := ts.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 {
|
|
ts := GetStore()
|
|
_, exists := ts.LookupByIndex("byCoords", coordsKey(x, y))
|
|
return exists
|
|
}
|
|
|
|
func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) {
|
|
ts := GetStore()
|
|
maxDistance2 := float64(maxDistance * maxDistance)
|
|
|
|
result := ts.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
|
|
}
|
|
|
|
// Insert with ID assignment
|
|
func (t *Town) Insert() error {
|
|
ts := GetStore()
|
|
if t.ID == 0 {
|
|
t.ID = ts.GetNextID()
|
|
}
|
|
return ts.AddTown(t)
|
|
}
|
|
|
|
// 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
|
|
}
|