mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
The festa handler contained event lifecycle management (cleanup expired events, create new ones) and the repo enforced a business rule (skip zero-value soul submissions). Move these into a new FestaService to keep repos as pure data access and consolidate business logic.
528 lines
16 KiB
Go
528 lines
16 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"sort"
|
|
"time"
|
|
|
|
"erupe-ce/common/byteframe"
|
|
ps "erupe-ce/common/pascalstring"
|
|
"erupe-ce/common/token"
|
|
cfg "erupe-ce/config"
|
|
"erupe-ce/network/mhfpacket"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfSaveMezfesData)
|
|
saveCharacterData(s, pkt.AckHandle, "mezfes", pkt.RawDataPayload, 4096)
|
|
}
|
|
|
|
func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfLoadMezfesData)
|
|
loadCharacterData(s, pkt.AckHandle, "mezfes",
|
|
[]byte{0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
|
}
|
|
|
|
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
|
|
bf := byteframe.NewByteFrame()
|
|
state := s.server.erupeConfig.DebugOptions.TournamentOverride
|
|
// Unk
|
|
// Unk
|
|
// Start?
|
|
// End?
|
|
midnight := TimeMidnight()
|
|
switch state {
|
|
case 1:
|
|
bf.WriteUint32(uint32(midnight.Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(13 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(20 * 24 * time.Hour).Unix()))
|
|
case 2:
|
|
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(10 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
|
|
case 3:
|
|
bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(-10 * 24 * time.Hour).Unix()))
|
|
bf.WriteUint32(uint32(midnight.Unix()))
|
|
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
|
|
default:
|
|
bf.WriteBytes(make([]byte, 16))
|
|
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // TS Current Time
|
|
bf.WriteUint8(3)
|
|
bf.WriteBytes(make([]byte, 4))
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
return
|
|
}
|
|
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // TS Current Time
|
|
bf.WriteUint8(3)
|
|
ps.Uint8(bf, "", false)
|
|
bf.WriteUint16(0) // numEvents
|
|
bf.WriteUint8(0) // numCups
|
|
|
|
/*
|
|
struct event
|
|
uint32 eventID
|
|
uint16 unk
|
|
uint16 unk
|
|
uint32 unk
|
|
psUint8 name
|
|
|
|
struct cup
|
|
uint32 cupID
|
|
uint16 unk
|
|
uint16 unk
|
|
uint16 unk
|
|
psUint8 name
|
|
psUint16 desc
|
|
*/
|
|
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
// Festa timing constants (all values in seconds)
|
|
const (
|
|
festaVotingDuration = 9000 // 150 min voting window
|
|
festaRewardDuration = 1240200 // ~14.35 days reward period
|
|
festaEventLifespan = 2977200 // ~34.5 days total event window
|
|
)
|
|
|
|
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
|
timestamps := make([]uint32, 5)
|
|
midnight := TimeMidnight()
|
|
if debug && start <= 3 {
|
|
midnight := uint32(midnight.Unix())
|
|
switch start {
|
|
case 1:
|
|
timestamps[0] = midnight
|
|
timestamps[1] = timestamps[0] + secsPerWeek
|
|
timestamps[2] = timestamps[1] + secsPerWeek
|
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
|
case 2:
|
|
timestamps[0] = midnight - secsPerWeek
|
|
timestamps[1] = midnight
|
|
timestamps[2] = timestamps[1] + secsPerWeek
|
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
|
case 3:
|
|
timestamps[0] = midnight - 2*secsPerWeek
|
|
timestamps[1] = midnight - secsPerWeek
|
|
timestamps[2] = midnight
|
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
|
}
|
|
return timestamps
|
|
}
|
|
var err error
|
|
start, err = s.server.festaService.EnsureActiveEvent(start, TimeAdjusted(), midnight.Add(24*time.Hour))
|
|
if err != nil {
|
|
s.logger.Error("Failed to ensure active festa event", zap.Error(err))
|
|
}
|
|
timestamps[0] = start
|
|
timestamps[1] = timestamps[0] + secsPerWeek
|
|
timestamps[2] = timestamps[1] + secsPerWeek
|
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
|
return timestamps
|
|
}
|
|
|
|
// FestaTrial represents a festa trial/challenge entry.
|
|
type FestaTrial struct {
|
|
ID uint32 `db:"id"`
|
|
Objective uint16 `db:"objective"`
|
|
GoalID uint32 `db:"goal_id"`
|
|
TimesReq uint16 `db:"times_req"`
|
|
Locale uint16 `db:"locale_req"`
|
|
Reward uint16 `db:"reward"`
|
|
Monopoly FestivalColor `db:"monopoly"`
|
|
Unk uint16
|
|
}
|
|
|
|
// FestaReward represents a festa reward entry.
|
|
type FestaReward struct {
|
|
Unk0 uint8
|
|
Unk1 uint8
|
|
ItemType uint16
|
|
Quantity uint16
|
|
ItemID uint16
|
|
MinHR uint16 // Minimum Hunter Rank to receive this reward
|
|
MinSR uint16 // Minimum Skill Rank (max across weapon types) to receive this reward
|
|
MinGR uint8 // Minimum G Rank to receive this reward
|
|
}
|
|
|
|
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
|
|
bf := byteframe.NewByteFrame()
|
|
|
|
const festaIDSentinel = uint32(0xDEADBEEF)
|
|
id, start := festaIDSentinel, uint32(0)
|
|
events, err := s.server.festaRepo.GetFestaEvents()
|
|
if err != nil {
|
|
s.logger.Error("Failed to query festa schedule", zap.Error(err))
|
|
} else {
|
|
for _, e := range events {
|
|
id = e.ID
|
|
start = e.StartTime
|
|
}
|
|
}
|
|
|
|
var timestamps []uint32
|
|
if s.server.erupeConfig.DebugOptions.FestaOverride >= 0 {
|
|
if s.server.erupeConfig.DebugOptions.FestaOverride == 0 {
|
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DebugOptions.FestaOverride), true)
|
|
} else {
|
|
timestamps = generateFestaTimestamps(s, start, false)
|
|
}
|
|
|
|
if timestamps[0] > uint32(TimeAdjusted().Unix()) {
|
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
blueSouls, err := s.server.festaRepo.GetTeamSouls("blue")
|
|
if err != nil {
|
|
s.logger.Error("Failed to get blue souls", zap.Error(err))
|
|
}
|
|
redSouls, err := s.server.festaRepo.GetTeamSouls("red")
|
|
if err != nil {
|
|
s.logger.Error("Failed to get red souls", zap.Error(err))
|
|
}
|
|
|
|
bf.WriteUint32(id)
|
|
for _, timestamp := range timestamps {
|
|
bf.WriteUint32(timestamp)
|
|
}
|
|
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
|
bf.WriteUint8(4)
|
|
ps.Uint8(bf, "", false)
|
|
bf.WriteUint32(0)
|
|
bf.WriteUint32(blueSouls)
|
|
bf.WriteUint32(redSouls)
|
|
|
|
trials, err := s.server.festaRepo.GetTrialsWithMonopoly()
|
|
if err != nil {
|
|
s.logger.Error("Failed to query festa trials", zap.Error(err))
|
|
}
|
|
bf.WriteUint16(uint16(len(trials)))
|
|
for _, trial := range trials {
|
|
bf.WriteUint32(trial.ID)
|
|
bf.WriteUint16(trial.Objective)
|
|
bf.WriteUint32(trial.GoalID)
|
|
bf.WriteUint16(trial.TimesReq)
|
|
bf.WriteUint16(trial.Locale)
|
|
bf.WriteUint16(trial.Reward)
|
|
bf.WriteInt16(FestivalColorCodes[trial.Monopoly])
|
|
if s.server.erupeConfig.RealClientMode >= cfg.F4 { // Not in S6.0
|
|
bf.WriteUint16(trial.Unk)
|
|
}
|
|
}
|
|
|
|
// The Winner and Loser Armor IDs are missing
|
|
// Item 7011 may not exist in older versions, remove to prevent crashes
|
|
// Fields: {Unk0, Unk1, ItemType, Quantity, ItemID, MinHR, MinSR, MinGR}
|
|
rewards := []FestaReward{
|
|
{1, 0, 7, 350, 1520, 0, 0, 0},
|
|
{1, 0, 7, 1000, 7011, 0, 0, 1},
|
|
{1, 0, 12, 1000, 0, 0, 0, 0},
|
|
{1, 0, 13, 0, 0, 0, 0, 0},
|
|
//{1, 0, 1, 0, 0, 0, 0, 0},
|
|
{2, 0, 7, 350, 1520, 0, 0, 0},
|
|
{2, 0, 7, 1000, 7011, 0, 0, 1},
|
|
{2, 0, 12, 1000, 0, 0, 0, 0},
|
|
{2, 0, 13, 0, 0, 0, 0, 0},
|
|
//{2, 0, 4, 0, 0, 0, 0, 0},
|
|
{3, 0, 7, 350, 1520, 0, 0, 0},
|
|
{3, 0, 7, 1000, 7011, 0, 0, 1},
|
|
{3, 0, 12, 1000, 0, 0, 0, 0},
|
|
{3, 0, 13, 0, 0, 0, 0, 0},
|
|
//{3, 0, 1, 0, 0, 0, 0, 0},
|
|
{4, 0, 7, 350, 1520, 0, 0, 0},
|
|
{4, 0, 7, 1000, 7011, 0, 0, 1},
|
|
{4, 0, 12, 1000, 0, 0, 0, 0},
|
|
{4, 0, 13, 0, 0, 0, 0, 0},
|
|
//{4, 0, 4, 0, 0, 0, 0, 0},
|
|
{5, 0, 7, 350, 1520, 0, 0, 0},
|
|
{5, 0, 7, 1000, 7011, 0, 0, 1},
|
|
{5, 0, 12, 1000, 0, 0, 0, 0},
|
|
{5, 0, 13, 0, 0, 0, 0, 0},
|
|
//{5, 0, 1, 0, 0, 0, 0, 0},
|
|
}
|
|
|
|
bf.WriteUint16(uint16(len(rewards)))
|
|
for _, reward := range rewards {
|
|
bf.WriteUint8(reward.Unk0)
|
|
bf.WriteUint8(reward.Unk1)
|
|
bf.WriteUint16(reward.ItemType)
|
|
bf.WriteUint16(reward.Quantity)
|
|
bf.WriteUint16(reward.ItemID)
|
|
// Confirmed present in G3 via Wii U disassembly of import_festa_info
|
|
if s.server.erupeConfig.RealClientMode >= cfg.G3 {
|
|
bf.WriteUint16(reward.MinHR)
|
|
bf.WriteUint16(reward.MinSR)
|
|
bf.WriteUint8(reward.MinGR)
|
|
}
|
|
}
|
|
if s.server.erupeConfig.RealClientMode <= cfg.G61 {
|
|
if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF {
|
|
s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF
|
|
}
|
|
bf.WriteUint16(uint16(s.server.erupeConfig.GameplayOptions.MaximumFP))
|
|
} else {
|
|
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
|
|
}
|
|
bf.WriteUint16(100) // Reward multiplier (%)
|
|
|
|
bf.WriteUint16(4)
|
|
for i := uint16(0); i < 4; i++ {
|
|
ranking, err := s.server.festaRepo.GetTopGuildForTrial(i + 1)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
s.logger.Error("Failed to get festa trial ranking", zap.Error(err))
|
|
}
|
|
bf.WriteUint32(ranking.GuildID)
|
|
bf.WriteUint16(i + 1)
|
|
bf.WriteInt16(FestivalColorCodes[ranking.Team])
|
|
ps.Uint8(bf, ranking.GuildName, true)
|
|
}
|
|
bf.WriteUint16(7)
|
|
for i := uint16(0); i < 7; i++ {
|
|
offset := secsPerDay * uint32(i)
|
|
ranking, err := s.server.festaRepo.GetTopGuildInWindow(timestamps[1]+offset, timestamps[1]+offset+secsPerDay)
|
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
|
s.logger.Error("Failed to get festa daily ranking", zap.Error(err))
|
|
}
|
|
bf.WriteUint32(ranking.GuildID)
|
|
bf.WriteUint16(i + 1)
|
|
bf.WriteInt16(FestivalColorCodes[ranking.Team])
|
|
ps.Uint8(bf, ranking.GuildName, true)
|
|
}
|
|
|
|
bf.WriteUint32(0) // Clan goal
|
|
// Final bonus rates
|
|
bf.WriteUint32(5000) // 5000+ souls
|
|
bf.WriteUint32(2000) // 2000-4999 souls
|
|
bf.WriteUint32(1000) // 1000-1999 souls
|
|
bf.WriteUint32(100) // 100-999 souls
|
|
bf.WriteUint16(300) // 300% bonus
|
|
bf.WriteUint16(200) // 200% bonus
|
|
bf.WriteUint16(150) // 150% bonus
|
|
bf.WriteUint16(100) // Normal rate
|
|
bf.WriteUint16(50) // 50% penalty
|
|
|
|
if s.server.erupeConfig.RealClientMode >= cfg.G52 {
|
|
ps.Uint16(bf, "", false)
|
|
}
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
// state festa (U)ser
|
|
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
|
|
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
|
applicant := false
|
|
if guild != nil {
|
|
var appErr error
|
|
applicant, appErr = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
|
if appErr != nil {
|
|
s.logger.Warn("Failed to check guild application status", zap.Error(appErr))
|
|
}
|
|
}
|
|
if err != nil || guild == nil || applicant {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
souls, err := s.server.festaRepo.GetCharSouls(s.charID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get festa user souls", zap.Error(err))
|
|
}
|
|
claimed := s.server.festaRepo.HasClaimedMainPrize(s.charID)
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(souls)
|
|
if !claimed {
|
|
bf.WriteBool(true)
|
|
bf.WriteBool(false)
|
|
} else {
|
|
bf.WriteBool(false)
|
|
bf.WriteBool(true)
|
|
}
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
// state festa (G)uild
|
|
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
|
|
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
|
applicant := false
|
|
if guild != nil {
|
|
var appErr error
|
|
applicant, appErr = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
|
if appErr != nil {
|
|
s.logger.Warn("Failed to check guild application status", zap.Error(appErr))
|
|
}
|
|
}
|
|
resp := byteframe.NewByteFrame()
|
|
if err != nil || guild == nil || applicant {
|
|
resp.WriteUint32(0)
|
|
resp.WriteInt32(0)
|
|
resp.WriteInt32(-1)
|
|
resp.WriteInt32(0)
|
|
resp.WriteInt32(0)
|
|
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
|
return
|
|
}
|
|
resp.WriteUint32(guild.Souls)
|
|
resp.WriteInt32(1) // unk
|
|
resp.WriteInt32(1) // unk, rank?
|
|
resp.WriteInt32(1) // unk
|
|
resp.WriteInt32(1) // unk
|
|
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
|
}
|
|
|
|
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
|
|
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
|
if err != nil || guild == nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
members, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
sort.Slice(members, func(i, j int) bool {
|
|
return members[i].Souls > members[j].Souls
|
|
})
|
|
var validMembers []*GuildMember
|
|
for _, member := range members {
|
|
if member.Souls > 0 {
|
|
validMembers = append(validMembers, member)
|
|
}
|
|
}
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint16(uint16(len(validMembers)))
|
|
bf.WriteUint16(0) // Unk
|
|
for _, member := range validMembers {
|
|
bf.WriteUint32(member.CharID)
|
|
if s.server.erupeConfig.RealClientMode <= cfg.Z1 {
|
|
bf.WriteUint16(uint16(member.Souls))
|
|
bf.WriteUint16(0)
|
|
} else {
|
|
bf.WriteUint32(member.Souls)
|
|
}
|
|
}
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
|
|
if err := s.server.festaRepo.VoteTrial(s.charID, pkt.TrialID); err != nil {
|
|
s.logger.Error("Failed to update festa trial vote", zap.Error(err))
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
|
|
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
|
if err != nil || guild == nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
team := uint32(token.RNG.Intn(2))
|
|
teamName := "blue"
|
|
if team == 1 {
|
|
teamName = "red"
|
|
}
|
|
if err := s.server.festaRepo.RegisterGuild(guild.ID, teamName); err != nil {
|
|
s.logger.Error("Failed to register guild for festa", zap.Error(err))
|
|
}
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(team)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
|
|
if err := s.server.festaService.SubmitSouls(s.charID, pkt.GuildID, pkt.Souls); err != nil {
|
|
s.logger.Error("Failed to submit festa souls", zap.Error(err))
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
|
|
if err := s.server.festaRepo.ClaimPrize(0, s.charID); err != nil {
|
|
s.logger.Error("Failed to accept festa prize", zap.Error(err))
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
|
|
if err := s.server.festaRepo.ClaimPrize(pkt.PrizeID, s.charID); err != nil {
|
|
s.logger.Error("Failed to accept festa personal prize", zap.Error(err))
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
|
|
if err := s.server.festaRepo.ClaimPrize(pkt.PrizeID, s.charID); err != nil {
|
|
s.logger.Error("Failed to accept festa intermediate prize", zap.Error(err))
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
// Prize represents a festa prize entry.
|
|
type Prize struct {
|
|
ID uint32 `db:"id"`
|
|
Tier uint32 `db:"tier"`
|
|
SoulsReq uint32 `db:"souls_req"`
|
|
ItemID uint32 `db:"item_id"`
|
|
NumItem uint32 `db:"num_item"`
|
|
Claimed int `db:"claimed"`
|
|
}
|
|
|
|
func writePrizeList(s *Session, pkt mhfpacket.MHFPacket, ackHandle uint32, prizeType string) {
|
|
prizes, err := s.server.festaRepo.ListPrizes(s.charID, prizeType)
|
|
var count uint32
|
|
prizeData := byteframe.NewByteFrame()
|
|
if err != nil {
|
|
s.logger.Error("Failed to query festa prizes", zap.Error(err), zap.String("type", prizeType))
|
|
} else {
|
|
for _, prize := range prizes {
|
|
count++
|
|
prizeData.WriteUint32(prize.ID)
|
|
prizeData.WriteUint32(prize.Tier)
|
|
prizeData.WriteUint32(prize.SoulsReq)
|
|
prizeData.WriteUint32(7) // Unk
|
|
prizeData.WriteUint32(prize.ItemID)
|
|
prizeData.WriteUint32(prize.NumItem)
|
|
prizeData.WriteBool(prize.Claimed > 0)
|
|
}
|
|
}
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(count)
|
|
bf.WriteBytes(prizeData.Data())
|
|
doAckBufSucceed(s, ackHandle, bf.Data())
|
|
}
|
|
|
|
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
|
|
writePrizeList(s, p, pkt.AckHandle, "personal")
|
|
}
|
|
|
|
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
|
|
writePrizeList(s, p, pkt.AckHandle, "guild")
|
|
}
|