295 lines
6.0 KiB
Go
295 lines
6.0 KiB
Go
package items
|
|
|
|
import (
|
|
"dk/internal/store"
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
)
|
|
|
|
// Item represents an item in the game
|
|
type Item struct {
|
|
ID int `json:"id"`
|
|
Type int `json:"type"`
|
|
Name string `json:"name"`
|
|
Value int `json:"value"`
|
|
Att int `json:"att"`
|
|
Special string `json:"special"`
|
|
}
|
|
|
|
func (i *Item) Save() error {
|
|
itemStore := GetStore()
|
|
itemStore.UpdateItem(i)
|
|
return nil
|
|
}
|
|
|
|
func (i *Item) Delete() error {
|
|
itemStore := GetStore()
|
|
itemStore.RemoveItem(i.ID)
|
|
return nil
|
|
}
|
|
|
|
// Creates a new Item with sensible defaults
|
|
func New() *Item {
|
|
return &Item{
|
|
Type: TypeWeapon, // Default to weapon
|
|
Name: "",
|
|
Value: 0,
|
|
Att: 0,
|
|
Special: "",
|
|
}
|
|
}
|
|
|
|
// Validate checks if item has valid values
|
|
func (i *Item) Validate() error {
|
|
if i.Name == "" {
|
|
return fmt.Errorf("item name cannot be empty")
|
|
}
|
|
if i.Type < TypeWeapon || i.Type > TypeShield {
|
|
return fmt.Errorf("invalid item type: %d", i.Type)
|
|
}
|
|
if i.Value < 0 {
|
|
return fmt.Errorf("item Value cannot be negative")
|
|
}
|
|
if i.Att < 0 {
|
|
return fmt.Errorf("item Att cannot be negative")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ItemType constants for item types
|
|
const (
|
|
TypeWeapon = 1
|
|
TypeArmor = 2
|
|
TypeShield = 3
|
|
)
|
|
|
|
// ItemStore provides in-memory storage with O(1) lookups and item-specific indices
|
|
type ItemStore struct {
|
|
*store.BaseStore[Item] // Embedded generic store
|
|
byType map[int][]int // Type -> []ID
|
|
allByID []int // All IDs sorted by ID
|
|
mu sync.RWMutex // Protects indices
|
|
}
|
|
|
|
// Global in-memory store
|
|
var itemStore *ItemStore
|
|
var storeOnce sync.Once
|
|
|
|
// Initialize the in-memory store
|
|
func initStore() {
|
|
itemStore = &ItemStore{
|
|
BaseStore: store.NewBaseStore[Item](),
|
|
byType: make(map[int][]int),
|
|
allByID: make([]int, 0),
|
|
}
|
|
}
|
|
|
|
// GetStore returns the global item store
|
|
func GetStore() *ItemStore {
|
|
storeOnce.Do(initStore)
|
|
return itemStore
|
|
}
|
|
|
|
// AddItem adds an item to the in-memory store and updates all indices
|
|
func (is *ItemStore) AddItem(item *Item) {
|
|
is.mu.Lock()
|
|
defer is.mu.Unlock()
|
|
|
|
// Validate item
|
|
if err := item.Validate(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Add to base store
|
|
is.Add(item.ID, item)
|
|
|
|
// Rebuild indices
|
|
is.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// RemoveItem removes an item from the store and updates indices
|
|
func (is *ItemStore) RemoveItem(id int) {
|
|
is.mu.Lock()
|
|
defer is.mu.Unlock()
|
|
|
|
// Remove from base store
|
|
is.Remove(id)
|
|
|
|
// Rebuild indices
|
|
is.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// UpdateItem updates an item efficiently
|
|
func (is *ItemStore) UpdateItem(item *Item) {
|
|
is.mu.Lock()
|
|
defer is.mu.Unlock()
|
|
|
|
// Validate item
|
|
if err := item.Validate(); err != nil {
|
|
return
|
|
}
|
|
|
|
// Update base store
|
|
is.Add(item.ID, item)
|
|
|
|
// Rebuild indices
|
|
is.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// LoadData loads item data from JSON file, or starts with empty store
|
|
func LoadData(dataPath string) error {
|
|
is := GetStore()
|
|
|
|
// Load from base store, which handles JSON loading
|
|
if err := is.BaseStore.LoadData(dataPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Rebuild indices from loaded data
|
|
is.rebuildIndices()
|
|
return nil
|
|
}
|
|
|
|
// SaveData saves item data to JSON file
|
|
func SaveData(dataPath string) error {
|
|
is := GetStore()
|
|
return is.BaseStore.SaveData(dataPath)
|
|
}
|
|
|
|
// rebuildIndicesUnsafe rebuilds all indices from base store data (caller must hold lock)
|
|
func (is *ItemStore) rebuildIndicesUnsafe() {
|
|
// Clear indices
|
|
is.byType = make(map[int][]int)
|
|
is.allByID = make([]int, 0)
|
|
|
|
// Collect all items and build indices
|
|
allItems := is.GetAll()
|
|
|
|
for id, item := range allItems {
|
|
// Type index
|
|
is.byType[item.Type] = append(is.byType[item.Type], id)
|
|
|
|
// All IDs
|
|
is.allByID = append(is.allByID, id)
|
|
}
|
|
|
|
// Sort allByID by ID
|
|
sort.Ints(is.allByID)
|
|
|
|
// Sort type indices by ID
|
|
for itemType := range is.byType {
|
|
sort.Ints(is.byType[itemType])
|
|
}
|
|
}
|
|
|
|
// rebuildIndices rebuilds all item-specific indices from base store data
|
|
func (is *ItemStore) rebuildIndices() {
|
|
is.mu.Lock()
|
|
defer is.mu.Unlock()
|
|
is.rebuildIndicesUnsafe()
|
|
}
|
|
|
|
// Retrieves an item by ID
|
|
func Find(id int) (*Item, error) {
|
|
is := GetStore()
|
|
item, exists := is.GetByID(id)
|
|
if !exists {
|
|
return nil, fmt.Errorf("item with ID %d not found", id)
|
|
}
|
|
return item, nil
|
|
}
|
|
|
|
// Retrieves all items
|
|
func All() ([]*Item, error) {
|
|
is := GetStore()
|
|
is.mu.RLock()
|
|
defer is.mu.RUnlock()
|
|
|
|
result := make([]*Item, 0, len(is.allByID))
|
|
for _, id := range is.allByID {
|
|
if item, exists := is.GetByID(id); exists {
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Retrieves items by type
|
|
func ByType(itemType int) ([]*Item, error) {
|
|
is := GetStore()
|
|
is.mu.RLock()
|
|
defer is.mu.RUnlock()
|
|
|
|
ids, exists := is.byType[itemType]
|
|
if !exists {
|
|
return []*Item{}, nil
|
|
}
|
|
|
|
result := make([]*Item, 0, len(ids))
|
|
for _, id := range ids {
|
|
if item, exists := is.GetByID(id); exists {
|
|
result = append(result, item)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Saves a new item to the in-memory store and sets the ID
|
|
func (i *Item) Insert() error {
|
|
is := GetStore()
|
|
|
|
// Validate before insertion
|
|
if err := i.Validate(); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
// Assign new ID if not set
|
|
if i.ID == 0 {
|
|
i.ID = is.GetNextID()
|
|
}
|
|
|
|
// Add to store
|
|
is.AddItem(i)
|
|
return nil
|
|
}
|
|
|
|
// Returns true if the item is a weapon
|
|
func (i *Item) IsWeapon() bool {
|
|
return i.Type == TypeWeapon
|
|
}
|
|
|
|
// Returns true if the item is armor
|
|
func (i *Item) IsArmor() bool {
|
|
return i.Type == TypeArmor
|
|
}
|
|
|
|
// Returns true if the item is a shield
|
|
func (i *Item) IsShield() bool {
|
|
return i.Type == TypeShield
|
|
}
|
|
|
|
// Returns the string representation of the item type
|
|
func (i *Item) TypeName() string {
|
|
switch i.Type {
|
|
case TypeWeapon:
|
|
return "Weapon"
|
|
case TypeArmor:
|
|
return "Armor"
|
|
case TypeShield:
|
|
return "Shield"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// Returns true if the item has special properties
|
|
func (i *Item) HasSpecial() bool {
|
|
return i.Special != ""
|
|
}
|
|
|
|
// Returns true if the item can be equipped
|
|
func (i *Item) IsEquippable() bool {
|
|
return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield
|
|
}
|