package users import ( "fmt" "slices" "strings" "time" "dk/internal/database" "dk/internal/helpers" "dk/internal/helpers/exp" ) // User represents a user in the game type User struct { ID int Username string Password string Email string Verified int Token string Registered int64 LastOnline int64 Auth int X int Y int ClassID int Currently string FightID int HP int MP int TP int MaxHP int MaxMP int MaxTP int Level int Gold int Exp int GoldBonus int ExpBonus int Strength int Dexterity int Attack int Defense int WeaponID int ArmorID int ShieldID int Slot1ID int Slot2ID int Slot3ID int WeaponName string ArmorName string ShieldName string Slot1Name string Slot2Name string Slot3Name string Spells string Towns string } // New creates a new User with sensible defaults 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", FightID: 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 } func (u *User) Delete() error { return database.Exec("DELETE FROM users WHERE id = %d", u.ID) } func (u *User) Insert() error { id, err := database.Insert("users", u, "id") if err != nil { return err } u.ID = int(id) return nil } func Find(id int) (*User, error) { var user User err := database.Get(&user, "SELECT * FROM users WHERE id = %d", id) if err != nil { return nil, fmt.Errorf("user with ID %d not found", id) } return &user, nil } func GetByID(id int) *User { user, err := Find(id) if err != nil { return nil } return user } func All() ([]*User, error) { var users []*User err := database.Select(&users, "SELECT * FROM users ORDER BY registered DESC, id DESC") return users, err } func ByUsername(username string) (*User, error) { var user User err := database.Get(&user, "SELECT * FROM users WHERE username = %s COLLATE NOCASE", username) if err != nil { return nil, fmt.Errorf("user with username '%s' not found", username) } return &user, nil } func ByEmail(email string) (*User, error) { var user User err := database.Get(&user, "SELECT * FROM users WHERE email = %s", email) if err != nil { return nil, fmt.Errorf("user with email '%s' not found", email) } return &user, nil } func ByLevel(level int) ([]*User, error) { var users []*User err := database.Select(&users, "SELECT * FROM users WHERE level = %d ORDER BY exp DESC, id ASC", level) return users, err } func Online(within time.Duration) ([]*User, error) { cutoff := time.Now().Add(-within).Unix() var users []*User err := database.Select(&users, "SELECT * FROM users WHERE last_online >= %d ORDER BY last_online DESC, id ASC", cutoff) return users, err } 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.FightID > 0 } 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, } } func (u *User) GetPosition() (int, int) { return u.X, u.Y } func (u *User) SetPosition(x, y int) { u.X = x u.Y = y } func (u *User) ExpNeededForNextLevel() int { return exp.Calc(u.Level + 1) } func (u *User) GrantExp(expAmount int) map[string]any { newLevel, newStr, newDex, newExp := u.CalculateLevelUp(expAmount) updates := map[string]any{ "exp": newExp, } // Only include level/stats if they actually changed if newLevel > u.Level { updates["level"] = newLevel updates["strength"] = newStr updates["dexterity"] = newDex } return updates } func (u *User) CalculateLevelUp(expGain int) (newLevel, newStr, newDex, newExp int) { level := u.Level str := u.Strength dex := u.Dexterity totalExp := u.Exp + expGain for { expNeeded := exp.Calc(level + 1) if totalExp < expNeeded { break } level++ str++ dex++ totalExp -= expNeeded } return level, str, dex, totalExp } func (u *User) ExpProgress() float64 { if u.Level == 1 { return float64(u.Exp) / float64(u.ExpNeededForNextLevel()) * 100 } currentLevelExp := exp.Calc(u.Level) nextLevelExp := u.ExpNeededForNextLevel() progressExp := u.Exp return float64(progressExp) / float64(nextLevelExp-currentLevelExp) * 100 }