mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
refactor(channelserver): extract GuildRepository for guild table access
Per anti-patterns.md item #9, guild-related SQL was scattered across ~15 handler files with no repository abstraction. Following the same pattern established by CharacterRepository, this centralizes all guilds, guild_characters, and guild_applications table access into a single GuildRepository (~30 methods). guild_model.go and handlers_guild_member.go are trimmed to types and pure business logic only. All handler files (guild_*, festa, mail, house, mercenary, rengoku) now call s.server.guildRepo methods instead of direct DB queries or methods on domain objects.
This commit is contained in:
@@ -1,18 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"erupe-ce/common/mhfitem"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// FestivalColor is a festival color identifier string.
|
||||
@@ -150,486 +142,3 @@ func (g *Guild) Rank(mode _config.Mode) uint16 {
|
||||
}
|
||||
return 17
|
||||
}
|
||||
|
||||
const guildInfoSelectQuery = `
|
||||
SELECT
|
||||
g.id,
|
||||
g.name,
|
||||
rank_rp,
|
||||
event_rp,
|
||||
room_rp,
|
||||
COALESCE(room_expiry, '1970-01-01') AS room_expiry,
|
||||
main_motto,
|
||||
sub_motto,
|
||||
created_at,
|
||||
leader_id,
|
||||
c.name AS leader_name,
|
||||
comment,
|
||||
COALESCE(pugi_name_1, '') AS pugi_name_1,
|
||||
COALESCE(pugi_name_2, '') AS pugi_name_2,
|
||||
COALESCE(pugi_name_3, '') AS pugi_name_3,
|
||||
pugi_outfit_1,
|
||||
pugi_outfit_2,
|
||||
pugi_outfit_3,
|
||||
pugi_outfits,
|
||||
recruiting,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color,
|
||||
COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls,
|
||||
COALESCE((
|
||||
SELECT id FROM guild_alliances ga WHERE
|
||||
ga.parent_id = g.id OR
|
||||
ga.sub1_id = g.id OR
|
||||
ga.sub2_id = g.id
|
||||
), 0) AS alliance_id,
|
||||
icon,
|
||||
(SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count
|
||||
FROM guilds g
|
||||
JOIN guild_characters gc ON gc.character_id = leader_id
|
||||
JOIN characters c on leader_id = c.id
|
||||
`
|
||||
|
||||
func (guild *Guild) Save(s *Session) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7,
|
||||
pugi_outfit_1=$8, pugi_outfit_2=$9, pugi_outfit_3=$10, pugi_outfits=$11, icon=$12, leader_id=$13 WHERE id=$1
|
||||
`, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3,
|
||||
guild.PugiOutfit1, guild.PugiOutfit2, guild.PugiOutfit3, guild.PugiOutfits, guild.Icon, guild.LeaderCharID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to update guild data", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) CreateApplication(s *Session, charID uint32, applicationType GuildApplicationType, transaction *sql.Tx) error {
|
||||
|
||||
query := `
|
||||
INSERT INTO guild_applications (guild_id, character_id, actor_id, application_type)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`
|
||||
|
||||
var err error
|
||||
|
||||
if transaction == nil {
|
||||
_, err = s.server.db.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
} else {
|
||||
_, err = transaction.Exec(query, guild.ID, charID, s.charID, applicationType)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to add guild application",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) Disband(s *Session) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to begin transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guild_characters WHERE guild_id = $1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild characters", zap.Error(err), zap.Uint32("guildId", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guilds WHERE id = $1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("DELETE FROM guild_alliances WHERE parent_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub1_id=sub2_id, sub2_id=NULL WHERE sub1_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild from alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec("UPDATE guild_alliances SET sub2_id=NULL WHERE sub2_id=$1", guild.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to remove guild from alliance", zap.Error(err), zap.Uint32("guildID", guild.ID))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("Character disbanded guild", zap.Uint32("charID", s.charID), zap.Uint32("guildID", guild.ID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) RemoveCharacter(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec("DELETE FROM guild_characters WHERE character_id=$1", charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to remove character from guild",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) AcceptApplication(s *Session, charID uint32) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`DELETE FROM guild_applications WHERE character_id = $1`, charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to accept character's guild application", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`
|
||||
INSERT INTO guild_characters (guild_id, character_id, order_index)
|
||||
VALUES ($1, $2, (SELECT MAX(order_index) + 1 FROM guild_characters WHERE guild_id = $1))
|
||||
`, guild.ID, charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to add applicant to guild",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit db transaction", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is relying on the fact that invitation ID is also character ID right now
|
||||
// if invitation ID changes, this will break.
|
||||
func (guild *Guild) CancelInvitation(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'invited'`,
|
||||
charID, guild.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to cancel guild invitation",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) RejectApplication(s *Session, charID uint32) error {
|
||||
_, err := s.server.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'applied'`,
|
||||
charID, guild.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to reject guild application",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) ArrangeCharacters(s *Session, charIDs []uint32) error {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
for i, id := range charIDs {
|
||||
_, err := transaction.Exec("UPDATE guild_characters SET order_index = $1 WHERE character_id = $2", 2+i, id)
|
||||
|
||||
if err != nil {
|
||||
err = transaction.Rollback()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to rollback db transaction", zap.Error(err))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit db transaction", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (guild *Guild) GetApplicationForCharID(s *Session, charID uint32, applicationType GuildApplicationType) (*GuildApplication, error) {
|
||||
row := s.server.db.QueryRowx(`
|
||||
SELECT * from guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = $3
|
||||
`, charID, guild.ID, applicationType)
|
||||
|
||||
application := &GuildApplication{}
|
||||
|
||||
err := row.StructScan(application)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve guild application for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return application, nil
|
||||
}
|
||||
|
||||
func (guild *Guild) HasApplicationForCharID(s *Session, charID uint32) (bool, error) {
|
||||
row := s.server.db.QueryRowx(`
|
||||
SELECT 1 from guild_applications WHERE character_id = $1 AND guild_id = $2
|
||||
`, charID, guild.ID)
|
||||
|
||||
num := 0
|
||||
|
||||
err := row.Scan(&num)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve guild applications for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
)
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateGuild creates a new guild in the database and adds the session's character as its leader.
|
||||
func CreateGuild(s *Session, guildName string) (int32, error) {
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to start db transaction", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
guildResult, err := transaction.Query(
|
||||
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
|
||||
guildName, s.charID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to create guild", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var guildId int32
|
||||
|
||||
guildResult.Next()
|
||||
|
||||
err = guildResult.Scan(&guildId)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild ID", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = guildResult.Close()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to finalise query", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = transaction.Exec(`
|
||||
INSERT INTO guild_characters (guild_id, character_id)
|
||||
VALUES ($1, $2)
|
||||
`, guildId, s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to add character to guild", zap.Error(err))
|
||||
rollbackTransaction(s, transaction)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to commit guild creation", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return guildId, nil
|
||||
}
|
||||
|
||||
func rollbackTransaction(s *Session, transaction *sql.Tx) {
|
||||
err := transaction.Rollback()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to rollback transaction", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetGuildInfoByID retrieves guild info by guild ID, returning nil if not found.
|
||||
func GetGuildInfoByID(s *Session, guildID uint32) (*Guild, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE g.id = $1
|
||||
LIMIT 1
|
||||
`, guildInfoSelectQuery), guildID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild", zap.Error(err), zap.Uint32("guildID", guildID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildObjectFromDbResult(rows, err, s)
|
||||
}
|
||||
|
||||
// GetGuildInfoByCharacterId retrieves guild info for a character, including applied guilds.
|
||||
func GetGuildInfoByCharacterId(s *Session, charID uint32) (*Guild, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_characters gc1
|
||||
WHERE gc1.character_id = $1
|
||||
AND gc1.guild_id = g.id
|
||||
)
|
||||
OR EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_applications ga
|
||||
WHERE ga.character_id = $1
|
||||
AND ga.guild_id = g.id
|
||||
AND ga.application_type = 'applied'
|
||||
)
|
||||
LIMIT 1
|
||||
`, guildInfoSelectQuery), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild for character", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildObjectFromDbResult(rows, err, s)
|
||||
}
|
||||
|
||||
func buildGuildObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*Guild, error) {
|
||||
guild := &Guild{}
|
||||
|
||||
err := result.StructScan(guild)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return guild, nil
|
||||
}
|
||||
|
||||
func guildGetItems(s *Session, guildID uint32) []mhfitem.MHFItemStack {
|
||||
var data []byte
|
||||
var items []mhfitem.MHFItemStack
|
||||
if err := s.server.db.QueryRow(`SELECT item_box FROM guilds WHERE id=$1`, guildID).Scan(&data); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
s.logger.Error("Failed to get guild item box", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -389,10 +389,10 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
// state festa (U)ser
|
||||
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
applicant := false
|
||||
if guild != nil {
|
||||
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
|
||||
applicant, _ = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
||||
}
|
||||
if err != nil || guild == nil || applicant {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -418,10 +418,10 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
// state festa (G)uild
|
||||
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
applicant := false
|
||||
if guild != nil {
|
||||
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
|
||||
applicant, _ = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
if err != nil || guild == nil || applicant {
|
||||
@@ -443,12 +443,12 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
members, err := GetGuildMembers(s, guild.ID, false)
|
||||
members, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -487,7 +487,7 @@ func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func handleMsgMhfCreateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateGuild)
|
||||
|
||||
guildId, err := CreateGuild(s, pkt.Name)
|
||||
guildId, err := s.server.guildRepo.Create(s.charID, pkt.Name)
|
||||
|
||||
if err != nil {
|
||||
bf := byteframe.NewByteFrame()
|
||||
@@ -37,7 +37,7 @@ func handleMsgMhfCreateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfArrangeGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfArrangeGuildMember)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
@@ -57,7 +57,7 @@ func handleMsgMhfArrangeGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
err = guild.ArrangeCharacters(s, pkt.CharIDs)
|
||||
err = s.server.guildRepo.ArrangeCharacters(pkt.CharIDs)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
@@ -79,13 +79,13 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
var err error
|
||||
|
||||
if pkt.GuildID > 0 {
|
||||
guild, err = GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err = s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
} else {
|
||||
guild, err = GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err = s.server.guildRepo.GetByCharID(s.charID)
|
||||
}
|
||||
|
||||
if guild != nil {
|
||||
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
|
||||
isApplicant, _ := s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
||||
if isApplicant {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
||||
return
|
||||
@@ -93,7 +93,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
if guild == nil && s.prevGuildID > 0 {
|
||||
guild, err = GetGuildInfoByID(s, s.prevGuildID)
|
||||
guild, err = s.server.guildRepo.GetByID(s.prevGuildID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -105,7 +105,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
guildMembers, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild")
|
||||
@@ -157,7 +157,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
if guild.AllianceID > 0 {
|
||||
bf.WriteUint16(alliance.TotalMembers - uint16(len(guildMembers)))
|
||||
if guild.ID != alliance.ParentGuildID {
|
||||
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
|
||||
mems, err := s.server.guildRepo.GetMembers(alliance.ParentGuildID, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get parent guild members for alliance", zap.Error(err))
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -168,7 +168,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
if guild.ID != alliance.SubGuild1ID {
|
||||
mems, err := GetGuildMembers(s, alliance.SubGuild1ID, false)
|
||||
mems, err := s.server.guildRepo.GetMembers(alliance.SubGuild1ID, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sub guild 1 members for alliance", zap.Error(err))
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -179,7 +179,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
if guild.ID != alliance.SubGuild2ID {
|
||||
mems, err := GetGuildMembers(s, alliance.SubGuild2ID, false)
|
||||
mems, err := s.server.guildRepo.GetMembers(alliance.SubGuild2ID, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get sub guild 2 members for alliance", zap.Error(err))
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -204,9 +204,9 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight)
|
||||
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if guild == nil || s.prevGuildID != 0 {
|
||||
guild, err := GetGuildInfoByID(s, s.prevGuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(s.prevGuildID)
|
||||
s.prevGuildID = 0
|
||||
if guild == nil || err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -216,7 +216,7 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(uint32(guild.MemberCount))
|
||||
members, _ := GetGuildMembers(s, guild.ID, false)
|
||||
members, _ := s.server.guildRepo.GetMembers(guild.ID, false)
|
||||
for _, member := range members {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteBool(member.Recruiter)
|
||||
@@ -237,9 +237,9 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
|
||||
var err error
|
||||
|
||||
if pkt.GuildID == 0x0 {
|
||||
guild, err = GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err = s.server.guildRepo.GetByCharID(s.charID)
|
||||
} else {
|
||||
guild, err = GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err = s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
}
|
||||
|
||||
if err != nil || guild == nil {
|
||||
@@ -266,7 +266,7 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateGuildItem)
|
||||
newStacks := mhfitem.DiffItemStacks(guildGetItems(s, pkt.GuildID), pkt.UpdatedItems)
|
||||
if _, err := s.server.db.Exec(`UPDATE guilds SET item_box=$1 WHERE id=$2`, mhfitem.SerializeWarehouseItems(newStacks), pkt.GuildID); err != nil {
|
||||
if err := s.server.guildRepo.SaveItemBox(pkt.GuildID, mhfitem.SerializeWarehouseItems(newStacks)); err != nil {
|
||||
s.logger.Error("Failed to update guild item box", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -275,7 +275,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateGuildIcon)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild info for icon update", zap.Error(err))
|
||||
@@ -283,7 +283,7 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
characterInfo, err := GetCharacterGuildData(s, s.charID)
|
||||
characterInfo, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get character guild data for icon update", zap.Error(err))
|
||||
@@ -322,7 +322,7 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
guild.Icon = icon
|
||||
|
||||
err = guild.Save(s)
|
||||
err = s.server.guildRepo.Save(guild)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -364,7 +364,7 @@ func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight)
|
||||
if _, err := s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID); err != nil {
|
||||
if err := s.server.guildRepo.SetRecruiter(pkt.CharID, pkt.Allowed); err != nil {
|
||||
s.logger.Error("Failed to update guild manage right", zap.Error(err))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -393,3 +393,22 @@ func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
// guildGetItems reads and parses the guild item box.
|
||||
func guildGetItems(s *Session, guildID uint32) []mhfitem.MHFItemStack {
|
||||
data, err := s.server.guildRepo.GetItemBox(guildID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild item box", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
var items []mhfitem.MHFItemStack
|
||||
if len(data) > 0 {
|
||||
box := byteframe.NewByteFrameFromBytes(data)
|
||||
numStacks := box.ReadUint16()
|
||||
box.ReadUint16() // Unused
|
||||
for i := 0; i < int(numStacks); i++ {
|
||||
items = append(items, mhfitem.ReadWarehouseItem(box))
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ type GuildAdventure struct {
|
||||
|
||||
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, 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)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild adventures from db", zap.Error(err))
|
||||
@@ -52,7 +52,7 @@ func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, 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 != nil {
|
||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||
@@ -87,7 +87,7 @@ func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, 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 != nil {
|
||||
s.logger.Error("Failed to register guild adventure", zap.Error(err))
|
||||
|
||||
@@ -73,7 +73,7 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*G
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID)
|
||||
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
|
||||
@@ -83,7 +83,7 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*G
|
||||
}
|
||||
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
|
||||
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
|
||||
@@ -94,7 +94,7 @@ func buildAllianceObjectFromDbResult(result *sqlx.Rows, _ error, s *Session) (*G
|
||||
}
|
||||
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
|
||||
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
|
||||
@@ -119,7 +119,7 @@ func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateJoint)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild info", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ type MessageBoardPost struct {
|
||||
|
||||
func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildMessageBoard)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if pkt.BoardType == 1 {
|
||||
pkt.MaxPosts = 4
|
||||
}
|
||||
@@ -63,10 +63,10 @@ func handleMsgMhfEnumerateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateGuildMessageBoard)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
applicant := false
|
||||
if guild != nil {
|
||||
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
|
||||
applicant, _ = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
||||
}
|
||||
if err != nil || guild == nil || applicant {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -18,7 +18,7 @@ type GuildMeal struct {
|
||||
|
||||
func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadGuildCooking)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, 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)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild meals from db", zap.Error(err))
|
||||
@@ -49,7 +49,7 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||
startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second)
|
||||
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 {
|
||||
@@ -121,7 +121,7 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(count)
|
||||
}
|
||||
case 2: // Check
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err == nil {
|
||||
var count uint8
|
||||
err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// Guild sentinel and cost constants
|
||||
@@ -25,9 +24,9 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
var err error
|
||||
|
||||
if pkt.GuildID > 0 {
|
||||
guild, err = GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err = s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
} else {
|
||||
guild, err = GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err = s.server.guildRepo.GetByCharID(s.charID)
|
||||
}
|
||||
|
||||
if err == nil && guild != nil {
|
||||
@@ -37,7 +36,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
guildComment := stringsupport.UTF8ToSJIS(guild.Comment)
|
||||
guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName)
|
||||
|
||||
characterGuildData, err := GetCharacterGuildData(s, s.charID)
|
||||
characterGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
characterJoinedAt := guildNotJoinedSentinel
|
||||
|
||||
if characterGuildData != nil && characterGuildData.JoinedAt != nil {
|
||||
@@ -196,7 +195,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(0) // No alliance
|
||||
}
|
||||
|
||||
applicants, err := GetGuildMembers(s, guild.ID, true)
|
||||
applicants, err := s.server.guildRepo.GetMembers(guild.ID, true)
|
||||
if err != nil || (characterGuildData != nil && !characterGuildData.CanRecruit()) {
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
@@ -280,89 +279,81 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
var guilds []*Guild
|
||||
var alliances []*GuildAlliance
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
|
||||
if pkt.Type <= 8 {
|
||||
var tempGuilds []*Guild
|
||||
rows, err = s.server.db.Queryx(guildInfoSelectQuery)
|
||||
tempGuilds, err = s.server.guildRepo.ListAll()
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, err := buildGuildObjectFromDbResult(rows, err, s)
|
||||
if err != nil {
|
||||
continue
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
|
||||
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.Name, searchName) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
tempGuilds = append(tempGuilds, guild)
|
||||
}
|
||||
}
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
|
||||
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.Name, searchName) {
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
|
||||
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.LeaderName, searchName) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
|
||||
searchName, _ := stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.LeaderName, searchName) {
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
|
||||
CID := pkt.Data1.ReadUint32()
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.LeaderCharID == CID {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
|
||||
CID := pkt.Data1.ReadUint32()
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.LeaderCharID == CID {
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount > tempGuilds[j].MemberCount
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount < tempGuilds[j].MemberCount
|
||||
})
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount > tempGuilds[j].MemberCount
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount < tempGuilds[j].MemberCount
|
||||
})
|
||||
}
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() > tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() < tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
}
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP > tempGuilds[j].RankRP
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP < tempGuilds[j].RankRP
|
||||
})
|
||||
}
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
|
||||
mainMotto := uint8(pkt.Data1.ReadUint16())
|
||||
subMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
|
||||
guilds = append(guilds, guild)
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() > tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() < tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
|
||||
recruitingMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == recruitingMotto {
|
||||
guilds = append(guilds, guild)
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP > tempGuilds[j].RankRP
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP < tempGuilds[j].RankRP
|
||||
})
|
||||
}
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
|
||||
mainMotto := uint8(pkt.Data1.ReadUint16())
|
||||
subMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
|
||||
recruitingMotto := uint8(pkt.Data1.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == recruitingMotto {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,10 +361,12 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
if pkt.Type > 8 {
|
||||
var tempAlliances []*GuildAlliance
|
||||
rows, err = s.server.db.Queryx(allianceInfoSelectQuery)
|
||||
if err == nil {
|
||||
rows, queryErr := s.server.db.Queryx(allianceInfoSelectQuery)
|
||||
if queryErr != nil {
|
||||
err = queryErr
|
||||
} else {
|
||||
for rows.Next() {
|
||||
alliance, _ := buildAllianceObjectFromDbResult(rows, err, s)
|
||||
alliance, _ := buildAllianceObjectFromDbResult(rows, queryErr, s)
|
||||
tempAlliances = append(tempAlliances, alliance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// GuildMember represents a guild member with role and stats.
|
||||
@@ -45,112 +41,3 @@ func (gm *GuildMember) CanRecruit() bool {
|
||||
func (gm *GuildMember) IsSubLeader() bool {
|
||||
return gm.OrderIndex <= 3
|
||||
}
|
||||
|
||||
func (gm *GuildMember) Save(s *Session) error {
|
||||
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3", gm.AvoidLeadership, gm.OrderIndex, gm.CharID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to update guild member data",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", gm.CharID),
|
||||
zap.Uint32("guildID", gm.GuildID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const guildMembersSelectSQL = `
|
||||
SELECT
|
||||
COALESCE(g.id, 0) AS guild_id,
|
||||
joined_at,
|
||||
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
|
||||
COALESCE(rp_today, 0) AS rp_today,
|
||||
COALESCE(rp_yesterday, 0) AS rp_yesterday,
|
||||
c.name,
|
||||
c.id AS character_id,
|
||||
COALESCE(order_index, 0) AS order_index,
|
||||
c.last_login,
|
||||
COALESCE(recruiter, false) AS recruiter,
|
||||
COALESCE(avoid_leadership, false) AS avoid_leadership,
|
||||
c.hr,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader,
|
||||
character.is_applicant
|
||||
FROM (
|
||||
SELECT character_id, true as is_applicant, guild_id
|
||||
FROM guild_applications ga
|
||||
WHERE ga.application_type = 'applied'
|
||||
UNION
|
||||
SELECT character_id, false as is_applicant, guild_id
|
||||
FROM guild_characters gc
|
||||
) character
|
||||
JOIN characters c on character.character_id = c.id
|
||||
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
|
||||
LEFT JOIN guilds g ON g.id = gc.guild_id
|
||||
`
|
||||
|
||||
// GetGuildMembers loads all members of a guild.
|
||||
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE character.guild_id = $1 AND is_applicant = $2
|
||||
`, guildMembersSelectSQL), guildID, applicants)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve membership data for guild", zap.Error(err), zap.Uint32("guildID", guildID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
members := make([]*GuildMember, 0)
|
||||
|
||||
for rows.Next() {
|
||||
member, err := buildGuildMemberObjectFromDBResult(rows, err, s)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
members = append(members, member)
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
// GetCharacterGuildData loads a character's guild membership.
|
||||
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildMemberObjectFromDBResult(rows, err, s)
|
||||
}
|
||||
|
||||
func buildGuildMemberObjectFromDBResult(rows *sqlx.Rows, _ error, s *Session) (*GuildMember, error) {
|
||||
memberData := &GuildMember{}
|
||||
|
||||
err := rows.StructScan(&memberData)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberData, nil
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuild)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
characterGuildInfo, err := GetCharacterGuildData(s, s.charID)
|
||||
characterGuildInfo, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -34,14 +34,14 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Warn(fmt.Sprintf("character '%d' is attempting to manage guild '%d' without permission", s.charID, guild.ID))
|
||||
response = 0
|
||||
} else {
|
||||
err = guild.Disband(s)
|
||||
err = s.server.guildRepo.Disband(guild.ID)
|
||||
if err != nil {
|
||||
response = 0
|
||||
}
|
||||
}
|
||||
bf.WriteUint32(uint32(response))
|
||||
case mhfpacket.OperateGuildResign:
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
guildMembers, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
||||
if err == nil {
|
||||
sort.Slice(guildMembers[:], func(i, j int) bool {
|
||||
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
|
||||
@@ -51,16 +51,16 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild.LeaderCharID = guildMembers[i].CharID
|
||||
guildMembers[0].OrderIndex = guildMembers[i].OrderIndex
|
||||
guildMembers[i].OrderIndex = 1
|
||||
_ = guildMembers[0].Save(s)
|
||||
_ = guildMembers[i].Save(s)
|
||||
_ = s.server.guildRepo.SaveMember(guildMembers[0])
|
||||
_ = s.server.guildRepo.SaveMember(guildMembers[i])
|
||||
bf.WriteUint32(guildMembers[i].CharID)
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = guild.Save(s)
|
||||
_ = s.server.guildRepo.Save(guild)
|
||||
}
|
||||
case mhfpacket.OperateGuildApply:
|
||||
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
|
||||
err = s.server.guildRepo.CreateApplication(guild.ID, s.charID, s.charID, GuildApplicationTypeApplied, nil)
|
||||
if err == nil {
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
} else {
|
||||
@@ -68,9 +68,9 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
case mhfpacket.OperateGuildLeave:
|
||||
if characterGuildInfo.IsApplicant {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
err = s.server.guildRepo.RejectApplication(guild.ID, s.charID)
|
||||
} else {
|
||||
err = guild.RemoveCharacter(s, s.charID)
|
||||
err = s.server.guildRepo.RemoveCharacter(s.charID)
|
||||
}
|
||||
response := 1
|
||||
if err != nil {
|
||||
@@ -88,11 +88,11 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
case mhfpacket.OperateGuildDonateRank:
|
||||
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, 0))
|
||||
case mhfpacket.OperateGuildSetApplicationDeny:
|
||||
if _, err := s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID); err != nil {
|
||||
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.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID); err != nil {
|
||||
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:
|
||||
@@ -105,7 +105,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
guild.Comment, _ = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
_ = guild.Save(s)
|
||||
_ = s.server.guildRepo.Save(guild)
|
||||
case mhfpacket.OperateGuildUpdateMotto:
|
||||
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -114,7 +114,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
_ = pkt.Data1.ReadUint16()
|
||||
guild.SubMotto = pkt.Data1.ReadUint8()
|
||||
guild.MainMotto = pkt.Data1.ReadUint8()
|
||||
_ = guild.Save(s)
|
||||
_ = s.server.guildRepo.Save(guild)
|
||||
case mhfpacket.OperateGuildRenamePugi1:
|
||||
handleRenamePugi(s, pkt.Data2, guild, 1)
|
||||
case mhfpacket.OperateGuildRenamePugi2:
|
||||
@@ -128,7 +128,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
case mhfpacket.OperateGuildChangePugi3:
|
||||
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3)
|
||||
case mhfpacket.OperateGuildUnlockOutfit:
|
||||
if _, err := s.server.db.Exec(`UPDATE guilds SET pugi_outfits=$1 WHERE id=$2`, pkt.Data1.ReadUint32(), guild.ID); err != nil {
|
||||
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:
|
||||
@@ -138,13 +138,13 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
quantity := uint16(pkt.Data1.ReadUint32())
|
||||
bf.WriteBytes(handleDonateRP(s, quantity, guild, 1))
|
||||
// TODO: Move this value onto rp_yesterday and reset to 0... daily?
|
||||
if _, err := s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID); err != nil {
|
||||
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())
|
||||
var balance uint32
|
||||
if err := s.server.db.QueryRow(`UPDATE guilds SET event_rp=event_rp-$1 WHERE id=$2 RETURNING event_rp`, rp, guild.ID).Scan(&balance); err != nil {
|
||||
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)
|
||||
@@ -169,7 +169,7 @@ func handleRenamePugi(s *Session, bf *byteframe.ByteFrame, guild *Guild, num int
|
||||
default:
|
||||
guild.PugiName3 = name
|
||||
}
|
||||
_ = guild.Save(s)
|
||||
_ = s.server.guildRepo.Save(guild)
|
||||
}
|
||||
|
||||
func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
|
||||
@@ -181,7 +181,7 @@ func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
|
||||
case 3:
|
||||
guild.PugiOutfit3 = outfit
|
||||
}
|
||||
_ = guild.Save(s)
|
||||
_ = s.server.guildRepo.Save(guild)
|
||||
}
|
||||
|
||||
func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
||||
@@ -193,8 +193,8 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
||||
}
|
||||
var resetRoom bool
|
||||
if _type == 2 {
|
||||
var currentRP uint16
|
||||
if err := s.server.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guild.ID).Scan(¤tRP); err != nil {
|
||||
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 {
|
||||
@@ -206,23 +206,23 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
||||
saveData.Save(s)
|
||||
switch _type {
|
||||
case 0:
|
||||
if _, err := s.server.db.Exec(`UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2`, amount, guild.ID); err != nil {
|
||||
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.db.Exec(`UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2`, amount, guild.ID); err != nil {
|
||||
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.db.Exec(`UPDATE guilds SET room_rp = 0 WHERE id = $1`, guild.ID); err != nil {
|
||||
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.db.Exec(`UPDATE guilds SET room_expiry = $1 WHERE id = $2`, TimeAdjusted().Add(time.Hour*24*7), guild.ID); err != nil {
|
||||
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.db.Exec(`UPDATE guilds SET room_rp = room_rp + $1 WHERE id = $2`, amount, guild.ID); err != nil {
|
||||
if err := s.server.guildRepo.AddRoomRP(guild.ID, amount); err != nil {
|
||||
s.logger.Error("Failed to update guild room RP", zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
||||
}
|
||||
|
||||
func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, avoidLeadership bool) {
|
||||
characterGuildData, err := GetCharacterGuildData(s, s.charID)
|
||||
characterGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -242,7 +242,7 @@ func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild,
|
||||
|
||||
characterGuildData.AvoidLeadership = avoidLeadership
|
||||
|
||||
err = characterGuildData.Save(s)
|
||||
err = s.server.guildRepo.SaveMember(characterGuildData)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -255,14 +255,14 @@ func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild,
|
||||
func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuildMember)
|
||||
|
||||
guild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(pkt.CharID)
|
||||
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
actorCharacter, err := GetCharacterGuildData(s, s.charID)
|
||||
actorCharacter, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
|
||||
if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -272,7 +272,7 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
var mail Mail
|
||||
switch pkt.Action {
|
||||
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
|
||||
err = guild.AcceptApplication(s, pkt.CharID)
|
||||
err = s.server.guildRepo.AcceptApplication(guild.ID, pkt.CharID)
|
||||
mail = Mail{
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: "Accepted!",
|
||||
@@ -280,7 +280,7 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
IsSystemMessage: true,
|
||||
}
|
||||
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
|
||||
err = guild.RejectApplication(s, pkt.CharID)
|
||||
err = s.server.guildRepo.RejectApplication(guild.ID, pkt.CharID)
|
||||
mail = Mail{
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: "Rejected",
|
||||
@@ -288,7 +288,7 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
IsSystemMessage: true,
|
||||
}
|
||||
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
|
||||
err = guild.RemoveCharacter(s, pkt.CharID)
|
||||
err = s.server.guildRepo.RemoveCharacter(pkt.CharID)
|
||||
mail = Mail{
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: "Kicked",
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGuildScout)
|
||||
|
||||
actorCharGuildData, err := GetCharacterGuildData(s, s.charID)
|
||||
actorCharGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get character guild data for scout", zap.Error(err))
|
||||
@@ -25,7 +25,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID)
|
||||
guildInfo, err := s.server.guildRepo.GetByID(actorCharGuildData.GuildID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild info for scout", zap.Error(err))
|
||||
@@ -33,7 +33,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID)
|
||||
hasApplication, err := s.server.guildRepo.HasApplication(guildInfo.ID, pkt.CharID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to check application for scout", zap.Error(err))
|
||||
@@ -54,10 +54,10 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction)
|
||||
err = s.server.guildRepo.CreateApplication(guildInfo.ID, pkt.CharID, s.charID, GuildApplicationTypeInvited, transaction)
|
||||
|
||||
if err != nil {
|
||||
rollbackTransaction(s, transaction)
|
||||
_ = transaction.Rollback()
|
||||
s.logger.Error("Failed to create guild scout application", zap.Error(err))
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
@@ -77,7 +77,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
err = mail.Send(s, transaction)
|
||||
|
||||
if err != nil {
|
||||
rollbackTransaction(s, transaction)
|
||||
_ = transaction.Rollback()
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCancelGuildScout)
|
||||
|
||||
guildCharData, err := GetCharacterGuildData(s, s.charID)
|
||||
guildCharData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get character guild data for cancel scout", zap.Error(err))
|
||||
@@ -109,14 +109,14 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
guild, err := GetGuildInfoByID(s, guildCharData.GuildID)
|
||||
guild, err := s.server.guildRepo.GetByID(guildCharData.GuildID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
err = guild.CancelInvitation(s, pkt.InvitationID)
|
||||
err = s.server.guildRepo.CancelInvitation(guild.ID, pkt.InvitationID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
@@ -129,7 +129,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
|
||||
bf := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(pkt.LeaderID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild info for answer scout", zap.Error(err))
|
||||
@@ -137,7 +137,7 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
|
||||
app, err := s.server.guildRepo.GetApplication(guild.ID, s.charID, GuildApplicationTypeInvited)
|
||||
|
||||
if app == nil || err != nil {
|
||||
s.logger.Warn(
|
||||
@@ -154,7 +154,7 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
var mail []Mail
|
||||
if pkt.Answer {
|
||||
err = guild.AcceptApplication(s, s.charID)
|
||||
err = s.server.guildRepo.AcceptApplication(guild.ID, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.i18n.guild.invite.success.title,
|
||||
@@ -169,7 +169,7 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
IsSystemMessage: true,
|
||||
})
|
||||
} else {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
err = s.server.guildRepo.RejectApplication(guild.ID, s.charID)
|
||||
mail = append(mail, Mail{
|
||||
RecipientID: s.charID,
|
||||
Subject: s.server.i18n.guild.invite.rejected.title,
|
||||
@@ -201,13 +201,13 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
|
||||
|
||||
guildInfo, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guildInfo, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||
|
||||
if guildInfo == nil && s.prevGuildID == 0 {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
guildInfo, err := GetGuildInfoByID(s, s.prevGuildID)
|
||||
guildInfo, err := s.server.guildRepo.GetByID(s.prevGuildID)
|
||||
if guildInfo == nil || err != nil {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -216,7 +216,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT c.id, c.name, c.hr, c.gr, ga.actor_id
|
||||
FROM guild_applications ga
|
||||
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)
|
||||
|
||||
@@ -25,7 +25,7 @@ type TreasureHunt struct {
|
||||
|
||||
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -86,7 +86,7 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildTresure)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
|
||||
huntData := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
|
||||
@@ -82,11 +82,11 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil || guild == nil {
|
||||
break
|
||||
}
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
guildMembers, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -190,10 +190,10 @@ func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Guild verification
|
||||
if state > 3 {
|
||||
ownGuild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
isApplicant, _ := ownGuild.HasApplicationForCharID(s, s.charID)
|
||||
ownGuild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
isApplicant, _ := s.server.guildRepo.HasApplication(ownGuild.ID, s.charID)
|
||||
if err == nil && ownGuild != nil {
|
||||
othersGuild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
|
||||
othersGuild, err := s.server.guildRepo.GetByCharID(pkt.CharID)
|
||||
if err == nil && othersGuild != nil {
|
||||
if othersGuild.ID == ownGuild.ID && !isApplicant {
|
||||
allowed = true
|
||||
|
||||
@@ -339,13 +339,13 @@ func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
`
|
||||
|
||||
if pkt.RecipientID == 0 { // Guild mail
|
||||
g, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
g, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild info for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
gm, err := GetGuildMembers(s, g.ID, false)
|
||||
gm, err := s.server.guildRepo.GetMembers(g.ID, false)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to get guild members for mail")
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
|
||||
@@ -388,7 +388,7 @@ type Airou struct {
|
||||
func getGuildAirouList(s *Session) []Airou {
|
||||
var guildCats []Airou
|
||||
bannedCats := make(map[uint32]int)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
guild, err := s.server.guildRepo.GetByCharID(s.charID)
|
||||
if err != nil {
|
||||
return guildCats
|
||||
}
|
||||
|
||||
@@ -213,8 +213,11 @@ type RengokuScore struct {
|
||||
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
|
||||
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
|
||||
guild, _ := s.server.guildRepo.GetByCharID(s.charID)
|
||||
var isApplicant bool
|
||||
if guild != nil {
|
||||
isApplicant, _ = s.server.guildRepo.HasApplication(guild.ID, s.charID)
|
||||
}
|
||||
if isApplicant {
|
||||
guild = nil
|
||||
}
|
||||
|
||||
472
server/channelserver/repo_guild.go
Normal file
472
server/channelserver/repo_guild.go
Normal file
@@ -0,0 +1,472 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
// GuildRepository centralizes all database access for guild-related tables
|
||||
// (guilds, guild_characters, guild_applications).
|
||||
type GuildRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
// NewGuildRepository creates a new GuildRepository.
|
||||
func NewGuildRepository(db *sqlx.DB) *GuildRepository {
|
||||
return &GuildRepository{db: db}
|
||||
}
|
||||
|
||||
const guildInfoSelectSQL = `
|
||||
SELECT
|
||||
g.id,
|
||||
g.name,
|
||||
rank_rp,
|
||||
event_rp,
|
||||
room_rp,
|
||||
COALESCE(room_expiry, '1970-01-01') AS room_expiry,
|
||||
main_motto,
|
||||
sub_motto,
|
||||
created_at,
|
||||
leader_id,
|
||||
c.name AS leader_name,
|
||||
comment,
|
||||
COALESCE(pugi_name_1, '') AS pugi_name_1,
|
||||
COALESCE(pugi_name_2, '') AS pugi_name_2,
|
||||
COALESCE(pugi_name_3, '') AS pugi_name_3,
|
||||
pugi_outfit_1,
|
||||
pugi_outfit_2,
|
||||
pugi_outfit_3,
|
||||
pugi_outfits,
|
||||
recruiting,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color,
|
||||
COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls,
|
||||
COALESCE((
|
||||
SELECT id FROM guild_alliances ga WHERE
|
||||
ga.parent_id = g.id OR
|
||||
ga.sub1_id = g.id OR
|
||||
ga.sub2_id = g.id
|
||||
), 0) AS alliance_id,
|
||||
icon,
|
||||
(SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count
|
||||
FROM guilds g
|
||||
JOIN guild_characters gc ON gc.character_id = leader_id
|
||||
JOIN characters c on leader_id = c.id
|
||||
`
|
||||
|
||||
const guildMembersSelectSQL = `
|
||||
SELECT
|
||||
COALESCE(g.id, 0) AS guild_id,
|
||||
joined_at,
|
||||
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
|
||||
COALESCE(rp_today, 0) AS rp_today,
|
||||
COALESCE(rp_yesterday, 0) AS rp_yesterday,
|
||||
c.name,
|
||||
c.id AS character_id,
|
||||
COALESCE(order_index, 0) AS order_index,
|
||||
c.last_login,
|
||||
COALESCE(recruiter, false) AS recruiter,
|
||||
COALESCE(avoid_leadership, false) AS avoid_leadership,
|
||||
c.hr,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader,
|
||||
character.is_applicant
|
||||
FROM (
|
||||
SELECT character_id, true as is_applicant, guild_id
|
||||
FROM guild_applications ga
|
||||
WHERE ga.application_type = 'applied'
|
||||
UNION
|
||||
SELECT character_id, false as is_applicant, guild_id
|
||||
FROM guild_characters gc
|
||||
) character
|
||||
JOIN characters c on character.character_id = c.id
|
||||
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
|
||||
LEFT JOIN guilds g ON g.id = gc.guild_id
|
||||
`
|
||||
|
||||
func scanGuild(rows *sqlx.Rows) (*Guild, error) {
|
||||
guild := &Guild{}
|
||||
if err := rows.StructScan(guild); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return guild, nil
|
||||
}
|
||||
|
||||
func scanGuildMember(rows *sqlx.Rows) (*GuildMember, error) {
|
||||
member := &GuildMember{}
|
||||
if err := rows.StructScan(member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return member, nil
|
||||
}
|
||||
|
||||
// GetByID retrieves guild info by guild ID, returning nil if not found.
|
||||
func (r *GuildRepository) GetByID(guildID uint32) (*Guild, error) {
|
||||
rows, err := r.db.Queryx(fmt.Sprintf(`%s WHERE g.id = $1 LIMIT 1`, guildInfoSelectSQL), guildID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanGuild(rows)
|
||||
}
|
||||
|
||||
// GetByCharID retrieves guild info for a character, including applied guilds.
|
||||
func (r *GuildRepository) GetByCharID(charID uint32) (*Guild, error) {
|
||||
rows, err := r.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_characters gc1
|
||||
WHERE gc1.character_id = $1
|
||||
AND gc1.guild_id = g.id
|
||||
)
|
||||
OR EXISTS(
|
||||
SELECT 1
|
||||
FROM guild_applications ga
|
||||
WHERE ga.character_id = $1
|
||||
AND ga.guild_id = g.id
|
||||
AND ga.application_type = 'applied'
|
||||
)
|
||||
LIMIT 1
|
||||
`, guildInfoSelectSQL), charID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanGuild(rows)
|
||||
}
|
||||
|
||||
// ListAll returns all guilds. Used for guild enumeration/search.
|
||||
func (r *GuildRepository) ListAll() ([]*Guild, error) {
|
||||
rows, err := r.db.Queryx(guildInfoSelectSQL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var guilds []*Guild
|
||||
for rows.Next() {
|
||||
guild, err := scanGuild(rows)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
return guilds, nil
|
||||
}
|
||||
|
||||
// Create creates a new guild and adds the leader as its first member.
|
||||
func (r *GuildRepository) Create(leaderCharID uint32, guildName string) (int32, error) {
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var guildID int32
|
||||
err = tx.QueryRow(
|
||||
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
|
||||
guildName, leaderCharID,
|
||||
).Scan(&guildID)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = tx.Exec(`INSERT INTO guild_characters (guild_id, character_id) VALUES ($1, $2)`, guildID, leaderCharID)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return guildID, nil
|
||||
}
|
||||
|
||||
// Save persists guild metadata changes.
|
||||
func (r *GuildRepository) Save(guild *Guild) error {
|
||||
_, err := r.db.Exec(`
|
||||
UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7,
|
||||
pugi_outfit_1=$8, pugi_outfit_2=$9, pugi_outfit_3=$10, pugi_outfits=$11, icon=$12, leader_id=$13 WHERE id=$1
|
||||
`, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3,
|
||||
guild.PugiOutfit1, guild.PugiOutfit2, guild.PugiOutfit3, guild.PugiOutfits, guild.Icon, guild.LeaderCharID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Disband removes a guild, its members, and cleans up alliance references.
|
||||
func (r *GuildRepository) Disband(guildID uint32) error {
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmts := []string{
|
||||
"DELETE FROM guild_characters WHERE guild_id = $1",
|
||||
"DELETE FROM guilds WHERE id = $1",
|
||||
"DELETE FROM guild_alliances WHERE parent_id=$1",
|
||||
}
|
||||
for _, stmt := range stmts {
|
||||
if _, err := tx.Exec(stmt, guildID); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tx.Exec("UPDATE guild_alliances SET sub1_id=sub2_id, sub2_id=NULL WHERE sub1_id=$1", guildID); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec("UPDATE guild_alliances SET sub2_id=NULL WHERE sub2_id=$1", guildID); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// RemoveCharacter removes a character from their guild.
|
||||
func (r *GuildRepository) RemoveCharacter(charID uint32) error {
|
||||
_, err := r.db.Exec("DELETE FROM guild_characters WHERE character_id=$1", charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// AcceptApplication deletes the application and adds the character to the guild.
|
||||
func (r *GuildRepository) AcceptApplication(guildID, charID uint32) error {
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM guild_applications WHERE character_id = $1`, charID); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`
|
||||
INSERT INTO guild_characters (guild_id, character_id, order_index)
|
||||
VALUES ($1, $2, (SELECT MAX(order_index) + 1 FROM guild_characters WHERE guild_id = $1))
|
||||
`, guildID, charID); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// CreateApplication inserts a guild application or invitation.
|
||||
// If tx is non-nil, the operation participates in the given transaction.
|
||||
func (r *GuildRepository) CreateApplication(guildID, charID, actorID uint32, appType GuildApplicationType, tx *sql.Tx) error {
|
||||
query := `INSERT INTO guild_applications (guild_id, character_id, actor_id, application_type) VALUES ($1, $2, $3, $4)`
|
||||
if tx != nil {
|
||||
_, err := tx.Exec(query, guildID, charID, actorID, appType)
|
||||
return err
|
||||
}
|
||||
_, err := r.db.Exec(query, guildID, charID, actorID, appType)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelInvitation removes an invitation for a character.
|
||||
func (r *GuildRepository) CancelInvitation(guildID, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'invited'`,
|
||||
charID, guildID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// RejectApplication removes an applied application for a character.
|
||||
func (r *GuildRepository) RejectApplication(guildID, charID uint32) error {
|
||||
_, err := r.db.Exec(
|
||||
`DELETE FROM guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = 'applied'`,
|
||||
charID, guildID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// ArrangeCharacters reorders guild members by updating their order_index values.
|
||||
func (r *GuildRepository) ArrangeCharacters(charIDs []uint32) error {
|
||||
tx, err := r.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, id := range charIDs {
|
||||
if _, err := tx.Exec("UPDATE guild_characters SET order_index = $1 WHERE character_id = $2", 2+i, id); err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// GetApplication retrieves a specific application by character, guild, and type.
|
||||
// Returns nil, nil if not found.
|
||||
func (r *GuildRepository) GetApplication(guildID, charID uint32, appType GuildApplicationType) (*GuildApplication, error) {
|
||||
app := &GuildApplication{}
|
||||
err := r.db.QueryRowx(`
|
||||
SELECT * from guild_applications WHERE character_id = $1 AND guild_id = $2 AND application_type = $3
|
||||
`, charID, guildID, appType).StructScan(app)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// HasApplication checks whether any application exists for the character in the guild.
|
||||
func (r *GuildRepository) HasApplication(guildID, charID uint32) (bool, error) {
|
||||
var n int
|
||||
err := r.db.QueryRow(`SELECT 1 from guild_applications WHERE character_id = $1 AND guild_id = $2`, charID, guildID).Scan(&n)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetItemBox returns the raw item_box bytes for a guild.
|
||||
func (r *GuildRepository) GetItemBox(guildID uint32) ([]byte, error) {
|
||||
var data []byte
|
||||
err := r.db.QueryRow(`SELECT item_box FROM guilds WHERE id=$1`, guildID).Scan(&data)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// SaveItemBox writes the serialized item box data for a guild.
|
||||
func (r *GuildRepository) SaveItemBox(guildID uint32, data []byte) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET item_box=$1 WHERE id=$2`, data, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetMembers loads all members (or applicants) of a guild.
|
||||
func (r *GuildRepository) GetMembers(guildID uint32, applicants bool) ([]*GuildMember, error) {
|
||||
rows, err := r.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE character.guild_id = $1 AND is_applicant = $2
|
||||
`, guildMembersSelectSQL), guildID, applicants)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
members := make([]*GuildMember, 0)
|
||||
for rows.Next() {
|
||||
member, err := scanGuildMember(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members = append(members, member)
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
// GetCharacterMembership loads a character's guild membership data.
|
||||
// Returns nil, nil if the character is not in any guild.
|
||||
func (r *GuildRepository) GetCharacterMembership(charID uint32) (*GuildMember, error) {
|
||||
rows, err := r.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
return nil, nil
|
||||
}
|
||||
return scanGuildMember(rows)
|
||||
}
|
||||
|
||||
// SaveMember persists guild member changes (avoid_leadership and order_index).
|
||||
func (r *GuildRepository) SaveMember(member *GuildMember) error {
|
||||
_, err := r.db.Exec(
|
||||
"UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3",
|
||||
member.AvoidLeadership, member.OrderIndex, member.CharID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRecruiting updates whether a guild is accepting applications.
|
||||
func (r *GuildRepository) SetRecruiting(guildID uint32, recruiting bool) error {
|
||||
_, err := r.db.Exec("UPDATE guilds SET recruiting=$1 WHERE id=$2", recruiting, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetPugiOutfits updates the unlocked pugi outfit bitmask.
|
||||
func (r *GuildRepository) SetPugiOutfits(guildID uint32, outfits uint32) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET pugi_outfits=$1 WHERE id=$2`, outfits, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRecruiter updates whether a character has recruiter rights.
|
||||
func (r *GuildRepository) SetRecruiter(charID uint32, allowed bool) error {
|
||||
_, err := r.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", allowed, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddMemberDailyRP adds RP to a member's daily total.
|
||||
func (r *GuildRepository) AddMemberDailyRP(charID uint32, amount uint16) error {
|
||||
_, err := r.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, amount, charID)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExchangeEventRP subtracts RP from a guild's event pool and returns the new balance.
|
||||
func (r *GuildRepository) ExchangeEventRP(guildID uint32, amount uint16) (uint32, error) {
|
||||
var balance uint32
|
||||
err := r.db.QueryRow(`UPDATE guilds SET event_rp=event_rp-$1 WHERE id=$2 RETURNING event_rp`, amount, guildID).Scan(&balance)
|
||||
return balance, err
|
||||
}
|
||||
|
||||
// AddRankRP adds RP to a guild's rank total.
|
||||
func (r *GuildRepository) AddRankRP(guildID uint32, amount uint16) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET rank_rp = rank_rp + $1 WHERE id = $2`, amount, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddEventRP adds RP to a guild's event total.
|
||||
func (r *GuildRepository) AddEventRP(guildID uint32, amount uint16) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET event_rp = event_rp + $1 WHERE id = $2`, amount, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetRoomRP returns the current room RP for a guild.
|
||||
func (r *GuildRepository) GetRoomRP(guildID uint32) (uint16, error) {
|
||||
var rp uint16
|
||||
err := r.db.QueryRow(`SELECT room_rp FROM guilds WHERE id = $1`, guildID).Scan(&rp)
|
||||
return rp, err
|
||||
}
|
||||
|
||||
// SetRoomRP sets the room RP for a guild.
|
||||
func (r *GuildRepository) SetRoomRP(guildID uint32, rp uint16) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET room_rp = $1 WHERE id = $2`, rp, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddRoomRP atomically adds RP to a guild's room total.
|
||||
func (r *GuildRepository) AddRoomRP(guildID uint32, amount uint16) error {
|
||||
_, err := r.db.Exec(`UPDATE guilds SET room_rp = room_rp + $1 WHERE id = $2`, amount, guildID)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRoomExpiry sets the room expiry time for a guild.
|
||||
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)
|
||||
return err
|
||||
}
|
||||
531
server/channelserver/repo_guild_test.go
Normal file
531
server/channelserver/repo_guild_test.go
Normal file
@@ -0,0 +1,531 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func setupGuildRepo(t *testing.T) (*GuildRepository, *sqlx.DB, uint32, uint32) {
|
||||
t.Helper()
|
||||
db := SetupTestDB(t)
|
||||
userID := CreateTestUser(t, db, "guild_test_user")
|
||||
charID := CreateTestCharacter(t, db, userID, "GuildLeader")
|
||||
repo := NewGuildRepository(db)
|
||||
guildID := CreateTestGuild(t, db, charID, "TestGuild")
|
||||
t.Cleanup(func() { TeardownTestDB(t, db) })
|
||||
return repo, db, guildID, charID
|
||||
}
|
||||
|
||||
func TestGetByID(t *testing.T) {
|
||||
repo, _, guildID, charID := setupGuildRepo(t)
|
||||
|
||||
guild, err := repo.GetByID(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID failed: %v", err)
|
||||
}
|
||||
if guild == nil {
|
||||
t.Fatal("Expected guild, got nil")
|
||||
}
|
||||
if guild.ID != guildID {
|
||||
t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
|
||||
}
|
||||
if guild.Name != "TestGuild" {
|
||||
t.Errorf("Expected name 'TestGuild', got %q", guild.Name)
|
||||
}
|
||||
if guild.LeaderCharID != charID {
|
||||
t.Errorf("Expected leader %d, got %d", charID, guild.LeaderCharID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByIDNotFound(t *testing.T) {
|
||||
repo, _, _, _ := setupGuildRepo(t)
|
||||
|
||||
guild, err := repo.GetByID(999999)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID failed: %v", err)
|
||||
}
|
||||
if guild != nil {
|
||||
t.Errorf("Expected nil for non-existent guild, got: %+v", guild)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByCharID(t *testing.T) {
|
||||
repo, _, guildID, charID := setupGuildRepo(t)
|
||||
|
||||
guild, err := repo.GetByCharID(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByCharID failed: %v", err)
|
||||
}
|
||||
if guild == nil {
|
||||
t.Fatal("Expected guild, got nil")
|
||||
}
|
||||
if guild.ID != guildID {
|
||||
t.Errorf("Expected guild ID %d, got %d", guildID, guild.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByCharIDNotFound(t *testing.T) {
|
||||
repo, _, _, _ := setupGuildRepo(t)
|
||||
|
||||
guild, err := repo.GetByCharID(999999)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByCharID failed: %v", err)
|
||||
}
|
||||
if guild != nil {
|
||||
t.Errorf("Expected nil for non-member, got: %+v", guild)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
db := SetupTestDB(t)
|
||||
defer TeardownTestDB(t, db)
|
||||
repo := NewGuildRepository(db)
|
||||
userID := CreateTestUser(t, db, "create_guild_user")
|
||||
charID := CreateTestCharacter(t, db, userID, "CreateLeader")
|
||||
|
||||
guildID, err := repo.Create(charID, "NewGuild")
|
||||
if err != nil {
|
||||
t.Fatalf("Create failed: %v", err)
|
||||
}
|
||||
if guildID <= 0 {
|
||||
t.Errorf("Expected positive guild ID, got %d", guildID)
|
||||
}
|
||||
|
||||
// Verify guild exists
|
||||
guild, err := repo.GetByID(uint32(guildID))
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID after Create failed: %v", err)
|
||||
}
|
||||
if guild == nil {
|
||||
t.Fatal("Created guild not found")
|
||||
}
|
||||
if guild.Name != "NewGuild" {
|
||||
t.Errorf("Expected name 'NewGuild', got %q", guild.Name)
|
||||
}
|
||||
|
||||
// Verify leader is a member
|
||||
member, err := repo.GetCharacterMembership(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership failed: %v", err)
|
||||
}
|
||||
if member == nil {
|
||||
t.Fatal("Leader not found as guild member")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveGuild(t *testing.T) {
|
||||
repo, _, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
guild, err := repo.GetByID(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID failed: %v", err)
|
||||
}
|
||||
|
||||
guild.Comment = "Updated comment"
|
||||
guild.MainMotto = 5
|
||||
guild.SubMotto = 3
|
||||
|
||||
if err := repo.Save(guild); err != nil {
|
||||
t.Fatalf("Save failed: %v", err)
|
||||
}
|
||||
|
||||
updated, err := repo.GetByID(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID after Save failed: %v", err)
|
||||
}
|
||||
if updated.Comment != "Updated comment" {
|
||||
t.Errorf("Expected comment 'Updated comment', got %q", updated.Comment)
|
||||
}
|
||||
if updated.MainMotto != 5 || updated.SubMotto != 3 {
|
||||
t.Errorf("Expected mottos 5/3, got %d/%d", updated.MainMotto, updated.SubMotto)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisband(t *testing.T) {
|
||||
repo, _, guildID, charID := setupGuildRepo(t)
|
||||
|
||||
if err := repo.Disband(guildID); err != nil {
|
||||
t.Fatalf("Disband failed: %v", err)
|
||||
}
|
||||
|
||||
guild, err := repo.GetByID(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetByID after Disband failed: %v", err)
|
||||
}
|
||||
if guild != nil {
|
||||
t.Errorf("Expected nil after disband, got: %+v", guild)
|
||||
}
|
||||
|
||||
member, err := repo.GetCharacterMembership(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership after Disband failed: %v", err)
|
||||
}
|
||||
if member != nil {
|
||||
t.Errorf("Expected nil membership after disband, got: %+v", member)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMembers(t *testing.T) {
|
||||
repo, db, guildID, leaderID := setupGuildRepo(t)
|
||||
|
||||
// Add a second member
|
||||
user2 := CreateTestUser(t, db, "member_user")
|
||||
member2 := CreateTestCharacter(t, db, user2, "Member2")
|
||||
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, member2); err != nil {
|
||||
t.Fatalf("Failed to add member: %v", err)
|
||||
}
|
||||
|
||||
members, err := repo.GetMembers(guildID, false)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMembers failed: %v", err)
|
||||
}
|
||||
if len(members) != 2 {
|
||||
t.Fatalf("Expected 2 members, got %d", len(members))
|
||||
}
|
||||
|
||||
ids := map[uint32]bool{leaderID: false, member2: false}
|
||||
for _, m := range members {
|
||||
ids[m.CharID] = true
|
||||
}
|
||||
if !ids[leaderID] || !ids[member2] {
|
||||
t.Errorf("Expected members %d and %d, got: %v", leaderID, member2, members)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCharacterMembership(t *testing.T) {
|
||||
repo, _, guildID, charID := setupGuildRepo(t)
|
||||
|
||||
member, err := repo.GetCharacterMembership(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership failed: %v", err)
|
||||
}
|
||||
if member == nil {
|
||||
t.Fatal("Expected membership, got nil")
|
||||
}
|
||||
if member.GuildID != guildID {
|
||||
t.Errorf("Expected guild ID %d, got %d", guildID, member.GuildID)
|
||||
}
|
||||
if !member.IsLeader {
|
||||
t.Error("Expected leader flag to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveMember(t *testing.T) {
|
||||
repo, _, _, charID := setupGuildRepo(t)
|
||||
|
||||
member, err := repo.GetCharacterMembership(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership failed: %v", err)
|
||||
}
|
||||
|
||||
member.AvoidLeadership = true
|
||||
member.OrderIndex = 5
|
||||
|
||||
if err := repo.SaveMember(member); err != nil {
|
||||
t.Fatalf("SaveMember failed: %v", err)
|
||||
}
|
||||
|
||||
updated, err := repo.GetCharacterMembership(charID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership after Save failed: %v", err)
|
||||
}
|
||||
if !updated.AvoidLeadership {
|
||||
t.Error("Expected avoid_leadership=true")
|
||||
}
|
||||
if updated.OrderIndex != 5 {
|
||||
t.Errorf("Expected order_index=5, got %d", updated.OrderIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveCharacter(t *testing.T) {
|
||||
repo, db, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
// Add and remove a member
|
||||
user2 := CreateTestUser(t, db, "remove_user")
|
||||
char2 := CreateTestCharacter(t, db, user2, "RemoveMe")
|
||||
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
|
||||
t.Fatalf("Failed to add member: %v", err)
|
||||
}
|
||||
|
||||
if err := repo.RemoveCharacter(char2); err != nil {
|
||||
t.Fatalf("RemoveCharacter failed: %v", err)
|
||||
}
|
||||
|
||||
member, err := repo.GetCharacterMembership(char2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership after remove failed: %v", err)
|
||||
}
|
||||
if member != nil {
|
||||
t.Errorf("Expected nil membership after remove, got: %+v", member)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationWorkflow(t *testing.T) {
|
||||
repo, db, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
user2 := CreateTestUser(t, db, "applicant_user")
|
||||
applicantID := CreateTestCharacter(t, db, user2, "Applicant")
|
||||
|
||||
// Create application
|
||||
err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateApplication failed: %v", err)
|
||||
}
|
||||
|
||||
// Check HasApplication
|
||||
has, err := repo.HasApplication(guildID, applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("HasApplication failed: %v", err)
|
||||
}
|
||||
if !has {
|
||||
t.Error("Expected application to exist")
|
||||
}
|
||||
|
||||
// Get application
|
||||
app, err := repo.GetApplication(guildID, applicantID, GuildApplicationTypeApplied)
|
||||
if err != nil {
|
||||
t.Fatalf("GetApplication failed: %v", err)
|
||||
}
|
||||
if app == nil {
|
||||
t.Fatal("Expected application, got nil")
|
||||
}
|
||||
|
||||
// Accept
|
||||
err = repo.AcceptApplication(guildID, applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("AcceptApplication failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify membership
|
||||
member, err := repo.GetCharacterMembership(applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCharacterMembership after accept failed: %v", err)
|
||||
}
|
||||
if member == nil {
|
||||
t.Fatal("Expected membership after accept")
|
||||
}
|
||||
|
||||
// Verify application removed
|
||||
has, err = repo.HasApplication(guildID, applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("HasApplication after accept failed: %v", err)
|
||||
}
|
||||
if has {
|
||||
t.Error("Expected no application after accept")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectApplication(t *testing.T) {
|
||||
repo, db, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
user2 := CreateTestUser(t, db, "reject_user")
|
||||
applicantID := CreateTestCharacter(t, db, user2, "Rejected")
|
||||
|
||||
err := repo.CreateApplication(guildID, applicantID, applicantID, GuildApplicationTypeApplied, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateApplication failed: %v", err)
|
||||
}
|
||||
|
||||
err = repo.RejectApplication(guildID, applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("RejectApplication failed: %v", err)
|
||||
}
|
||||
|
||||
has, err := repo.HasApplication(guildID, applicantID)
|
||||
if err != nil {
|
||||
t.Fatalf("HasApplication after reject failed: %v", err)
|
||||
}
|
||||
if has {
|
||||
t.Error("Expected no application after reject")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRecruiting(t *testing.T) {
|
||||
repo, db, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
if err := repo.SetRecruiting(guildID, false); err != nil {
|
||||
t.Fatalf("SetRecruiting failed: %v", err)
|
||||
}
|
||||
|
||||
var recruiting bool
|
||||
if err := db.QueryRow("SELECT recruiting FROM guilds WHERE id=$1", guildID).Scan(&recruiting); err != nil {
|
||||
t.Fatalf("Verification query failed: %v", err)
|
||||
}
|
||||
if recruiting {
|
||||
t.Error("Expected recruiting=false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPOperations(t *testing.T) {
|
||||
repo, db, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
// AddRankRP
|
||||
if err := repo.AddRankRP(guildID, 100); err != nil {
|
||||
t.Fatalf("AddRankRP failed: %v", err)
|
||||
}
|
||||
var rankRP uint16
|
||||
if err := db.QueryRow("SELECT rank_rp FROM guilds WHERE id=$1", guildID).Scan(&rankRP); err != nil {
|
||||
t.Fatalf("Verification failed: %v", err)
|
||||
}
|
||||
if rankRP != 100 {
|
||||
t.Errorf("Expected rank_rp=100, got %d", rankRP)
|
||||
}
|
||||
|
||||
// AddEventRP
|
||||
if err := repo.AddEventRP(guildID, 50); err != nil {
|
||||
t.Fatalf("AddEventRP failed: %v", err)
|
||||
}
|
||||
|
||||
// ExchangeEventRP
|
||||
balance, err := repo.ExchangeEventRP(guildID, 20)
|
||||
if err != nil {
|
||||
t.Fatalf("ExchangeEventRP failed: %v", err)
|
||||
}
|
||||
if balance != 30 {
|
||||
t.Errorf("Expected event_rp balance=30, got %d", balance)
|
||||
}
|
||||
|
||||
// Room RP operations
|
||||
if err := repo.AddRoomRP(guildID, 10); err != nil {
|
||||
t.Fatalf("AddRoomRP failed: %v", err)
|
||||
}
|
||||
roomRP, err := repo.GetRoomRP(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRoomRP failed: %v", err)
|
||||
}
|
||||
if roomRP != 10 {
|
||||
t.Errorf("Expected room_rp=10, got %d", roomRP)
|
||||
}
|
||||
|
||||
if err := repo.SetRoomRP(guildID, 0); err != nil {
|
||||
t.Fatalf("SetRoomRP failed: %v", err)
|
||||
}
|
||||
roomRP, err = repo.GetRoomRP(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRoomRP after reset failed: %v", err)
|
||||
}
|
||||
if roomRP != 0 {
|
||||
t.Errorf("Expected room_rp=0, got %d", roomRP)
|
||||
}
|
||||
|
||||
// SetRoomExpiry
|
||||
expiry := time.Date(2025, 7, 1, 0, 0, 0, 0, time.UTC)
|
||||
if err := repo.SetRoomExpiry(guildID, expiry); err != nil {
|
||||
t.Fatalf("SetRoomExpiry failed: %v", err)
|
||||
}
|
||||
var gotExpiry time.Time
|
||||
if err := db.QueryRow("SELECT room_expiry FROM guilds WHERE id=$1", guildID).Scan(&gotExpiry); err != nil {
|
||||
t.Fatalf("Verification failed: %v", err)
|
||||
}
|
||||
if !gotExpiry.Equal(expiry) {
|
||||
t.Errorf("Expected expiry %v, got %v", expiry, gotExpiry)
|
||||
}
|
||||
}
|
||||
|
||||
func TestItemBox(t *testing.T) {
|
||||
repo, _, guildID, _ := setupGuildRepo(t)
|
||||
|
||||
// Initially nil
|
||||
data, err := repo.GetItemBox(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetItemBox failed: %v", err)
|
||||
}
|
||||
if data != nil {
|
||||
t.Errorf("Expected nil item box initially, got %x", data)
|
||||
}
|
||||
|
||||
// Save and retrieve
|
||||
blob := []byte{0x01, 0x02, 0x03}
|
||||
if err := repo.SaveItemBox(guildID, blob); err != nil {
|
||||
t.Fatalf("SaveItemBox failed: %v", err)
|
||||
}
|
||||
|
||||
data, err = repo.GetItemBox(guildID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetItemBox after save failed: %v", err)
|
||||
}
|
||||
if len(data) != 3 || data[0] != 0x01 || data[2] != 0x03 {
|
||||
t.Errorf("Expected %x, got %x", blob, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAll(t *testing.T) {
|
||||
repo, db, _, _ := setupGuildRepo(t)
|
||||
|
||||
// Create a second guild
|
||||
user2 := CreateTestUser(t, db, "list_user")
|
||||
char2 := CreateTestCharacter(t, db, user2, "ListLeader")
|
||||
CreateTestGuild(t, db, char2, "SecondGuild")
|
||||
|
||||
guilds, err := repo.ListAll()
|
||||
if err != nil {
|
||||
t.Fatalf("ListAll failed: %v", err)
|
||||
}
|
||||
if len(guilds) < 2 {
|
||||
t.Errorf("Expected at least 2 guilds, got %d", len(guilds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrangeCharacters(t *testing.T) {
|
||||
repo, db, guildID, leaderID := setupGuildRepo(t)
|
||||
|
||||
// Add two more members
|
||||
user2 := CreateTestUser(t, db, "arrange_user2")
|
||||
char2 := CreateTestCharacter(t, db, user2, "Char2")
|
||||
user3 := CreateTestUser(t, db, "arrange_user3")
|
||||
char3 := CreateTestCharacter(t, db, user3, "Char3")
|
||||
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 2)", guildID, char2); err != nil {
|
||||
t.Fatalf("Failed to add member: %v", err)
|
||||
}
|
||||
if _, err := db.Exec("INSERT INTO guild_characters (guild_id, character_id, order_index) VALUES ($1, $2, 3)", guildID, char3); err != nil {
|
||||
t.Fatalf("Failed to add member: %v", err)
|
||||
}
|
||||
|
||||
// Rearrange (excludes leader, sets order_index starting at 2)
|
||||
if err := repo.ArrangeCharacters([]uint32{char3, char2}); err != nil {
|
||||
t.Fatalf("ArrangeCharacters failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify order changed
|
||||
var order2, order3 uint16
|
||||
_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char2).Scan(&order2)
|
||||
_ = db.QueryRow("SELECT order_index FROM guild_characters WHERE character_id=$1", char3).Scan(&order3)
|
||||
if order3 != 2 || order2 != 3 {
|
||||
t.Errorf("Expected char3=2, char2=3 but got char3=%d, char2=%d", order3, order2)
|
||||
}
|
||||
_ = leaderID
|
||||
}
|
||||
|
||||
func TestSetRecruiter(t *testing.T) {
|
||||
repo, db, _, charID := setupGuildRepo(t)
|
||||
|
||||
if err := repo.SetRecruiter(charID, true); err != nil {
|
||||
t.Fatalf("SetRecruiter failed: %v", err)
|
||||
}
|
||||
|
||||
var recruiter bool
|
||||
if err := db.QueryRow("SELECT recruiter FROM guild_characters WHERE character_id=$1", charID).Scan(&recruiter); err != nil {
|
||||
t.Fatalf("Verification failed: %v", err)
|
||||
}
|
||||
if !recruiter {
|
||||
t.Error("Expected recruiter=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddMemberDailyRP(t *testing.T) {
|
||||
repo, db, _, charID := setupGuildRepo(t)
|
||||
|
||||
if err := repo.AddMemberDailyRP(charID, 25); err != nil {
|
||||
t.Fatalf("AddMemberDailyRP failed: %v", err)
|
||||
}
|
||||
|
||||
var rp uint16
|
||||
if err := db.QueryRow("SELECT rp_today FROM guild_characters WHERE character_id=$1", charID).Scan(&rp); err != nil {
|
||||
t.Fatalf("Verification failed: %v", err)
|
||||
}
|
||||
if rp != 25 {
|
||||
t.Errorf("Expected rp_today=25, got %d", rp)
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ type Server struct {
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
charRepo *CharacterRepository
|
||||
guildRepo *GuildRepository
|
||||
erupeConfig *_config.Config
|
||||
acceptConns chan net.Conn
|
||||
deleteConns chan net.Conn
|
||||
@@ -117,6 +118,7 @@ func NewServer(config *Config) *Server {
|
||||
}
|
||||
|
||||
s.charRepo = NewCharacterRepository(config.DB)
|
||||
s.guildRepo = NewGuildRepository(config.DB)
|
||||
|
||||
// Mezeporta
|
||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||
|
||||
@@ -258,3 +258,38 @@ func CreateTestCharacter(t *testing.T, db *sqlx.DB, userID uint32, name string)
|
||||
|
||||
return charID
|
||||
}
|
||||
|
||||
// CreateTestGuild creates a test guild with the given leader and returns the guild ID
|
||||
func CreateTestGuild(t *testing.T, db *sqlx.DB, leaderCharID uint32, name string) uint32 {
|
||||
t.Helper()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to begin transaction: %v", err)
|
||||
}
|
||||
|
||||
var guildID uint32
|
||||
err = tx.QueryRow(
|
||||
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
|
||||
name, leaderCharID,
|
||||
).Scan(&guildID)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
t.Fatalf("Failed to create test guild: %v", err)
|
||||
}
|
||||
|
||||
_, err = tx.Exec(
|
||||
"INSERT INTO guild_characters (guild_id, character_id) VALUES ($1, $2)",
|
||||
guildID, leaderCharID,
|
||||
)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
t.Fatalf("Failed to add leader to guild: %v", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatalf("Failed to commit guild creation: %v", err)
|
||||
}
|
||||
|
||||
return guildID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user