package users import ( "dk/internal/store" "fmt" "slices" "sort" "strings" "time" "dk/internal/helpers" ) // User represents a user in the game type User struct { ID int `json:"id"` Username string `json:"username"` Password string `json:"password"` Email string `json:"email"` Verified int `json:"verified"` Token string `json:"token"` Registered int64 `json:"registered"` LastOnline int64 `json:"last_online"` Auth int `json:"auth"` X int `json:"x"` Y int `json:"y"` ClassID int `json:"class_id"` Currently string `json:"currently"` Fighting int `json:"fighting"` MonsterID int `json:"monster_id"` MonsterHP int `json:"monster_hp"` MonsterSleep int `json:"monster_sleep"` MonsterImmune int `json:"monster_immune"` UberDamage int `json:"uber_damage"` UberDefense int `json:"uber_defense"` HP int `json:"hp"` MP int `json:"mp"` TP int `json:"tp"` MaxHP int `json:"max_hp"` MaxMP int `json:"max_mp"` MaxTP int `json:"max_tp"` Level int `json:"level"` Gold int `json:"gold"` Exp int `json:"exp"` GoldBonus int `json:"gold_bonus"` ExpBonus int `json:"exp_bonus"` Strength int `json:"strength"` Dexterity int `json:"dexterity"` Attack int `json:"attack"` Defense int `json:"defense"` WeaponID int `json:"weapon_id"` ArmorID int `json:"armor_id"` ShieldID int `json:"shield_id"` Slot1ID int `json:"slot_1_id"` Slot2ID int `json:"slot_2_id"` Slot3ID int `json:"slot_3_id"` WeaponName string `json:"weapon_name"` ArmorName string `json:"armor_name"` ShieldName string `json:"shield_name"` Slot1Name string `json:"slot_1_name"` Slot2Name string `json:"slot_2_name"` Slot3Name string `json:"slot_3_name"` DropCode int `json:"drop_code"` Spells string `json:"spells"` Towns string `json:"towns"` } func (u *User) Save() error { return GetStore().UpdateWithRebuild(u.ID, u) } func (u *User) Delete() error { GetStore().RemoveWithRebuild(u.ID) return nil } func New() *User { now := time.Now().Unix() return &User{ Verified: 0, Token: "", Registered: now, LastOnline: now, Auth: 0, X: 0, Y: 0, ClassID: 1, Currently: "In Town", Fighting: 0, HP: 10, MP: 10, TP: 10, MaxHP: 10, MaxMP: 10, MaxTP: 10, Level: 1, Gold: 100, Exp: 0, Strength: 0, Dexterity: 0, Attack: 0, Defense: 0, Spells: "", Towns: "", } } // Validate checks if user has valid values func (u *User) Validate() error { if strings.TrimSpace(u.Username) == "" { return fmt.Errorf("user username cannot be empty") } if strings.TrimSpace(u.Email) == "" { return fmt.Errorf("user email cannot be empty") } if u.Registered <= 0 { return fmt.Errorf("user Registered timestamp must be positive") } if u.LastOnline <= 0 { return fmt.Errorf("user LastOnline timestamp must be positive") } if u.Level < 1 { return fmt.Errorf("user Level must be at least 1") } if u.HP < 0 { return fmt.Errorf("user HP cannot be negative") } if u.MaxHP < 1 { return fmt.Errorf("user MaxHP must be at least 1") } return nil } // UserStore with enhanced BaseStore type UserStore struct { *store.BaseStore[User] } // Global store with singleton pattern var GetStore = store.NewSingleton(func() *UserStore { us := &UserStore{BaseStore: store.NewBaseStore[User]()} // Register indices us.RegisterIndex("byUsername", store.BuildCaseInsensitiveLookupIndex(func(u *User) string { return u.Username })) us.RegisterIndex("byEmail", store.BuildStringLookupIndex(func(u *User) string { return u.Email })) us.RegisterIndex("byLevel", store.BuildIntGroupIndex(func(u *User) int { return u.Level })) us.RegisterIndex("allByRegistered", store.BuildSortedListIndex(func(a, b *User) bool { if a.Registered != b.Registered { return a.Registered > b.Registered // DESC } return a.ID > b.ID // DESC })) us.RegisterIndex("allByLevelExp", store.BuildSortedListIndex(func(a, b *User) bool { if a.Level != b.Level { return a.Level > b.Level // Level DESC } if a.Exp != b.Exp { return a.Exp > b.Exp // Exp DESC } return a.ID < b.ID // ID ASC })) return us }) // Enhanced CRUD operations func (us *UserStore) AddUser(user *User) error { return us.AddWithRebuild(user.ID, user) } func (us *UserStore) RemoveUser(id int) { us.RemoveWithRebuild(id) } func (us *UserStore) UpdateUser(user *User) error { return us.UpdateWithRebuild(user.ID, user) } // Data persistence func LoadData(dataPath string) error { us := GetStore() return us.BaseStore.LoadData(dataPath) } func SaveData(dataPath string) error { us := GetStore() return us.BaseStore.SaveData(dataPath) } // Query functions using enhanced store func Find(id int) (*User, error) { us := GetStore() user, exists := us.Find(id) if !exists { return nil, fmt.Errorf("user with ID %d not found", id) } return user, nil } func All() ([]*User, error) { us := GetStore() return us.AllSorted("allByRegistered"), nil } func ByUsername(username string) (*User, error) { us := GetStore() user, exists := us.LookupByIndex("byUsername", strings.ToLower(username)) if !exists { return nil, fmt.Errorf("user with username '%s' not found", username) } return user, nil } func ByEmail(email string) (*User, error) { us := GetStore() user, exists := us.LookupByIndex("byEmail", email) if !exists { return nil, fmt.Errorf("user with email '%s' not found", email) } return user, nil } func ByLevel(level int) ([]*User, error) { us := GetStore() return us.GroupByIndex("byLevel", level), nil } func Online(within time.Duration) ([]*User, error) { us := GetStore() cutoff := time.Now().Add(-within).Unix() result := us.FilterByIndex("allByRegistered", func(u *User) bool { return u.LastOnline >= cutoff }) // Sort by last_online DESC, then ID ASC sort.Slice(result, func(i, j int) bool { if result[i].LastOnline != result[j].LastOnline { return result[i].LastOnline > result[j].LastOnline // DESC } return result[i].ID < result[j].ID // ASC }) return result, nil } // Insert with ID assignment func (u *User) Insert() error { us := GetStore() if u.ID == 0 { u.ID = us.GetNextID() } return us.AddUser(u) } // Helper methods func (u *User) RegisteredTime() time.Time { return time.Unix(u.Registered, 0) } func (u *User) LastOnlineTime() time.Time { return time.Unix(u.LastOnline, 0) } func (u *User) UpdateLastOnline() { u.LastOnline = time.Now().Unix() } func (u *User) IsVerified() bool { return u.Verified == 1 } func (u *User) IsAdmin() bool { return u.Auth >= 4 } func (u *User) IsModerator() bool { return u.Auth >= 3 } func (u *User) IsFighting() bool { return u.Fighting == 1 } func (u *User) IsAlive() bool { return u.HP > 0 } func (u *User) GetSpellIDs() []int { return helpers.StringToInts(u.Spells) } func (u *User) SetSpellIDs(spells []int) { u.Spells = helpers.IntsToString(spells) } func (u *User) HasSpell(spellID int) bool { return slices.Contains(u.GetSpellIDs(), spellID) } func (u *User) GetTownIDs() []int { return helpers.StringToInts(u.Towns) } func (u *User) SetTownIDs(towns []int) { u.Towns = helpers.IntsToString(towns) } func (u *User) HasTownMap(townID int) bool { return slices.Contains(u.GetTownIDs(), townID) } func (u *User) GetEquipment() map[string]any { return map[string]any{ "weapon": map[string]any{"id": u.WeaponID, "name": u.WeaponName}, "armor": map[string]any{"id": u.ArmorID, "name": u.ArmorName}, "shield": map[string]any{"id": u.ShieldID, "name": u.ShieldName}, "slot1": map[string]any{"id": u.Slot1ID, "name": u.Slot1Name}, "slot2": map[string]any{"id": u.Slot2ID, "name": u.Slot2Name}, "slot3": map[string]any{"id": u.Slot3ID, "name": u.Slot3Name}, } } func (u *User) GetStats() map[string]int { return map[string]int{ "level": u.Level, "hp": u.HP, "mp": u.MP, "tp": u.TP, "max_hp": u.MaxHP, "max_mp": u.MaxMP, "max_tp": u.MaxTP, "strength": u.Strength, "dexterity": u.Dexterity, "attack": u.Attack, "defense": u.Defense, "uber_damage": u.UberDamage, "uber_defense": u.UberDefense, } } func (u *User) GetPosition() (int, int) { return u.X, u.Y } func (u *User) SetPosition(x, y int) { u.X = x u.Y = y }