package users import ( "fmt" "slices" "time" "dk/internal/database" "dk/internal/helpers" "dk/internal/helpers/scanner" "zombiezen.com/go/sqlite" ) // User represents a user in the database type User struct { database.BaseModel ID int `db:"id" json:"id"` Username string `db:"username" json:"username"` Password string `db:"password" json:"password"` Email string `db:"email" json:"email"` Verified int `db:"verified" json:"verified"` Token string `db:"token" json:"token"` Registered int64 `db:"registered" json:"registered"` LastOnline int64 `db:"last_online" json:"last_online"` Auth int `db:"auth" json:"auth"` X int `db:"x" json:"x"` Y int `db:"y" json:"y"` ClassID int `db:"class_id" json:"class_id"` Currently string `db:"currently" json:"currently"` Fighting int `db:"fighting" json:"fighting"` MonsterID int `db:"monster_id" json:"monster_id"` MonsterHP int `db:"monster_hp" json:"monster_hp"` MonsterSleep int `db:"monster_sleep" json:"monster_sleep"` MonsterImmune int `db:"monster_immune" json:"monster_immune"` UberDamage int `db:"uber_damage" json:"uber_damage"` UberDefense int `db:"uber_defense" json:"uber_defense"` HP int `db:"hp" json:"hp"` MP int `db:"mp" json:"mp"` TP int `db:"tp" json:"tp"` MaxHP int `db:"max_hp" json:"max_hp"` MaxMP int `db:"max_mp" json:"max_mp"` MaxTP int `db:"max_tp" json:"max_tp"` Level int `db:"level" json:"level"` Gold int `db:"gold" json:"gold"` Exp int `db:"exp" json:"exp"` GoldBonus int `db:"gold_bonus" json:"gold_bonus"` ExpBonus int `db:"exp_bonus" json:"exp_bonus"` Strength int `db:"strength" json:"strength"` Dexterity int `db:"dexterity" json:"dexterity"` Attack int `db:"attack" json:"attack"` Defense int `db:"defense" json:"defense"` WeaponID int `db:"weapon_id" json:"weapon_id"` ArmorID int `db:"armor_id" json:"armor_id"` ShieldID int `db:"shield_id" json:"shield_id"` Slot1ID int `db:"slot_1_id" json:"slot_1_id"` Slot2ID int `db:"slot_2_id" json:"slot_2_id"` Slot3ID int `db:"slot_3_id" json:"slot_3_id"` WeaponName string `db:"weapon_name" json:"weapon_name"` ArmorName string `db:"armor_name" json:"armor_name"` ShieldName string `db:"shield_name" json:"shield_name"` Slot1Name string `db:"slot_1_name" json:"slot_1_name"` Slot2Name string `db:"slot_2_name" json:"slot_2_name"` Slot3Name string `db:"slot_3_name" json:"slot_3_name"` DropCode int `db:"drop_code" json:"drop_code"` Spells string `db:"spells" json:"spells"` Towns string `db:"towns" json:"towns"` } func (u *User) GetTableName() string { return "users" } func (u *User) GetID() int { return u.ID } func (u *User) SetID(id int) { u.ID = id } func (u *User) Set(field string, value any) error { return database.Set(u, field, value) } func (u *User) Save() error { return database.Save(u) } func (u *User) Delete() error { return database.Delete(u) } 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: 15, MP: 0, TP: 10, MaxHP: 15, MaxMP: 0, MaxTP: 10, Level: 1, Gold: 100, Exp: 0, Strength: 5, Dexterity: 5, Attack: 5, Defense: 5, Spells: "", Towns: "", } } var userScanner = scanner.New[User]() func userColumns() string { return userScanner.Columns() } func scanUser(stmt *sqlite.Stmt) *User { user := &User{} userScanner.Scan(stmt, user) return user } func Find(id int) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE id = ?` err := database.Query(query, func(stmt *sqlite.Stmt) error { user = scanUser(stmt) return nil }, id) if err != nil { return nil, fmt.Errorf("failed to find user: %w", err) } if user == nil { return nil, fmt.Errorf("user with ID %d not found", id) } return user, nil } func All() ([]*User, error) { var users []*User query := `SELECT ` + userColumns() + ` FROM users ORDER BY registered DESC, id DESC` err := database.Query(query, func(stmt *sqlite.Stmt) error { user := scanUser(stmt) users = append(users, user) return nil }) if err != nil { return nil, fmt.Errorf("failed to retrieve all users: %w", err) } return users, nil } func ByUsername(username string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE LOWER(username) = LOWER(?) LIMIT 1` err := database.Query(query, func(stmt *sqlite.Stmt) error { user = scanUser(stmt) return nil }, username) if err != nil { return nil, fmt.Errorf("failed to find user by username: %w", err) } if user == nil { return nil, fmt.Errorf("user with username '%s' not found", username) } return user, nil } func ByEmail(email string) (*User, error) { var user *User query := `SELECT ` + userColumns() + ` FROM users WHERE email = ? LIMIT 1` err := database.Query(query, func(stmt *sqlite.Stmt) error { user = scanUser(stmt) return nil }, email) if err != nil { return nil, fmt.Errorf("failed to find user by email: %w", err) } if user == nil { return nil, fmt.Errorf("user with email '%s' not found", email) } return user, nil } func ByLevel(level int) ([]*User, error) { var users []*User query := `SELECT ` + userColumns() + ` FROM users WHERE level = ? ORDER BY exp DESC, id ASC` err := database.Query(query, func(stmt *sqlite.Stmt) error { user := scanUser(stmt) users = append(users, user) return nil }, level) if err != nil { return nil, fmt.Errorf("failed to retrieve users by level: %w", err) } return users, nil } func Online(within time.Duration) ([]*User, error) { var users []*User cutoff := time.Now().Add(-within).Unix() query := `SELECT ` + userColumns() + ` FROM users WHERE last_online >= ? ORDER BY last_online DESC, id ASC` err := database.Query(query, func(stmt *sqlite.Stmt) error { user := scanUser(stmt) users = append(users, user) return nil }, cutoff) if err != nil { return nil, fmt.Errorf("failed to retrieve online users: %w", err) } return users, nil } func (u *User) Insert() error { columns := `username, password, email, verified, token, registered, last_online, auth, x, y, class_id, currently, fighting, monster_id, monster_hp, monster_sleep, monster_immune, uber_damage, uber_defense, hp, mp, tp, max_hp, max_mp, max_tp, level, gold, exp, gold_bonus, exp_bonus, strength, dexterity, attack, defense, weapon_id, armor_id, shield_id, slot_1_id, slot_2_id, slot_3_id, weapon_name, armor_name, shield_name, slot_1_name, slot_2_name, slot_3_name, drop_code, spells, towns` values := []any{u.Username, u.Password, u.Email, u.Verified, u.Token, u.Registered, u.LastOnline, u.Auth, u.X, u.Y, u.ClassID, u.Currently, u.Fighting, u.MonsterID, u.MonsterHP, u.MonsterSleep, u.MonsterImmune, u.UberDamage, u.UberDefense, u.HP, u.MP, u.TP, u.MaxHP, u.MaxMP, u.MaxTP, u.Level, u.Gold, u.Exp, u.GoldBonus, u.ExpBonus, u.Strength, u.Dexterity, u.Attack, u.Defense, u.WeaponID, u.ArmorID, u.ShieldID, u.Slot1ID, u.Slot2ID, u.Slot3ID, u.WeaponName, u.ArmorName, u.ShieldName, u.Slot1Name, u.Slot2Name, u.Slot3Name, u.DropCode, u.Spells, u.Towns} return database.Insert(u, columns, values...) } 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.Set("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.Set("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.Set("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.Set("X", x) u.Set("Y", y) }