func (s *sqlService) PlayerLoad(player *world.Player) bool {
	loadProfile := func() error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT player.id, player.x, player.y, player.group_id, appearance.haircolour, appearance.topcolour, appearance.trousercolour, appearance.skincolour, appearance.head, appearance.body FROM player INNER JOIN appearance ON appearance.playerid=player.id AND player.userhash=$1", player.UsernameHash())
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		if !rows.Next() {
			return errors.NewDatabaseError("Could not find player")
		}
		var x, y, rank int
		rows.Scan(&player.DatabaseIndex, &x, &y, &rank, &player.Appearance.HeadColor, &player.Appearance.BodyColor, &player.Appearance.LegsColor, &player.Appearance.SkinColor, &player.Appearance.Head, &player.Appearance.Body)
		player.SetVar("rank", rank)
		player.Equips()[0] = player.Appearance.Head
		player.Equips()[1] = player.Appearance.Body
		player.SetX(x)
		player.SetY(y)
		return nil
	}
	loadAttributes := func() error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT name, value FROM player_attr WHERE player_id=$1", player.DatabaseIndex)
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		for rows.Next() {
			var name, value string
			rows.Scan(&name, &value)
			switch value[0] {
			case 'i':
				val, err := strconv.ParseInt(value[1:], 10, 64)
				if err != nil {
					log.Info.Printf("Error loading int attribute[%v]: value=%v\n", name, value[1:])
					log.Info.Println(err)
				}
				player.Attributes.SetVar(name, int(val))
				break
			case 'l':
				val, err := strconv.ParseUint(value[1:], 10, 64)
				if err != nil {
					log.Info.Printf("Error loading long int attribute[%v]: value=%v\n", name, value[1:])
					log.Info.Println(err)
				}
				player.Attributes.SetVar(name, uint(val))
				break
			case 'b':
				val, err := strconv.ParseBool(value[1:])
				if err != nil {
					log.Info.Printf("Error loading boolean attribute[%v]: value=%v\n", name, value[1:])
					log.Info.Println(err)
				}
				player.Attributes.SetVar(name, val)
				break
			case 's':
				player.Attributes.SetVar(name, value[1:])
				break
			case 'd':
				t, err := time.ParseDuration(value[1:])
				if err != nil {
					continue
				}
				player.Attributes.SetVar(name, time.Now().Add(t))
			case 't':
				t, err := time.ParseInLocation(time.RFC822, value[1:], time.Local)
				if err != nil {
					continue
				}
				player.Attributes.SetVar(name, t)
			}
		}
		return nil
	}
	loadContactList := func(list string) error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT playerhash FROM contacts WHERE playerid=$1 AND type=$2", player.DatabaseIndex, list)
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		for rows.Next() {
			var hash uint64
			rows.Scan(&hash)
			switch list {
			case "friend":
				player.FriendList.Add(strutil.Base37.Decode(hash))
				if p1, ok := world.Players.FindHash(hash); ok && p1 != nil &&
					(!p1.ChatBlocked() || p1.FriendList.ContainsHash(hash)) {
					player.FriendList.ToggleStatus(strutil.Base37.Decode(hash))
				}
			case "ignore":
				player.IgnoreList = append(player.IgnoreList, hash)
			}
		}
		return nil
	}
	loadInventory := func() error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT itemid, amount, wielded FROM inventory WHERE playerid=$1", player.DatabaseIndex)
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		for rows.Next() {
			var id, amt int
			wielded := false
			rows.Scan(&id, &amt, &wielded)
			index := player.Inventory.Add(id, amt)
			if e := definitions.Equip(id); e != nil && wielded {
				player.Inventory.Get(index).Worn = true
				player.Equips()[e.Position] = e.Sprite
				player.SetAimPoints(player.AimPoints() + e.Aim)
				player.SetPowerPoints(player.PowerPoints() + e.Power)
				player.SetArmourPoints(player.ArmourPoints() + e.Armour)
				player.SetMagicPoints(player.MagicPoints() + e.Magic)
				player.SetPrayerPoints(player.PrayerPoints() + e.Prayer)
				player.SetRangedPoints(player.RangedPoints() + e.Ranged)
			}
		}
		return nil
	}
	loadBank := func() error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT itemid, amount FROM bank WHERE playerid=$1", player.DatabaseIndex)
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		for rows.Next() {
			var id, amt int
			rows.Scan(&id, &amt)
			player.Bank().Add(id, amt)
		}
		return nil
	}
	loadStats := func() error {
		database := s.connect(context.Background())
		rows, err := database.QueryContext(context.Background(), "SELECT cur, exp FROM stats WHERE playerid=$1 ORDER BY num", player.DatabaseIndex)
		if err != nil {
			log.Info.Println("Load error: Could not prepare statement:", err)
			return errors.NewDatabaseError(err.Error())
		}
		defer rows.Close()
		i := 0
		for rows.Next() {
			var cur, exp int
			rows.Scan(&cur, &exp)
			player.Skills().SetCur(i, cur)
			player.Skills().SetMax(i, entity.ExperienceToLevel(exp))
			player.Skills().SetExp(i, exp)
			i++
		}
		return nil
	}
	if err := loadProfile(); err != nil {
		return false
	}
	if err := loadAttributes(); err != nil {
		return false
	}
	if err := loadContactList("friend"); err != nil {
		return false
	}
	if err := loadContactList("ignore"); err != nil {
		return false
	}
	if err := loadInventory(); err != nil {
		return false
	}
	if err := loadBank(); err != nil {
		return false
	}
	if err := loadStats(); err != nil {
		return false
	}
	s.PlayerUpdateStatus(player.DatabaseIndex, true)
	return true
}
PlayerLoad Loads a player from the SQLite3 database, returns a login response code.
Returns: true on success, false on failure
package db
import (
	"context"
	"strconv"
	"strings"
	"time"
	"github.com/spkaeros/rscgo/pkg/definitions"
	"github.com/spkaeros/rscgo/pkg/game/entity"
	"github.com/spkaeros/rscgo/pkg/config"
	"github.com/spkaeros/rscgo/pkg/errors"
	"github.com/spkaeros/rscgo/pkg/game/world"
	"github.com/spkaeros/rscgo/pkg/log"
	"github.com/spkaeros/rscgo/pkg/strutil"
)
type PlayerService interface {
	PlayerCreate(string, string, string) bool
	PlayerNameExists(username string) bool
	PlayerHasRecoverys(uint64) bool
	PlayerValidLogin(uint64, string) bool
	PlayerChangePassword(uint64, string) bool
	PlayerLoadRecoverys(uint64) []string
	PlayerLoad(*world.Player) bool
	PlayerSave(*world.Player)
	OnlineCount() int
}
func NewPlayerServiceSql() PlayerService {
	s := newSqlService(config.PlayerDriver())
	s.sqlOpen(config.PlayerDB())
	return dbConn
}
var DefaultPlayerService PlayerService
func (s *sqlService) PlayerCreate(username, password, ip string) bool {
	db := s.connect(context.Background())
	tx, err := db.BeginTx(context.Background(), nil)
	if err != nil {
		log.Info.Println("SQLiteService Could not begin transaction:", err)
		return false
	}
	var playerID int
	if config.PlayerDriver() != "postgres" {
		stmt, err := tx.Exec("INSERT INTO player(username, userhash, password, x, y, group_id) VALUES($1, $2, $3, 220, 445, 0)", username, strutil.Base37.Encode(username), password)
		if err != nil {
			log.Info.Println("SQLiteService Could not insert new player profile information:", err)
			return false
		}
		pID, err := stmt.LastInsertId()
		if err != nil || playerID < 0 {
			tx.Rollback()
			log.Info.Printf("PlayerCreate(): Could not retrieve player database ID(%d):\n%v", playerID, err)
			return false
		}
		playerID = int(pID)
	} else {
		stmt := tx.QueryRow("INSERT INTO player(username, userhash, password, x, y, group_id) VALUES($1, $2, $3, 220, 445, 0) RETURNING id", username, strutil.Base37.Encode(username), password)
		err = stmt.Scan(&playerID)
		if err != nil || playerID < 0 {
			tx.Rollback()
			log.Info.Printf("PlayerCreate(): Could not retrieve player database ID(%d):\n%v", playerID, err)
			return false
		}
	}
	_, err = tx.Exec("INSERT INTO appearance VALUES($1, 2, 8, 14, 0, 1, 2)", playerID)
	if err != nil {
		log.Info.Println("PlayerCreate(): Could not insert new player profile information:", err)
		return false
	}
	_, err = tx.Exec("INSERT INTO player_attr VALUES($1, 'lastIP', $2)", playerID, "s"+ip)
	if err != nil {
		log.Info.Println("PlayerCreate(): Could not insert new player profile information:", err)
		return false
	}
	_, err = tx.Exec("INSERT INTO stats (playerid, num, cur, exp) VALUES ($1, 0, 1, 0), ($1, 1, 1, 0), "+
		"($1, 2, 1, 0), ($1, 3, 10, 1156), ($1, 4, 1, 0), ($1, 5, 1, 0), ($1, 6, 1, 0), ($1, 7, 1, 0), ($1, 8, 1, 0), "+
		"($1, 9, 1, 0), ($1, 10, 1, 0), ($1, 11, 1, 0), ($1, 12, 1, 0), ($1, 13, 1, 0), ($1, 14, 1, 0), ($1, 15, 1, 0), "+
		"($1, 16, 1, 0), ($1, 17, 1, 0)", playerID)
	if err != nil {
		log.Info.Println("PlayerCreate(): Could not insert new player profile information:", err)
		return false
	}
	_, err = tx.Exec("INSERT INTO bank (playerid, itemid, amount) VALUES ($1, 546, 96000), ($1, 373, 96000)", playerID)
	if err != nil {
		log.Info.Println("PlayerCreate(): Could not insert new player profile information:", err)
		return false
	}
	_, err = tx.Exec("INSERT INTO inventory (playerid, itemid, amount) VALUES ($1, 1263, 1), ($1, 77, 1), ($1, 71, 1), "+
		"($1, 6, 1), ($1, 7, 1), ($1, 8, 1), ($1, 9, 1), ($1, 316, 1), ($1, 198, 1), ($1, 185, 1), ($1, 184, 1), "+
		"($1, 187, 1), ($1, 35, 100), ($1, 33, 100), ($1, 36, 100), ($1, 188, 1), ($1, 189, 1), ($1, 11, 100)", playerID)
	if err != nil {
		log.Info.Println("PlayerCreate(): Could not insert new player profile information:", err)
		return false
	}
	if err := tx.Commit(); err != nil {
		log.Warning.Println("PlayerCreate(): Error committing transaction for new player:", err)
		return false
	}
	return true
}
func (s *sqlService) PlayerNameExists(username string) bool {
	database := s.connect(context.Background())
	stmt, err := database.QueryContext(context.Background(), "SELECT id FROM player WHERE userhash=$1", strutil.Base37.Encode(username))
	if err != nil {
		log.Info.Println("UsernameTaken: Could not query player profile information:", err)
		return true
	}
	defer stmt.Close()
	return stmt.Next()
}
func (s *sqlService) OnlineCount() int {
	database := s.connect(context.Background())
	stmt, err := database.QueryContext(context.Background(), "SELECT COUNT(id) FROM player WHERE loggedIn=TRUE")
	if err != nil {
		log.Info.Println("UsernameTaken: Could not query player profile information:", err)
		return -1
	}
	defer stmt.Close()
	if stmt.Next() {
		var online int
		stmt.Scan(&online)
		return online
	}
	return 0
}
func (s *sqlService) PlayerUpdateStatus(id int, in bool) {
	database := s.connect(context.Background())
	result, err := database.ExecContext(context.Background(), "UPDATE player SET loggedIn=$1 WHERE id=$2", in, id)
	if err != nil {
		log.Info.Println("Load error: Could not prepare statement:", err)
		return
	}
	count, err := result.RowsAffected()
	if count <= 0 || err != nil {
		log.Info.Println("PlayerUpdateStatus: Could not update player status:", err)
		return
	}
	return
}
func (s *sqlService) PlayerValidLogin(userHash uint64, password string) bool {
	database := s.connect(context.Background())
	rows, err := database.QueryContext(context.Background(), "SELECT id FROM player WHERE userhash=$1 AND password=$2", userHash, password)
	if err != nil {
		log.Info.Println("Validate: Could not validate user credentials:", err)
		return false
	}
	defer rows.Close()
	return rows.Next()
}
func (s *sqlService) PlayerChangePassword(userHash uint64, password string) bool {
	database := s.connect(context.Background())
	stmt, err := database.ExecContext(context.Background(), "UPDATE player SET password=$1 WHERE userhash=$2", password, userHash)
	if err != nil {
		log.Info.Println("PlayerChangePassword: Could not update player password:", err)
		return false
	}
	count, err := stmt.RowsAffected()
	if count <= 0 || err != nil {
		log.Info.Println("PlayerChangePassword: Could not update player password:", err)
		return false
	}
	return true
}
func (s *sqlService) PlayerHasRecoverys(userHash uint64) bool {
	database := s.connect(context.Background())
	rows, err := database.QueryContext(context.Background(), "SELECT question1 FROM recovery_questions WHERE userhash=$1", userHash)
	if err != nil {
		log.Info.Println("PlayerHasRecoverys: Could not search for recovery questions:", err)
		return false
	}
	defer rows.Close()
	return rows.Next()
}
func (s *sqlService) PlayerLoadRecoverys(userHash uint64) []string {
	database := s.connect(context.Background())
	rows, err := database.QueryContext(context.Background(), "SELECT question1, question2, question3, question4, question5 FROM recovery_questions WHERE userhash=$1", userHash)
	if err != nil {
		log.Info.Println("PlayerLoadRecoverys: Could not find recovery questions:", err)
		return nil
	}
	defer rows.Close()
	var question1, question2, question3, question4, question5 string
	if rows.Next() {
		err := rows.Scan(&question1, &question2, &question3, &question4, &question5)
		if err != nil {
			log.Info.Println("PlayerLoadRecoverys: Could not scan recovery questions to variables:", err)
			return nil
		}
		return []string{question1, question2, question3, question4, question5}
	}
	return nil
}
func (s *sqlService) SaveRecoveryQuestions(userHash uint64, questions []string, answers []uint64) {
}