mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-28 10:32:55 +01:00
refactor(channelserver): extract guild model, chat commands, and seibattle
Split three large files into focused modules: - handlers_guild.go: extract types/ORM into guild_model.go - handlers_cast_binary.go: extract command parser into handlers_commands.go - handlers.go: move seibattle types/handlers into handlers_seibattle.go
This commit is contained in:
628
server/channelserver/guild_model.go
Normal file
628
server/channelserver/guild_model.go
Normal file
@@ -0,0 +1,628 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FestivalColor string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FestivalColorNone FestivalColor = "none"
|
||||||
|
FestivalColorBlue FestivalColor = "blue"
|
||||||
|
FestivalColorRed FestivalColor = "red"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FestivalColorCodes = map[FestivalColor]int16{
|
||||||
|
FestivalColorNone: -1,
|
||||||
|
FestivalColorBlue: 0,
|
||||||
|
FestivalColorRed: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuildApplicationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GuildApplicationTypeApplied GuildApplicationType = "applied"
|
||||||
|
GuildApplicationTypeInvited GuildApplicationType = "invited"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Guild struct {
|
||||||
|
ID uint32 `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
MainMotto uint8 `db:"main_motto"`
|
||||||
|
SubMotto uint8 `db:"sub_motto"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
MemberCount uint16 `db:"member_count"`
|
||||||
|
RankRP uint32 `db:"rank_rp"`
|
||||||
|
EventRP uint32 `db:"event_rp"`
|
||||||
|
RoomRP uint16 `db:"room_rp"`
|
||||||
|
RoomExpiry time.Time `db:"room_expiry"`
|
||||||
|
Comment string `db:"comment"`
|
||||||
|
PugiName1 string `db:"pugi_name_1"`
|
||||||
|
PugiName2 string `db:"pugi_name_2"`
|
||||||
|
PugiName3 string `db:"pugi_name_3"`
|
||||||
|
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
||||||
|
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
||||||
|
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
||||||
|
PugiOutfits uint32 `db:"pugi_outfits"`
|
||||||
|
Recruiting bool `db:"recruiting"`
|
||||||
|
FestivalColor FestivalColor `db:"festival_color"`
|
||||||
|
Souls uint32 `db:"souls"`
|
||||||
|
AllianceID uint32 `db:"alliance_id"`
|
||||||
|
Icon *GuildIcon `db:"icon"`
|
||||||
|
|
||||||
|
GuildLeader
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuildLeader struct {
|
||||||
|
LeaderCharID uint32 `db:"leader_id"`
|
||||||
|
LeaderName string `db:"leader_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuildIconPart struct {
|
||||||
|
Index uint16
|
||||||
|
ID uint16
|
||||||
|
Page uint8
|
||||||
|
Size uint8
|
||||||
|
Rotation uint8
|
||||||
|
Red uint8
|
||||||
|
Green uint8
|
||||||
|
Blue uint8
|
||||||
|
PosX uint16
|
||||||
|
PosY uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuildApplication struct {
|
||||||
|
ID int `db:"id"`
|
||||||
|
GuildID uint32 `db:"guild_id"`
|
||||||
|
CharID uint32 `db:"character_id"`
|
||||||
|
ActorID uint32 `db:"actor_id"`
|
||||||
|
ApplicationType GuildApplicationType `db:"application_type"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GuildIcon struct {
|
||||||
|
Parts []GuildIconPart
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *GuildIcon) Scan(val interface{}) (err error) {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case []byte:
|
||||||
|
err = json.Unmarshal(v, &gi)
|
||||||
|
case string:
|
||||||
|
err = json.Unmarshal([]byte(v), &gi)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gi *GuildIcon) Value() (valuer driver.Value, err error) {
|
||||||
|
return json.Marshal(gi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Guild) Rank() uint16 {
|
||||||
|
rpMap := []uint32{
|
||||||
|
24, 48, 96, 144, 192, 240, 288, 360, 432,
|
||||||
|
504, 600, 696, 792, 888, 984, 1080, 1200,
|
||||||
|
}
|
||||||
|
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||||
|
rpMap = []uint32{
|
||||||
|
3500, 6000, 8500, 11000, 13500, 16000, 20000, 24000, 28000,
|
||||||
|
33000, 38000, 43000, 48000, 55000, 70000, 90000, 120000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, u := range rpMap {
|
||||||
|
if g.RankRP < u {
|
||||||
|
if _config.ErupeConfig.RealClientMode <= _config.S6 && i >= 12 {
|
||||||
|
return 12
|
||||||
|
} else if _config.ErupeConfig.RealClientMode <= _config.F5 && i >= 13 {
|
||||||
|
return 13
|
||||||
|
} else if _config.ErupeConfig.RealClientMode <= _config.G32 && i >= 14 {
|
||||||
|
return 14
|
||||||
|
}
|
||||||
|
return uint16(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||||
|
return 12
|
||||||
|
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||||
|
return 13
|
||||||
|
} else if _config.ErupeConfig.RealClientMode <= _config.G32 {
|
||||||
|
return 14
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -254,149 +254,6 @@ func handleMsgMhfGetSenyuDailyCount(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeibattleTimetable struct {
|
|
||||||
Start time.Time
|
|
||||||
End time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleKeyScore struct {
|
|
||||||
Unk0 uint8
|
|
||||||
Unk1 int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleCareer struct {
|
|
||||||
Unk0 uint16
|
|
||||||
Unk1 uint16
|
|
||||||
Unk2 uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleOpponent struct {
|
|
||||||
Unk0 int32
|
|
||||||
Unk1 int8
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleConventionResult struct {
|
|
||||||
Unk0 uint32
|
|
||||||
Unk1 uint16
|
|
||||||
Unk2 uint16
|
|
||||||
Unk3 uint16
|
|
||||||
Unk4 uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleCharScore struct {
|
|
||||||
Unk0 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type SeibattleCurResult struct {
|
|
||||||
Unk0 uint32
|
|
||||||
Unk1 uint16
|
|
||||||
Unk2 uint16
|
|
||||||
Unk3 uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type Seibattle struct {
|
|
||||||
Timetable []SeibattleTimetable
|
|
||||||
KeyScore []SeibattleKeyScore
|
|
||||||
Career []SeibattleCareer
|
|
||||||
Opponent []SeibattleOpponent
|
|
||||||
ConventionResult []SeibattleConventionResult
|
|
||||||
CharScore []SeibattleCharScore
|
|
||||||
CurResult []SeibattleCurResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetSeibattle)
|
|
||||||
var data []*byteframe.ByteFrame
|
|
||||||
seibattle := Seibattle{
|
|
||||||
Timetable: []SeibattleTimetable{
|
|
||||||
{TimeMidnight(), TimeMidnight().Add(time.Hour * 8)},
|
|
||||||
{TimeMidnight().Add(time.Hour * 8), TimeMidnight().Add(time.Hour * 16)},
|
|
||||||
{TimeMidnight().Add(time.Hour * 16), TimeMidnight().Add(time.Hour * 24)},
|
|
||||||
},
|
|
||||||
KeyScore: []SeibattleKeyScore{
|
|
||||||
{0, 0},
|
|
||||||
},
|
|
||||||
Career: []SeibattleCareer{
|
|
||||||
{0, 0, 0},
|
|
||||||
},
|
|
||||||
Opponent: []SeibattleOpponent{
|
|
||||||
{1, 1},
|
|
||||||
},
|
|
||||||
ConventionResult: []SeibattleConventionResult{
|
|
||||||
{0, 0, 0, 0, 0},
|
|
||||||
},
|
|
||||||
CharScore: []SeibattleCharScore{
|
|
||||||
{0},
|
|
||||||
},
|
|
||||||
CurResult: []SeibattleCurResult{
|
|
||||||
{0, 0, 0, 0},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
switch pkt.Type {
|
|
||||||
case 1:
|
|
||||||
for _, timetable := range seibattle.Timetable {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint32(uint32(timetable.Start.Unix()))
|
|
||||||
bf.WriteUint32(uint32(timetable.End.Unix()))
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 3: // Key score?
|
|
||||||
for _, keyScore := range seibattle.KeyScore {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint8(keyScore.Unk0)
|
|
||||||
bf.WriteInt32(keyScore.Unk1)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 4: // Career?
|
|
||||||
for _, career := range seibattle.Career {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint16(career.Unk0)
|
|
||||||
bf.WriteUint16(career.Unk1)
|
|
||||||
bf.WriteUint16(career.Unk2)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 5: // Opponent?
|
|
||||||
for _, opponent := range seibattle.Opponent {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteInt32(opponent.Unk0)
|
|
||||||
bf.WriteInt8(opponent.Unk1)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 6: // Convention result?
|
|
||||||
for _, conventionResult := range seibattle.ConventionResult {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint32(conventionResult.Unk0)
|
|
||||||
bf.WriteUint16(conventionResult.Unk1)
|
|
||||||
bf.WriteUint16(conventionResult.Unk2)
|
|
||||||
bf.WriteUint16(conventionResult.Unk3)
|
|
||||||
bf.WriteUint16(conventionResult.Unk4)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 7: // Char score?
|
|
||||||
for _, charScore := range seibattle.CharScore {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint32(charScore.Unk0)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
case 8: // Cur result?
|
|
||||||
for _, curResult := range seibattle.CurResult {
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.WriteUint32(curResult.Unk0)
|
|
||||||
bf.WriteUint16(curResult.Unk1)
|
|
||||||
bf.WriteUint16(curResult.Unk2)
|
|
||||||
bf.WriteUint16(curResult.Unk3)
|
|
||||||
data = append(data, bf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
|
||||||
pkt := p.(*mhfpacket.MsgMhfPostSeibattle)
|
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}
|
func handleMsgMhfGetDailyMissionMaster(s *Session, p mhfpacket.MHFPacket) {}
|
||||||
|
|
||||||
func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/common/mhfcid"
|
|
||||||
"erupe-ce/common/mhfcourse"
|
|
||||||
"erupe-ce/common/token"
|
"erupe-ce/common/token"
|
||||||
"erupe-ce/config"
|
|
||||||
"erupe-ce/network"
|
|
||||||
"erupe-ce/network/binpacket"
|
"erupe-ce/network/binpacket"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -39,409 +30,6 @@ const (
|
|||||||
BroadcastTypeWorld = 0x0a
|
BroadcastTypeWorld = 0x0a
|
||||||
)
|
)
|
||||||
|
|
||||||
var commands map[string]_config.Command
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commands = make(map[string]_config.Command)
|
|
||||||
zapConfig := zap.NewDevelopmentConfig()
|
|
||||||
zapConfig.DisableCaller = true
|
|
||||||
zapLogger, _ := zapConfig.Build()
|
|
||||||
defer func() { _ = zapLogger.Sync() }()
|
|
||||||
logger := zapLogger.Named("commands")
|
|
||||||
cmds := _config.ErupeConfig.Commands
|
|
||||||
for _, cmd := range cmds {
|
|
||||||
commands[cmd.Name] = cmd
|
|
||||||
if cmd.Enabled {
|
|
||||||
logger.Info(fmt.Sprintf("Command %s: Enabled, prefix: %s", cmd.Name, cmd.Prefix))
|
|
||||||
} else {
|
|
||||||
logger.Info(fmt.Sprintf("Command %s: Disabled", cmd.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendServerChatMessage(s *Session, message string) {
|
|
||||||
// Make the inside of the casted binary
|
|
||||||
bf := byteframe.NewByteFrame()
|
|
||||||
bf.SetLE()
|
|
||||||
msgBinChat := &binpacket.MsgBinChat{
|
|
||||||
Unk0: 0,
|
|
||||||
Type: 5,
|
|
||||||
Flags: 0x80,
|
|
||||||
Message: message,
|
|
||||||
SenderName: "Erupe",
|
|
||||||
}
|
|
||||||
_ = msgBinChat.Build(bf)
|
|
||||||
|
|
||||||
castedBin := &mhfpacket.MsgSysCastedBinary{
|
|
||||||
CharID: 0,
|
|
||||||
MessageType: BinaryMessageTypeChat,
|
|
||||||
RawDataPayload: bf.Data(),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.QueueSendMHFNonBlocking(castedBin)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseChatCommand(s *Session, command string) {
|
|
||||||
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
|
|
||||||
switch args[0] {
|
|
||||||
case commands["Ban"].Prefix:
|
|
||||||
if s.isOp() {
|
|
||||||
if len(args) > 1 {
|
|
||||||
var expiry time.Time
|
|
||||||
if len(args) > 2 {
|
|
||||||
var length int
|
|
||||||
var unit string
|
|
||||||
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
|
|
||||||
if err == nil && n == 2 {
|
|
||||||
switch unit {
|
|
||||||
case "s", "second", "seconds":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Second)
|
|
||||||
case "m", "mi", "minute", "minutes":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Minute)
|
|
||||||
case "h", "hour", "hours":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Hour)
|
|
||||||
case "d", "day", "days":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
|
|
||||||
case "mo", "month", "months":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
|
|
||||||
case "y", "year", "years":
|
|
||||||
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cid := mhfcid.ConvertCID(args[1])
|
|
||||||
if cid > 0 {
|
|
||||||
var uid uint32
|
|
||||||
var uname string
|
|
||||||
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
|
|
||||||
if err == nil {
|
|
||||||
if expiry.IsZero() {
|
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO bans VALUES ($1)
|
|
||||||
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid); err != nil {
|
|
||||||
s.logger.Error("Failed to ban user", zap.Error(err))
|
|
||||||
}
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
|
|
||||||
} else {
|
|
||||||
if _, err := s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
|
|
||||||
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry); err != nil {
|
|
||||||
s.logger.Error("Failed to ban user with expiry", zap.Error(err))
|
|
||||||
}
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
|
|
||||||
}
|
|
||||||
s.server.DisconnectUser(uid)
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.noOp)
|
|
||||||
}
|
|
||||||
case commands["Timer"].Prefix:
|
|
||||||
if commands["Timer"].Enabled || s.isOp() {
|
|
||||||
var state bool
|
|
||||||
if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state); err != nil {
|
|
||||||
s.logger.Error("Failed to get timer state", zap.Error(err))
|
|
||||||
}
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID); err != nil {
|
|
||||||
s.logger.Error("Failed to update timer setting", zap.Error(err))
|
|
||||||
}
|
|
||||||
if state {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Timer"])
|
|
||||||
}
|
|
||||||
case commands["PSN"].Prefix:
|
|
||||||
if commands["PSN"].Enabled || s.isOp() {
|
|
||||||
if len(args) > 1 {
|
|
||||||
var exists int
|
|
||||||
if err := s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists); err != nil {
|
|
||||||
s.logger.Error("Failed to check PSN ID existence", zap.Error(err))
|
|
||||||
}
|
|
||||||
if exists == 0 {
|
|
||||||
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
|
|
||||||
if err == nil {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["PSN"])
|
|
||||||
}
|
|
||||||
case commands["Reload"].Prefix:
|
|
||||||
if commands["Reload"].Enabled || s.isOp() {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.reload)
|
|
||||||
var temp mhfpacket.MHFPacket
|
|
||||||
deleteNotif := byteframe.NewByteFrame()
|
|
||||||
for _, object := range s.stage.objects {
|
|
||||||
if object.ownerCharID == s.charID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
|
|
||||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
|
||||||
_ = temp.Build(deleteNotif, s.clientContext)
|
|
||||||
}
|
|
||||||
for _, session := range s.server.sessions {
|
|
||||||
if s == session {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
|
|
||||||
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
|
||||||
_ = temp.Build(deleteNotif, s.clientContext)
|
|
||||||
}
|
|
||||||
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
|
||||||
s.QueueSendNonBlocking(deleteNotif.Data())
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
reloadNotif := byteframe.NewByteFrame()
|
|
||||||
for _, session := range s.server.sessions {
|
|
||||||
if s == session {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
|
||||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
|
||||||
_ = temp.Build(reloadNotif, s.clientContext)
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
|
||||||
CharID: session.charID,
|
|
||||||
BinaryType: uint8(i + 1),
|
|
||||||
}
|
|
||||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
|
||||||
_ = temp.Build(reloadNotif, s.clientContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, obj := range s.stage.objects {
|
|
||||||
if obj.ownerCharID == s.charID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
temp = &mhfpacket.MsgSysDuplicateObject{
|
|
||||||
ObjID: obj.id,
|
|
||||||
X: obj.x,
|
|
||||||
Y: obj.y,
|
|
||||||
Z: obj.z,
|
|
||||||
Unk0: 0,
|
|
||||||
OwnerCharID: obj.ownerCharID,
|
|
||||||
}
|
|
||||||
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
|
||||||
_ = temp.Build(reloadNotif, s.clientContext)
|
|
||||||
}
|
|
||||||
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
|
||||||
s.QueueSendNonBlocking(reloadNotif.Data())
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Reload"])
|
|
||||||
}
|
|
||||||
case commands["KeyQuest"].Prefix:
|
|
||||||
if commands["KeyQuest"].Enabled || s.isOp() {
|
|
||||||
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
|
|
||||||
} else {
|
|
||||||
if len(args) > 1 {
|
|
||||||
switch args[1] {
|
|
||||||
case "get":
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf))
|
|
||||||
case "set":
|
|
||||||
if len(args) > 2 && len(args[2]) == 16 {
|
|
||||||
hexd, _ := hex.DecodeString(args[2])
|
|
||||||
s.kqf = hexd
|
|
||||||
s.kqfOverride = true
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
|
||||||
}
|
|
||||||
case commands["Rights"].Prefix:
|
|
||||||
if commands["Rights"].Enabled || s.isOp() {
|
|
||||||
if len(args) > 1 {
|
|
||||||
v, _ := strconv.Atoi(args[1])
|
|
||||||
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
|
||||||
if err == nil {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Rights"])
|
|
||||||
}
|
|
||||||
case commands["Course"].Prefix:
|
|
||||||
if commands["Course"].Enabled || s.isOp() {
|
|
||||||
if len(args) > 1 {
|
|
||||||
for _, course := range mhfcourse.Courses() {
|
|
||||||
for _, alias := range course.Aliases() {
|
|
||||||
if strings.EqualFold(args[1], alias) {
|
|
||||||
if slices.Contains(s.server.erupeConfig.Courses, _config.Course{Name: course.Aliases()[0], Enabled: true}) {
|
|
||||||
var delta, rightsInt uint32
|
|
||||||
if mhfcourse.CourseExists(course.ID, s.courses) {
|
|
||||||
ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool {
|
|
||||||
for _, alias := range c.Aliases() {
|
|
||||||
if strings.EqualFold(args[1], alias) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if ei != -1 {
|
|
||||||
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delta = uint32(math.Pow(2, float64(course.ID)))
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0]))
|
|
||||||
}
|
|
||||||
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
|
||||||
if err == nil {
|
|
||||||
if _, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID); err != nil {
|
|
||||||
s.logger.Error("Failed to update user rights", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateRights(s)
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Course"])
|
|
||||||
}
|
|
||||||
case commands["Raviente"].Prefix:
|
|
||||||
if commands["Raviente"].Enabled || s.isOp() {
|
|
||||||
if len(args) > 1 {
|
|
||||||
if s.server.getRaviSemaphore() != nil {
|
|
||||||
switch args[1] {
|
|
||||||
case "start":
|
|
||||||
if s.server.raviente.register[1] == 0 {
|
|
||||||
s.server.raviente.register[1] = s.server.raviente.register[3]
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
|
|
||||||
s.notifyRavi()
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
|
|
||||||
}
|
|
||||||
case "cm", "check", "checkmultiplier", "multiplier":
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier()))
|
|
||||||
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
|
|
||||||
if s.server.erupeConfig.RealClientMode == _config.ZZ {
|
|
||||||
switch args[1] {
|
|
||||||
case "sr", "sendres", "resurrection":
|
|
||||||
if s.server.raviente.state[28] > 0 {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success)
|
|
||||||
s.server.raviente.state[28] = 0
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
|
|
||||||
}
|
|
||||||
case "ss", "sendsed":
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
|
|
||||||
// Total BerRavi HP
|
|
||||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
|
||||||
s.server.raviente.support[1] = HP
|
|
||||||
case "rs", "reqsed":
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
|
|
||||||
// Total BerRavi HP
|
|
||||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
|
||||||
s.server.raviente.support[1] = HP + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
|
||||||
}
|
|
||||||
case commands["Teleport"].Prefix:
|
|
||||||
if commands["Teleport"].Enabled || s.isOp() {
|
|
||||||
if len(args) > 2 {
|
|
||||||
x, _ := strconv.ParseInt(args[1], 10, 16)
|
|
||||||
y, _ := strconv.ParseInt(args[2], 10, 16)
|
|
||||||
payload := byteframe.NewByteFrame()
|
|
||||||
payload.SetLE()
|
|
||||||
payload.WriteUint8(2) // SetState type(position == 2)
|
|
||||||
payload.WriteInt16(int16(x)) // X
|
|
||||||
payload.WriteInt16(int16(y)) // Y
|
|
||||||
payloadBytes := payload.Data()
|
|
||||||
s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCastedBinary{
|
|
||||||
CharID: s.charID,
|
|
||||||
MessageType: BinaryMessageTypeState,
|
|
||||||
RawDataPayload: payloadBytes,
|
|
||||||
})
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
|
|
||||||
} else {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
|
||||||
}
|
|
||||||
case commands["Discord"].Prefix:
|
|
||||||
if commands["Discord"].Enabled || s.isOp() {
|
|
||||||
var _token string
|
|
||||||
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
|
|
||||||
if err != nil {
|
|
||||||
randToken := make([]byte, 4)
|
|
||||||
_, _ = rand.Read(randToken)
|
|
||||||
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
|
|
||||||
if _, err := s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID); err != nil {
|
|
||||||
s.logger.Error("Failed to update discord token", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Discord"])
|
|
||||||
}
|
|
||||||
case commands["Playtime"].Prefix:
|
|
||||||
if commands["Playtime"].Enabled || s.isOp() {
|
|
||||||
playtime := s.playtime + uint32(time.Since(s.playtimeTime).Seconds())
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.playtime, playtime/60/60, playtime/60%60, playtime%60))
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Playtime"])
|
|
||||||
}
|
|
||||||
case commands["Help"].Prefix:
|
|
||||||
if commands["Help"].Enabled || s.isOp() {
|
|
||||||
for _, command := range commands {
|
|
||||||
if command.Enabled || s.isOp() {
|
|
||||||
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sendDisabledCommandMessage(s, commands["Help"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
||||||
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||||
|
|||||||
424
server/channelserver/handlers_commands.go
Normal file
424
server/channelserver/handlers_commands.go
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"erupe-ce/common/byteframe"
|
||||||
|
"erupe-ce/common/mhfcid"
|
||||||
|
"erupe-ce/common/mhfcourse"
|
||||||
|
"erupe-ce/config"
|
||||||
|
"erupe-ce/network"
|
||||||
|
"erupe-ce/network/binpacket"
|
||||||
|
"erupe-ce/network/mhfpacket"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commands map[string]_config.Command
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands = make(map[string]_config.Command)
|
||||||
|
zapConfig := zap.NewDevelopmentConfig()
|
||||||
|
zapConfig.DisableCaller = true
|
||||||
|
zapLogger, _ := zapConfig.Build()
|
||||||
|
defer func() { _ = zapLogger.Sync() }()
|
||||||
|
logger := zapLogger.Named("commands")
|
||||||
|
cmds := _config.ErupeConfig.Commands
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
commands[cmd.Name] = cmd
|
||||||
|
if cmd.Enabled {
|
||||||
|
logger.Info(fmt.Sprintf("Command %s: Enabled, prefix: %s", cmd.Name, cmd.Prefix))
|
||||||
|
} else {
|
||||||
|
logger.Info(fmt.Sprintf("Command %s: Disabled", cmd.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendServerChatMessage(s *Session, message string) {
|
||||||
|
// Make the inside of the casted binary
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.SetLE()
|
||||||
|
msgBinChat := &binpacket.MsgBinChat{
|
||||||
|
Unk0: 0,
|
||||||
|
Type: 5,
|
||||||
|
Flags: 0x80,
|
||||||
|
Message: message,
|
||||||
|
SenderName: "Erupe",
|
||||||
|
}
|
||||||
|
_ = msgBinChat.Build(bf)
|
||||||
|
|
||||||
|
castedBin := &mhfpacket.MsgSysCastedBinary{
|
||||||
|
CharID: 0,
|
||||||
|
MessageType: BinaryMessageTypeChat,
|
||||||
|
RawDataPayload: bf.Data(),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.QueueSendMHFNonBlocking(castedBin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseChatCommand(s *Session, command string) {
|
||||||
|
args := strings.Split(command[len(s.server.erupeConfig.CommandPrefix):], " ")
|
||||||
|
switch args[0] {
|
||||||
|
case commands["Ban"].Prefix:
|
||||||
|
if s.isOp() {
|
||||||
|
if len(args) > 1 {
|
||||||
|
var expiry time.Time
|
||||||
|
if len(args) > 2 {
|
||||||
|
var length int
|
||||||
|
var unit string
|
||||||
|
n, err := fmt.Sscanf(args[2], `%d%s`, &length, &unit)
|
||||||
|
if err == nil && n == 2 {
|
||||||
|
switch unit {
|
||||||
|
case "s", "second", "seconds":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Second)
|
||||||
|
case "m", "mi", "minute", "minutes":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Minute)
|
||||||
|
case "h", "hour", "hours":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Hour)
|
||||||
|
case "d", "day", "days":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24)
|
||||||
|
case "mo", "month", "months":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 30)
|
||||||
|
case "y", "year", "years":
|
||||||
|
expiry = time.Now().Add(time.Duration(length) * time.Hour * 24 * 365)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cid := mhfcid.ConvertCID(args[1])
|
||||||
|
if cid > 0 {
|
||||||
|
var uid uint32
|
||||||
|
var uname string
|
||||||
|
err := s.server.db.QueryRow(`SELECT id, username FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, cid).Scan(&uid, &uname)
|
||||||
|
if err == nil {
|
||||||
|
if expiry.IsZero() {
|
||||||
|
if _, err := s.server.db.Exec(`INSERT INTO bans VALUES ($1)
|
||||||
|
ON CONFLICT (user_id) DO UPDATE SET expires=NULL`, uid); err != nil {
|
||||||
|
s.logger.Error("Failed to ban user", zap.Error(err))
|
||||||
|
}
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname))
|
||||||
|
} else {
|
||||||
|
if _, err := s.server.db.Exec(`INSERT INTO bans VALUES ($1, $2)
|
||||||
|
ON CONFLICT (user_id) DO UPDATE SET expires=$2`, uid, expiry); err != nil {
|
||||||
|
s.logger.Error("Failed to ban user with expiry", zap.Error(err))
|
||||||
|
}
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ban.success, uname)+fmt.Sprintf(s.server.i18n.commands.ban.length, expiry.Format(time.DateTime)))
|
||||||
|
}
|
||||||
|
s.server.DisconnectUser(uid)
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ban.noUser)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ban.invalid)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ban.error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.noOp)
|
||||||
|
}
|
||||||
|
case commands["Timer"].Prefix:
|
||||||
|
if commands["Timer"].Enabled || s.isOp() {
|
||||||
|
var state bool
|
||||||
|
if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state); err != nil {
|
||||||
|
s.logger.Error("Failed to get timer state", zap.Error(err))
|
||||||
|
}
|
||||||
|
if _, err := s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID); err != nil {
|
||||||
|
s.logger.Error("Failed to update timer setting", zap.Error(err))
|
||||||
|
}
|
||||||
|
if state {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.timer.disabled)
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.timer.enabled)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Timer"])
|
||||||
|
}
|
||||||
|
case commands["PSN"].Prefix:
|
||||||
|
if commands["PSN"].Enabled || s.isOp() {
|
||||||
|
if len(args) > 1 {
|
||||||
|
var exists int
|
||||||
|
if err := s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists); err != nil {
|
||||||
|
s.logger.Error("Failed to check PSN ID existence", zap.Error(err))
|
||||||
|
}
|
||||||
|
if exists == 0 {
|
||||||
|
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
|
||||||
|
if err == nil {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.success, args[1]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.psn.exists)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.psn.error, commands["PSN"].Prefix))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["PSN"])
|
||||||
|
}
|
||||||
|
case commands["Reload"].Prefix:
|
||||||
|
if commands["Reload"].Enabled || s.isOp() {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.reload)
|
||||||
|
var temp mhfpacket.MHFPacket
|
||||||
|
deleteNotif := byteframe.NewByteFrame()
|
||||||
|
for _, object := range s.stage.objects {
|
||||||
|
if object.ownerCharID == s.charID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
|
||||||
|
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
|
_ = temp.Build(deleteNotif, s.clientContext)
|
||||||
|
}
|
||||||
|
for _, session := range s.server.sessions {
|
||||||
|
if s == session {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
|
||||||
|
deleteNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
|
_ = temp.Build(deleteNotif, s.clientContext)
|
||||||
|
}
|
||||||
|
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
||||||
|
s.QueueSendNonBlocking(deleteNotif.Data())
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
reloadNotif := byteframe.NewByteFrame()
|
||||||
|
for _, session := range s.server.sessions {
|
||||||
|
if s == session {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||||
|
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
|
_ = temp.Build(reloadNotif, s.clientContext)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||||
|
CharID: session.charID,
|
||||||
|
BinaryType: uint8(i + 1),
|
||||||
|
}
|
||||||
|
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
|
_ = temp.Build(reloadNotif, s.clientContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, obj := range s.stage.objects {
|
||||||
|
if obj.ownerCharID == s.charID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
temp = &mhfpacket.MsgSysDuplicateObject{
|
||||||
|
ObjID: obj.id,
|
||||||
|
X: obj.x,
|
||||||
|
Y: obj.y,
|
||||||
|
Z: obj.z,
|
||||||
|
Unk0: 0,
|
||||||
|
OwnerCharID: obj.ownerCharID,
|
||||||
|
}
|
||||||
|
reloadNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
|
_ = temp.Build(reloadNotif, s.clientContext)
|
||||||
|
}
|
||||||
|
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
|
||||||
|
s.QueueSendNonBlocking(reloadNotif.Data())
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Reload"])
|
||||||
|
}
|
||||||
|
case commands["KeyQuest"].Prefix:
|
||||||
|
if commands["KeyQuest"].Enabled || s.isOp() {
|
||||||
|
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.kqf.version)
|
||||||
|
} else {
|
||||||
|
if len(args) > 1 {
|
||||||
|
switch args[1] {
|
||||||
|
case "get":
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.get, s.kqf))
|
||||||
|
case "set":
|
||||||
|
if len(args) > 2 && len(args[2]) == 16 {
|
||||||
|
hexd, _ := hex.DecodeString(args[2])
|
||||||
|
s.kqf = hexd
|
||||||
|
s.kqfOverride = true
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.kqf.set.success)
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.kqf.set.error, commands["KeyQuest"].Prefix))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||||
|
}
|
||||||
|
case commands["Rights"].Prefix:
|
||||||
|
if commands["Rights"].Enabled || s.isOp() {
|
||||||
|
if len(args) > 1 {
|
||||||
|
v, _ := strconv.Atoi(args[1])
|
||||||
|
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||||
|
if err == nil {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.success, v))
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.rights.error, commands["Rights"].Prefix))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Rights"])
|
||||||
|
}
|
||||||
|
case commands["Course"].Prefix:
|
||||||
|
if commands["Course"].Enabled || s.isOp() {
|
||||||
|
if len(args) > 1 {
|
||||||
|
for _, course := range mhfcourse.Courses() {
|
||||||
|
for _, alias := range course.Aliases() {
|
||||||
|
if strings.EqualFold(args[1], alias) {
|
||||||
|
if slices.Contains(s.server.erupeConfig.Courses, _config.Course{Name: course.Aliases()[0], Enabled: true}) {
|
||||||
|
var delta, rightsInt uint32
|
||||||
|
if mhfcourse.CourseExists(course.ID, s.courses) {
|
||||||
|
ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool {
|
||||||
|
for _, alias := range c.Aliases() {
|
||||||
|
if strings.EqualFold(args[1], alias) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if ei != -1 {
|
||||||
|
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.disabled, course.Aliases()[0]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delta = uint32(math.Pow(2, float64(course.ID)))
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.enabled, course.Aliases()[0]))
|
||||||
|
}
|
||||||
|
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
||||||
|
if err == nil {
|
||||||
|
if _, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID); err != nil {
|
||||||
|
s.logger.Error("Failed to update user rights", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateRights(s)
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.locked, course.Aliases()[0]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.course.error, commands["Course"].Prefix))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Course"])
|
||||||
|
}
|
||||||
|
case commands["Raviente"].Prefix:
|
||||||
|
if commands["Raviente"].Enabled || s.isOp() {
|
||||||
|
if len(args) > 1 {
|
||||||
|
if s.server.getRaviSemaphore() != nil {
|
||||||
|
switch args[1] {
|
||||||
|
case "start":
|
||||||
|
if s.server.raviente.register[1] == 0 {
|
||||||
|
s.server.raviente.register[1] = s.server.raviente.register[3]
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.success)
|
||||||
|
s.notifyRavi()
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.start.error)
|
||||||
|
}
|
||||||
|
case "cm", "check", "checkmultiplier", "multiplier":
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.ravi.multiplier, s.server.GetRaviMultiplier()))
|
||||||
|
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
|
||||||
|
if s.server.erupeConfig.RealClientMode == _config.ZZ {
|
||||||
|
switch args[1] {
|
||||||
|
case "sr", "sendres", "resurrection":
|
||||||
|
if s.server.raviente.state[28] > 0 {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.success)
|
||||||
|
s.server.raviente.state[28] = 0
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.res.error)
|
||||||
|
}
|
||||||
|
case "ss", "sendsed":
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.sed.success)
|
||||||
|
// Total BerRavi HP
|
||||||
|
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||||
|
s.server.raviente.support[1] = HP
|
||||||
|
case "rs", "reqsed":
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.request)
|
||||||
|
// Total BerRavi HP
|
||||||
|
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||||
|
s.server.raviente.support[1] = HP + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.version)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.noPlayers)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, s.server.i18n.commands.ravi.error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||||
|
}
|
||||||
|
case commands["Teleport"].Prefix:
|
||||||
|
if commands["Teleport"].Enabled || s.isOp() {
|
||||||
|
if len(args) > 2 {
|
||||||
|
x, _ := strconv.ParseInt(args[1], 10, 16)
|
||||||
|
y, _ := strconv.ParseInt(args[2], 10, 16)
|
||||||
|
payload := byteframe.NewByteFrame()
|
||||||
|
payload.SetLE()
|
||||||
|
payload.WriteUint8(2) // SetState type(position == 2)
|
||||||
|
payload.WriteInt16(int16(x)) // X
|
||||||
|
payload.WriteInt16(int16(y)) // Y
|
||||||
|
payloadBytes := payload.Data()
|
||||||
|
s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCastedBinary{
|
||||||
|
CharID: s.charID,
|
||||||
|
MessageType: BinaryMessageTypeState,
|
||||||
|
RawDataPayload: payloadBytes,
|
||||||
|
})
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.success, x, y))
|
||||||
|
} else {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.teleport.error, commands["Teleport"].Prefix))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||||
|
}
|
||||||
|
case commands["Discord"].Prefix:
|
||||||
|
if commands["Discord"].Enabled || s.isOp() {
|
||||||
|
var _token string
|
||||||
|
err := s.server.db.QueryRow(`SELECT discord_token FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&_token)
|
||||||
|
if err != nil {
|
||||||
|
randToken := make([]byte, 4)
|
||||||
|
_, _ = rand.Read(randToken)
|
||||||
|
_token = fmt.Sprintf("%x-%x", randToken[:2], randToken[2:])
|
||||||
|
if _, err := s.server.db.Exec(`UPDATE users u SET discord_token = $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, _token, s.charID); err != nil {
|
||||||
|
s.logger.Error("Failed to update discord token", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.discord.success, _token))
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Discord"])
|
||||||
|
}
|
||||||
|
case commands["Playtime"].Prefix:
|
||||||
|
if commands["Playtime"].Enabled || s.isOp() {
|
||||||
|
playtime := s.playtime + uint32(time.Since(s.playtimeTime).Seconds())
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.playtime, playtime/60/60, playtime/60%60, playtime%60))
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Playtime"])
|
||||||
|
}
|
||||||
|
case commands["Help"].Prefix:
|
||||||
|
if commands["Help"].Enabled || s.isOp() {
|
||||||
|
for _, command := range commands {
|
||||||
|
if command.Enabled || s.isOp() {
|
||||||
|
sendServerChatMessage(s, fmt.Sprintf("%s%s: %s", s.server.erupeConfig.CommandPrefix, command.Prefix, command.Description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendDisabledCommandMessage(s, commands["Help"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,618 +1,16 @@
|
|||||||
package channelserver
|
package channelserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"erupe-ce/common/byteframe"
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"erupe-ce/common/mhfitem"
|
"erupe-ce/common/mhfitem"
|
||||||
_config "erupe-ce/config"
|
_config "erupe-ce/config"
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
|
||||||
|
|
||||||
"erupe-ce/common/byteframe"
|
|
||||||
ps "erupe-ce/common/pascalstring"
|
ps "erupe-ce/common/pascalstring"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FestivalColor string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FestivalColorNone FestivalColor = "none"
|
|
||||||
FestivalColorBlue FestivalColor = "blue"
|
|
||||||
FestivalColorRed FestivalColor = "red"
|
|
||||||
)
|
|
||||||
|
|
||||||
var FestivalColorCodes = map[FestivalColor]int16{
|
|
||||||
FestivalColorNone: -1,
|
|
||||||
FestivalColorBlue: 0,
|
|
||||||
FestivalColorRed: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildApplicationType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
GuildApplicationTypeApplied GuildApplicationType = "applied"
|
|
||||||
GuildApplicationTypeInvited GuildApplicationType = "invited"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Guild struct {
|
|
||||||
ID uint32 `db:"id"`
|
|
||||||
Name string `db:"name"`
|
|
||||||
MainMotto uint8 `db:"main_motto"`
|
|
||||||
SubMotto uint8 `db:"sub_motto"`
|
|
||||||
CreatedAt time.Time `db:"created_at"`
|
|
||||||
MemberCount uint16 `db:"member_count"`
|
|
||||||
RankRP uint32 `db:"rank_rp"`
|
|
||||||
EventRP uint32 `db:"event_rp"`
|
|
||||||
RoomRP uint16 `db:"room_rp"`
|
|
||||||
RoomExpiry time.Time `db:"room_expiry"`
|
|
||||||
Comment string `db:"comment"`
|
|
||||||
PugiName1 string `db:"pugi_name_1"`
|
|
||||||
PugiName2 string `db:"pugi_name_2"`
|
|
||||||
PugiName3 string `db:"pugi_name_3"`
|
|
||||||
PugiOutfit1 uint8 `db:"pugi_outfit_1"`
|
|
||||||
PugiOutfit2 uint8 `db:"pugi_outfit_2"`
|
|
||||||
PugiOutfit3 uint8 `db:"pugi_outfit_3"`
|
|
||||||
PugiOutfits uint32 `db:"pugi_outfits"`
|
|
||||||
Recruiting bool `db:"recruiting"`
|
|
||||||
FestivalColor FestivalColor `db:"festival_color"`
|
|
||||||
Souls uint32 `db:"souls"`
|
|
||||||
AllianceID uint32 `db:"alliance_id"`
|
|
||||||
Icon *GuildIcon `db:"icon"`
|
|
||||||
|
|
||||||
GuildLeader
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildLeader struct {
|
|
||||||
LeaderCharID uint32 `db:"leader_id"`
|
|
||||||
LeaderName string `db:"leader_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildIconPart struct {
|
|
||||||
Index uint16
|
|
||||||
ID uint16
|
|
||||||
Page uint8
|
|
||||||
Size uint8
|
|
||||||
Rotation uint8
|
|
||||||
Red uint8
|
|
||||||
Green uint8
|
|
||||||
Blue uint8
|
|
||||||
PosX uint16
|
|
||||||
PosY uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildApplication struct {
|
|
||||||
ID int `db:"id"`
|
|
||||||
GuildID uint32 `db:"guild_id"`
|
|
||||||
CharID uint32 `db:"character_id"`
|
|
||||||
ActorID uint32 `db:"actor_id"`
|
|
||||||
ApplicationType GuildApplicationType `db:"application_type"`
|
|
||||||
CreatedAt time.Time `db:"created_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GuildIcon struct {
|
|
||||||
Parts []GuildIconPart
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gi *GuildIcon) Scan(val interface{}) (err error) {
|
|
||||||
switch v := val.(type) {
|
|
||||||
case []byte:
|
|
||||||
err = json.Unmarshal(v, &gi)
|
|
||||||
case string:
|
|
||||||
err = json.Unmarshal([]byte(v), &gi)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gi *GuildIcon) Value() (valuer driver.Value, err error) {
|
|
||||||
return json.Marshal(gi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Guild) Rank() uint16 {
|
|
||||||
rpMap := []uint32{
|
|
||||||
24, 48, 96, 144, 192, 240, 288, 360, 432,
|
|
||||||
504, 600, 696, 792, 888, 984, 1080, 1200,
|
|
||||||
}
|
|
||||||
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
|
||||||
rpMap = []uint32{
|
|
||||||
3500, 6000, 8500, 11000, 13500, 16000, 20000, 24000, 28000,
|
|
||||||
33000, 38000, 43000, 48000, 55000, 70000, 90000, 120000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, u := range rpMap {
|
|
||||||
if g.RankRP < u {
|
|
||||||
if _config.ErupeConfig.RealClientMode <= _config.S6 && i >= 12 {
|
|
||||||
return 12
|
|
||||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 && i >= 13 {
|
|
||||||
return 13
|
|
||||||
} else if _config.ErupeConfig.RealClientMode <= _config.G32 && i >= 14 {
|
|
||||||
return 14
|
|
||||||
}
|
|
||||||
return uint16(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
|
||||||
return 12
|
|
||||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
|
||||||
return 13
|
|
||||||
} else if _config.ErupeConfig.RealClientMode <= _config.G32 {
|
|
||||||
return 14
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 handleMsgMhfCreateGuild(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfCreateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfCreateGuild)
|
pkt := p.(*mhfpacket.MsgMhfCreateGuild)
|
||||||
|
|
||||||
@@ -846,23 +244,6 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
|
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
|
||||||
items := guildGetItems(s, pkt.GuildID)
|
items := guildGetItems(s, pkt.GuildID)
|
||||||
|
|||||||
@@ -3,8 +3,152 @@ package channelserver
|
|||||||
import (
|
import (
|
||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SeibattleTimetable struct {
|
||||||
|
Start time.Time
|
||||||
|
End time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleKeyScore struct {
|
||||||
|
Unk0 uint8
|
||||||
|
Unk1 int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleCareer struct {
|
||||||
|
Unk0 uint16
|
||||||
|
Unk1 uint16
|
||||||
|
Unk2 uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleOpponent struct {
|
||||||
|
Unk0 int32
|
||||||
|
Unk1 int8
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleConventionResult struct {
|
||||||
|
Unk0 uint32
|
||||||
|
Unk1 uint16
|
||||||
|
Unk2 uint16
|
||||||
|
Unk3 uint16
|
||||||
|
Unk4 uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleCharScore struct {
|
||||||
|
Unk0 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeibattleCurResult struct {
|
||||||
|
Unk0 uint32
|
||||||
|
Unk1 uint16
|
||||||
|
Unk2 uint16
|
||||||
|
Unk3 uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type Seibattle struct {
|
||||||
|
Timetable []SeibattleTimetable
|
||||||
|
KeyScore []SeibattleKeyScore
|
||||||
|
Career []SeibattleCareer
|
||||||
|
Opponent []SeibattleOpponent
|
||||||
|
ConventionResult []SeibattleConventionResult
|
||||||
|
CharScore []SeibattleCharScore
|
||||||
|
CurResult []SeibattleCurResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMsgMhfGetSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
||||||
|
pkt := p.(*mhfpacket.MsgMhfGetSeibattle)
|
||||||
|
var data []*byteframe.ByteFrame
|
||||||
|
seibattle := Seibattle{
|
||||||
|
Timetable: []SeibattleTimetable{
|
||||||
|
{TimeMidnight(), TimeMidnight().Add(time.Hour * 8)},
|
||||||
|
{TimeMidnight().Add(time.Hour * 8), TimeMidnight().Add(time.Hour * 16)},
|
||||||
|
{TimeMidnight().Add(time.Hour * 16), TimeMidnight().Add(time.Hour * 24)},
|
||||||
|
},
|
||||||
|
KeyScore: []SeibattleKeyScore{
|
||||||
|
{0, 0},
|
||||||
|
},
|
||||||
|
Career: []SeibattleCareer{
|
||||||
|
{0, 0, 0},
|
||||||
|
},
|
||||||
|
Opponent: []SeibattleOpponent{
|
||||||
|
{1, 1},
|
||||||
|
},
|
||||||
|
ConventionResult: []SeibattleConventionResult{
|
||||||
|
{0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
CharScore: []SeibattleCharScore{
|
||||||
|
{0},
|
||||||
|
},
|
||||||
|
CurResult: []SeibattleCurResult{
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkt.Type {
|
||||||
|
case 1:
|
||||||
|
for _, timetable := range seibattle.Timetable {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint32(uint32(timetable.Start.Unix()))
|
||||||
|
bf.WriteUint32(uint32(timetable.End.Unix()))
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 3: // Key score?
|
||||||
|
for _, keyScore := range seibattle.KeyScore {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint8(keyScore.Unk0)
|
||||||
|
bf.WriteInt32(keyScore.Unk1)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 4: // Career?
|
||||||
|
for _, career := range seibattle.Career {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint16(career.Unk0)
|
||||||
|
bf.WriteUint16(career.Unk1)
|
||||||
|
bf.WriteUint16(career.Unk2)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 5: // Opponent?
|
||||||
|
for _, opponent := range seibattle.Opponent {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteInt32(opponent.Unk0)
|
||||||
|
bf.WriteInt8(opponent.Unk1)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 6: // Convention result?
|
||||||
|
for _, conventionResult := range seibattle.ConventionResult {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint32(conventionResult.Unk0)
|
||||||
|
bf.WriteUint16(conventionResult.Unk1)
|
||||||
|
bf.WriteUint16(conventionResult.Unk2)
|
||||||
|
bf.WriteUint16(conventionResult.Unk3)
|
||||||
|
bf.WriteUint16(conventionResult.Unk4)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 7: // Char score?
|
||||||
|
for _, charScore := range seibattle.CharScore {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint32(charScore.Unk0)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
case 8: // Cur result?
|
||||||
|
for _, curResult := range seibattle.CurResult {
|
||||||
|
bf := byteframe.NewByteFrame()
|
||||||
|
bf.WriteUint32(curResult.Unk0)
|
||||||
|
bf.WriteUint16(curResult.Unk1)
|
||||||
|
bf.WriteUint16(curResult.Unk2)
|
||||||
|
bf.WriteUint16(curResult.Unk3)
|
||||||
|
data = append(data, bf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMsgMhfPostSeibattle(s *Session, p mhfpacket.MHFPacket) {
|
||||||
|
pkt := p.(*mhfpacket.MsgMhfPostSeibattle)
|
||||||
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
|
}
|
||||||
|
|
||||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
|
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|||||||
Reference in New Issue
Block a user