2023-03-22 05:30:58 +00:00
package db
import (
"database/sql"
2023-12-02 01:56:23 +00:00
"github.com/CPunch/gopenfusion/cnet/protocol"
2023-12-03 04:09:11 +00:00
"github.com/CPunch/gopenfusion/internal/config"
2023-03-22 05:30:58 +00:00
"github.com/blockloop/scan"
)
2023-11-28 03:23:28 +00:00
type Player struct {
PlayerID int
AccountID int
AccountLevel int
Slot int
PCStyle protocol . SPCStyle
PCStyle2 protocol . SPCStyle2
EquippedNanos [ 3 ] int
Nanos [ config . NANO_COUNT ] protocol . SNano
Equip [ config . AEQUIP_COUNT ] protocol . SItemBase
Inven [ config . AINVEN_COUNT ] protocol . SItemBase
Bank [ config . ABANK_COUNT ] protocol . SItemBase
SkywayLocationFlag [ ] byte
FirstUseFlag [ ] byte
Quests [ ] byte
HP int
Level int
Taros int
FusionMatter int
Mentor int
X , Y , Z int
Angle int
BatteryN int
BatteryW int
WarpLocationFlag int
ActiveNanoSlotNum int
Fatigue int
CurrentMissionID int
IPCState int8
}
2023-03-22 05:30:58 +00:00
// returns PlayerID, error
func ( db * DBHandler ) NewPlayer ( AccountID int , FirstName , LastName string , slot int ) ( int , error ) {
nameCheck := 1 // for now, we approve all names
QuestFlag := make ( [ ] byte , 128 )
SkywayLocationFlag := make ( [ ] byte , 16 )
FirstUseFlag := make ( [ ] byte , 16 )
var PlayerID int
if err := db . Transaction ( func ( tx * sql . Tx ) error {
// create player
2023-06-22 06:53:38 +00:00
rows , err := tx . Query (
"INSERT INTO Players (AccountID, Slot, FirstName, LastName, XCoordinate, YCoordinate, ZCoordinate, Angle, HP, NameCheck, Quests, SkywayLocationFlag, FirstUseFlag) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING PlayerID" ,
2023-03-22 05:30:58 +00:00
AccountID , slot , FirstName , LastName , config . SPAWN_X , config . SPAWN_Y , config . SPAWN_Z , 0 , config . GetMaxHP ( 1 ) , nameCheck , QuestFlag , SkywayLocationFlag , FirstUseFlag )
if err != nil {
return err
}
2023-06-22 06:53:38 +00:00
if err := scan . Row ( & PlayerID , rows ) ; err != nil {
2023-03-22 05:30:58 +00:00
return err
}
// create appearance
2023-06-22 06:53:38 +00:00
if _ , err := tx . Exec ( "INSERT INTO Appearances (PlayerID) VALUES ($1)" , PlayerID ) ; err != nil {
2023-03-22 05:30:58 +00:00
return err
}
return nil
} ) ; err != nil {
return - 1 , nil
}
return PlayerID , nil
}
// TODO: should this operate on the raw packet? should we do validation here or prior?
func ( db * DBHandler ) FinishPlayer ( character * protocol . SP_CL2LS_REQ_CHAR_CREATE , AccountId int ) error {
return db . Transaction ( func ( tx * sql . Tx ) error {
// update AppearanceFlag
2023-06-22 06:53:38 +00:00
_ , err := tx . Exec ( "UPDATE Players SET AppearanceFlag = 1 WHERE PlayerID = $1 AND AccountID = $2 AND AppearanceFlag = 0" , character . PCStyle . IPC_UID , AccountId )
2023-03-22 05:30:58 +00:00
if err != nil {
return err
}
// update Appearance
2023-06-22 06:53:38 +00:00
_ , err = tx . Exec ( "UPDATE Appearances SET Body = $1, EyeColor = $2, FaceStyle = $3, Gender = $4, HairColor = $5, HairStyle = $6, Height = $7, SkinColor = $8 WHERE PlayerID = $9" ,
2023-03-22 05:30:58 +00:00
character . PCStyle . IBody ,
character . PCStyle . IEyeColor ,
character . PCStyle . IFaceStyle ,
character . PCStyle . IGender ,
character . PCStyle . IHairColor ,
character . PCStyle . IHairStyle ,
character . PCStyle . IHeight ,
character . PCStyle . ISkinColor ,
character . PCStyle . IPC_UID )
if err != nil {
return err
}
// update Inventory
items := [ 3 ] int16 { character . SOn_Item . IEquipUBID , character . SOn_Item . IEquipLBID , character . SOn_Item . IEquipFootID }
for i := 0 ; i < len ( items ) ; i ++ {
2023-07-02 22:24:15 +00:00
_ , err = tx . Exec ( "INSERT INTO Inventory (PlayerID, Slot, ID, Type, Opt) VALUES ($1, $2, $3, $4, 1)" , character . PCStyle . IPC_UID , i + 1 , items [ i ] , i + 1 )
2023-03-22 05:30:58 +00:00
if err != nil {
return err
}
}
return nil
} )
}
func ( db * DBHandler ) FinishTutorial ( PlayerID , AccountID int ) error {
2023-06-22 06:53:38 +00:00
_ , err := db . Exec ( "UPDATE Players SET TutorialFlag = 1 WHERE PlayerID = $1 AND AccountID = $2 AND TutorialFlag = 0" , PlayerID , AccountID )
2023-03-22 05:30:58 +00:00
if err != nil {
return err
}
// TODO: reference openfusion's finishTutorial for their academy specific patches
return nil
}
// returns the deleted Slot number
func ( db * DBHandler ) DeletePlayer ( PlayerID , AccountID int ) ( int , error ) {
2023-06-22 06:53:38 +00:00
row , err := db . Query ( "DELETE FROM Players WHERE AccountID = $1 AND PlayerID = $2 RETURNING Slot" )
2023-03-22 05:30:58 +00:00
if err != nil {
return - 1 , err
}
var slot int
2023-06-22 06:53:38 +00:00
if err := row . Scan ( & slot ) ; err != nil {
return - 1 , err
2023-03-22 05:30:58 +00:00
}
return slot , nil
}
const (
QUERY_PLAYERS = ` SELECT
p . PlayerID , p . AccountID , p . Slot , p . FirstName , p . LastName ,
p . Level , p . Nano1 , p . Nano2 , p . Nano3 ,
p . AppearanceFlag , p . TutorialFlag , p . PayZoneFlag ,
p . XCoordinate , p . YCoordinate , p . ZCoordinate , p . NameCheck ,
p . Angle , p . HP , p . FusionMatter , p . Taros , p . Quests ,
p . BatteryW , p . BatteryN , p . Mentor , p . WarpLocationFlag ,
p . SkywayLocationFlag , p . CurrentMissionID , p . FirstUseFlag ,
a . Body , a . EyeColor , a . FaceStyle , a . Gender , a . HairColor , a . HairStyle ,
a . Height , a . SkinColor , acc . AccountLevel
FROM Players as p
INNER JOIN Appearances as a ON p . PlayerID = a . PlayerID
INNER JOIN Accounts as acc ON p . AccountID = acc . AccountID `
)
2023-11-28 03:23:28 +00:00
func ( db * DBHandler ) readPlayer ( rows * sql . Rows ) ( * Player , error ) {
plr := Player { ActiveNanoSlotNum : 0 }
2023-03-22 05:30:58 +00:00
if err := rows . Scan (
& plr . PlayerID , & plr . AccountID , & plr . Slot , & plr . PCStyle . SzFirstName , & plr . PCStyle . SzLastName ,
& plr . Level , & plr . EquippedNanos [ 0 ] , & plr . EquippedNanos [ 1 ] , & plr . EquippedNanos [ 2 ] ,
& plr . PCStyle2 . IAppearanceFlag , & plr . PCStyle2 . ITutorialFlag , & plr . PCStyle2 . IPayzoneFlag ,
& plr . X , & plr . Y , & plr . Z , & plr . PCStyle . INameCheck ,
& plr . Angle , & plr . HP , & plr . FusionMatter , & plr . Taros , & plr . Quests ,
& plr . BatteryW , & plr . BatteryN , & plr . Mentor , & plr . WarpLocationFlag ,
& plr . SkywayLocationFlag , & plr . CurrentMissionID , & plr . FirstUseFlag ,
& plr . PCStyle . IBody , & plr . PCStyle . IEyeColor , & plr . PCStyle . IFaceStyle , & plr . PCStyle . IGender , & plr . PCStyle . IHairColor , & plr . PCStyle . IHairStyle ,
& plr . PCStyle . IHeight , & plr . PCStyle . ISkinColor , & plr . AccountLevel ) ; err != nil {
return nil , err
}
plr . PCStyle . IPC_UID = int64 ( plr . PlayerID )
inven , err := db . GetPlayerInventorySlots ( plr . PlayerID , 0 , config . AEQUIP_COUNT + config . AINVEN_COUNT + config . ABANK_COUNT )
if err != nil {
return nil , err
}
// populate player inven
for i , item := range inven {
if item . IID == 0 { // skip empty slots
continue
}
switch {
case i < config . AEQUIP_COUNT :
plr . Equip [ i ] = item
case i < config . AEQUIP_COUNT + config . AINVEN_COUNT :
plr . Inven [ i - config . AEQUIP_COUNT ] = item
default :
plr . Bank [ i - config . AEQUIP_COUNT - config . AINVEN_COUNT ] = item
}
}
return & plr , nil
}
2023-11-28 03:23:28 +00:00
func ( db * DBHandler ) GetPlayer ( PlayerID int ) ( * Player , error ) {
2023-06-22 06:53:38 +00:00
rows , err := db . Query ( QUERY_PLAYERS + "WHERE p.PlayerID = $1" , PlayerID )
2023-03-22 05:30:58 +00:00
if err != nil {
return nil , err
}
2023-11-28 03:23:28 +00:00
var plr * Player
2023-03-22 05:30:58 +00:00
for rows . Next ( ) {
plr , err = db . readPlayer ( rows )
if err != nil {
return nil , err
}
}
return plr , nil
}
2023-11-28 03:23:28 +00:00
func ( db * DBHandler ) GetPlayers ( AccountID int ) ( [ ] Player , error ) {
2023-06-22 06:53:38 +00:00
rows , err := db . Query ( QUERY_PLAYERS + "WHERE p.AccountID = $1" , AccountID )
2023-03-22 05:30:58 +00:00
if err != nil {
return nil , err
}
2023-11-28 03:23:28 +00:00
var plrs [ ] Player
2023-03-22 05:30:58 +00:00
for rows . Next ( ) {
plr , err := db . readPlayer ( rows )
if err != nil {
return nil , err
}
plrs = append ( plrs , * plr )
}
return plrs , nil
}