mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 15:43:49 +01:00
refactor(channelserver): migrate remaining character queries to CharacterRepository
Add 18 new typed methods to CharacterRepository (ReadTime, SaveTime, SaveInt, SaveBool, SaveString, ReadBool, ReadString, LoadColumnWithDefault, SetDeleted, UpdateDailyCafe, ResetDailyQuests, ReadEtcPoints, ResetCafeTime, UpdateGuildPostChecked, ReadGuildPostChecked, SaveMercenary, UpdateGCPAndPact, FindByRastaID) and migrate ~56 inline SQL queries across 13 handler files. Pure refactor — zero behavior change. Each handler produces identical SQL with identical parameters. Cross-table JOINs and bulk CharacterSaveData operations are intentionally left out of scope.
This commit is contained in:
@@ -43,8 +43,7 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
// get time after which daily claiming would be valid from db
|
||||
var dailyTime time.Time
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
|
||||
dailyTime, err := s.server.charRepo.ReadTime(s.charID, "daily_time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err))
|
||||
}
|
||||
@@ -56,7 +55,7 @@ func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
bondBonus = 5 // Bond point bonus quests
|
||||
bonusQuests = s.server.erupeConfig.GameplayOptions.BonusQuestAllowance
|
||||
dailyQuests = s.server.erupeConfig.GameplayOptions.DailyQuestAllowance
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID); err != nil {
|
||||
if err := s.server.charRepo.UpdateDailyCafe(s.charID, midday, bonusQuests, dailyQuests); err != nil {
|
||||
s.logger.Error("Failed to update daily cafe data", zap.Error(err))
|
||||
}
|
||||
bf.WriteBool(true) // Success?
|
||||
@@ -73,17 +72,16 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
var cafeReset time.Time
|
||||
err := s.server.db.QueryRow(`SELECT cafe_reset FROM characters WHERE id=$1`, s.charID).Scan(&cafeReset)
|
||||
cafeReset, err := s.server.charRepo.ReadTime(s.charID, "cafe_reset", time.Time{})
|
||||
if err != nil {
|
||||
cafeReset = TimeWeekNext()
|
||||
if _, err := s.server.db.Exec(`UPDATE characters SET cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveTime(s.charID, "cafe_reset", cafeReset); err != nil {
|
||||
s.logger.Error("Failed to set cafe reset time", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if TimeAdjusted().After(cafeReset) {
|
||||
cafeReset = TimeWeekNext()
|
||||
if _, err := s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID); err != nil {
|
||||
if err := s.server.charRepo.ResetCafeTime(s.charID, cafeReset); err != nil {
|
||||
s.logger.Error("Failed to reset cafe time", zap.Error(err))
|
||||
}
|
||||
if _, err := s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID); err != nil {
|
||||
@@ -220,7 +218,7 @@ func addPointNetcafe(s *Session, p int) error {
|
||||
return err
|
||||
}
|
||||
points = min(points+p, s.server.erupeConfig.GameplayOptions.MaximumNP)
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(s.charID, "netcafe_points", points); err != nil {
|
||||
s.logger.Error("Failed to update netcafe points", zap.Error(err))
|
||||
}
|
||||
return nil
|
||||
@@ -235,7 +233,7 @@ func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveTime(s.charID, "boost_time", boostLimit); err != nil {
|
||||
s.logger.Error("Failed to update boost time", zap.Error(err))
|
||||
}
|
||||
bf.WriteUint32(uint32(boostLimit.Unix()))
|
||||
@@ -250,8 +248,7 @@ func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var boostLimit time.Time
|
||||
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
|
||||
boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{})
|
||||
if err != nil {
|
||||
bf.WriteUint32(0)
|
||||
} else {
|
||||
@@ -263,8 +260,7 @@ func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
|
||||
var boostLimit time.Time
|
||||
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
|
||||
boostLimit, err := s.server.charRepo.ReadTime(s.charID, "boost_time", time.Time{})
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
|
||||
@@ -58,16 +58,14 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfListMember)
|
||||
|
||||
var csv string
|
||||
var count uint32
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // Blacklist count
|
||||
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
csv, err := s.server.charRepo.ReadString(s.charID, "blocked")
|
||||
if err == nil {
|
||||
cids := stringsupport.CSVElems(csv)
|
||||
for _, cid := range cids {
|
||||
var name string
|
||||
err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name)
|
||||
name, err := s.server.charRepo.GetName(uint32(cid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -84,29 +82,28 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOprMember)
|
||||
var csv string
|
||||
for _, cid := range pkt.CharIDs {
|
||||
if pkt.Blacklist {
|
||||
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
csv, err := s.server.charRepo.ReadString(s.charID, "blocked")
|
||||
if err == nil {
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(cid))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(cid))
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveString(s.charID, "blocked", csv); err != nil {
|
||||
s.logger.Error("Failed to update blocked list", zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else { // Friendlist
|
||||
err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
csv, err := s.server.charRepo.ReadString(s.charID, "friends")
|
||||
if err == nil {
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(cid))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(cid))
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveString(s.charID, "friends", csv); err != nil {
|
||||
s.logger.Error("Failed to update friends list", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +78,13 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
|
||||
_ = s.rawConn.Close()
|
||||
s.logger.Warn("Save cancelled due to corruption.")
|
||||
if s.server.erupeConfig.DeleteOnSaveCorruption {
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET deleted=true WHERE id=$1", s.charID); err != nil {
|
||||
if err := s.server.charRepo.SetDeleted(s.charID); err != nil {
|
||||
s.logger.Error("Failed to mark character as deleted", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err = s.server.db.Exec("UPDATE characters SET name=$1 WHERE id=$2", characterSaveData.Name, s.charID)
|
||||
if err != nil {
|
||||
if err := s.server.charRepo.SaveString(s.charID, "name", characterSaveData.Name); err != nil {
|
||||
s.logger.Error("Failed to update character name in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -160,8 +159,7 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumn(s.charID, "savedata")
|
||||
if err != nil || len(data) == 0 {
|
||||
s.logger.Warn(fmt.Sprintf("Failed to load savedata (CID: %d)", s.charID), zap.Error(err))
|
||||
_ = s.rawConn.Close() // Terminate the connection
|
||||
|
||||
@@ -138,8 +138,7 @@ func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem {
|
||||
}
|
||||
|
||||
func addGachaItem(s *Session, items []GachaItem) {
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data)
|
||||
data, _ := s.server.charRepo.LoadColumn(s.charID, "gacha_items")
|
||||
if len(data) > 0 {
|
||||
numItems := int(data[0])
|
||||
data = data[1:]
|
||||
@@ -159,7 +158,7 @@ func addGachaItem(s *Session, items []GachaItem) {
|
||||
newItem.WriteUint16(items[i].ItemID)
|
||||
newItem.WriteUint16(items[i].Quantity)
|
||||
}
|
||||
if _, err := s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "gacha_items", newItem.Data()); err != nil {
|
||||
s.logger.Error("Failed to update gacha items", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -193,8 +192,7 @@ func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumnWithDefault(s.charID, "gacha_items", []byte{0x00})
|
||||
if err != nil {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
@@ -214,11 +212,11 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
update := byteframe.NewByteFrame()
|
||||
update.WriteUint8(uint8(len(data[181:]) / 5))
|
||||
update.WriteBytes(data[181:])
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "gacha_items", update.Data()); err != nil {
|
||||
s.logger.Error("Failed to update gacha items overflow", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "gacha_items", nil); err != nil {
|
||||
s.logger.Error("Failed to clear gacha items", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET guild_post_checked = now() WHERE id = $1", s.charID); err != nil {
|
||||
if err := s.server.charRepo.UpdateGuildPostChecked(s.charID); err != nil {
|
||||
s.logger.Error("Failed to update guild post checked time", zap.Error(err))
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
@@ -118,9 +118,8 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
case 5: // Check for new messages
|
||||
var timeChecked time.Time
|
||||
var newPosts int
|
||||
err := s.server.db.QueryRow("SELECT guild_post_checked FROM characters WHERE id = $1", s.charID).Scan(&timeChecked)
|
||||
timeChecked, err := s.server.charRepo.ReadGuildPostChecked(s.charID)
|
||||
if err == nil {
|
||||
_ = s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND deleted = false AND (EXTRACT(epoch FROM created_at)::int) > $2", guild.ID, timeChecked.Unix()).Scan(&newPosts)
|
||||
if newPosts > 0 {
|
||||
|
||||
@@ -279,11 +279,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRejectGuildScout)
|
||||
|
||||
row := s.server.db.QueryRow("SELECT restrict_guild_scout FROM characters WHERE id=$1", s.charID)
|
||||
|
||||
var currentStatus bool
|
||||
|
||||
err := row.Scan(¤tStatus)
|
||||
currentStatus, err := s.server.charRepo.ReadBool(s.charID, "restrict_guild_scout")
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
@@ -307,7 +303,7 @@ func handleMsgMhfGetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfSetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetRejectGuildScout)
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET restrict_guild_scout=$1 WHERE id=$2", pkt.Reject, s.charID)
|
||||
err := s.server.charRepo.SaveBool(s.charID, "restrict_guild_scout", pkt.Reject)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
|
||||
@@ -71,8 +71,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
|
||||
switch pkt.Method {
|
||||
case 1:
|
||||
var friendsList string
|
||||
_ = s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&friendsList)
|
||||
friendsList, _ := s.server.charRepo.ReadString(s.charID, "friends")
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
house := HouseData{}
|
||||
@@ -179,8 +178,7 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Friends list verification
|
||||
if state == 3 || state == 5 {
|
||||
var friendsList string
|
||||
_ = s.server.db.QueryRow(`SELECT friends FROM characters WHERE id=$1`, pkt.CharID).Scan(&friendsList)
|
||||
friendsList, _ := s.server.charRepo.ReadString(pkt.CharID, "friends")
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
if uint32(cid) == s.charID {
|
||||
@@ -286,8 +284,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var temp []byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&temp)
|
||||
temp, err := s.server.charRepo.LoadColumn(s.charID, "decomyset")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load decomyset", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -333,7 +330,7 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
dumpSaveData(s, bf.Data(), "decomyset")
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", bf.Data(), s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "decomyset", bf.Data()); err != nil {
|
||||
s.logger.Error("Failed to save decomyset", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -333,7 +333,9 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
var stamps, rewardTier, rewardUnk uint16
|
||||
reward := mhfitem.MHFItemStack{Item: mhfitem.MHFItem{}}
|
||||
if err := s.server.db.QueryRow(`UPDATE characters SET stampcard = stampcard + $1 WHERE id = $2 RETURNING stampcard`, pkt.Stamps, s.charID).Scan(&stamps); err != nil {
|
||||
stamps32, err := s.server.charRepo.AdjustInt(s.charID, "stampcard", int(pkt.Stamps))
|
||||
stamps = uint16(stamps32)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update stampcard", zap.Error(err))
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
|
||||
@@ -77,9 +77,8 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||
naviLength = hunterNaviSizeG7
|
||||
}
|
||||
var data []byte
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumn(s.charID, "hunternavi")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load hunternavi",
|
||||
zap.Error(err),
|
||||
@@ -102,7 +101,7 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
|
||||
dataSize = len(saveOutput)
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
err = s.server.charRepo.SaveColumn(s.charID, "hunternavi", saveOutput)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save hunternavi",
|
||||
zap.Error(err),
|
||||
@@ -117,7 +116,7 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
dataSize = len(pkt.RawDataPayload)
|
||||
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
err := s.server.charRepo.SaveColumn(s.charID, "hunternavi", pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save hunternavi",
|
||||
zap.Error(err),
|
||||
@@ -175,7 +174,7 @@ func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(s.charID, "rasta_id", int(nextID)); err != nil {
|
||||
s.logger.Error("Failed to set rasta ID", zap.Error(err))
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
@@ -195,11 +194,11 @@ func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
dumpSaveData(s, pkt.MercData, "mercenary")
|
||||
if len(pkt.MercData) >= 4 {
|
||||
temp := byteframe.NewByteFrameFromBytes(pkt.MercData)
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", pkt.MercData, temp.ReadUint32(), s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveMercenary(s.charID, pkt.MercData, temp.ReadUint32()); err != nil {
|
||||
s.logger.Error("Failed to save mercenary data", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", pkt.GCP, pkt.PactMercID, s.charID); err != nil {
|
||||
if err := s.server.charRepo.UpdateGCPAndPact(s.charID, pkt.GCP, pkt.PactMercID); err != nil {
|
||||
s.logger.Error("Failed to update GCP and pact ID", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
@@ -209,11 +208,11 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
pactID, _ := readCharacterInt(s, "pact_id")
|
||||
var cid uint32
|
||||
var name string
|
||||
pactID, _ := readCharacterInt(s, "pact_id")
|
||||
if pactID > 0 {
|
||||
_ = s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
|
||||
cid, name, _ = s.server.charRepo.FindByRastaID(pactID)
|
||||
bf.WriteUint8(1) // numLends
|
||||
bf.WriteUint32(uint32(pactID))
|
||||
bf.WriteUint32(cid)
|
||||
@@ -249,8 +248,7 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteBytes(temp.Data())
|
||||
|
||||
if pkt.Op != 1 && pkt.Op != 4 {
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
|
||||
data, _ := s.server.charRepo.LoadColumn(s.charID, "savemercenary")
|
||||
gcp, _ := readCharacterInt(s, "gcp")
|
||||
|
||||
if len(data) == 0 {
|
||||
@@ -268,8 +266,7 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
|
||||
data, _ := s.server.charRepo.LoadColumn(pkt.CharID, "savemercenary")
|
||||
resp := byteframe.NewByteFrame()
|
||||
if len(data) == 0 {
|
||||
resp.WriteBool(false)
|
||||
@@ -283,15 +280,15 @@ func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfContractMercenary)
|
||||
switch pkt.Op {
|
||||
case 0: // Form loan
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, pkt.CID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(pkt.CID, "pact_id", int(pkt.PactMercID)); err != nil {
|
||||
s.logger.Error("Failed to form mercenary loan", zap.Error(err))
|
||||
}
|
||||
case 1: // Cancel lend
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(s.charID, "pact_id", 0); err != nil {
|
||||
s.logger.Error("Failed to cancel mercenary lend", zap.Error(err))
|
||||
}
|
||||
case 2: // Cancel loan
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", pkt.CID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(pkt.CID, "pact_id", 0); err != nil {
|
||||
s.logger.Error("Failed to cancel mercenary loan", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -350,7 +347,7 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Error("Failed to compress airou", zap.Error(err))
|
||||
} else {
|
||||
comp = append([]byte{0x01}, comp...)
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET otomoairou=$1 WHERE id=$2", comp, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "otomoairou", comp); err != nil {
|
||||
s.logger.Error("Failed to save otomoairou", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,16 +13,15 @@ import (
|
||||
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetEtcPoints)
|
||||
|
||||
var dailyTime time.Time
|
||||
_ = s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
|
||||
dailyTime, _ := s.server.charRepo.ReadTime(s.charID, "daily_time", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
if TimeAdjusted().After(dailyTime) {
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET bonus_quests = 0, daily_quests = 0 WHERE id=$1", s.charID); err != nil {
|
||||
if err := s.server.charRepo.ResetDailyQuests(s.charID); err != nil {
|
||||
s.logger.Error("Failed to reset daily quests", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
var bonusQuests, dailyQuests, promoPoints uint32
|
||||
if err := s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints); err != nil {
|
||||
bonusQuests, dailyQuests, promoPoints, err := s.server.charRepo.ReadEtcPoints(s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get etc points", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
@@ -52,7 +51,7 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
value, err := readCharacterInt(s, column)
|
||||
if err == nil {
|
||||
newVal := max(value+int(pkt.Delta), 0)
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", newVal, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveInt(s.charID, column, newVal); err != nil {
|
||||
s.logger.Error("Failed to update etc point", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -178,8 +177,7 @@ func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateEquipSkinHist)
|
||||
size := equipSkinHistSize(s.server.erupeConfig.RealClientMode)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist, $2) FROM characters WHERE id = $1", s.charID, make([]byte, size)).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumnWithDefault(s.charID, "skin_hist", make([]byte, size))
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get skin_hist", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -201,7 +199,7 @@ func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
bitInByte := bit % 8
|
||||
data[startByte+byteInd] |= bits.Reverse8(1 << uint(bitInByte))
|
||||
dumpSaveData(s, data, "skinhist")
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", data, s.charID); err != nil {
|
||||
if err := s.server.charRepo.SaveColumn(s.charID, "skin_hist", data); err != nil {
|
||||
s.logger.Error("Failed to update skin history", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -64,7 +64,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumn(s.charID, "platedata")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load platedata",
|
||||
zap.Error(err),
|
||||
@@ -104,7 +104,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
dataSize = len(saveOutput)
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
err = s.server.charRepo.SaveColumn(s.charID, "platedata", saveOutput)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platedata",
|
||||
zap.Error(err),
|
||||
@@ -118,7 +118,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
dataSize = len(pkt.RawDataPayload)
|
||||
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
err := s.server.charRepo.SaveColumn(s.charID, "platedata", pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platedata",
|
||||
zap.Error(err),
|
||||
@@ -164,7 +164,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumn(s.charID, "platebox")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
@@ -195,7 +195,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
err = s.server.charRepo.SaveColumn(s.charID, "platebox", saveOutput)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platebox", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
@@ -206,7 +206,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platebox")
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
err := s.server.charRepo.SaveColumn(s.charID, "platebox", pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platebox", zap.Error(err))
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// looks to always return the full thing, simply update database, no extra processing
|
||||
dumpSaveData(s, pkt.RawDataPayload, "platemyset")
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
err := s.server.charRepo.SaveColumn(s.charID, "platemyset", pkt.RawDataPayload)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save platemyset",
|
||||
zap.Error(err),
|
||||
|
||||
@@ -86,8 +86,8 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
// the character data area. This produces a save with zeroed skill fields but
|
||||
// preserved point totals. Detect this pattern and merge existing skill data.
|
||||
if len(saveData) >= rengokuPointsEnd && rengokuSkillsZeroed(saveData) && rengokuHasPoints(saveData) {
|
||||
var existing []byte
|
||||
if err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id=$1", s.charID).Scan(&existing); err == nil {
|
||||
existing, err := s.server.charRepo.LoadColumn(s.charID, "rengokudata")
|
||||
if err == nil {
|
||||
if len(existing) >= rengokuPointsEnd && !rengokuSkillsZeroed(existing) {
|
||||
s.logger.Info("Rengoku save has zeroed skills with invested points, preserving existing skills",
|
||||
zap.Uint32("charID", s.charID))
|
||||
@@ -101,8 +101,8 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Also reject saves where the sentinel is 0 (no data) if valid data already exists.
|
||||
if len(saveData) >= 4 && binary.BigEndian.Uint32(saveData[:4]) == 0 {
|
||||
var existing []byte
|
||||
if err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id=$1", s.charID).Scan(&existing); err == nil {
|
||||
existing, err := s.server.charRepo.LoadColumn(s.charID, "rengokudata")
|
||||
if err == nil {
|
||||
if len(existing) >= 4 && binary.BigEndian.Uint32(existing[:4]) != 0 {
|
||||
s.logger.Warn("Refusing to overwrite valid rengoku data with empty sentinel",
|
||||
zap.Uint32("charID", s.charID))
|
||||
@@ -112,7 +112,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", saveData, s.charID)
|
||||
err := s.server.charRepo.SaveColumn(s.charID, "rengokudata", saveData)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to save rengokudata", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -140,8 +140,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadRengokuData)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
data, err := s.server.charRepo.LoadColumn(s.charID, "rengokudata")
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to load rengokudata", zap.Error(err),
|
||||
zap.Uint32("charID", s.charID))
|
||||
|
||||
@@ -250,7 +250,7 @@ func logoutPlayer(s *Session) {
|
||||
if mhfcourse.CourseExists(30, s.courses) {
|
||||
rpGained = timePlayed / rpAccrualCafe
|
||||
timePlayed = timePlayed % rpAccrualCafe
|
||||
if _, err := s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID); err != nil {
|
||||
if _, err := s.server.charRepo.AdjustInt(s.charID, "cafe_time", sessionTime); err != nil {
|
||||
s.logger.Error("Failed to update cafe time", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package channelserver
|
||||
|
||||
import "github.com/jmoiron/sqlx"
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// CharacterRepository centralizes all database access for the characters table.
|
||||
type CharacterRepository struct {
|
||||
@@ -74,3 +79,134 @@ func (r *CharacterRepository) GetCharIDsByUserID(userID uint32) ([]uint32, error
|
||||
err := r.db.Select(&ids, "SELECT id FROM characters WHERE user_id=$1", userID)
|
||||
return ids, err
|
||||
}
|
||||
|
||||
// ReadTime reads a single time.Time column by character ID.
|
||||
// Returns the provided default if the column is NULL.
|
||||
func (r *CharacterRepository) ReadTime(charID uint32, column string, defaultVal time.Time) (time.Time, error) {
|
||||
var t sql.NullTime
|
||||
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&t)
|
||||
if err != nil {
|
||||
return defaultVal, err
|
||||
}
|
||||
if !t.Valid {
|
||||
return defaultVal, nil
|
||||
}
|
||||
return t.Time, nil
|
||||
}
|
||||
|
||||
// SaveTime writes a single time.Time column by character ID.
|
||||
func (r *CharacterRepository) SaveTime(charID uint32, column string, value time.Time) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveInt writes a single integer column by character ID.
|
||||
func (r *CharacterRepository) SaveInt(charID uint32, column string, value int) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveBool writes a single boolean column by character ID.
|
||||
func (r *CharacterRepository) SaveBool(charID uint32, column string, value bool) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveString writes a single string column by character ID.
|
||||
func (r *CharacterRepository) SaveString(charID uint32, column string, value string) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", value, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadBool reads a single boolean column by character ID.
|
||||
func (r *CharacterRepository) ReadBool(charID uint32, column string) (bool, error) {
|
||||
var value bool
|
||||
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
// ReadString reads a single string column by character ID (empty string for NULL).
|
||||
func (r *CharacterRepository) ReadString(charID uint32, column string) (string, error) {
|
||||
var value sql.NullString
|
||||
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value.String, nil
|
||||
}
|
||||
|
||||
// LoadColumnWithDefault reads a []byte column, returning defaultVal if NULL.
|
||||
func (r *CharacterRepository) LoadColumnWithDefault(charID uint32, column string, defaultVal []byte) ([]byte, error) {
|
||||
var data []byte
|
||||
err := r.db.QueryRow("SELECT "+column+" FROM characters WHERE id=$1", charID).Scan(&data)
|
||||
if err != nil {
|
||||
return defaultVal, err
|
||||
}
|
||||
if data == nil {
|
||||
return defaultVal, nil
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// SetDeleted marks a character as deleted.
|
||||
func (r *CharacterRepository) SetDeleted(charID uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET deleted=true WHERE id=$1", charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateDailyCafe sets daily_time, bonus_quests, and daily_quests atomically.
|
||||
func (r *CharacterRepository) UpdateDailyCafe(charID uint32, dailyTime time.Time, bonusQuests, dailyQuests uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests=$2, daily_quests=$3 WHERE id=$4",
|
||||
dailyTime, bonusQuests, dailyQuests, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ResetDailyQuests zeroes bonus_quests and daily_quests.
|
||||
func (r *CharacterRepository) ResetDailyQuests(charID uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET bonus_quests=0, daily_quests=0 WHERE id=$1", charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadEtcPoints reads bonus_quests, daily_quests, and promo_points.
|
||||
func (r *CharacterRepository) ReadEtcPoints(charID uint32) (bonusQuests, dailyQuests, promoPoints uint32, err error) {
|
||||
err = r.db.QueryRow("SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id=$1", charID).
|
||||
Scan(&bonusQuests, &dailyQuests, &promoPoints)
|
||||
return
|
||||
}
|
||||
|
||||
// ResetCafeTime zeroes cafe_time and sets cafe_reset.
|
||||
func (r *CharacterRepository) ResetCafeTime(charID uint32, cafeReset time.Time) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2", cafeReset, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateGuildPostChecked sets guild_post_checked to now().
|
||||
func (r *CharacterRepository) UpdateGuildPostChecked(charID uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET guild_post_checked=now() WHERE id=$1", charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadGuildPostChecked reads guild_post_checked timestamp.
|
||||
func (r *CharacterRepository) ReadGuildPostChecked(charID uint32) (time.Time, error) {
|
||||
var t time.Time
|
||||
err := r.db.QueryRow("SELECT guild_post_checked FROM characters WHERE id=$1", charID).Scan(&t)
|
||||
return t, err
|
||||
}
|
||||
|
||||
// SaveMercenary updates savemercenary and rasta_id atomically.
|
||||
func (r *CharacterRepository) SaveMercenary(charID uint32, data []byte, rastaID uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", data, rastaID, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateGCPAndPact updates gcp and pact_id atomically.
|
||||
func (r *CharacterRepository) UpdateGCPAndPact(charID uint32, gcp uint32, pactID uint32) error {
|
||||
_, err := r.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", gcp, pactID, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// FindByRastaID looks up name and id by rasta_id.
|
||||
func (r *CharacterRepository) FindByRastaID(rastaID int) (charID uint32, name string, err error) {
|
||||
err = r.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id=$1", rastaID).Scan(&name, &charID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
@@ -221,3 +222,360 @@ func TestGetCharIDsByUserIDEmpty(t *testing.T) {
|
||||
t.Errorf("Expected 0 character IDs for user with no chars, got: %d", len(ids))
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTimeNull(t *testing.T) {
|
||||
repo, _, charID := setupCharRepo(t)
|
||||
|
||||
defaultTime := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
got, err := repo.ReadTime(charID, "daily_time", defaultTime)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadTime failed: %v", err)
|
||||
}
|
||||
if !got.Equal(defaultTime) {
|
||||
t.Errorf("Expected default time %v, got: %v", defaultTime, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadTimeWithValue(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
expected := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||
if _, err := db.Exec("UPDATE characters SET daily_time=$1 WHERE id=$2", expected, charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.ReadTime(charID, "daily_time", time.Time{})
|
||||
if err != nil {
|
||||
t.Fatalf("ReadTime failed: %v", err)
|
||||
}
|
||||
if !got.Equal(expected) {
|
||||
t.Errorf("Expected %v, got: %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveTime(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
expected := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||
if err := repo.SaveTime(charID, "daily_time", expected); err != nil {
|
||||
t.Fatalf("SaveTime failed: %v", err)
|
||||
}
|
||||
|
||||
var got time.Time
|
||||
if err := db.QueryRow("SELECT daily_time FROM characters WHERE id=$1", charID).Scan(&got); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if !got.Equal(expected) {
|
||||
t.Errorf("Expected %v, got: %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveInt(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if err := repo.SaveInt(charID, "netcafe_points", 500); err != nil {
|
||||
t.Fatalf("SaveInt failed: %v", err)
|
||||
}
|
||||
|
||||
var got int
|
||||
if err := db.QueryRow("SELECT netcafe_points FROM characters WHERE id=$1", charID).Scan(&got); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if got != 500 {
|
||||
t.Errorf("Expected 500, got: %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveBool(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if err := repo.SaveBool(charID, "restrict_guild_scout", true); err != nil {
|
||||
t.Fatalf("SaveBool failed: %v", err)
|
||||
}
|
||||
|
||||
var got bool
|
||||
if err := db.QueryRow("SELECT restrict_guild_scout FROM characters WHERE id=$1", charID).Scan(&got); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if !got {
|
||||
t.Errorf("Expected true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadBool(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET restrict_guild_scout=true WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.ReadBool(charID, "restrict_guild_scout")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadBool failed: %v", err)
|
||||
}
|
||||
if !got {
|
||||
t.Errorf("Expected true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveString(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if err := repo.SaveString(charID, "friends", "1,2,3"); err != nil {
|
||||
t.Fatalf("SaveString failed: %v", err)
|
||||
}
|
||||
|
||||
var got string
|
||||
if err := db.QueryRow("SELECT friends FROM characters WHERE id=$1", charID).Scan(&got); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if got != "1,2,3" {
|
||||
t.Errorf("Expected '1,2,3', got: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadString(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET friends='4,5,6' WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.ReadString(charID, "friends")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadString failed: %v", err)
|
||||
}
|
||||
if got != "4,5,6" {
|
||||
t.Errorf("Expected '4,5,6', got: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadStringNull(t *testing.T) {
|
||||
repo, _, charID := setupCharRepo(t)
|
||||
|
||||
got, err := repo.ReadString(charID, "friends")
|
||||
if err != nil {
|
||||
t.Fatalf("ReadString failed: %v", err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf("Expected empty string for NULL, got: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadColumnWithDefault(t *testing.T) {
|
||||
repo, _, charID := setupCharRepo(t)
|
||||
|
||||
defaultVal := []byte{0x00, 0x01, 0x02}
|
||||
got, err := repo.LoadColumnWithDefault(charID, "skin_hist", defaultVal)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadColumnWithDefault failed: %v", err)
|
||||
}
|
||||
if len(got) != 3 || got[0] != 0x00 || got[2] != 0x02 {
|
||||
t.Errorf("Expected default value, got: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadColumnWithDefaultExistingData(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
blob := []byte{0xAA, 0xBB}
|
||||
if _, err := db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", blob, charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.LoadColumnWithDefault(charID, "skin_hist", []byte{0x00})
|
||||
if err != nil {
|
||||
t.Fatalf("LoadColumnWithDefault failed: %v", err)
|
||||
}
|
||||
if len(got) != 2 || got[0] != 0xAA || got[1] != 0xBB {
|
||||
t.Errorf("Expected stored data, got: %x", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDeleted(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if err := repo.SetDeleted(charID); err != nil {
|
||||
t.Fatalf("SetDeleted failed: %v", err)
|
||||
}
|
||||
|
||||
var deleted bool
|
||||
if err := db.QueryRow("SELECT deleted FROM characters WHERE id=$1", charID).Scan(&deleted); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if !deleted {
|
||||
t.Errorf("Expected deleted=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDailyCafe(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
dailyTime := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||
if err := repo.UpdateDailyCafe(charID, dailyTime, 5, 10); err != nil {
|
||||
t.Fatalf("UpdateDailyCafe failed: %v", err)
|
||||
}
|
||||
|
||||
var gotTime time.Time
|
||||
var bonus, daily uint32
|
||||
if err := db.QueryRow("SELECT daily_time, bonus_quests, daily_quests FROM characters WHERE id=$1", charID).Scan(&gotTime, &bonus, &daily); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if !gotTime.Equal(dailyTime) {
|
||||
t.Errorf("Expected daily_time %v, got: %v", dailyTime, gotTime)
|
||||
}
|
||||
if bonus != 5 || daily != 10 {
|
||||
t.Errorf("Expected bonus=5 daily=10, got bonus=%d daily=%d", bonus, daily)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetDailyQuests(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET bonus_quests=5, daily_quests=10 WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.ResetDailyQuests(charID); err != nil {
|
||||
t.Fatalf("ResetDailyQuests failed: %v", err)
|
||||
}
|
||||
|
||||
var bonus, daily uint32
|
||||
if err := db.QueryRow("SELECT bonus_quests, daily_quests FROM characters WHERE id=$1", charID).Scan(&bonus, &daily); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if bonus != 0 || daily != 0 {
|
||||
t.Errorf("Expected bonus=0 daily=0, got bonus=%d daily=%d", bonus, daily)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadEtcPoints(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET bonus_quests=3, daily_quests=7, promo_points=100 WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
bonus, daily, promo, err := repo.ReadEtcPoints(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadEtcPoints failed: %v", err)
|
||||
}
|
||||
if bonus != 3 || daily != 7 || promo != 100 {
|
||||
t.Errorf("Expected 3/7/100, got %d/%d/%d", bonus, daily, promo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetCafeTime(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET cafe_time=999 WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
cafeReset := time.Date(2025, 6, 22, 0, 0, 0, 0, time.UTC)
|
||||
if err := repo.ResetCafeTime(charID, cafeReset); err != nil {
|
||||
t.Fatalf("ResetCafeTime failed: %v", err)
|
||||
}
|
||||
|
||||
var cafeTime int
|
||||
var gotReset time.Time
|
||||
if err := db.QueryRow("SELECT cafe_time, cafe_reset FROM characters WHERE id=$1", charID).Scan(&cafeTime, &gotReset); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if cafeTime != 0 {
|
||||
t.Errorf("Expected cafe_time=0, got: %d", cafeTime)
|
||||
}
|
||||
if !gotReset.Equal(cafeReset) {
|
||||
t.Errorf("Expected cafe_reset %v, got: %v", cafeReset, gotReset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateGuildPostChecked(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
before := time.Now().Add(-time.Second)
|
||||
if err := repo.UpdateGuildPostChecked(charID); err != nil {
|
||||
t.Fatalf("UpdateGuildPostChecked failed: %v", err)
|
||||
}
|
||||
|
||||
var got time.Time
|
||||
if err := db.QueryRow("SELECT guild_post_checked FROM characters WHERE id=$1", charID).Scan(&got); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if got.Before(before) {
|
||||
t.Errorf("Expected guild_post_checked to be recent, got: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadGuildPostChecked(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
expected := time.Date(2025, 6, 15, 12, 0, 0, 0, time.UTC)
|
||||
if _, err := db.Exec("UPDATE characters SET guild_post_checked=$1 WHERE id=$2", expected, charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
got, err := repo.ReadGuildPostChecked(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadGuildPostChecked failed: %v", err)
|
||||
}
|
||||
if !got.Equal(expected) {
|
||||
t.Errorf("Expected %v, got: %v", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveMercenary(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
data := []byte{0x01, 0x02, 0x03, 0x04}
|
||||
if err := repo.SaveMercenary(charID, data, 42); err != nil {
|
||||
t.Fatalf("SaveMercenary failed: %v", err)
|
||||
}
|
||||
|
||||
var gotData []byte
|
||||
var gotRastaID uint32
|
||||
if err := db.QueryRow("SELECT savemercenary, rasta_id FROM characters WHERE id=$1", charID).Scan(&gotData, &gotRastaID); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if len(gotData) != 4 || gotData[0] != 0x01 {
|
||||
t.Errorf("Expected mercenary data, got: %x", gotData)
|
||||
}
|
||||
if gotRastaID != 42 {
|
||||
t.Errorf("Expected rasta_id=42, got: %d", gotRastaID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateGCPAndPact(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if err := repo.UpdateGCPAndPact(charID, 100, 55); err != nil {
|
||||
t.Fatalf("UpdateGCPAndPact failed: %v", err)
|
||||
}
|
||||
|
||||
var gcp, pactID uint32
|
||||
if err := db.QueryRow("SELECT gcp, pact_id FROM characters WHERE id=$1", charID).Scan(&gcp, &pactID); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if gcp != 100 || pactID != 55 {
|
||||
t.Errorf("Expected gcp=100 pact_id=55, got gcp=%d pact_id=%d", gcp, pactID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindByRastaID(t *testing.T) {
|
||||
repo, db, charID := setupCharRepo(t)
|
||||
|
||||
if _, err := db.Exec("UPDATE characters SET rasta_id=999 WHERE id=$1", charID); err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
|
||||
gotID, gotName, err := repo.FindByRastaID(999)
|
||||
if err != nil {
|
||||
t.Fatalf("FindByRastaID failed: %v", err)
|
||||
}
|
||||
if gotID != charID {
|
||||
t.Errorf("Expected charID %d, got: %d", charID, gotID)
|
||||
}
|
||||
if gotName != "RepoChar" {
|
||||
t.Errorf("Expected 'RepoChar', got: %q", gotName)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user