mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
CharacterSaveData.Save() silently returned on failure (nil decompressed data, compression error, DB error) while the caller unconditionally logged "Saved character data successfully". This made diagnosing save failures difficult (ref #163). Save() now returns an error, and all six call sites check it. The success log in saveAllCharacterData only fires when the save actually persisted.
504 lines
13 KiB
Go
504 lines
13 KiB
Go
package channelserver
|
|
|
|
import (
|
|
cfg "erupe-ce/config"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/common/stringsupport"
|
|
"erupe-ce/network/mhfpacket"
|
|
)
|
|
|
|
// TowerInfoTRP represents tower RP (points) info.
|
|
type TowerInfoTRP struct {
|
|
TR int32
|
|
TRP int32
|
|
}
|
|
|
|
// TowerInfoSkill represents tower skill info.
|
|
type TowerInfoSkill struct {
|
|
TSP int32
|
|
Skills []int16 // 64
|
|
}
|
|
|
|
// TowerInfoHistory represents tower clear history.
|
|
type TowerInfoHistory struct {
|
|
Unk0 []int16 // 5
|
|
Unk1 []int16 // 5
|
|
}
|
|
|
|
// TowerInfoLevel represents tower level info.
|
|
type TowerInfoLevel struct {
|
|
Floors int32
|
|
Unk1 int32
|
|
Unk2 int32
|
|
Unk3 int32
|
|
}
|
|
|
|
// EmptyTowerCSV creates an empty CSV string of the given length.
|
|
func EmptyTowerCSV(len int) string {
|
|
temp := make([]string, len)
|
|
for i := range temp {
|
|
temp[i] = "0"
|
|
}
|
|
return strings.Join(temp, ",")
|
|
}
|
|
|
|
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
|
|
var data []*byteframe.ByteFrame
|
|
type TowerInfo struct {
|
|
TRP []TowerInfoTRP
|
|
Skill []TowerInfoSkill
|
|
History []TowerInfoHistory
|
|
Level []TowerInfoLevel
|
|
}
|
|
|
|
towerInfo := TowerInfo{
|
|
TRP: []TowerInfoTRP{{0, 0}},
|
|
Skill: []TowerInfoSkill{{0, make([]int16, 64)}},
|
|
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
|
|
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
|
|
}
|
|
|
|
td, err := s.server.towerRepo.GetTowerData(s.charID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to initialize tower data", zap.Error(err))
|
|
} else {
|
|
towerInfo.TRP[0].TR = td.TR
|
|
towerInfo.TRP[0].TRP = td.TRP
|
|
towerInfo.Skill[0].TSP = td.TSP
|
|
towerInfo.Level[0].Floors = td.Block1
|
|
towerInfo.Level[1].Floors = td.Block2
|
|
}
|
|
|
|
if s.server.erupeConfig.RealClientMode <= cfg.G7 {
|
|
towerInfo.Level = towerInfo.Level[:1]
|
|
}
|
|
|
|
for i, skill := range stringsupport.CSVElems(td.Skills) {
|
|
if skill < math.MinInt16 || skill > math.MaxInt16 {
|
|
continue
|
|
}
|
|
towerInfo.Skill[0].Skills[i] = int16(skill)
|
|
}
|
|
|
|
switch pkt.InfoType {
|
|
case 1:
|
|
for _, trp := range towerInfo.TRP {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteInt32(trp.TR)
|
|
bf.WriteInt32(trp.TRP)
|
|
data = append(data, bf)
|
|
}
|
|
case 2:
|
|
for _, skills := range towerInfo.Skill {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteInt32(skills.TSP)
|
|
for i := range skills.Skills {
|
|
bf.WriteInt16(skills.Skills[i])
|
|
}
|
|
data = append(data, bf)
|
|
}
|
|
case 4:
|
|
for _, history := range towerInfo.History {
|
|
bf := byteframe.NewByteFrame()
|
|
for i := range history.Unk0 {
|
|
bf.WriteInt16(history.Unk0[i])
|
|
}
|
|
for i := range history.Unk1 {
|
|
bf.WriteInt16(history.Unk1[i])
|
|
}
|
|
data = append(data, bf)
|
|
}
|
|
case 3, 5:
|
|
for _, level := range towerInfo.Level {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteInt32(level.Floors)
|
|
bf.WriteInt32(level.Unk1)
|
|
bf.WriteInt32(level.Unk2)
|
|
bf.WriteInt32(level.Unk3)
|
|
data = append(data, bf)
|
|
}
|
|
}
|
|
doAckEarthSucceed(s, pkt.AckHandle, data)
|
|
}
|
|
|
|
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
|
|
|
if s.server.erupeConfig.DebugOptions.QuestTools {
|
|
s.logger.Debug(
|
|
p.Opcode().String(),
|
|
zap.Uint32("InfoType", pkt.InfoType),
|
|
zap.Uint32("Unk1", pkt.Unk1),
|
|
zap.Int32("Skill", pkt.Skill),
|
|
zap.Int32("TR", pkt.TR),
|
|
zap.Int32("TRP", pkt.TRP),
|
|
zap.Int32("Cost", pkt.Cost),
|
|
zap.Int32("Unk6", pkt.Unk6),
|
|
zap.Int32("Unk7", pkt.Unk7),
|
|
zap.Int32("Block1", pkt.Block1),
|
|
zap.Int64("Unk9", pkt.Unk9),
|
|
)
|
|
}
|
|
|
|
switch pkt.InfoType {
|
|
case 2:
|
|
skills, _ := s.server.towerRepo.GetSkills(s.charID)
|
|
newSkills := stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1)
|
|
if err := s.server.towerRepo.UpdateSkills(s.charID, newSkills, pkt.Cost); err != nil {
|
|
s.logger.Error("Failed to update tower skills", zap.Error(err))
|
|
}
|
|
case 1, 7:
|
|
// This might give too much TSP? No idea what the rate is supposed to be
|
|
if err := s.server.towerRepo.UpdateProgress(s.charID, pkt.TR, pkt.TRP, pkt.Cost, pkt.Block1); err != nil {
|
|
s.logger.Error("Failed to update tower progress", zap.Error(err))
|
|
}
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
// Default missions
|
|
var tenrouiraiData = []TenrouiraiData{
|
|
{1, 1, 80, 0, 2, 2, 1, 1, 2, 2},
|
|
{1, 4, 16, 0, 2, 2, 1, 1, 2, 2},
|
|
{1, 6, 50, 0, 2, 2, 1, 0, 2, 2},
|
|
{1, 4, 12, 50, 2, 2, 1, 1, 2, 2},
|
|
{1, 3, 50, 0, 2, 2, 1, 1, 2, 2},
|
|
{2, 5, 40000, 0, 2, 2, 1, 0, 2, 2},
|
|
{1, 5, 50000, 50, 2, 2, 1, 1, 2, 2},
|
|
{2, 1, 60, 0, 2, 2, 1, 1, 2, 2},
|
|
{2, 3, 50, 0, 2, 1, 1, 0, 1, 2},
|
|
{2, 3, 40, 50, 2, 1, 1, 1, 1, 2},
|
|
{2, 4, 12, 0, 2, 1, 1, 1, 1, 2},
|
|
{2, 6, 40, 0, 2, 1, 1, 0, 1, 2},
|
|
{1, 1, 60, 50, 2, 1, 2, 1, 1, 2},
|
|
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
|
{1, 6, 50, 0, 3, 1, 2, 0, 1, 2},
|
|
{1, 4, 16, 50, 3, 1, 2, 1, 1, 2},
|
|
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
|
{2, 3, 40, 0, 3, 1, 2, 0, 1, 2},
|
|
{1, 3, 50, 50, 3, 1, 2, 1, 1, 2},
|
|
{2, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
|
{2, 6, 40, 0, 3, 1, 2, 0, 1, 1},
|
|
{2, 1, 60, 50, 3, 1, 2, 1, 1, 1},
|
|
{2, 6, 50, 0, 3, 1, 2, 1, 1, 1},
|
|
{2, 4, 12, 0, 3, 1, 2, 0, 1, 1},
|
|
{1, 1, 80, 50, 3, 1, 2, 1, 1, 1},
|
|
{1, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
|
{1, 3, 50, 0, 3, 1, 2, 0, 1, 1},
|
|
{1, 4, 16, 50, 3, 1, 0, 1, 1, 1},
|
|
{1, 6, 50, 0, 3, 1, 0, 1, 1, 1},
|
|
{2, 3, 40, 0, 3, 1, 0, 1, 1, 1},
|
|
{1, 1, 80, 50, 3, 1, 0, 0, 1, 1},
|
|
{2, 5, 40000, 0, 3, 1, 0, 0, 1, 1},
|
|
{2, 6, 40, 0, 3, 1, 0, 0, 1, 1},
|
|
}
|
|
|
|
// TenrouiraiProgress represents Tenrouirai (sky corridor) progress.
|
|
type TenrouiraiProgress struct {
|
|
Page uint8
|
|
Mission1 uint16
|
|
Mission2 uint16
|
|
Mission3 uint16
|
|
}
|
|
|
|
// TenrouiraiReward represents a Tenrouirai reward.
|
|
type TenrouiraiReward struct {
|
|
Index uint8
|
|
Item []uint16 // 5
|
|
Quantity []uint8 // 5
|
|
}
|
|
|
|
// TenrouiraiKeyScore represents a Tenrouirai key score.
|
|
type TenrouiraiKeyScore struct {
|
|
Unk0 uint8
|
|
Unk1 int32
|
|
}
|
|
|
|
// TenrouiraiData represents Tenrouirai data.
|
|
type TenrouiraiData struct {
|
|
Block uint8
|
|
Mission uint8
|
|
// 1 = Floors climbed
|
|
// 2 = Collect antiques
|
|
// 3 = Open chests
|
|
// 4 = Cats saved
|
|
// 5 = TRP acquisition
|
|
// 6 = Monster slays
|
|
Goal uint16
|
|
Cost uint16
|
|
Skill1 uint8 // 80
|
|
Skill2 uint8 // 40
|
|
Skill3 uint8 // 40
|
|
Skill4 uint8 // 20
|
|
Skill5 uint8 // 40
|
|
Skill6 uint8 // 50
|
|
}
|
|
|
|
// TenrouiraiCharScore represents a Tenrouirai per-character score.
|
|
type TenrouiraiCharScore struct {
|
|
Score int32
|
|
Name string
|
|
}
|
|
|
|
// TenrouiraiTicket represents a Tenrouirai ticket entry.
|
|
type TenrouiraiTicket struct {
|
|
Unk0 uint8
|
|
RP uint32
|
|
Unk2 uint32
|
|
}
|
|
|
|
// Tenrouirai represents complete Tenrouirai data.
|
|
type Tenrouirai struct {
|
|
Progress []TenrouiraiProgress
|
|
Reward []TenrouiraiReward
|
|
KeyScore []TenrouiraiKeyScore
|
|
Data []TenrouiraiData
|
|
CharScore []TenrouiraiCharScore
|
|
Ticket []TenrouiraiTicket
|
|
}
|
|
|
|
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
|
|
var data []*byteframe.ByteFrame
|
|
|
|
tenrouirai := Tenrouirai{
|
|
Progress: []TenrouiraiProgress{{1, 0, 0, 0}},
|
|
Data: tenrouiraiData,
|
|
Ticket: []TenrouiraiTicket{{0, 0, 0}},
|
|
}
|
|
|
|
switch pkt.DataType {
|
|
case 1:
|
|
for _, tdata := range tenrouirai.Data {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint8(tdata.Block)
|
|
bf.WriteUint8(tdata.Mission)
|
|
bf.WriteUint16(tdata.Goal)
|
|
bf.WriteUint16(tdata.Cost)
|
|
bf.WriteUint8(tdata.Skill1)
|
|
bf.WriteUint8(tdata.Skill2)
|
|
bf.WriteUint8(tdata.Skill3)
|
|
bf.WriteUint8(tdata.Skill4)
|
|
bf.WriteUint8(tdata.Skill5)
|
|
bf.WriteUint8(tdata.Skill6)
|
|
data = append(data, bf)
|
|
}
|
|
case 2:
|
|
for _, reward := range tenrouirai.Reward {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint8(reward.Index)
|
|
bf.WriteUint16(reward.Item[0])
|
|
bf.WriteUint16(reward.Item[1])
|
|
bf.WriteUint16(reward.Item[2])
|
|
bf.WriteUint16(reward.Item[3])
|
|
bf.WriteUint16(reward.Item[4])
|
|
bf.WriteUint8(reward.Quantity[0])
|
|
bf.WriteUint8(reward.Quantity[1])
|
|
bf.WriteUint8(reward.Quantity[2])
|
|
bf.WriteUint8(reward.Quantity[3])
|
|
bf.WriteUint8(reward.Quantity[4])
|
|
data = append(data, bf)
|
|
}
|
|
case 4:
|
|
progress, err := s.server.towerService.GetTenrouiraiProgressCapped(pkt.GuildID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to read tower mission page", zap.Error(err))
|
|
} else {
|
|
tenrouirai.Progress[0].Page = progress.Page
|
|
tenrouirai.Progress[0].Mission1 = progress.Mission1
|
|
tenrouirai.Progress[0].Mission2 = progress.Mission2
|
|
tenrouirai.Progress[0].Mission3 = progress.Mission3
|
|
}
|
|
|
|
for _, progress := range tenrouirai.Progress {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint8(progress.Page)
|
|
bf.WriteUint16(progress.Mission1)
|
|
bf.WriteUint16(progress.Mission2)
|
|
bf.WriteUint16(progress.Mission3)
|
|
data = append(data, bf)
|
|
}
|
|
case 5:
|
|
scores, err := s.server.towerRepo.GetTenrouiraiMissionScores(pkt.GuildID, pkt.MissionIndex)
|
|
if err != nil {
|
|
s.logger.Error("Failed to query tower mission scores", zap.Error(err))
|
|
}
|
|
for _, charScore := range scores {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteInt32(charScore.Score)
|
|
bf.WriteBytes(stringsupport.PaddedString(charScore.Name, 14, true))
|
|
data = append(data, bf)
|
|
}
|
|
case 6:
|
|
rp, _ := s.server.towerRepo.GetGuildTowerRP(pkt.GuildID)
|
|
tenrouirai.Ticket[0].RP = rp
|
|
for _, ticket := range tenrouirai.Ticket {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint8(ticket.Unk0)
|
|
bf.WriteUint32(ticket.RP)
|
|
bf.WriteUint32(ticket.Unk2)
|
|
data = append(data, bf)
|
|
}
|
|
}
|
|
|
|
doAckEarthSucceed(s, pkt.AckHandle, data)
|
|
}
|
|
|
|
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
|
|
|
if s.server.erupeConfig.DebugOptions.QuestTools {
|
|
s.logger.Debug(
|
|
p.Opcode().String(),
|
|
zap.Uint8("Unk0", pkt.Unk0),
|
|
zap.Uint8("Op", pkt.Op),
|
|
zap.Uint32("GuildID", pkt.GuildID),
|
|
zap.Uint8("Unk1", pkt.Unk1),
|
|
zap.Uint16("Floors", pkt.Floors),
|
|
zap.Uint16("Antiques", pkt.Antiques),
|
|
zap.Uint16("Chests", pkt.Chests),
|
|
zap.Uint16("Cats", pkt.Cats),
|
|
zap.Uint16("TRP", pkt.TRP),
|
|
zap.Uint16("Slays", pkt.Slays),
|
|
)
|
|
}
|
|
|
|
if pkt.Op == 2 {
|
|
bf := byteframe.NewByteFrame()
|
|
|
|
sd, err := GetCharacterSaveData(s, s.charID)
|
|
if err == nil && sd != nil {
|
|
sd.RP -= pkt.DonatedRP
|
|
if err := sd.Save(s); err != nil {
|
|
s.logger.Error("Failed to save RP after tower donation", zap.Error(err))
|
|
}
|
|
result, err := s.server.towerService.DonateGuildTowerRP(pkt.GuildID, pkt.DonatedRP)
|
|
if err != nil {
|
|
s.logger.Error("Failed to process tower RP donation", zap.Error(err))
|
|
bf.WriteUint32(0)
|
|
} else {
|
|
bf.WriteUint32(uint32(result.ActualDonated))
|
|
}
|
|
} else {
|
|
bf.WriteUint32(0)
|
|
}
|
|
|
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
|
} else {
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
}
|
|
|
|
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfPresentBox)
|
|
var data []*byteframe.ByteFrame
|
|
/*
|
|
bf.WriteUint32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
bf.WriteInt32(0)
|
|
*/
|
|
doAckEarthSucceed(s, pkt.AckHandle, data)
|
|
}
|
|
|
|
// GemInfo represents gem (decoration) info.
|
|
type GemInfo struct {
|
|
Gem uint16
|
|
Quantity uint16
|
|
}
|
|
|
|
// GemHistory represents gem usage history.
|
|
type GemHistory struct {
|
|
Gem uint16
|
|
Message uint16
|
|
Timestamp time.Time
|
|
Sender string
|
|
}
|
|
|
|
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
|
|
var data []*byteframe.ByteFrame
|
|
gemInfo := []GemInfo{}
|
|
gemHistory := []GemHistory{}
|
|
|
|
tempGems, _ := s.server.towerRepo.GetGems(s.charID)
|
|
for i, v := range stringsupport.CSVElems(tempGems) {
|
|
if v < 0 || v > math.MaxUint16 {
|
|
continue
|
|
}
|
|
gemInfo = append(gemInfo, GemInfo{uint16((i / 5 << 8) + (i%5 + 1)), uint16(v)})
|
|
}
|
|
|
|
switch pkt.QueryType {
|
|
case 1:
|
|
for _, info := range gemInfo {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint16(info.Gem)
|
|
bf.WriteUint16(info.Quantity)
|
|
data = append(data, bf)
|
|
}
|
|
case 2:
|
|
for _, history := range gemHistory {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint16(history.Gem)
|
|
bf.WriteUint16(history.Message)
|
|
bf.WriteUint32(uint32(history.Timestamp.Unix()))
|
|
bf.WriteBytes(stringsupport.PaddedString(history.Sender, 14, true))
|
|
data = append(data, bf)
|
|
}
|
|
}
|
|
doAckEarthSucceed(s, pkt.AckHandle, data)
|
|
}
|
|
|
|
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
|
|
|
|
if s.server.erupeConfig.DebugOptions.QuestTools {
|
|
s.logger.Debug(
|
|
p.Opcode().String(),
|
|
zap.Uint32("Op", pkt.Op),
|
|
zap.Uint32("Unk1", pkt.Unk1),
|
|
zap.Int32("Gem", pkt.Gem),
|
|
zap.Int32("Quantity", pkt.Quantity),
|
|
zap.Int32("CID", pkt.CID),
|
|
zap.Int32("Message", pkt.Message),
|
|
zap.Int32("Unk6", pkt.Unk6),
|
|
)
|
|
}
|
|
|
|
switch pkt.Op {
|
|
case 1: // Add gem
|
|
i := int((pkt.Gem >> 8 * 5) + (pkt.Gem - pkt.Gem&0xFF00 - 1%5))
|
|
if err := s.server.towerService.AddGem(s.charID, i, int(pkt.Quantity)); err != nil {
|
|
s.logger.Error("Failed to update tower gems", zap.Error(err))
|
|
}
|
|
case 2: // Transfer gem
|
|
// no way im doing this for now
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfGetNotice(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfGetNotice)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfPostNotice)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|