263 lines
7.0 KiB
Go

package towns
import (
"fmt"
"strings"
"dk/internal/database"
"zombiezen.com/go/sqlite"
)
// Town represents a town in the database
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"`
}
// Find retrieves a town by ID
func Find(id int) (*Town, error) {
town := &Town{}
query := "SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list FROM towns WHERE id = ?"
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town.ID = stmt.ColumnInt(0)
town.Name = stmt.ColumnText(1)
town.X = stmt.ColumnInt(2)
town.Y = stmt.ColumnInt(3)
town.InnCost = stmt.ColumnInt(4)
town.MapCost = stmt.ColumnInt(5)
town.TPCost = stmt.ColumnInt(6)
town.ShopList = stmt.ColumnText(7)
return nil
}, id)
if err != nil {
return nil, fmt.Errorf("failed to find town: %w", err)
}
if town.ID == 0 {
return nil, fmt.Errorf("town with ID %d not found", id)
}
return town, nil
}
// All retrieves all towns
func All() ([]*Town, error) {
var towns []*Town
query := "SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list FROM towns ORDER BY id"
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town := &Town{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
X: stmt.ColumnInt(2),
Y: stmt.ColumnInt(3),
InnCost: stmt.ColumnInt(4),
MapCost: stmt.ColumnInt(5),
TPCost: stmt.ColumnInt(6),
ShopList: stmt.ColumnText(7),
}
towns = append(towns, town)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to retrieve all towns: %w", err)
}
return towns, nil
}
// ByName retrieves a town by name (case-insensitive)
func ByName(name string) (*Town, error) {
town := &Town{}
query := "SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list FROM towns WHERE LOWER(name) = LOWER(?) LIMIT 1"
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town.ID = stmt.ColumnInt(0)
town.Name = stmt.ColumnText(1)
town.X = stmt.ColumnInt(2)
town.Y = stmt.ColumnInt(3)
town.InnCost = stmt.ColumnInt(4)
town.MapCost = stmt.ColumnInt(5)
town.TPCost = stmt.ColumnInt(6)
town.ShopList = stmt.ColumnText(7)
return nil
}, name)
if err != nil {
return nil, fmt.Errorf("failed to find town by name: %w", err)
}
if town.ID == 0 {
return nil, fmt.Errorf("town with name '%s' not found", name)
}
return town, nil
}
// ByMaxInnCost retrieves towns with inn cost at most the specified amount
func ByMaxInnCost(maxCost int) ([]*Town, error) {
var towns []*Town
query := "SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list FROM towns WHERE inn_cost <= ? ORDER BY inn_cost, id"
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town := &Town{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
X: stmt.ColumnInt(2),
Y: stmt.ColumnInt(3),
InnCost: stmt.ColumnInt(4),
MapCost: stmt.ColumnInt(5),
TPCost: stmt.ColumnInt(6),
ShopList: stmt.ColumnText(7),
}
towns = append(towns, town)
return nil
}, maxCost)
if err != nil {
return nil, fmt.Errorf("failed to retrieve towns by max inn cost: %w", err)
}
return towns, nil
}
// ByMaxTPCost retrieves towns with teleport cost at most the specified amount
func ByMaxTPCost(maxCost int) ([]*Town, error) {
var towns []*Town
query := "SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list FROM towns WHERE tp_cost <= ? ORDER BY tp_cost, id"
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town := &Town{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
X: stmt.ColumnInt(2),
Y: stmt.ColumnInt(3),
InnCost: stmt.ColumnInt(4),
MapCost: stmt.ColumnInt(5),
TPCost: stmt.ColumnInt(6),
ShopList: stmt.ColumnText(7),
}
towns = append(towns, town)
return nil
}, maxCost)
if err != nil {
return nil, fmt.Errorf("failed to retrieve towns by max TP cost: %w", err)
}
return towns, nil
}
// ByDistance retrieves towns within a certain distance from a point
func ByDistance(fromX, fromY, maxDistance int) ([]*Town, error) {
var towns []*Town
query := `SELECT id, name, x, y, inn_cost, map_cost, tp_cost, shop_list
FROM towns
WHERE ((x - ?) * (x - ?) + (y - ?) * (y - ?)) <= ?
ORDER BY ((x - ?) * (x - ?) + (y - ?) * (y - ?)), id`
maxDistance2 := maxDistance * maxDistance
err := database.Query(query, func(stmt *sqlite.Stmt) error {
town := &Town{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
X: stmt.ColumnInt(2),
Y: stmt.ColumnInt(3),
InnCost: stmt.ColumnInt(4),
MapCost: stmt.ColumnInt(5),
TPCost: stmt.ColumnInt(6),
ShopList: stmt.ColumnText(7),
}
towns = append(towns, town)
return nil
}, fromX, fromX, fromY, fromY, maxDistance2, fromX, fromX, fromY, fromY)
if err != nil {
return nil, fmt.Errorf("failed to retrieve towns by distance: %w", err)
}
return towns, nil
}
// Save updates an existing town in the database
func (t *Town) Save() error {
if t.ID == 0 {
return fmt.Errorf("cannot save town without ID")
}
query := `UPDATE towns SET name = ?, x = ?, y = ?, inn_cost = ?, map_cost = ?, tp_cost = ?, shop_list = ? WHERE id = ?`
return database.Exec(query, t.Name, t.X, t.Y, t.InnCost, t.MapCost, t.TPCost, t.ShopList, t.ID)
}
// Delete removes the town from the database
func (t *Town) Delete() error {
if t.ID == 0 {
return fmt.Errorf("cannot delete town without ID")
}
query := "DELETE FROM towns WHERE id = ?"
return database.Exec(query, t.ID)
}
// GetShopItems returns the shop items as a slice of item IDs
func (t *Town) GetShopItems() []string {
if t.ShopList == "" {
return []string{}
}
return strings.Split(t.ShopList, ",")
}
// SetShopItems sets the shop items from a slice of item IDs
func (t *Town) SetShopItems(items []string) {
t.ShopList = strings.Join(items, ",")
}
// HasShopItem checks if the town's shop sells a specific item ID
func (t *Town) HasShopItem(itemID string) bool {
items := t.GetShopItems()
for _, item := range items {
if strings.TrimSpace(item) == itemID {
return true
}
}
return false
}
// DistanceFrom calculates the distance from this town to given coordinates
func (t *Town) DistanceFrom(x, y int) float64 {
dx := float64(t.X - x)
dy := float64(t.Y - y)
return dx*dx + dy*dy // Return squared distance for performance
}
// IsStartingTown returns true if this is the starting town (Midworld)
func (t *Town) IsStartingTown() bool {
return t.X == 0 && t.Y == 0
}
// CanAffordInn returns true if the player can afford the inn
func (t *Town) CanAffordInn(gold int) bool {
return gold >= t.InnCost
}
// CanAffordMap returns true if the player can afford to buy the map
func (t *Town) CanAffordMap(gold int) bool {
return gold >= t.MapCost
}
// CanAffordTeleport returns true if the player can afford to teleport here
func (t *Town) CanAffordTeleport(gold int) bool {
return gold >= t.TPCost
}