301 lines
6.3 KiB
Go
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"
|
|
}
|
|
}
|