mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-27 10:03:06 +01:00
refactor(channelserver): migrate remaining guild SQL into GuildRepository
Move ~39 inline SQL queries from six handler files into repo_guild.go, consolidating all guild-related DB access (posts, alliances, adventures, treasure hunts, meals, kill logs, scouts) behind GuildRepository methods. Handler files now contain only packet serialization, business logic, and ACK responses with no direct database calls.
This commit is contained in:
@@ -113,7 +113,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
alliance, err := GetAllianceData(s, guild.AllianceID)
|
alliance, err := s.server.guildRepo.GetAllianceByID(guild.AllianceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get alliance data")
|
s.logger.Error("Failed to get alliance data")
|
||||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||||
|
|||||||
@@ -22,21 +22,14 @@ type GuildAdventure struct {
|
|||||||
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
|
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
|
||||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID)
|
adventures, err := s.server.guildRepo.ListAdventures(guild.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get guild adventures from db", zap.Error(err))
|
s.logger.Error("Failed to get guild adventures from db", zap.Error(err))
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
temp := byteframe.NewByteFrame()
|
temp := byteframe.NewByteFrame()
|
||||||
count := 0
|
for _, adventureData := range adventures {
|
||||||
for data.Next() {
|
|
||||||
count++
|
|
||||||
adventureData := &GuildAdventure{}
|
|
||||||
err = data.StructScan(&adventureData)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
temp.WriteUint32(adventureData.ID)
|
temp.WriteUint32(adventureData.ID)
|
||||||
temp.WriteUint32(adventureData.Destination)
|
temp.WriteUint32(adventureData.Destination)
|
||||||
temp.WriteUint32(adventureData.Charge)
|
temp.WriteUint32(adventureData.Charge)
|
||||||
@@ -45,7 +38,7 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
temp.WriteBool(stringsupport.CSVContains(adventureData.CollectedBy, int(s.charID)))
|
temp.WriteBool(stringsupport.CSVContains(adventureData.CollectedBy, int(s.charID)))
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
bf.WriteUint8(uint8(count))
|
bf.WriteUint8(uint8(len(adventures)))
|
||||||
bf.WriteBytes(temp.Data())
|
bf.WriteBytes(temp.Data())
|
||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
@@ -53,8 +46,7 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
|
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
|
||||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, TimeAdjusted().Unix(), TimeAdjusted().Add(6*time.Hour).Unix())
|
if err := s.server.guildRepo.CreateAdventure(guild.ID, pkt.Destination, TimeAdjusted().Unix(), TimeAdjusted().Add(6*time.Hour).Unix()); err != nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -62,24 +54,15 @@ func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildAdventure)
|
pkt := p.(*mhfpacket.MsgMhfAcquireGuildAdventure)
|
||||||
var collectedBy string
|
if err := s.server.guildRepo.CollectAdventure(pkt.ID, s.charID); err != nil {
|
||||||
err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy)
|
s.logger.Error("Failed to collect adventure", zap.Error(err))
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Error parsing adventure collected by", zap.Error(err))
|
|
||||||
} else {
|
|
||||||
collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID))
|
|
||||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to collect adventure in db", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
|
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
|
||||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID)
|
if err := s.server.guildRepo.ChargeAdventure(pkt.ID, pkt.Amount); err != nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to charge guild adventure", zap.Error(err))
|
s.logger.Error("Failed to charge guild adventure", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -88,8 +71,7 @@ func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
|
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
|
||||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, TimeAdjusted().Unix(), TimeAdjusted().Add(1*time.Hour).Unix())
|
if err := s.server.guildRepo.CreateAdventureWithCharge(guild.ID, pkt.Destination, pkt.Charge, TimeAdjusted().Unix(), TimeAdjusted().Add(1*time.Hour).Unix()); err != nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
|
|||||||
@@ -3,31 +3,12 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
ps "erupe-ce/common/pascalstring"
|
ps "erupe-ce/common/pascalstring"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const allianceInfoSelectQuery = `
|
|
||||||
SELECT
|
|
||||||
ga.id,
|
|
||||||
ga.name,
|
|
||||||
created_at,
|
|
||||||
parent_id,
|
|
||||||
CASE
|
|
||||||
WHEN sub1_id IS NULL THEN 0
|
|
||||||
ELSE sub1_id
|
|
||||||
END,
|
|
||||||
CASE
|
|
||||||
WHEN sub2_id IS NULL THEN 0
|
|
||||||
ELSE sub2_id
|
|
||||||
END
|
|
||||||
FROM guild_alliances ga
|
|
||||||
`
|
|
||||||
|
|
||||||
// GuildAlliance represents a multi-guild alliance.
|
// GuildAlliance represents a multi-guild alliance.
|
||||||
type GuildAlliance struct {
|
type GuildAlliance struct {
|
||||||
ID uint32 `db:"id"`
|
ID uint32 `db:"id"`
|
||||||
@@ -44,73 +25,9 @@ type GuildAlliance struct {
|
|||||||
SubGuild2 Guild
|
SubGuild2 Guild
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllianceData loads alliance data from the database.
|
|
||||||
func GetAllianceData(s *Session, AllianceID uint32) (*GuildAlliance, error) {
|
|
||||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
|
||||||
%s
|
|
||||||
WHERE ga.id = $1
|
|
||||||
`, allianceInfoSelectQuery), AllianceID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to retrieve alliance data from database", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer func() { _ = rows.Close() }()
|
|
||||||
hasRow := rows.Next()
|
|
||||||
if !hasRow {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildAllianceObjectFromDbResult(rows, err, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAllianceObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*GuildAlliance, error) {
|
|
||||||
alliance := &GuildAlliance{}
|
|
||||||
|
|
||||||
err := result.StructScan(alliance)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to retrieve alliance from database", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
parentGuild, err := s.server.guildRepo.GetByID(alliance.ParentGuildID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to get parent guild info", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
alliance.ParentGuild = *parentGuild
|
|
||||||
alliance.TotalMembers += parentGuild.MemberCount
|
|
||||||
}
|
|
||||||
|
|
||||||
if alliance.SubGuild1ID > 0 {
|
|
||||||
subGuild1, err := s.server.guildRepo.GetByID(alliance.SubGuild1ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to get sub guild 1 info", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
alliance.SubGuild1 = *subGuild1
|
|
||||||
alliance.TotalMembers += subGuild1.MemberCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if alliance.SubGuild2ID > 0 {
|
|
||||||
subGuild2, err := s.server.guildRepo.GetByID(alliance.SubGuild2ID)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to get sub guild 2 info", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
alliance.SubGuild2 = *subGuild2
|
|
||||||
alliance.TotalMembers += subGuild2.MemberCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return alliance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
|
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
|
||||||
_, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID)
|
if err := s.server.guildRepo.CreateAlliance(pkt.Name, pkt.GuildID); err != nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to create guild alliance in db", zap.Error(err))
|
s.logger.Error("Failed to create guild alliance in db", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
|
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
|
||||||
@@ -123,7 +40,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get guild info", zap.Error(err))
|
s.logger.Error("Failed to get guild info", zap.Error(err))
|
||||||
}
|
}
|
||||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
alliance, err := s.server.guildRepo.GetAllianceByID(pkt.AllianceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get alliance info", zap.Error(err))
|
s.logger.Error("Failed to get alliance info", zap.Error(err))
|
||||||
}
|
}
|
||||||
@@ -131,8 +48,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
switch pkt.Action {
|
switch pkt.Action {
|
||||||
case mhfpacket.OPERATE_JOINT_DISBAND:
|
case mhfpacket.OPERATE_JOINT_DISBAND:
|
||||||
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
|
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
|
||||||
_, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID)
|
if err := s.server.guildRepo.DeleteAlliance(alliance.ID); err != nil {
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to disband alliance", zap.Error(err))
|
s.logger.Error("Failed to disband alliance", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -146,18 +62,8 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
case mhfpacket.OPERATE_JOINT_LEAVE:
|
case mhfpacket.OPERATE_JOINT_LEAVE:
|
||||||
if guild.LeaderCharID == s.charID {
|
if guild.LeaderCharID == s.charID {
|
||||||
if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
if err := s.server.guildRepo.RemoveGuildFromAlliance(alliance.ID, guild.ID, alliance.SubGuild1ID, alliance.SubGuild2ID); err != nil {
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
s.logger.Error("Failed to remove guild from alliance", zap.Error(err))
|
||||||
s.logger.Error("Failed to update alliance on guild leave", zap.Error(err))
|
|
||||||
}
|
|
||||||
} else if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
|
||||||
s.logger.Error("Failed to remove sub guild 1 from alliance", zap.Error(err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
|
||||||
s.logger.Error("Failed to remove sub guild 2 from alliance", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// NOTE: Alliance join requests are not yet implemented (no DB table exists),
|
// NOTE: Alliance join requests are not yet implemented (no DB table exists),
|
||||||
// so there are no pending applications to clean up on leave.
|
// so there are no pending applications to clean up on leave.
|
||||||
@@ -172,18 +78,8 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
case mhfpacket.OPERATE_JOINT_KICK:
|
case mhfpacket.OPERATE_JOINT_KICK:
|
||||||
if alliance.ParentGuild.LeaderCharID == s.charID {
|
if alliance.ParentGuild.LeaderCharID == s.charID {
|
||||||
kickedGuildID := pkt.Data1.ReadUint32()
|
kickedGuildID := pkt.Data1.ReadUint32()
|
||||||
if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
|
if err := s.server.guildRepo.RemoveGuildFromAlliance(alliance.ID, kickedGuildID, alliance.SubGuild1ID, alliance.SubGuild2ID); err != nil {
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
s.logger.Error("Failed to kick guild from alliance", zap.Error(err))
|
||||||
s.logger.Error("Failed to update alliance on guild kick", zap.Error(err))
|
|
||||||
}
|
|
||||||
} else if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
|
||||||
s.logger.Error("Failed to remove kicked sub guild 1 from alliance", zap.Error(err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID); err != nil {
|
|
||||||
s.logger.Error("Failed to remove kicked sub guild 2 from alliance", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
} else {
|
} else {
|
||||||
@@ -203,7 +99,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfInfoJoint)
|
pkt := p.(*mhfpacket.MsgMhfInfoJoint)
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
alliance, err := s.server.guildRepo.GetAllianceByID(pkt.AllianceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if pkt.BoardType == 1 {
|
if pkt.BoardType == 1 {
|
||||||
pkt.MaxPosts = 4
|
pkt.MaxPosts = 4
|
||||||
}
|
}
|
||||||
msgs, err := s.server.db.Queryx("SELECT id, stamp_id, title, body, author_id, created_at, liked_by FROM guild_posts WHERE guild_id = $1 AND post_type = $2 AND deleted = false ORDER BY created_at DESC", guild.ID, int(pkt.BoardType))
|
posts, err := s.server.guildRepo.ListPosts(guild.ID, int(pkt.BoardType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get guild messages from db", zap.Error(err))
|
s.logger.Error("Failed to get guild messages from db", zap.Error(err))
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -37,14 +37,7 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
s.logger.Error("Failed to update guild post checked time", zap.Error(err))
|
s.logger.Error("Failed to update guild post checked time", zap.Error(err))
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
var postCount uint32
|
for _, postData := range posts {
|
||||||
for msgs.Next() {
|
|
||||||
postData := &MessageBoardPost{}
|
|
||||||
err = msgs.StructScan(&postData)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
postCount++
|
|
||||||
bf.WriteUint32(postData.ID)
|
bf.WriteUint32(postData.ID)
|
||||||
bf.WriteUint32(postData.AuthorID)
|
bf.WriteUint32(postData.AuthorID)
|
||||||
bf.WriteUint32(0)
|
bf.WriteUint32(0)
|
||||||
@@ -56,7 +49,7 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
ps.Uint32(bf, postData.Body, true)
|
ps.Uint32(bf, postData.Body, true)
|
||||||
}
|
}
|
||||||
data := byteframe.NewByteFrame()
|
data := byteframe.NewByteFrame()
|
||||||
data.WriteUint32(postCount)
|
data.WriteUint32(uint32(len(posts)))
|
||||||
data.WriteBytes(bf.Data())
|
data.WriteBytes(bf.Data())
|
||||||
doAckBufSucceed(s, pkt.AckHandle, data.Data())
|
doAckBufSucceed(s, pkt.AckHandle, data.Data())
|
||||||
}
|
}
|
||||||
@@ -74,54 +67,43 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
switch pkt.MessageOp {
|
switch pkt.MessageOp {
|
||||||
case 0: // Create message
|
case 0: // Create message
|
||||||
if _, err := s.server.db.Exec("INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)", guild.ID, s.charID, pkt.StampID, pkt.PostType, pkt.Title, pkt.Body); err != nil {
|
|
||||||
s.logger.Error("Failed to insert guild post", zap.Error(err))
|
|
||||||
}
|
|
||||||
maxPosts := 100
|
maxPosts := 100
|
||||||
if pkt.PostType == 1 {
|
if pkt.PostType == 1 {
|
||||||
maxPosts = 4
|
maxPosts = 4
|
||||||
}
|
}
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_posts SET deleted = true WHERE id IN (
|
if err := s.server.guildRepo.CreatePost(guild.ID, s.charID, pkt.StampID, int(pkt.PostType), pkt.Title, pkt.Body, maxPosts); err != nil {
|
||||||
SELECT id FROM guild_posts WHERE guild_id = $1 AND post_type = $2 AND deleted = false
|
s.logger.Error("Failed to create guild post", zap.Error(err))
|
||||||
ORDER BY created_at DESC OFFSET $3
|
|
||||||
)`, guild.ID, pkt.PostType, maxPosts); err != nil {
|
|
||||||
s.logger.Error("Failed to soft-delete excess guild posts", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
case 1: // Delete message
|
case 1: // Delete message
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_posts SET deleted = true WHERE id = $1", pkt.PostID); err != nil {
|
if err := s.server.guildRepo.DeletePost(pkt.PostID); err != nil {
|
||||||
s.logger.Error("Failed to soft-delete guild post", zap.Error(err))
|
s.logger.Error("Failed to soft-delete guild post", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 2: // Update message
|
case 2: // Update message
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", pkt.Title, pkt.Body, pkt.PostID); err != nil {
|
if err := s.server.guildRepo.UpdatePost(pkt.PostID, pkt.Title, pkt.Body); err != nil {
|
||||||
s.logger.Error("Failed to update guild post", zap.Error(err))
|
s.logger.Error("Failed to update guild post", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 3: // Update stamp
|
case 3: // Update stamp
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", pkt.StampID, pkt.PostID); err != nil {
|
if err := s.server.guildRepo.UpdatePostStamp(pkt.PostID, pkt.StampID); err != nil {
|
||||||
s.logger.Error("Failed to update guild post stamp", zap.Error(err))
|
s.logger.Error("Failed to update guild post stamp", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 4: // Like message
|
case 4: // Like message
|
||||||
var likedBy string
|
likedBy, err := s.server.guildRepo.GetPostLikedBy(pkt.PostID)
|
||||||
err := s.server.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", pkt.PostID).Scan(&likedBy)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get guild message like data from db", zap.Error(err))
|
s.logger.Error("Failed to get guild message like data from db", zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
if pkt.LikeState {
|
if pkt.LikeState {
|
||||||
likedBy = stringsupport.CSVAdd(likedBy, int(s.charID))
|
likedBy = stringsupport.CSVAdd(likedBy, int(s.charID))
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID); err != nil {
|
|
||||||
s.logger.Error("Failed to update guild post likes", zap.Error(err))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
likedBy = stringsupport.CSVRemove(likedBy, int(s.charID))
|
likedBy = stringsupport.CSVRemove(likedBy, int(s.charID))
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, pkt.PostID); err != nil {
|
}
|
||||||
s.logger.Error("Failed to update guild post likes", zap.Error(err))
|
if err := s.server.guildRepo.SetPostLikedBy(pkt.PostID, likedBy); err != nil {
|
||||||
}
|
s.logger.Error("Failed to update guild post likes", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 5: // Check for new messages
|
case 5: // Check for new messages
|
||||||
var newPosts int
|
|
||||||
timeChecked, err := s.server.charRepo.ReadGuildPostChecked(s.charID)
|
timeChecked, err := s.server.charRepo.ReadGuildPostChecked(s.charID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = s.server.db.QueryRow("SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND deleted = false AND (EXTRACT(epoch FROM created_at)::int) > $2", guild.ID, timeChecked.Unix()).Scan(&newPosts)
|
newPosts, _ := s.server.guildRepo.CountNewPosts(guild.ID, timeChecked)
|
||||||
if newPosts > 0 {
|
if newPosts > 0 {
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
|
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,21 +19,16 @@ type GuildMeal struct {
|
|||||||
func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfLoadGuildCooking)
|
pkt := p.(*mhfpacket.MsgMhfLoadGuildCooking)
|
||||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
data, err := s.server.db.Queryx("SELECT id, meal_id, level, created_at FROM guild_meals WHERE guild_id = $1", guild.ID)
|
allMeals, err := s.server.guildRepo.ListMeals(guild.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get guild meals from db", zap.Error(err))
|
s.logger.Error("Failed to get guild meals from db", zap.Error(err))
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var meals []GuildMeal
|
var meals []*GuildMeal
|
||||||
var temp GuildMeal
|
for _, meal := range allMeals {
|
||||||
for data.Next() {
|
if meal.CreatedAt.Add(60 * time.Minute).After(TimeAdjusted()) {
|
||||||
err = data.StructScan(&temp)
|
meals = append(meals, meal)
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if temp.CreatedAt.Add(60 * time.Minute).After(TimeAdjusted()) {
|
|
||||||
meals = append(meals, temp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
@@ -52,15 +47,17 @@ func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second)
|
startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second)
|
||||||
if pkt.OverwriteID != 0 {
|
if pkt.OverwriteID != 0 {
|
||||||
if _, err := s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, startTime, pkt.OverwriteID); err != nil {
|
if err := s.server.guildRepo.UpdateMeal(pkt.OverwriteID, uint32(pkt.MealID), uint32(pkt.Success), startTime); err != nil {
|
||||||
s.logger.Error("Failed to update guild meal", zap.Error(err))
|
s.logger.Error("Failed to update guild meal", zap.Error(err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := s.server.db.QueryRow("INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id", guild.ID, pkt.MealID, pkt.Success, startTime).Scan(&pkt.OverwriteID); err != nil {
|
id, err := s.server.guildRepo.CreateMeal(guild.ID, uint32(pkt.MealID), uint32(pkt.Success), startTime)
|
||||||
|
if err != nil {
|
||||||
s.logger.Error("Failed to insert guild meal", zap.Error(err))
|
s.logger.Error("Failed to insert guild meal", zap.Error(err))
|
||||||
doAckBufFail(s, pkt.AckHandle, nil)
|
doAckBufFail(s, pkt.AckHandle, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pkt.OverwriteID = id
|
||||||
}
|
}
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
bf.WriteUint16(1)
|
bf.WriteUint16(1)
|
||||||
@@ -91,31 +88,21 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
switch pkt.Operation {
|
switch pkt.Operation {
|
||||||
case 0: // Acquire
|
case 0: // Acquire
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, TimeAdjusted(), s.charID); err != nil {
|
if err := s.server.guildRepo.ClaimHuntBox(s.charID, TimeAdjusted()); err != nil {
|
||||||
s.logger.Error("Failed to update guild hunt box claimed time", zap.Error(err))
|
s.logger.Error("Failed to update guild hunt box claimed time", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 1: // Enumerate
|
case 1: // Enumerate
|
||||||
bf.WriteUint8(0) // Entries
|
bf.WriteUint8(0) // Entries
|
||||||
rows, err := s.server.db.Query(`SELECT kl.id, kl.monster FROM kill_logs kl
|
kills, err := s.server.guildRepo.ListGuildKills(pkt.GuildID, s.charID)
|
||||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
|
||||||
WHERE gc.guild_id=$1
|
|
||||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
|
||||||
`, pkt.GuildID, s.charID)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var count uint8
|
var count uint8
|
||||||
var huntID, monID uint32
|
for _, kill := range kills {
|
||||||
for rows.Next() {
|
|
||||||
err = rows.Scan(&huntID, &monID)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if count == 255 {
|
if count == 255 {
|
||||||
_ = rows.Close()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count++
|
count++
|
||||||
bf.WriteUint32(huntID)
|
bf.WriteUint32(kill.ID)
|
||||||
bf.WriteUint32(monID)
|
bf.WriteUint32(kill.Monster)
|
||||||
}
|
}
|
||||||
_, _ = bf.Seek(0, 0)
|
_, _ = bf.Seek(0, 0)
|
||||||
bf.WriteUint8(count)
|
bf.WriteUint8(count)
|
||||||
@@ -123,12 +110,7 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
case 2: // Check
|
case 2: // Check
|
||||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var count uint8
|
count, err := s.server.guildRepo.CountGuildKills(guild.ID, s.charID)
|
||||||
err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
|
|
||||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
|
||||||
WHERE gc.guild_id=$1
|
|
||||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
|
||||||
`, guild.ID, s.charID).Scan(&count)
|
|
||||||
if err == nil && count > 0 {
|
if err == nil && count > 0 {
|
||||||
bf.WriteBool(true)
|
bf.WriteBool(true)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf.WriteUint16(0) // Ignored
|
bf.WriteUint16(0) // Ignored
|
||||||
|
|
||||||
if guild.AllianceID > 0 {
|
if guild.AllianceID > 0 {
|
||||||
alliance, err := GetAllianceData(s, guild.AllianceID)
|
alliance, err := s.server.guildRepo.GetAllianceByID(guild.AllianceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bf.WriteUint32(0) // Error, no alliance
|
bf.WriteUint32(0) // Error, no alliance
|
||||||
} else {
|
} else {
|
||||||
@@ -361,15 +361,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
if pkt.Type > 8 {
|
if pkt.Type > 8 {
|
||||||
var tempAlliances []*GuildAlliance
|
var tempAlliances []*GuildAlliance
|
||||||
rows, queryErr := s.server.db.Queryx(allianceInfoSelectQuery)
|
tempAlliances, err = s.server.guildRepo.ListAlliances()
|
||||||
if queryErr != nil {
|
|
||||||
err = queryErr
|
|
||||||
} else {
|
|
||||||
for rows.Next() {
|
|
||||||
alliance, _ := buildAllianceObjectFromDbResult(rows, queryErr, s)
|
|
||||||
tempAlliances = append(tempAlliances, alliance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch pkt.Type {
|
switch pkt.Type {
|
||||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
||||||
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||||
@@ -214,65 +213,30 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := s.server.db.Queryx(`
|
chars, err := s.server.guildRepo.ListInvitedCharacters(guildInfo.ID)
|
||||||
SELECT c.id, c.name, c.hr, c.gr, ga.actor_id
|
|
||||||
FROM guild_applications ga
|
|
||||||
JOIN characters c ON c.id = ga.character_id
|
|
||||||
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
|
|
||||||
`, guildInfo.ID)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
|
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = rows.Close() }()
|
|
||||||
|
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
bf.SetBE()
|
bf.SetBE()
|
||||||
|
bf.WriteUint32(uint32(len(chars)))
|
||||||
|
|
||||||
// Result count, we will overwrite this later
|
for _, sc := range chars {
|
||||||
bf.WriteUint32(0x00)
|
|
||||||
|
|
||||||
count := uint32(0)
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var charName string
|
|
||||||
var charID, actorID uint32
|
|
||||||
var HR, GR uint16
|
|
||||||
|
|
||||||
err = rows.Scan(&charID, &charName, &HR, &GR, &actorID)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// This seems to be used as a unique ID for the invitation sent
|
// This seems to be used as a unique ID for the invitation sent
|
||||||
// we can just use the charID and then filter on guild_id+charID when performing operations
|
// we can just use the charID and then filter on guild_id+charID when performing operations
|
||||||
// this might be a problem later with mails sent referencing IDs but we'll see.
|
// this might be a problem later with mails sent referencing IDs but we'll see.
|
||||||
bf.WriteUint32(charID)
|
bf.WriteUint32(sc.CharID)
|
||||||
bf.WriteUint32(actorID)
|
bf.WriteUint32(sc.ActorID)
|
||||||
bf.WriteUint32(charID)
|
bf.WriteUint32(sc.CharID)
|
||||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||||
bf.WriteUint16(HR) // HR?
|
bf.WriteUint16(sc.HR)
|
||||||
bf.WriteUint16(GR) // GR?
|
bf.WriteUint16(sc.GR)
|
||||||
bf.WriteBytes(stringsupport.PaddedString(charName, 32, true))
|
bf.WriteBytes(stringsupport.PaddedString(sc.Name, 32, true))
|
||||||
count++
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = bf.Seek(0, io.SeekStart)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("Failed to seek in guild scout list buffer", zap.Error(err))
|
|
||||||
doAckBufFail(s, pkt.AckHandle, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bf.WriteUint32(count)
|
|
||||||
|
|
||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,35 +31,22 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var hunts []TreasureHunt
|
var hunts []TreasureHunt
|
||||||
var hunt TreasureHunt
|
|
||||||
|
|
||||||
switch pkt.MaxHunts {
|
switch pkt.MaxHunts {
|
||||||
case 1:
|
case 1:
|
||||||
err = s.server.db.QueryRowx(`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`, s.charID).StructScan(&hunt)
|
hunt, err := s.server.guildRepo.GetPendingHunt(s.charID)
|
||||||
if err == nil {
|
if err == nil && hunt != nil {
|
||||||
hunts = append(hunts, hunt)
|
hunts = append(hunts, *hunt)
|
||||||
}
|
}
|
||||||
case 30:
|
case 30:
|
||||||
rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data,
|
guildHunts, err := s.server.guildRepo.ListGuildHunts(guild.ID, s.charID)
|
||||||
(SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters,
|
|
||||||
CASE
|
|
||||||
WHEN ghc.character_id IS NOT NULL THEN true
|
|
||||||
ELSE false
|
|
||||||
END AS claimed
|
|
||||||
FROM guild_hunts gh
|
|
||||||
LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1
|
|
||||||
WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE
|
|
||||||
`, s.charID, guild.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = rows.Close()
|
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
for rows.Next() {
|
for _, hunt := range guildHunts {
|
||||||
err = rows.StructScan(&hunt)
|
if hunt.Start.Add(time.Second * time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) {
|
||||||
if err == nil && hunt.Start.Add(time.Second*time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) {
|
hunts = append(hunts, *hunt)
|
||||||
hunts = append(hunts, hunt)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(hunts) > 30 {
|
if len(hunts) > 30 {
|
||||||
@@ -111,8 +98,7 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
huntData.WriteBytes(bf.ReadBytes(9))
|
huntData.WriteBytes(bf.ReadBytes(9))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6)
|
if err := s.server.guildRepo.CreateHunt(guild.ID, s.charID, destination, level, huntData.Data(), catsUsed); err != nil {
|
||||||
`, guild.ID, s.charID, destination, level, huntData.Data(), catsUsed); err != nil {
|
|
||||||
s.logger.Error("Failed to register guild treasure hunt", zap.Error(err))
|
s.logger.Error("Failed to register guild treasure hunt", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -120,7 +106,7 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
|
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, pkt.HuntID); err != nil {
|
if err := s.server.guildRepo.AcquireHunt(pkt.HuntID); err != nil {
|
||||||
s.logger.Error("Failed to acquire guild treasure hunt", zap.Error(err))
|
s.logger.Error("Failed to acquire guild treasure hunt", zap.Error(err))
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -130,18 +116,15 @@ func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
|
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
|
||||||
switch pkt.State {
|
switch pkt.State {
|
||||||
case 0: // Report registration
|
case 0: // Report registration
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID); err != nil {
|
if err := s.server.guildRepo.RegisterHuntReport(pkt.HuntID, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to register treasure hunt report", zap.Error(err))
|
s.logger.Error("Failed to register treasure hunt report", zap.Error(err))
|
||||||
}
|
}
|
||||||
case 1: // Collected by hunter
|
case 1: // Collected by hunter
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, pkt.HuntID); err != nil {
|
if err := s.server.guildRepo.CollectHunt(pkt.HuntID); err != nil {
|
||||||
s.logger.Error("Failed to mark treasure hunt collected", zap.Error(err))
|
s.logger.Error("Failed to collect treasure hunt", zap.Error(err))
|
||||||
}
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID); err != nil {
|
|
||||||
s.logger.Error("Failed to clear treasure hunt from guild characters", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
case 2: // Claim treasure
|
case 2: // Claim treasure
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, pkt.HuntID, s.charID); err != nil {
|
if err := s.server.guildRepo.ClaimHuntReward(pkt.HuntID, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to claim treasure hunt reward", zap.Error(err))
|
s.logger.Error("Failed to claim treasure hunt reward", zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"erupe-ce/common/stringsupport"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -470,3 +472,439 @@ func (r *GuildRepository) SetRoomExpiry(guildID uint32, expiry time.Time) error
|
|||||||
_, err := r.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, expiry, guildID)
|
_, err := r.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, expiry, guildID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Guild Posts ---
|
||||||
|
|
||||||
|
// ListPosts returns active guild posts of the given type, ordered by newest first.
|
||||||
|
func (r *GuildRepository) ListPosts(guildID uint32, postType int) ([]*MessageBoardPost, error) {
|
||||||
|
rows, err := r.db.Queryx(
|
||||||
|
`SELECT id, stamp_id, title, body, author_id, created_at, liked_by
|
||||||
|
FROM guild_posts WHERE guild_id = $1 AND post_type = $2 AND deleted = false
|
||||||
|
ORDER BY created_at DESC`, guildID, postType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var posts []*MessageBoardPost
|
||||||
|
for rows.Next() {
|
||||||
|
post := &MessageBoardPost{}
|
||||||
|
if err := rows.StructScan(post); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
posts = append(posts, post)
|
||||||
|
}
|
||||||
|
return posts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePost inserts a new guild post and soft-deletes excess posts beyond maxPosts.
|
||||||
|
func (r *GuildRepository) CreatePost(guildID, authorID, stampID uint32, postType int, title, body string, maxPosts int) error {
|
||||||
|
if _, err := r.db.Exec(
|
||||||
|
`INSERT INTO guild_posts (guild_id, author_id, stamp_id, post_type, title, body) VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||||
|
guildID, authorID, stampID, postType, title, body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_posts SET deleted = true WHERE id IN (
|
||||||
|
SELECT id FROM guild_posts WHERE guild_id = $1 AND post_type = $2 AND deleted = false
|
||||||
|
ORDER BY created_at DESC OFFSET $3
|
||||||
|
)`, guildID, postType, maxPosts)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePost soft-deletes a guild post by ID.
|
||||||
|
func (r *GuildRepository) DeletePost(postID uint32) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_posts SET deleted = true WHERE id = $1", postID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePost updates the title and body of a guild post.
|
||||||
|
func (r *GuildRepository) UpdatePost(postID uint32, title, body string) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_posts SET title = $1, body = $2 WHERE id = $3", title, body, postID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePostStamp updates the stamp of a guild post.
|
||||||
|
func (r *GuildRepository) UpdatePostStamp(postID, stampID uint32) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_posts SET stamp_id = $1 WHERE id = $2", stampID, postID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPostLikedBy returns the liked_by CSV string for a guild post.
|
||||||
|
func (r *GuildRepository) GetPostLikedBy(postID uint32) (string, error) {
|
||||||
|
var likedBy string
|
||||||
|
err := r.db.QueryRow("SELECT liked_by FROM guild_posts WHERE id = $1", postID).Scan(&likedBy)
|
||||||
|
return likedBy, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPostLikedBy updates the liked_by CSV string for a guild post.
|
||||||
|
func (r *GuildRepository) SetPostLikedBy(postID uint32, likedBy string) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_posts SET liked_by = $1 WHERE id = $2", likedBy, postID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountNewPosts returns the count of non-deleted posts created after the given time.
|
||||||
|
func (r *GuildRepository) CountNewPosts(guildID uint32, since time.Time) (int, error) {
|
||||||
|
var count int
|
||||||
|
err := r.db.QueryRow(
|
||||||
|
`SELECT COUNT(*) FROM guild_posts WHERE guild_id = $1 AND deleted = false AND (EXTRACT(epoch FROM created_at)::int) > $2`,
|
||||||
|
guildID, since.Unix()).Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Guild Alliances ---
|
||||||
|
|
||||||
|
const allianceInfoSelectSQL = `
|
||||||
|
SELECT
|
||||||
|
ga.id,
|
||||||
|
ga.name,
|
||||||
|
created_at,
|
||||||
|
parent_id,
|
||||||
|
CASE
|
||||||
|
WHEN sub1_id IS NULL THEN 0
|
||||||
|
ELSE sub1_id
|
||||||
|
END,
|
||||||
|
CASE
|
||||||
|
WHEN sub2_id IS NULL THEN 0
|
||||||
|
ELSE sub2_id
|
||||||
|
END
|
||||||
|
FROM guild_alliances ga
|
||||||
|
`
|
||||||
|
|
||||||
|
// GetAllianceByID loads alliance data including parent and sub guilds.
|
||||||
|
func (r *GuildRepository) GetAllianceByID(allianceID uint32) (*GuildAlliance, error) {
|
||||||
|
rows, err := r.db.Queryx(fmt.Sprintf(`%s WHERE ga.id = $1`, allianceInfoSelectSQL), allianceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
if !rows.Next() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return r.scanAllianceWithGuilds(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlliances returns all alliances with their guild data populated.
|
||||||
|
func (r *GuildRepository) ListAlliances() ([]*GuildAlliance, error) {
|
||||||
|
rows, err := r.db.Queryx(allianceInfoSelectSQL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var alliances []*GuildAlliance
|
||||||
|
for rows.Next() {
|
||||||
|
alliance, err := r.scanAllianceWithGuilds(rows)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
alliances = append(alliances, alliance)
|
||||||
|
}
|
||||||
|
return alliances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAlliance creates a new guild alliance with the given parent guild.
|
||||||
|
func (r *GuildRepository) CreateAlliance(name string, parentGuildID uint32) error {
|
||||||
|
_, err := r.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", name, parentGuildID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlliance removes an alliance by ID.
|
||||||
|
func (r *GuildRepository) DeleteAlliance(allianceID uint32) error {
|
||||||
|
_, err := r.db.Exec("DELETE FROM guild_alliances WHERE id=$1", allianceID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveGuildFromAlliance removes a guild from its alliance, shifting sub2 into sub1's slot if needed.
|
||||||
|
func (r *GuildRepository) RemoveGuildFromAlliance(allianceID, guildID, subGuild1ID, subGuild2ID uint32) error {
|
||||||
|
if guildID == subGuild1ID && subGuild2ID > 0 {
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, allianceID)
|
||||||
|
return err
|
||||||
|
} else if guildID == subGuild1ID {
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, allianceID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, allianceID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanAllianceWithGuilds scans an alliance row and populates its guild data.
|
||||||
|
func (r *GuildRepository) scanAllianceWithGuilds(rows *sqlx.Rows) (*GuildAlliance, error) {
|
||||||
|
alliance := &GuildAlliance{}
|
||||||
|
if err := rows.StructScan(alliance); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentGuild, err := r.GetByID(alliance.ParentGuildID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alliance.ParentGuild = *parentGuild
|
||||||
|
alliance.TotalMembers += parentGuild.MemberCount
|
||||||
|
|
||||||
|
if alliance.SubGuild1ID > 0 {
|
||||||
|
subGuild1, err := r.GetByID(alliance.SubGuild1ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alliance.SubGuild1 = *subGuild1
|
||||||
|
alliance.TotalMembers += subGuild1.MemberCount
|
||||||
|
}
|
||||||
|
|
||||||
|
if alliance.SubGuild2ID > 0 {
|
||||||
|
subGuild2, err := r.GetByID(alliance.SubGuild2ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
alliance.SubGuild2 = *subGuild2
|
||||||
|
alliance.TotalMembers += subGuild2.MemberCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return alliance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Guild Adventures ---
|
||||||
|
|
||||||
|
// ListAdventures returns all adventures for a guild.
|
||||||
|
func (r *GuildRepository) ListAdventures(guildID uint32) ([]*GuildAdventure, error) {
|
||||||
|
rows, err := r.db.Queryx(
|
||||||
|
"SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guildID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var adventures []*GuildAdventure
|
||||||
|
for rows.Next() {
|
||||||
|
adv := &GuildAdventure{}
|
||||||
|
if err := rows.StructScan(adv); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
adventures = append(adventures, adv)
|
||||||
|
}
|
||||||
|
return adventures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAdventure inserts a new guild adventure.
|
||||||
|
func (r *GuildRepository) CreateAdventure(guildID, destination uint32, depart, returnTime int64) error {
|
||||||
|
_, err := r.db.Exec(
|
||||||
|
"INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)",
|
||||||
|
guildID, destination, depart, returnTime)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateAdventureWithCharge inserts a new guild adventure with an initial charge (Diva variant).
|
||||||
|
func (r *GuildRepository) CreateAdventureWithCharge(guildID, destination, charge uint32, depart, returnTime int64) error {
|
||||||
|
_, err := r.db.Exec(
|
||||||
|
"INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
guildID, destination, charge, depart, returnTime)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectAdventure marks an adventure as collected by the given character (CSV append).
|
||||||
|
func (r *GuildRepository) CollectAdventure(adventureID uint32, charID uint32) error {
|
||||||
|
var collectedBy string
|
||||||
|
err := r.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", adventureID).Scan(&collectedBy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collectedBy = stringsupport.CSVAdd(collectedBy, int(charID))
|
||||||
|
_, err = r.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, adventureID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChargeAdventure adds charge to a guild adventure.
|
||||||
|
func (r *GuildRepository) ChargeAdventure(adventureID uint32, amount uint32) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", amount, adventureID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Guild Treasure Hunts ---
|
||||||
|
|
||||||
|
// GetPendingHunt returns the pending (unacquired) hunt for a character, or nil if none.
|
||||||
|
func (r *GuildRepository) GetPendingHunt(charID uint32) (*TreasureHunt, error) {
|
||||||
|
hunt := &TreasureHunt{}
|
||||||
|
err := r.db.QueryRowx(
|
||||||
|
`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`,
|
||||||
|
charID).StructScan(hunt)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return hunt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGuildHunts returns acquired level-2 hunts for a guild, with hunter counts and claim status.
|
||||||
|
func (r *GuildRepository) ListGuildHunts(guildID, charID uint32) ([]*TreasureHunt, error) {
|
||||||
|
rows, err := r.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data,
|
||||||
|
(SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters,
|
||||||
|
CASE
|
||||||
|
WHEN ghc.character_id IS NOT NULL THEN true
|
||||||
|
ELSE false
|
||||||
|
END AS claimed
|
||||||
|
FROM guild_hunts gh
|
||||||
|
LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1
|
||||||
|
WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE
|
||||||
|
`, charID, guildID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var hunts []*TreasureHunt
|
||||||
|
for rows.Next() {
|
||||||
|
hunt := &TreasureHunt{}
|
||||||
|
if err := rows.StructScan(hunt); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hunts = append(hunts, hunt)
|
||||||
|
}
|
||||||
|
return hunts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateHunt inserts a new guild treasure hunt.
|
||||||
|
func (r *GuildRepository) CreateHunt(guildID, hostID, destination, level uint32, huntData []byte, catsUsed string) error {
|
||||||
|
_, err := r.db.Exec(
|
||||||
|
`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||||
|
guildID, hostID, destination, level, huntData, catsUsed)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireHunt marks a treasure hunt as acquired.
|
||||||
|
func (r *GuildRepository) AcquireHunt(huntID uint32) error {
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, huntID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHuntReport sets a character's active treasure hunt.
|
||||||
|
func (r *GuildRepository) RegisterHuntReport(huntID, charID uint32) error {
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, huntID, charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectHunt marks a hunt as collected and clears all characters' treasure_hunt references.
|
||||||
|
func (r *GuildRepository) CollectHunt(huntID uint32) error {
|
||||||
|
if _, err := r.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, huntID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, huntID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimHuntReward records that a character has claimed a treasure hunt reward.
|
||||||
|
func (r *GuildRepository) ClaimHuntReward(huntID, charID uint32) error {
|
||||||
|
_, err := r.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, huntID, charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Guild Cooking/Meals ---
|
||||||
|
|
||||||
|
// ListMeals returns all meals for a guild.
|
||||||
|
func (r *GuildRepository) ListMeals(guildID uint32) ([]*GuildMeal, error) {
|
||||||
|
rows, err := r.db.Queryx("SELECT id, meal_id, level, created_at FROM guild_meals WHERE guild_id = $1", guildID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var meals []*GuildMeal
|
||||||
|
for rows.Next() {
|
||||||
|
meal := &GuildMeal{}
|
||||||
|
if err := rows.StructScan(meal); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
meals = append(meals, meal)
|
||||||
|
}
|
||||||
|
return meals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMeal inserts a new guild meal and returns the new ID.
|
||||||
|
func (r *GuildRepository) CreateMeal(guildID, mealID, level uint32, createdAt time.Time) (uint32, error) {
|
||||||
|
var id uint32
|
||||||
|
err := r.db.QueryRow(
|
||||||
|
"INSERT INTO guild_meals (guild_id, meal_id, level, created_at) VALUES ($1, $2, $3, $4) RETURNING id",
|
||||||
|
guildID, mealID, level, createdAt).Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateMeal updates an existing guild meal's fields.
|
||||||
|
func (r *GuildRepository) UpdateMeal(mealID, newMealID, level uint32, createdAt time.Time) error {
|
||||||
|
_, err := r.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4",
|
||||||
|
newMealID, level, createdAt, mealID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimHuntBox updates the box_claimed timestamp for a guild character.
|
||||||
|
func (r *GuildRepository) ClaimHuntBox(charID uint32, claimedAt time.Time) error {
|
||||||
|
_, err := r.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, claimedAt, charID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuildKill represents a kill log entry for guild hunt data.
|
||||||
|
type GuildKill struct {
|
||||||
|
ID uint32 `db:"id"`
|
||||||
|
Monster uint32 `db:"monster"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListGuildKills returns kill log entries for guild members since the character's last box claim.
|
||||||
|
func (r *GuildRepository) ListGuildKills(guildID, charID uint32) ([]*GuildKill, error) {
|
||||||
|
rows, err := r.db.Queryx(`SELECT kl.id, kl.monster FROM kill_logs kl
|
||||||
|
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||||
|
WHERE gc.guild_id=$1
|
||||||
|
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||||
|
`, guildID, charID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var kills []*GuildKill
|
||||||
|
for rows.Next() {
|
||||||
|
kill := &GuildKill{}
|
||||||
|
if err := rows.StructScan(kill); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kills = append(kills, kill)
|
||||||
|
}
|
||||||
|
return kills, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountGuildKills returns the count of kill log entries for guild members since the character's last box claim.
|
||||||
|
func (r *GuildRepository) CountGuildKills(guildID, charID uint32) (int, error) {
|
||||||
|
var count int
|
||||||
|
err := r.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
|
||||||
|
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||||
|
WHERE gc.guild_id=$1
|
||||||
|
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||||
|
`, guildID, charID).Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Guild Scouts ---
|
||||||
|
|
||||||
|
// ScoutedCharacter represents an invited character in the scout list.
|
||||||
|
type ScoutedCharacter struct {
|
||||||
|
CharID uint32 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
HR uint16 `db:"hr"`
|
||||||
|
GR uint16 `db:"gr"`
|
||||||
|
ActorID uint32 `db:"actor_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListInvitedCharacters returns all characters with pending guild invitations.
|
||||||
|
func (r *GuildRepository) ListInvitedCharacters(guildID uint32) ([]*ScoutedCharacter, error) {
|
||||||
|
rows, err := r.db.Queryx(`
|
||||||
|
SELECT c.id, c.name, c.hr, c.gr, ga.actor_id
|
||||||
|
FROM guild_applications ga
|
||||||
|
JOIN characters c ON c.id = ga.character_id
|
||||||
|
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
|
||||||
|
`, guildID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var chars []*ScoutedCharacter
|
||||||
|
for rows.Next() {
|
||||||
|
sc := &ScoutedCharacter{}
|
||||||
|
if err := rows.StructScan(sc); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chars = append(chars, sc)
|
||||||
|
}
|
||||||
|
return chars, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user