301 lines
6.3 KiB
Go

package drops
import (
"dk/internal/store"
"fmt"
"sort"
"sync"
)
// Drop represents a drop item in the game
type Drop struct {
ID int `json:"id"`
Name string `json:"name"`
Level int `json:"level"`
Type int `json:"type"`
Att string `json:"att"`
}
func (d *Drop) Save() error {
dropStore := GetStore()
dropStore.UpdateDrop(d)
return nil
}
func (d *Drop) Delete() error {
dropStore := GetStore()
dropStore.RemoveDrop(d.ID)
return nil
}
// Creates a new Drop with sensible defaults
func New() *Drop {
return &Drop{
Name: "",
Level: 1, // Default minimum level
Type: TypeConsumable, // Default to consumable
Att: "",
}
}
// Validate checks if drop has valid values
func (d *Drop) Validate() error {
if d.Name == "" {
return fmt.Errorf("drop name cannot be empty")
}
if d.Level < 1 {
return fmt.Errorf("drop Level must be at least 1")
}
if d.Type < TypeConsumable {
return fmt.Errorf("invalid drop type: %d", d.Type)
}
return nil
}
// DropType constants for drop types
const (
TypeConsumable = 1
)
// DropStore provides in-memory storage with O(1) lookups and drop-specific indices
type DropStore struct {
*store.BaseStore[Drop] // Embedded generic store
byLevel map[int][]int // Level -> []ID
byType map[int][]int // Type -> []ID
allByID []int // All IDs sorted by ID
mu sync.RWMutex // Protects indices
}
// Global in-memory store
var dropStore *DropStore
var storeOnce sync.Once
// Initialize the in-memory store
func initStore() {
dropStore = &DropStore{
BaseStore: store.NewBaseStore[Drop](),
byLevel: make(map[int][]int),
byType: make(map[int][]int),
allByID: make([]int, 0),
}
}
// GetStore returns the global drop store
func GetStore() *DropStore {
storeOnce.Do(initStore)
return dropStore
}
// AddDrop adds a drop to the in-memory store and updates all indices
func (ds *DropStore) AddDrop(drop *Drop) {
ds.mu.Lock()
defer ds.mu.Unlock()
// Validate drop
if err := drop.Validate(); err != nil {
return
}
// Add to base store
ds.Add(drop.ID, drop)
// Rebuild indices
ds.rebuildIndicesUnsafe()
}
// RemoveDrop removes a drop from the store and updates indices
func (ds *DropStore) RemoveDrop(id int) {
ds.mu.Lock()
defer ds.mu.Unlock()
// Remove from base store
ds.Remove(id)
// Rebuild indices
ds.rebuildIndicesUnsafe()
}
// UpdateDrop updates a drop efficiently
func (ds *DropStore) UpdateDrop(drop *Drop) {
ds.mu.Lock()
defer ds.mu.Unlock()
// Validate drop
if err := drop.Validate(); err != nil {
return
}
// Update base store
ds.Add(drop.ID, drop)
// Rebuild indices
ds.rebuildIndicesUnsafe()
}
// LoadData loads drop data from JSON file, or starts with empty store
func LoadData(dataPath string) error {
ds := GetStore()
// Load from base store, which handles JSON loading
if err := ds.BaseStore.LoadData(dataPath); err != nil {
return err
}
// Rebuild indices from loaded data
ds.rebuildIndices()
return nil
}
// SaveData saves drop data to JSON file
func SaveData(dataPath string) error {
ds := GetStore()
return ds.BaseStore.SaveData(dataPath)
}
// rebuildIndicesUnsafe rebuilds all indices from base store data (caller must hold lock)
func (ds *DropStore) rebuildIndicesUnsafe() {
// Clear indices
ds.byLevel = make(map[int][]int)
ds.byType = make(map[int][]int)
ds.allByID = make([]int, 0)
// Collect all drops and build indices
allDrops := ds.GetAll()
for id, drop := range allDrops {
// Level index
ds.byLevel[drop.Level] = append(ds.byLevel[drop.Level], id)
// Type index
ds.byType[drop.Type] = append(ds.byType[drop.Type], id)
// All IDs
ds.allByID = append(ds.allByID, id)
}
// Sort allByID by ID
sort.Ints(ds.allByID)
// Sort level indices by ID
for level := range ds.byLevel {
sort.Ints(ds.byLevel[level])
}
// Sort type indices by level, then ID
for dropType := range ds.byType {
sort.Slice(ds.byType[dropType], func(i, j int) bool {
dropI, _ := ds.GetByID(ds.byType[dropType][i])
dropJ, _ := ds.GetByID(ds.byType[dropType][j])
if dropI.Level != dropJ.Level {
return dropI.Level < dropJ.Level
}
return ds.byType[dropType][i] < ds.byType[dropType][j]
})
}
}
// rebuildIndices rebuilds all drop-specific indices from base store data
func (ds *DropStore) rebuildIndices() {
ds.mu.Lock()
defer ds.mu.Unlock()
ds.rebuildIndicesUnsafe()
}
// Retrieves a drop by ID
func Find(id int) (*Drop, error) {
ds := GetStore()
drop, exists := ds.GetByID(id)
if !exists {
return nil, fmt.Errorf("drop with ID %d not found", id)
}
return drop, nil
}
// Retrieves all drops
func All() ([]*Drop, error) {
ds := GetStore()
ds.mu.RLock()
defer ds.mu.RUnlock()
result := make([]*Drop, 0, len(ds.allByID))
for _, id := range ds.allByID {
if drop, exists := ds.GetByID(id); exists {
result = append(result, drop)
}
}
return result, nil
}
// Retrieves drops by minimum level requirement
func ByLevel(minLevel int) ([]*Drop, error) {
ds := GetStore()
ds.mu.RLock()
defer ds.mu.RUnlock()
var result []*Drop
for level := 1; level <= minLevel; level++ {
if ids, exists := ds.byLevel[level]; exists {
for _, id := range ids {
if drop, exists := ds.GetByID(id); exists {
result = append(result, drop)
}
}
}
}
return result, nil
}
// Retrieves drops by type
func ByType(dropType int) ([]*Drop, error) {
ds := GetStore()
ds.mu.RLock()
defer ds.mu.RUnlock()
ids, exists := ds.byType[dropType]
if !exists {
return []*Drop{}, nil
}
result := make([]*Drop, 0, len(ids))
for _, id := range ids {
if drop, exists := ds.GetByID(id); exists {
result = append(result, drop)
}
}
return result, nil
}
// Saves a new drop to the in-memory store and sets the ID
func (d *Drop) Insert() error {
ds := GetStore()
// Validate before insertion
if err := d.Validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
// Assign new ID if not set
if d.ID == 0 {
d.ID = ds.GetNextID()
}
// Add to store
ds.AddDrop(d)
return nil
}
// Returns true if the drop is a consumable item
func (d *Drop) IsConsumable() bool {
return d.Type == TypeConsumable
}
// Returns the string representation of the drop type
func (d *Drop) TypeName() string {
switch d.Type {
case TypeConsumable:
return "Consumable"
default:
return "Unknown"
}
}