mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
refactor(channelserver): extract GachaRepository and HouseRepository
Centralizes all gacha_shop/gacha_entries/gacha_items/gacha_stepup/gacha_box table access into GachaRepository (15 methods) and all user_binary house columns, warehouse, and titles table access into HouseRepository (17 methods). Eliminates all direct DB calls from handlers_gacha.go and handlers_house.go.
This commit is contained in:
@@ -91,10 +91,7 @@ func spendGachaCoin(s *Session, quantity uint16) {
|
||||
}
|
||||
|
||||
func transactGacha(s *Session, gachaID uint32, rollID uint8) (int, error) {
|
||||
var itemType uint8
|
||||
var itemNumber uint16
|
||||
var rolls int
|
||||
err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls)
|
||||
itemType, itemNumber, rolls, err := s.server.gachaRepo.GetEntryForTransaction(gachaID, rollID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -123,15 +120,7 @@ func transactGacha(s *Session, gachaID uint32, rollID uint8) (int, error) {
|
||||
}
|
||||
|
||||
func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem {
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID)
|
||||
if err == nil {
|
||||
for items.Next() {
|
||||
_ = items.StructScan(&reward)
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
}
|
||||
rewards, _ := s.server.gachaRepo.GetGuaranteedItems(rollID, gachaID)
|
||||
return rewards
|
||||
}
|
||||
|
||||
@@ -224,41 +213,27 @@ func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
rolls, err := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
entries, err := s.server.gachaRepo.GetRewardPool(pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
rewardEntries, _ := getRandomEntries(entries, rolls, false)
|
||||
temp := byteframe.NewByteFrame()
|
||||
for i := range rewardEntries {
|
||||
rows, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
entryItems, err := s.server.gachaRepo.GetItemsForEntry(rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&reward)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, reward := range entryItems {
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
@@ -276,10 +251,7 @@ func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
rolls, err := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
@@ -288,39 +260,28 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
if err := s.server.userRepo.AddFrontierPointsFromGacha(s.userID, pkt.GachaID, pkt.RollType); err != nil {
|
||||
s.logger.Error("Failed to award stepup gacha frontier points", zap.Error(err))
|
||||
}
|
||||
if _, err := s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID); err != nil {
|
||||
if err := s.server.gachaRepo.DeleteStepup(pkt.GachaID, s.charID); err != nil {
|
||||
s.logger.Error("Failed to delete gacha stepup state", zap.Error(err))
|
||||
}
|
||||
if _, err := s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID); err != nil {
|
||||
if err := s.server.gachaRepo.InsertStepup(pkt.GachaID, pkt.RollType+1, s.charID); err != nil {
|
||||
s.logger.Error("Failed to insert gacha stepup state", zap.Error(err))
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
entries, err := s.server.gachaRepo.GetRewardPool(pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType)
|
||||
rewardEntries, _ := getRandomEntries(entries, rolls, false)
|
||||
temp := byteframe.NewByteFrame()
|
||||
for i := range rewardEntries {
|
||||
rows, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
entryItems, err := s.server.gachaRepo.GetItemsForEntry(rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&reward)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, reward := range entryItems {
|
||||
rewards = append(rewards, reward)
|
||||
temp.WriteUint8(reward.ItemType)
|
||||
temp.WriteUint16(reward.ItemID)
|
||||
@@ -346,12 +307,10 @@ func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// TODO: Reset daily (noon)
|
||||
var step uint8
|
||||
_ = s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step)
|
||||
var stepCheck int
|
||||
_ = s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck)
|
||||
if stepCheck == 0 {
|
||||
if _, err := s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID); err != nil {
|
||||
step, _ := s.server.gachaRepo.GetStepupStep(pkt.GachaID, s.charID)
|
||||
hasEntry, _ := s.server.gachaRepo.HasEntryType(pkt.GachaID, step)
|
||||
if !hasEntry {
|
||||
if err := s.server.gachaRepo.DeleteStepup(pkt.GachaID, s.charID); err != nil {
|
||||
s.logger.Error("Failed to reset gacha stepup state", zap.Error(err))
|
||||
}
|
||||
step = 0
|
||||
@@ -364,17 +323,11 @@ func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
|
||||
entryIDs, err := s.server.gachaRepo.GetBoxEntryIDs(pkt.GachaID, s.charID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
var entryIDs []uint32
|
||||
for entries.Next() {
|
||||
var entryID uint32
|
||||
_ = entries.Scan(&entryID)
|
||||
entryIDs = append(entryIDs, entryID)
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(entryIDs)))
|
||||
for i := range entryIDs {
|
||||
@@ -387,41 +340,27 @@ func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var entries []GachaEntry
|
||||
var entry GachaEntry
|
||||
var rewards []GachaItem
|
||||
var reward GachaItem
|
||||
rolls, err := transactGacha(s, pkt.GachaID, pkt.RollType)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
rows, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
|
||||
entries, err := s.server.gachaRepo.GetRewardPool(pkt.GachaID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
rewardEntries, _ := getRandomEntries(entries, rolls, true)
|
||||
for i := range rewardEntries {
|
||||
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
|
||||
entryItems, err := s.server.gachaRepo.GetItemsForEntry(rewardEntries[i].ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if _, err := s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID); err != nil {
|
||||
if err := s.server.gachaRepo.InsertBoxEntry(pkt.GachaID, rewardEntries[i].ID, s.charID); err != nil {
|
||||
s.logger.Error("Failed to insert gacha box entry", zap.Error(err))
|
||||
}
|
||||
for items.Next() {
|
||||
err = items.StructScan(&reward)
|
||||
if err == nil {
|
||||
rewards = append(rewards, reward)
|
||||
}
|
||||
}
|
||||
rewards = append(rewards, entryItems...)
|
||||
}
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
for _, r := range rewards {
|
||||
@@ -436,7 +375,7 @@ func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
if _, err := s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID); err != nil {
|
||||
if err := s.server.gachaRepo.DeleteBoxEntries(pkt.GachaID, s.charID); err != nil {
|
||||
s.logger.Error("Failed to reset gacha box", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -8,37 +8,11 @@ import (
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const warehouseNamesQuery = `
|
||||
SELECT
|
||||
COALESCE(item0name, ''),
|
||||
COALESCE(item1name, ''),
|
||||
COALESCE(item2name, ''),
|
||||
COALESCE(item3name, ''),
|
||||
COALESCE(item4name, ''),
|
||||
COALESCE(item5name, ''),
|
||||
COALESCE(item6name, ''),
|
||||
COALESCE(item7name, ''),
|
||||
COALESCE(item8name, ''),
|
||||
COALESCE(item9name, ''),
|
||||
COALESCE(equip0name, ''),
|
||||
COALESCE(equip1name, ''),
|
||||
COALESCE(equip2name, ''),
|
||||
COALESCE(equip3name, ''),
|
||||
COALESCE(equip4name, ''),
|
||||
COALESCE(equip5name, ''),
|
||||
COALESCE(equip6name, ''),
|
||||
COALESCE(equip7name, ''),
|
||||
COALESCE(equip8name, ''),
|
||||
COALESCE(equip9name, '')
|
||||
FROM warehouse
|
||||
`
|
||||
|
||||
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
|
||||
if len(pkt.InteriorData) > 64 {
|
||||
@@ -46,7 +20,7 @@ func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if _, err := s.server.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, pkt.InteriorData, s.charID); err != nil {
|
||||
if err := s.server.houseRepo.UpdateInterior(s.charID, pkt.InteriorData); err != nil {
|
||||
s.logger.Error("Failed to update house furniture", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -67,16 +41,12 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
var houses []HouseData
|
||||
houseQuery := `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
|
||||
switch pkt.Method {
|
||||
case 1:
|
||||
friendsList, _ := s.server.charRepo.ReadString(s.charID, "friends")
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx(houseQuery, cid)
|
||||
err := row.StructScan(&house)
|
||||
house, err := s.server.houseRepo.GetHouseByCharID(uint32(cid))
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
@@ -91,32 +61,20 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
break
|
||||
}
|
||||
for _, member := range guildMembers {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx(houseQuery, member.CharID)
|
||||
err = row.StructScan(&house)
|
||||
house, err := s.server.houseRepo.GetHouseByCharID(member.CharID)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
houseQuery = `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`
|
||||
house := HouseData{}
|
||||
rows, err := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
|
||||
result, err := s.server.houseRepo.SearchHousesByName(pkt.Name)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to query houses by name", zap.Error(err))
|
||||
} else {
|
||||
defer func() { _ = rows.Close() }()
|
||||
for rows.Next() {
|
||||
if err := rows.StructScan(&house); err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
houses = result
|
||||
}
|
||||
case 4:
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx(houseQuery, pkt.CharID)
|
||||
err := row.StructScan(&house)
|
||||
house, err := s.server.houseRepo.GetHouseByCharID(pkt.CharID)
|
||||
if err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
@@ -149,7 +107,7 @@ func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
// 03 = open friends
|
||||
// 04 = open guild
|
||||
// 05 = open friends+guild
|
||||
if _, err := s.server.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, pkt.State, pkt.Password, s.charID); err != nil {
|
||||
if err := s.server.houseRepo.UpdateHouseState(s.charID, pkt.State, pkt.Password); err != nil {
|
||||
s.logger.Error("Failed to update house state", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -159,10 +117,8 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
state := uint8(2) // Default to password-protected if DB fails
|
||||
var password string
|
||||
if err := s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&state, &password); err != nil {
|
||||
state, password, err := s.server.houseRepo.GetHouseAccess(pkt.CharID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to read house state", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -208,9 +164,7 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
|
||||
var houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte
|
||||
_ = s.server.db.QueryRow(`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1
|
||||
`, pkt.CharID).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
|
||||
houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden, _ := s.server.houseRepo.GetHouseContents(pkt.CharID)
|
||||
if houseFurniture == nil {
|
||||
houseFurniture = make([]byte, 20)
|
||||
}
|
||||
@@ -247,8 +201,7 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetMyhouseInfo)
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, s.charID).Scan(&data)
|
||||
data, _ := s.server.houseRepo.GetMission(s.charID)
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
@@ -263,7 +216,7 @@ func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
if _, err := s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID); err != nil {
|
||||
if err := s.server.houseRepo.UpdateMission(s.charID, pkt.Data); err != nil {
|
||||
s.logger.Error("Failed to update myhouse mission", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -345,45 +298,30 @@ type Title struct {
|
||||
|
||||
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
|
||||
var count uint16
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0) // Unk
|
||||
rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID)
|
||||
titles, err := s.server.houseRepo.GetTitles(s.charID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
title := &Title{}
|
||||
err = rows.StructScan(&title)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
for _, title := range titles {
|
||||
bf.WriteUint16(title.ID)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint32(uint32(title.Acquired.Unix()))
|
||||
bf.WriteUint32(uint32(title.Updated.Unix()))
|
||||
}
|
||||
_, _ = bf.Seek(0, io.SeekStart)
|
||||
bf.WriteUint16(count)
|
||||
bf.WriteUint16(uint16(len(titles)))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireTitle)
|
||||
for _, title := range pkt.TitleIDs {
|
||||
var exists int
|
||||
err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, title, s.charID).Scan(&exists)
|
||||
if err != nil || exists == 0 {
|
||||
if _, err := s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, title, s.charID); err != nil {
|
||||
s.logger.Error("Failed to insert title", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
if _, err := s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, title, s.charID); err != nil {
|
||||
s.logger.Error("Failed to update title", zap.Error(err))
|
||||
}
|
||||
if err := s.server.houseRepo.AcquireTitle(title, s.charID); err != nil {
|
||||
s.logger.Error("Failed to acquire title", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -392,13 +330,7 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func initializeWarehouse(s *Session) {
|
||||
var t int
|
||||
err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
|
||||
if err != nil {
|
||||
if _, err := s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID); err != nil {
|
||||
s.logger.Error("Failed to initialize warehouse", zap.Error(err))
|
||||
}
|
||||
}
|
||||
s.server.houseRepo.InitializeWarehouse(s.charID)
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -409,11 +341,7 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
switch pkt.Operation {
|
||||
case 0:
|
||||
var count uint8
|
||||
itemNames := make([]string, 10)
|
||||
equipNames := make([]string, 10)
|
||||
_ = s.server.db.QueryRow(fmt.Sprintf("%s WHERE character_id=$1", warehouseNamesQuery), s.charID).Scan(&itemNames[0],
|
||||
&itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4], &itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9], &equipNames[0],
|
||||
&equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4], &equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9])
|
||||
itemNames, equipNames, _ := s.server.houseRepo.GetWarehouseNames(s.charID)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(10000) // Usages
|
||||
temp := byteframe.NewByteFrame()
|
||||
@@ -441,15 +369,8 @@ func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
if pkt.BoxIndex > 9 {
|
||||
break
|
||||
}
|
||||
switch pkt.BoxType {
|
||||
case 0:
|
||||
if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET item%dname=$1 WHERE character_id=$2", pkt.BoxIndex), pkt.Name, s.charID); err != nil {
|
||||
s.logger.Error("Failed to rename warehouse item box", zap.Error(err))
|
||||
}
|
||||
case 1:
|
||||
if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET equip%dname=$1 WHERE character_id=$2", pkt.BoxIndex), pkt.Name, s.charID); err != nil {
|
||||
s.logger.Error("Failed to rename warehouse equip box", zap.Error(err))
|
||||
}
|
||||
if err := s.server.houseRepo.RenameWarehouseBox(s.charID, pkt.BoxType, pkt.BoxIndex, pkt.Name); err != nil {
|
||||
s.logger.Error("Failed to rename warehouse box", zap.Error(err))
|
||||
}
|
||||
case 3:
|
||||
bf.WriteUint32(0) // Usage renewal time, >1 = disabled
|
||||
@@ -472,7 +393,7 @@ func addWarehouseItem(s *Session, item mhfitem.MHFItemStack) {
|
||||
giftBox := warehouseGetItems(s, 10)
|
||||
item.WarehouseID = token.RNG.Uint32()
|
||||
giftBox = append(giftBox, item)
|
||||
if _, err := s.server.db.Exec("UPDATE warehouse SET item10=$1 WHERE character_id=$2", mhfitem.SerializeWarehouseItems(giftBox), s.charID); err != nil {
|
||||
if err := s.server.houseRepo.SetWarehouseItemData(s.charID, 10, mhfitem.SerializeWarehouseItems(giftBox)); err != nil {
|
||||
s.logger.Error("Failed to update warehouse gift box", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -484,7 +405,7 @@ func warehouseGetItems(s *Session, index uint8) []mhfitem.MHFItemStack {
|
||||
if index > 10 {
|
||||
return items
|
||||
}
|
||||
_ = s.server.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)
|
||||
data, _ = s.server.houseRepo.GetWarehouseItemData(s.charID, index)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
@@ -502,7 +423,7 @@ func warehouseGetEquipment(s *Session, index uint8) []mhfitem.MHFEquipment {
|
||||
if index > 10 {
|
||||
return equipment
|
||||
}
|
||||
_ = s.server.db.QueryRow(fmt.Sprintf(`SELECT equip%d FROM warehouse WHERE character_id=$1`, index), s.charID).Scan(&data)
|
||||
data, _ = s.server.houseRepo.GetWarehouseEquipData(s.charID, index)
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
@@ -559,7 +480,7 @@ func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
zap.Int("data_size", dataSize),
|
||||
)
|
||||
|
||||
_, err = s.server.db.Exec(fmt.Sprintf(`UPDATE warehouse SET item%d=$1 WHERE character_id=$2`, pkt.BoxIndex), serialized, s.charID)
|
||||
err = s.server.houseRepo.SetWarehouseItemData(s.charID, pkt.BoxIndex, serialized)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update warehouse items",
|
||||
zap.Error(err),
|
||||
@@ -605,7 +526,7 @@ func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
zap.Int("data_size", dataSize),
|
||||
)
|
||||
|
||||
_, err = s.server.db.Exec(fmt.Sprintf(`UPDATE warehouse SET equip%d=$1 WHERE character_id=$2`, pkt.BoxIndex), serialized, s.charID)
|
||||
err = s.server.houseRepo.SetWarehouseEquipData(s.charID, pkt.BoxIndex, serialized)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update warehouse equipment",
|
||||
zap.Error(err),
|
||||
|
||||
@@ -95,20 +95,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
gachas, err := s.server.gachaRepo.ListShop()
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
var gacha Gacha
|
||||
var gachas []Gacha
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&gacha)
|
||||
if err == nil {
|
||||
gachas = append(gachas, gacha)
|
||||
}
|
||||
}
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
bf.WriteUint16(uint16(len(gachas)))
|
||||
for _, g := range gachas {
|
||||
@@ -141,25 +133,13 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
case 2: // Actual gacha
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.ShopID)
|
||||
var gachaType int
|
||||
_ = s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
|
||||
rows, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
gachaType, _ := s.server.gachaRepo.GetShopType(pkt.ShopID)
|
||||
entries, err := s.server.gachaRepo.GetAllEntries(pkt.ShopID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var divisor float64
|
||||
_ = s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor)
|
||||
|
||||
var entry GachaEntry
|
||||
var entries []GachaEntry
|
||||
var item GachaItem
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&entry)
|
||||
if err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
divisor, _ := s.server.gachaRepo.GetWeightDivisor(pkt.ShopID)
|
||||
bf.WriteUint16(uint16(len(entries)))
|
||||
for _, ge := range entries {
|
||||
var items []GachaItem
|
||||
@@ -176,16 +156,10 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(ge.Rarity)
|
||||
bf.WriteUint8(ge.Rolls)
|
||||
|
||||
rows, err = s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, ge.ID)
|
||||
items, err := s.server.gachaRepo.GetItemsForEntry(ge.ID)
|
||||
if err != nil {
|
||||
bf.WriteUint8(0)
|
||||
} else {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&item)
|
||||
if err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
bf.WriteUint8(uint8(len(items)))
|
||||
}
|
||||
|
||||
|
||||
226
server/channelserver/repo_gacha.go
Normal file
226
server/channelserver/repo_gacha.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// GachaRepository centralizes all database access for gacha-related tables
|
||||
// (gacha_shop, gacha_entries, gacha_items, gacha_stepup, gacha_box).
|
||||
type GachaRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewGachaRepository creates a new GachaRepository.
|
||||
func NewGachaRepository(db *sqlx.DB) *GachaRepository {
|
||||
return &GachaRepository{db: db}
|
||||
}
|
||||
|
||||
// GetEntryForTransaction reads the cost type/amount and roll count for a gacha transaction.
|
||||
func (r *GachaRepository) GetEntryForTransaction(gachaID uint32, rollID uint8) (itemType uint8, itemNumber uint16, rolls int, err error) {
|
||||
err = r.db.QueryRowx(
|
||||
`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`,
|
||||
gachaID, rollID,
|
||||
).Scan(&itemType, &itemNumber, &rolls)
|
||||
return
|
||||
}
|
||||
|
||||
// GetRewardPool returns the entry_type=100 reward pool for a gacha, ordered by weight descending.
|
||||
func (r *GachaRepository) GetRewardPool(gachaID uint32) ([]GachaEntry, error) {
|
||||
var entries []GachaEntry
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`,
|
||||
gachaID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var entry GachaEntry
|
||||
if err := rows.StructScan(&entry); err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// GetItemsForEntry returns the items associated with a gacha entry ID.
|
||||
func (r *GachaRepository) GetItemsForEntry(entryID uint32) ([]GachaItem, error) {
|
||||
var items []GachaItem
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`,
|
||||
entryID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var item GachaItem
|
||||
if err := rows.StructScan(&item); err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetGuaranteedItems returns items for the entry matching a roll type and gacha ID.
|
||||
func (r *GachaRepository) GetGuaranteedItems(rollType uint8, gachaID uint32) ([]GachaItem, error) {
|
||||
var items []GachaItem
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`,
|
||||
rollType, gachaID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var item GachaItem
|
||||
if err := rows.StructScan(&item); err == nil {
|
||||
items = append(items, item)
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// Stepup methods
|
||||
|
||||
// GetStepupStep returns the current stepup step for a character on a gacha.
|
||||
func (r *GachaRepository) GetStepupStep(gachaID uint32, charID uint32) (uint8, error) {
|
||||
var step uint8
|
||||
err := r.db.QueryRow(
|
||||
`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`,
|
||||
gachaID, charID,
|
||||
).Scan(&step)
|
||||
return step, err
|
||||
}
|
||||
|
||||
// HasEntryType returns whether a gacha has any entries of the given type.
|
||||
func (r *GachaRepository) HasEntryType(gachaID uint32, entryType uint8) (bool, error) {
|
||||
var count int
|
||||
err := r.db.QueryRow(
|
||||
`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`,
|
||||
gachaID, entryType,
|
||||
).Scan(&count)
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// DeleteStepup removes the stepup state for a character on a gacha.
|
||||
func (r *GachaRepository) DeleteStepup(gachaID uint32, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`,
|
||||
gachaID, charID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// InsertStepup records a new stepup step for a character on a gacha.
|
||||
func (r *GachaRepository) InsertStepup(gachaID uint32, step uint8, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`,
|
||||
gachaID, step, charID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Box gacha methods
|
||||
|
||||
// GetBoxEntryIDs returns the entry IDs already drawn for a box gacha.
|
||||
func (r *GachaRepository) GetBoxEntryIDs(gachaID uint32, charID uint32) ([]uint32, error) {
|
||||
var ids []uint32
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`,
|
||||
gachaID, charID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id uint32
|
||||
if err := rows.Scan(&id); err == nil {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// InsertBoxEntry records a drawn entry in a box gacha.
|
||||
func (r *GachaRepository) InsertBoxEntry(gachaID uint32, entryID uint32, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`,
|
||||
gachaID, entryID, charID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteBoxEntries resets all drawn entries for a box gacha.
|
||||
func (r *GachaRepository) DeleteBoxEntries(gachaID uint32, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`,
|
||||
gachaID, charID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Shop listing methods
|
||||
|
||||
// ListShop returns all gacha shop definitions.
|
||||
func (r *GachaRepository) ListShop() ([]Gacha, error) {
|
||||
var gachas []Gacha
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop`,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var g Gacha
|
||||
if err := rows.StructScan(&g); err == nil {
|
||||
gachas = append(gachas, g)
|
||||
}
|
||||
}
|
||||
return gachas, nil
|
||||
}
|
||||
|
||||
// GetShopType returns the gacha_type for a gacha shop ID.
|
||||
func (r *GachaRepository) GetShopType(shopID uint32) (int, error) {
|
||||
var gachaType int
|
||||
err := r.db.QueryRow(
|
||||
`SELECT gacha_type FROM gacha_shop WHERE id = $1`,
|
||||
shopID,
|
||||
).Scan(&gachaType)
|
||||
return gachaType, err
|
||||
}
|
||||
|
||||
// GetAllEntries returns all entries for a gacha, ordered by weight descending.
|
||||
func (r *GachaRepository) GetAllEntries(gachaID uint32) ([]GachaEntry, error) {
|
||||
var entries []GachaEntry
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, COALESCE(name, '') AS name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`,
|
||||
gachaID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var entry GachaEntry
|
||||
if err := rows.StructScan(&entry); err == nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// GetWeightDivisor returns the total weight / 100000 for probability display.
|
||||
func (r *GachaRepository) GetWeightDivisor(gachaID uint32) (float64, error) {
|
||||
var divisor float64
|
||||
err := r.db.QueryRow(
|
||||
`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`,
|
||||
gachaID,
|
||||
).Scan(&divisor)
|
||||
return divisor, err
|
||||
}
|
||||
215
server/channelserver/repo_house.go
Normal file
215
server/channelserver/repo_house.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// HouseRepository centralizes all database access for house-related tables
|
||||
// (user_binary house columns, warehouse, titles).
|
||||
type HouseRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewHouseRepository creates a new HouseRepository.
|
||||
func NewHouseRepository(db *sqlx.DB) *HouseRepository {
|
||||
return &HouseRepository{db: db}
|
||||
}
|
||||
|
||||
// user_binary house columns
|
||||
|
||||
// UpdateInterior saves the house furniture layout.
|
||||
func (r *HouseRepository) UpdateInterior(charID uint32, data []byte) error {
|
||||
_, err := r.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, data, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
const houseQuery = `SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
|
||||
|
||||
// GetHouseByCharID returns house data for a single character.
|
||||
func (r *HouseRepository) GetHouseByCharID(charID uint32) (HouseData, error) {
|
||||
var house HouseData
|
||||
err := r.db.QueryRowx(houseQuery, charID).StructScan(&house)
|
||||
return house, err
|
||||
}
|
||||
|
||||
// SearchHousesByName returns houses matching a name pattern (case-insensitive).
|
||||
func (r *HouseRepository) SearchHousesByName(name string) ([]HouseData, error) {
|
||||
var houses []HouseData
|
||||
rows, err := r.db.Queryx(
|
||||
`SELECT c.id, hr, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
|
||||
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`,
|
||||
fmt.Sprintf(`%%%s%%`, name),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var house HouseData
|
||||
if err := rows.StructScan(&house); err == nil {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
return houses, nil
|
||||
}
|
||||
|
||||
// UpdateHouseState sets the house visibility state and password.
|
||||
func (r *HouseRepository) UpdateHouseState(charID uint32, state uint8, password string) error {
|
||||
_, err := r.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, state, password, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetHouseAccess returns the house state and password for access control checks.
|
||||
func (r *HouseRepository) GetHouseAccess(charID uint32) (state uint8, password string, err error) {
|
||||
state = 2 // default to password-protected
|
||||
err = r.db.QueryRow(
|
||||
`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1`,
|
||||
charID,
|
||||
).Scan(&state, &password)
|
||||
return
|
||||
}
|
||||
|
||||
// GetHouseContents returns all house content columns for rendering a house visit.
|
||||
func (r *HouseRepository) GetHouseContents(charID uint32) (houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte, err error) {
|
||||
err = r.db.QueryRow(
|
||||
`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1`,
|
||||
charID,
|
||||
).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
|
||||
return
|
||||
}
|
||||
|
||||
// GetMission returns the myhouse mission data.
|
||||
func (r *HouseRepository) GetMission(charID uint32) ([]byte, error) {
|
||||
var data []byte
|
||||
err := r.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, charID).Scan(&data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
// UpdateMission saves the myhouse mission data.
|
||||
func (r *HouseRepository) UpdateMission(charID uint32, data []byte) error {
|
||||
_, err := r.db.Exec(`UPDATE user_binary SET mission=$1 WHERE id=$2`, data, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Warehouse methods
|
||||
|
||||
// InitializeWarehouse ensures a warehouse row exists for the character.
|
||||
func (r *HouseRepository) InitializeWarehouse(charID uint32) {
|
||||
var t int
|
||||
err := r.db.QueryRow(`SELECT character_id FROM warehouse WHERE character_id=$1`, charID).Scan(&t)
|
||||
if err != nil {
|
||||
_, _ = r.db.Exec(`INSERT INTO warehouse (character_id) VALUES ($1)`, charID)
|
||||
}
|
||||
}
|
||||
|
||||
const warehouseNamesSQL = `
|
||||
SELECT
|
||||
COALESCE(item0name, ''),
|
||||
COALESCE(item1name, ''),
|
||||
COALESCE(item2name, ''),
|
||||
COALESCE(item3name, ''),
|
||||
COALESCE(item4name, ''),
|
||||
COALESCE(item5name, ''),
|
||||
COALESCE(item6name, ''),
|
||||
COALESCE(item7name, ''),
|
||||
COALESCE(item8name, ''),
|
||||
COALESCE(item9name, ''),
|
||||
COALESCE(equip0name, ''),
|
||||
COALESCE(equip1name, ''),
|
||||
COALESCE(equip2name, ''),
|
||||
COALESCE(equip3name, ''),
|
||||
COALESCE(equip4name, ''),
|
||||
COALESCE(equip5name, ''),
|
||||
COALESCE(equip6name, ''),
|
||||
COALESCE(equip7name, ''),
|
||||
COALESCE(equip8name, ''),
|
||||
COALESCE(equip9name, '')
|
||||
FROM warehouse WHERE character_id=$1`
|
||||
|
||||
// GetWarehouseNames returns item and equipment box names.
|
||||
func (r *HouseRepository) GetWarehouseNames(charID uint32) (itemNames, equipNames [10]string, err error) {
|
||||
err = r.db.QueryRow(warehouseNamesSQL, charID).Scan(
|
||||
&itemNames[0], &itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4],
|
||||
&itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9],
|
||||
&equipNames[0], &equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4],
|
||||
&equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9],
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// RenameWarehouseBox renames an item or equipment warehouse box.
|
||||
// boxType 0 = items, 1 = equipment. boxIndex must be 0-9.
|
||||
func (r *HouseRepository) RenameWarehouseBox(charID uint32, boxType uint8, boxIndex uint8, name string) error {
|
||||
var col string
|
||||
switch boxType {
|
||||
case 0:
|
||||
col = fmt.Sprintf("item%dname", boxIndex)
|
||||
case 1:
|
||||
col = fmt.Sprintf("equip%dname", boxIndex)
|
||||
default:
|
||||
return fmt.Errorf("invalid box type: %d", boxType)
|
||||
}
|
||||
_, err := r.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s=$1 WHERE character_id=$2", col), name, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWarehouseItemData returns raw serialized item data for a warehouse box.
|
||||
// index 0-10 (10 = gift box).
|
||||
func (r *HouseRepository) GetWarehouseItemData(charID uint32, index uint8) ([]byte, error) {
|
||||
var data []byte
|
||||
err := r.db.QueryRow(fmt.Sprintf(`SELECT item%d FROM warehouse WHERE character_id=$1`, index), charID).Scan(&data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
// SetWarehouseItemData saves raw serialized item data for a warehouse box.
|
||||
func (r *HouseRepository) SetWarehouseItemData(charID uint32, index uint8, data []byte) error {
|
||||
_, err := r.db.Exec(fmt.Sprintf(`UPDATE warehouse SET item%d=$1 WHERE character_id=$2`, index), data, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWarehouseEquipData returns raw serialized equipment data for a warehouse box.
|
||||
func (r *HouseRepository) GetWarehouseEquipData(charID uint32, index uint8) ([]byte, error) {
|
||||
var data []byte
|
||||
err := r.db.QueryRow(fmt.Sprintf(`SELECT equip%d FROM warehouse WHERE character_id=$1`, index), charID).Scan(&data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
// SetWarehouseEquipData saves raw serialized equipment data for a warehouse box.
|
||||
func (r *HouseRepository) SetWarehouseEquipData(charID uint32, index uint8, data []byte) error {
|
||||
_, err := r.db.Exec(fmt.Sprintf(`UPDATE warehouse SET equip%d=$1 WHERE character_id=$2`, index), data, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Title methods
|
||||
|
||||
// GetTitles returns all titles for a character.
|
||||
func (r *HouseRepository) GetTitles(charID uint32) ([]Title, error) {
|
||||
var titles []Title
|
||||
rows, err := r.db.Queryx(`SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1`, charID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var title Title
|
||||
if err := rows.StructScan(&title); err == nil {
|
||||
titles = append(titles, title)
|
||||
}
|
||||
}
|
||||
return titles, nil
|
||||
}
|
||||
|
||||
// AcquireTitle inserts a new title or updates its timestamp if it already exists.
|
||||
func (r *HouseRepository) AcquireTitle(titleID uint16, charID uint32) error {
|
||||
var exists int
|
||||
err := r.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, titleID, charID).Scan(&exists)
|
||||
if err != nil || exists == 0 {
|
||||
_, err = r.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, titleID, charID)
|
||||
} else {
|
||||
_, err = r.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, titleID, charID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -48,6 +48,8 @@ type Server struct {
|
||||
charRepo *CharacterRepository
|
||||
guildRepo *GuildRepository
|
||||
userRepo *UserRepository
|
||||
gachaRepo *GachaRepository
|
||||
houseRepo *HouseRepository
|
||||
erupeConfig *_config.Config
|
||||
acceptConns chan net.Conn
|
||||
deleteConns chan net.Conn
|
||||
@@ -121,6 +123,8 @@ func NewServer(config *Config) *Server {
|
||||
s.charRepo = NewCharacterRepository(config.DB)
|
||||
s.guildRepo = NewGuildRepository(config.DB)
|
||||
s.userRepo = NewUserRepository(config.DB)
|
||||
s.gachaRepo = NewGachaRepository(config.DB)
|
||||
s.houseRepo = NewHouseRepository(config.DB)
|
||||
|
||||
// Mezeporta
|
||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||
|
||||
Reference in New Issue
Block a user