Files
Erupe/server/channelserver/handlers.go
Houmgaor ed2a9597f2 refactor(channelserver): extract guild model, chat commands, and seibattle
Split three large files into focused modules:
- handlers_guild.go: extract types/ORM into guild_model.go
- handlers_cast_binary.go: extract command parser into handlers_commands.go
- handlers.go: move seibattle types/handlers into handlers_seibattle.go
2026-02-18 18:24:36 +01:00

395 lines
12 KiB
Go

package channelserver
import (
"erupe-ce/common/byteframe"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"fmt"
"math/bits"
"time"
"go.uber.org/zap"
)
func getGoocooData(s *Session, cid uint32) [][]byte {
var goocoo []byte
var goocoos [][]byte
for i := 0; i < 5; i++ {
err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&goocoo)
if err != nil {
if _, err := s.server.db.Exec("INSERT INTO goocoo (id) VALUES ($1)", s.charID); err != nil {
s.logger.Error("Failed to insert goocoo record", zap.Error(err))
}
return goocoos
}
if err == nil && goocoo != nil {
goocoos = append(goocoos, goocoo)
}
}
return goocoos
}
func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuacot)
bf := byteframe.NewByteFrame()
goocoos := getGoocooData(s, s.charID)
bf.WriteUint16(uint16(len(goocoos)))
bf.WriteUint16(0)
for _, goocoo := range goocoos {
bf.WriteBytes(goocoo)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuacot)
for _, goocoo := range pkt.Goocoos {
if goocoo.Data1[0] == 0 {
if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID); err != nil {
s.logger.Error("Failed to clear goocoo slot", zap.Error(err))
}
} else {
bf := byteframe.NewByteFrame()
bf.WriteUint32(goocoo.Index)
for i := range goocoo.Data1 {
bf.WriteInt16(goocoo.Data1[i])
}
for i := range goocoo.Data2 {
bf.WriteUint32(goocoo.Data2[i])
}
bf.WriteUint8(uint8(len(goocoo.Name)))
bf.WriteBytes(goocoo.Name)
if _, err := s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=$1 WHERE id=$2", goocoo.Index), bf.Data(), s.charID); err != nil {
s.logger.Error("Failed to update goocoo slot", zap.Error(err))
}
dumpSaveData(s, bf.Data(), fmt.Sprintf("goocoo-%d", goocoo.Index))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type Scenario struct {
MainID uint32
// 0 = Basic
// 1 = Veteran
// 3 = Other
// 6 = Pallone
// 7 = Diva
CategoryID uint8
}
func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoScenarioCounter)
var scenarios []Scenario
var scenario Scenario
scenarioData, err := s.server.db.Queryx("SELECT scenario_id, category_id FROM scenario_counter")
if err != nil {
_ = scenarioData.Close()
s.logger.Error("Failed to get scenario counter info from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for scenarioData.Next() {
err = scenarioData.Scan(&scenario.MainID, &scenario.CategoryID)
if err != nil {
continue
}
scenarios = append(scenarios, scenario)
}
// Trim excess scenarios
if len(scenarios) > 128 {
scenarios = scenarios[:128]
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(len(scenarios)))
for _, scenario := range scenarios {
bf.WriteUint32(scenario.MainID)
// If item exchange
switch scenario.CategoryID {
case 3, 6, 7:
bf.WriteBool(true)
default:
bf.WriteBool(false)
}
bf.WriteUint8(scenario.CategoryID)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
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)
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 {
s.logger.Error("Failed to reset daily quests", zap.Error(err))
}
}
var bonusQuests, dailyQuests, promoPoints uint32
_ = s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints)
resp := byteframe.NewByteFrame()
resp.WriteUint8(3) // Maybe a count of uint32(s)?
resp.WriteUint32(bonusQuests)
resp.WriteUint32(dailyQuests)
resp.WriteUint32(promoPoints)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateEtcPoint)
var column string
switch pkt.PointType {
case 0:
column = "bonus_quests"
case 1:
column = "daily_quests"
case 2:
column = "promo_points"
}
var value int16
err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value)
if err == nil {
if value+pkt.Delta < 0 {
if _, err := s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID); err != nil {
s.logger.Error("Failed to reset etc point", zap.Error(err))
}
} else {
if _, err := s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID); err != nil {
s.logger.Error("Failed to update etc point", zap.Error(err))
}
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUnreserveSrg)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEarthStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeWeekStart().Unix())) // Start
bf.WriteUint32(uint32(TimeWeekNext().Unix())) // End
bf.WriteInt32(s.server.erupeConfig.EarthStatus)
bf.WriteInt32(s.server.erupeConfig.EarthID)
for i, m := range s.server.erupeConfig.EarthMonsters {
if _config.ErupeConfig.RealClientMode <= _config.G9 {
if i == 3 {
break
}
}
if i == 4 {
break
}
bf.WriteInt32(m)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfRegistSpabiTime(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEarthValue(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEarthValue)
type EarthValues struct {
Value []uint32
}
var earthValues []EarthValues
switch pkt.ReqType {
case 1:
earthValues = []EarthValues{
{[]uint32{1, 312, 0, 0, 0, 0}},
{[]uint32{2, 99, 0, 0, 0, 0}},
}
case 2:
earthValues = []EarthValues{
{[]uint32{1, 5771, 0, 0, 0, 0}},
{[]uint32{2, 1847, 0, 0, 0, 0}},
}
case 3:
earthValues = []EarthValues{
{[]uint32{1001, 36, 0, 0, 0, 0}},
{[]uint32{9001, 3, 0, 0, 0, 0}},
{[]uint32{9002, 10, 300, 0, 0, 0}},
}
}
var data []*byteframe.ByteFrame
for _, i := range earthValues {
bf := byteframe.NewByteFrame()
for _, j := range i.Value {
bf.WriteUint32(j)
}
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfDebugPostValue(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetRandFromTable(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRandFromTable)
bf := byteframe.NewByteFrame()
for i := uint16(0); i < pkt.Results; i++ {
bf.WriteUint32(0)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetSenyuDailyCount)
bf := byteframe.NewByteFrame()
bf.WriteUint16(0)
bf.WriteUint16(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
func equipSkinHistSize() int {
size := 3200
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
size = 2560
}
if _config.ErupeConfig.RealClientMode <= _config.Z1 {
size = 1280
}
return size
}
func handleMsgMhfGetEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEquipSkinHist)
size := equipSkinHistSize()
var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(skin_hist::bytea, $2::bytea) FROM characters WHERE id = $1", s.charID, make([]byte, size)).Scan(&data)
if err != nil {
s.logger.Error("Failed to load skin_hist", zap.Error(err))
data = make([]byte, size)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateEquipSkinHist)
size := equipSkinHistSize()
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)
if err != nil {
s.logger.Error("Failed to get skin_hist", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bit := int(pkt.ArmourID) - 10000
startByte := (size / 5) * int(pkt.MogType)
// psql set_bit could also work but I couldn't get it working
byteInd := bit / 8
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 {
s.logger.Error("Failed to update skin history", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetUdShopCoin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdShopCoin)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUseUdShopCoin(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetEnhancedMinidata)
// this looks to be the detailed chunk of information you can pull up on players in town
var data []byte
err := s.server.db.QueryRow("SELECT minidata FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load minidata")
data = make([]byte, 1)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSetEnhancedMinidata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetEnhancedMinidata)
dumpSaveData(s, pkt.RawDataPayload, "minidata")
_, err := s.server.db.Exec("UPDATE characters SET minidata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Error("Failed to save minidata", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetLobbyCrowd(s *Session, p mhfpacket.MHFPacket) {
// this requests a specific server's population but seems to have been
// broken at some point on live as every example response across multiple
// servers sends back the exact same information?
// It can be worried about later if we ever get to the point where there are
// full servers to actually need to migrate people from and empty ones to
pkt := p.(*mhfpacket.MsgMhfGetLobbyCrowd)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x320))
}
type TrendWeapon struct {
WeaponType uint8
WeaponID uint16
}
func handleMsgMhfGetTrendWeapon(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTrendWeapon)
trendWeapons := [14][3]TrendWeapon{}
for i := uint8(0); i < 14; i++ {
rows, err := s.server.db.Query(`SELECT weapon_id FROM trend_weapons WHERE weapon_type=$1 ORDER BY count DESC LIMIT 3`, i)
if err != nil {
continue
}
j := 0
for rows.Next() {
trendWeapons[i][j].WeaponType = i
_ = rows.Scan(&trendWeapons[i][j].WeaponID)
j++
}
}
x := uint8(0)
bf := byteframe.NewByteFrame()
bf.WriteUint8(0)
for _, weaponType := range trendWeapons {
for _, weapon := range weaponType {
bf.WriteUint8(weapon.WeaponType)
bf.WriteUint16(weapon.WeaponID)
x++
}
}
_, _ = bf.Seek(0, 0)
bf.WriteUint8(x)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateUseTrendWeaponLog(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateUseTrendWeaponLog)
if _, err := s.server.db.Exec(`INSERT INTO trend_weapons (weapon_id, weapon_type, count) VALUES ($1, $2, 1) ON CONFLICT (weapon_id) DO
UPDATE SET count = trend_weapons.count+1`, pkt.WeaponID, pkt.WeaponType); err != nil {
s.logger.Error("Failed to update trend weapon log", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}