mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 23:54:33 +01:00
Move business logic for guild disband, resign leadership, leave, post scout, and answer scout from handlers into GuildService methods. Handlers now delegate to the service layer and handle only protocol concerns (packet parsing, ACK responses, cross-channel notifications). Adds 22 new table-driven service tests and sentinel errors for typed error handling (ErrNoEligibleLeader, ErrAlreadyInvited, etc.). DonateRP left in handler due to Session coupling.
261 lines
8.3 KiB
Go
261 lines
8.3 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"time"
|
|
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/common/stringsupport"
|
|
"erupe-ce/network/mhfpacket"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfOperateGuild)
|
|
|
|
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
characterGuildInfo, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
bf := byteframe.NewByteFrame()
|
|
|
|
switch pkt.Action {
|
|
case mhfpacket.OperateGuildDisband:
|
|
result, _ := s.server.guildService.Disband(s.charID, guild.ID)
|
|
response := 0
|
|
if result != nil && result.Success {
|
|
response = 1
|
|
}
|
|
bf.WriteUint32(uint32(response))
|
|
case mhfpacket.OperateGuildResign:
|
|
result, err := s.server.guildService.ResignLeadership(s.charID, guild.ID)
|
|
if err == nil && result.NewLeaderCharID != 0 {
|
|
bf.WriteUint32(result.NewLeaderCharID)
|
|
}
|
|
case mhfpacket.OperateGuildApply:
|
|
err = s.server.guildRepo.CreateApplication(guild.ID, s.charID, s.charID, GuildApplicationTypeApplied)
|
|
if err == nil {
|
|
bf.WriteUint32(guild.LeaderCharID)
|
|
} else {
|
|
bf.WriteUint32(0)
|
|
}
|
|
case mhfpacket.OperateGuildLeave:
|
|
result, _ := s.server.guildService.Leave(s.charID, guild.ID, characterGuildInfo.IsApplicant, guild.Name)
|
|
response := 0
|
|
if result != nil && result.Success {
|
|
response = 1
|
|
}
|
|
bf.WriteUint32(uint32(response))
|
|
case mhfpacket.OperateGuildDonateRank:
|
|
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, 0))
|
|
case mhfpacket.OperateGuildSetApplicationDeny:
|
|
if err := s.server.guildRepo.SetRecruiting(guild.ID, false); err != nil {
|
|
s.logger.Error("Failed to deny guild applications", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildSetApplicationAllow:
|
|
if err := s.server.guildRepo.SetRecruiting(guild.ID, true); err != nil {
|
|
s.logger.Error("Failed to allow guild applications", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildSetAvoidLeadershipTrue:
|
|
handleAvoidLeadershipUpdate(s, pkt, true)
|
|
case mhfpacket.OperateGuildSetAvoidLeadershipFalse:
|
|
handleAvoidLeadershipUpdate(s, pkt, false)
|
|
case mhfpacket.OperateGuildUpdateComment:
|
|
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
guild.Comment = stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild comment", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildUpdateMotto:
|
|
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
_ = pkt.Data1.ReadUint16()
|
|
guild.SubMotto = pkt.Data1.ReadUint8()
|
|
guild.MainMotto = pkt.Data1.ReadUint8()
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild motto", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildRenamePugi1:
|
|
handleRenamePugi(s, pkt.Data2, guild, 1)
|
|
case mhfpacket.OperateGuildRenamePugi2:
|
|
handleRenamePugi(s, pkt.Data2, guild, 2)
|
|
case mhfpacket.OperateGuildRenamePugi3:
|
|
handleRenamePugi(s, pkt.Data2, guild, 3)
|
|
case mhfpacket.OperateGuildChangePugi1:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 1)
|
|
case mhfpacket.OperateGuildChangePugi2:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 2)
|
|
case mhfpacket.OperateGuildChangePugi3:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3)
|
|
case mhfpacket.OperateGuildUnlockOutfit:
|
|
if err := s.server.guildRepo.SetPugiOutfits(guild.ID, pkt.Data1.ReadUint32()); err != nil {
|
|
s.logger.Error("Failed to unlock guild pugi outfit", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildDonateRoom:
|
|
quantity := uint16(pkt.Data1.ReadUint32())
|
|
bf.WriteBytes(handleDonateRP(s, quantity, guild, 2))
|
|
case mhfpacket.OperateGuildDonateEvent:
|
|
quantity := uint16(pkt.Data1.ReadUint32())
|
|
bf.WriteBytes(handleDonateRP(s, quantity, guild, 1))
|
|
if err := s.server.guildRepo.AddMemberDailyRP(s.charID, quantity); err != nil {
|
|
s.logger.Error("Failed to update guild character daily RP", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildEventExchange:
|
|
rp := uint16(pkt.Data1.ReadUint32())
|
|
balance, err := s.server.guildRepo.ExchangeEventRP(guild.ID, rp)
|
|
if err != nil {
|
|
s.logger.Error("Failed to exchange guild event RP", zap.Error(err))
|
|
}
|
|
bf.WriteUint32(balance)
|
|
default:
|
|
s.logger.Error("unhandled operate guild action", zap.Uint8("action", uint8(pkt.Action)))
|
|
}
|
|
|
|
if len(bf.Data()) > 0 {
|
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
|
} else {
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
}
|
|
|
|
func handleRenamePugi(s *Session, bf *byteframe.ByteFrame, guild *Guild, num int) {
|
|
name := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
|
|
switch num {
|
|
case 1:
|
|
guild.PugiName1 = name
|
|
case 2:
|
|
guild.PugiName2 = name
|
|
default:
|
|
guild.PugiName3 = name
|
|
}
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild pugi name", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
|
|
switch num {
|
|
case 1:
|
|
guild.PugiOutfit1 = outfit
|
|
case 2:
|
|
guild.PugiOutfit2 = outfit
|
|
case 3:
|
|
guild.PugiOutfit3 = outfit
|
|
}
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild pugi outfit", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(0)
|
|
saveData, err := GetCharacterSaveData(s, s.charID)
|
|
if err != nil {
|
|
return bf.Data()
|
|
}
|
|
var resetRoom bool
|
|
if _type == 2 {
|
|
currentRP, err := s.server.guildRepo.GetRoomRP(guild.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get guild room RP", zap.Error(err))
|
|
}
|
|
if currentRP+amount >= 30 {
|
|
amount = 30 - currentRP
|
|
resetRoom = true
|
|
}
|
|
}
|
|
saveData.RP -= amount
|
|
saveData.Save(s)
|
|
switch _type {
|
|
case 0:
|
|
if err := s.server.guildRepo.AddRankRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild rank RP", zap.Error(err))
|
|
}
|
|
case 1:
|
|
if err := s.server.guildRepo.AddEventRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild event RP", zap.Error(err))
|
|
}
|
|
case 2:
|
|
if resetRoom {
|
|
if err := s.server.guildRepo.SetRoomRP(guild.ID, 0); err != nil {
|
|
s.logger.Error("Failed to reset guild room RP", zap.Error(err))
|
|
}
|
|
if err := s.server.guildRepo.SetRoomExpiry(guild.ID, TimeAdjusted().Add(time.Hour*24*7)); err != nil {
|
|
s.logger.Error("Failed to update guild room expiry", zap.Error(err))
|
|
}
|
|
} else {
|
|
if err := s.server.guildRepo.AddRoomRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild room RP", zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
_, _ = bf.Seek(0, 0)
|
|
bf.WriteUint32(uint32(saveData.RP))
|
|
return bf.Data()
|
|
}
|
|
|
|
func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, avoidLeadership bool) {
|
|
characterGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
|
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
characterGuildData.AvoidLeadership = avoidLeadership
|
|
|
|
err = s.server.guildRepo.SaveMember(characterGuildData)
|
|
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfOperateGuildMember)
|
|
|
|
action, ok := mapMemberAction(pkt.Action)
|
|
if !ok {
|
|
s.logger.Warn("Unhandled operateGuildMember action", zap.Uint8("action", pkt.Action))
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
result, err := s.server.guildService.OperateMember(s.charID, pkt.CharID, action)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
s.server.Registry.NotifyMailToCharID(result.MailRecipientID, s, &result.Mail)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func mapMemberAction(proto uint8) (GuildMemberAction, bool) {
|
|
switch proto {
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
|
|
return GuildMemberActionAccept, true
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
|
|
return GuildMemberActionReject, true
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
|
|
return GuildMemberActionKick, true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|