Merge pull request #34 from ZeruLight/feature/festa

feature/festa
This commit is contained in:
wish
2022-08-26 20:09:21 +10:00
committed by GitHub
11 changed files with 889 additions and 309 deletions

View File

@@ -2,11 +2,116 @@ package channelserver
import (
"encoding/hex"
"erupe-ce/common/stringsupport"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func cleanupDiva(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='diva'")
}
func generateDivaTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 6)
midnight := Time_Current_Midnight()
if debug && start <= 3 {
midnight := uint32(midnight.Unix())
switch start {
case 1:
timestamps[0] = midnight
timestamps[1] = timestamps[0] + 601200
timestamps[2] = timestamps[1] + 3900
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
case 2:
timestamps[0] = midnight - 605100
timestamps[1] = midnight - 3900
timestamps[2] = midnight
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
case 3:
timestamps[0] = midnight - 1213800
timestamps[1] = midnight - 608700
timestamps[2] = midnight - 604800
timestamps[3] = midnight - 3900
timestamps[4] = midnight
timestamps[5] = timestamps[3] + 604800
}
return timestamps
}
if start == 0 || Time_Current_Adjusted().Unix() > int64(start)+2977200 {
cleanupDiva(s)
// Generate a new diva defense, starting midnight tomorrow
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('diva', to_timestamp($1)::timestamp without time zone)", start)
}
timestamps[0] = start
timestamps[1] = timestamps[0] + 601200
timestamps[2] = timestamps[1] + 3900
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
return timestamps
}
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
bf := byteframe.NewByteFrame()
id, start := uint32(0xCAFEBEEF), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
for rows.Next() {
rows.Scan(&id, &start)
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
return
}
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
} else {
timestamps = generateDivaTimestamps(s, start, false)
}
bf.WriteUint32(id)
for _, timestamp := range timestamps {
bf.WriteUint32(timestamp)
}
bf.WriteUint16(0x19) // Unk 00011001
bf.WriteUint16(0x2D) // Unk 00101101
bf.WriteUint16(0x02) // Unk 00000010
bf.WriteUint16(0x02) // Unk 00000010
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
// Message that appears on the Diva Defense NPC and triggers the green exclamation mark
udInfos := []struct {
Text string
StartTime time.Time
EndTime time.Time
}{}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udInfos)))
for _, udInfo := range udInfos {
resp.WriteBytes(stringsupport.PaddedString(udInfo.Text, 1024, true))
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
// Temporary canned response

View File

@@ -7,7 +7,6 @@ import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
timeServerFix "erupe-ce/server/channelserver/timeserver"
)
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
@@ -234,75 +233,6 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
var t = timeServerFix.Tstatic_midnight()
var event int = s.server.erupeConfig.DevModeOptions.DivaEvent
year, month, day := t.Date()
midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location())
// Events with time limits are Festival with Sign up, Soul Week and Winners Weeks
// Diva Defense with Prayer, Interception and Song weeks
// Mezeporta Festival with simply 'available' being a weekend thing
resp := byteframe.NewByteFrame()
resp.WriteUint32(0x1d5fda5c) // Unk (1d5fda5c, 0b5397df)
if event == 1 {
resp.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
} else {
resp.WriteUint32(uint32(midnight.Add(-24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
}
if event == 2 {
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
} else {
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
}
if event == 3 {
resp.WriteUint32(uint32(midnight.Add((24) * 7 * time.Hour).Unix())) // Diva Defense Interception
resp.WriteUint32(uint32(midnight.Add((24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
} else {
resp.WriteUint32(uint32(midnight.Add((-24) * 7 * time.Hour).Unix())) // Diva Defense Interception
resp.WriteUint32(uint32(midnight.Add((-24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
}
resp.WriteUint16(0x19) // Unk 00011001
resp.WriteUint16(0x2d) // Unk 00101101
resp.WriteUint16(0x02) // Unk 00000010
resp.WriteUint16(0x02) // Unk 00000010
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
// Message that appears on the Diva Defense NPC and triggers the green exclamation mark
udInfos := []struct {
Text string
StartTime time.Time
EndTime time.Time
}{
/*{
Text: " ~C17【Erupe】 is dead event!\n\n■Features\n~C18 Dont bother walking around!\n~C17 Take down your DB by doing \n~C17 nearly anything!",
StartTime: Time_static().Add(time.Duration(-5) * time.Minute), // Event started 5 minutes ago,
EndTime: Time_static().Add(time.Duration(24) * time.Hour), // Event ends in 5 minutes,
}, */
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udInfos)))
for _, udInfo := range udInfos {
resp.WriteBytes(fixedSizeShiftJIS(udInfo.Text, 1024))
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -2,12 +2,11 @@ package channelserver
import (
"encoding/hex"
"math/rand"
"time"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"math/rand"
"time"
)
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
@@ -43,99 +42,200 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(12 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(21 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(20 * 24 * time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(9 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(16 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
case 3:
bf.WriteUint32(uint32(midnight.Add(-12 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-9 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
default:
bf.WriteBytes(make([]byte, 16))
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
bf.WriteUint16(1)
bf.WriteUint32(0)
bf.WriteUint8(3)
bf.WriteBytes(make([]byte, 4))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
d, _ := hex.DecodeString("031491E631353089F18CF68EAE8EEB97C291E589EF00001200000A54001000000000ED130D949A96B697B393A294B081490000000A55001000010000ED130D949A96B697B393A294B081490000000A56001000020000ED130D949A96B697B393A294B081490000000A57001000030000ED130D949A96B697B393A294B081490000000A58001000040000ED130D949A96B697B393A294B081490000000A59001000050000ED130D949A96B697B393A294B081490000000A5A001000060000ED130D949A96B697B393A294B081490000000A5B001000070000ED130D949A96B697B393A294B081490000000A5C001000080000ED130D949A96B697B393A294B081490000000A5D001000090000ED130D949A96B697B393A294B081490000000A5E0010000A0000ED130D949A96B697B393A294B081490000000A5F0010000B0000ED130D949A96B697B393A294B081490000000A600010000C0000ED130D949A96B697B393A294B081490000000A610010000D0000ED130D949A96B697B393A294B081490000000A620011FFFF0000ED121582DD82F182C882C5949A96B697B393A294B081490000000A63000600EA0000000009834C838C834183570000000A64000600ED000000000B836E838A837D834F838D0000000A65000600EF0000000011834A834E8354839383668381834C83930003000002390006000600000E8CC2906C208B9091E58B9B94740001617E43303581798BA38B5A93E09765817A0A7E433030834E83478358836782C592DE82C182BD8B9B82CC83548343835982F08BA382A40A7E433034817991CE8FDB8B9B817A0A7E433030834C838C8341835781410A836E838A837D834F838D8141834A834E8354839383668381834C83930A7E433037817993FC8FDC8FDC9569817A0A7E4330308B9B947482CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C5000000023A0011000700001297C292632082668B89E8E891CA935694740000ED7E43303581798BA38B5A93E09765817A0A7E43303081E182DD82F182C882C5949A96B697B393A294B0814981E282F00A93AF82B697C2926382C98F8A91AE82B782E934906C82DC82C582CC0A97C2926388F582C582A282A982C9918182AD834E838A834182B782E982A90A82F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303091E631343789F18EEB906C8DD582CC8DB02831816032303088CA290A0A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C50A000000023B001000070000128CC2906C2082668B89E8E891CA935694740001497E43303581798BA38B5A93E09765817A0A7E43303081E1949A96B697B393A294B0814981E282F00A82A282A982C9918182AD834E838A834182B782E982A982F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303089A48ED282CC8381835F838B283188CA290A2F8CF68EAE82CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C500")
bf.WriteBytes(d)
bf.WriteUint8(3)
ps.Uint8(bf, "", false)
bf.WriteUint16(0) // numEvents
bf.WriteUint8(0) // numCups
/*
struct event
uint32 eventID
uint16 unk
uint16 unk
uint32 unk
psUint8 name
struct cup
uint32 cupID
uint16 unk
uint16 unk
uint16 unk
psUint8 name
psUint16 desc
*/
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func cleanupFesta(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'")
s.server.db.Exec("DELETE FROM festa_registrations")
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
s.server.db.Exec("UPDATE guild_characters SET souls=0")
}
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 5)
midnight := Time_Current_Midnight()
if debug && start <= 3 {
midnight := uint32(midnight.Unix())
switch start {
case 1:
timestamps[0] = midnight
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
case 2:
timestamps[0] = midnight - 604800
timestamps[1] = midnight
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
case 3:
timestamps[0] = midnight - 1209600
timestamps[1] = midnight - 604800
timestamps[2] = midnight
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
}
return timestamps
}
if start == 0 || Time_Current_Adjusted().Unix() > int64(start)+2977200 {
cleanupFesta(s)
// Generate a new festa, starting midnight tomorrow
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('festa', to_timestamp($1)::timestamp without time zone)", start)
}
timestamps[0] = start
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
return timestamps
}
type Trial struct {
ID uint32 `db:"id"`
Objective uint8 `db:"objective"`
GoalID uint32 `db:"goal_id"`
TimesReq uint16 `db:"times_req"`
Locale uint16 `db:"locale_req"`
Reward uint16 `db:"reward"`
}
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.FestaEvent
bf.WriteUint32(0xdeadbeef) // festaID
// Registration Week Start
// Introductory Week Start
// Totalling Time
// Reward Festival Start (2.5hrs after totalling)
// 2 weeks after RewardFes (next fes?)
midnight := Time_Current_Midnight()
switch state {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 150*time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*28*time.Hour + 11*time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*7*time.Hour + 150*time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Add(11 * time.Hour).Unix()))
case 3:
bf.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-24*7*time.Hour + 11*time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(150 * time.Minute).Unix()))
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 11*time.Hour).Unix()))
default:
id, start := uint32(0xDEADBEEF), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
for rows.Next() {
rows.Scan(&id, &start)
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
} else {
timestamps = generateFestaTimestamps(s, start, false)
}
if timestamps[0] > uint32(Time_Current_Adjusted().Unix()) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
var blueSouls, redSouls uint32
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'blue'").Scan(&blueSouls)
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'red'").Scan(&redSouls)
bf.WriteUint32(id)
for _, timestamp := range timestamps {
bf.WriteUint32(timestamp)
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix()))
bf.WriteUint8(4)
ps.Uint8(bf, "", false)
bf.WriteUint32(0)
bf.WriteUint32(0) // Blue souls
bf.WriteUint32(0) // Red souls
bf.WriteUint32(blueSouls)
bf.WriteUint32(redSouls)
trials := 0
bf.WriteUint16(uint16(trials))
for i := 0; i < trials; i++ {
bf.WriteUint32(uint32(i + 1)) // trialID
bf.WriteUint8(0xFF) // unk
bf.WriteUint8(uint8(i)) // objective
bf.WriteUint32(0x1B) // monID, itemID if deliver
bf.WriteUint16(1) // huntsRemain?
bf.WriteUint16(0) // location
bf.WriteUint16(1) // numSoulsReward
bf.WriteUint8(0xFF) // unk
bf.WriteUint8(0xFF) // monopolised
bf.WriteUint16(0) // unk
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
trialData := byteframe.NewByteFrame()
var count uint16
for rows.Next() {
trial := &Trial{}
err := rows.StructScan(&trial)
if err != nil {
continue
}
count++
trialData.WriteUint32(trial.ID)
trialData.WriteUint8(0) // Unk
trialData.WriteUint8(trial.Objective)
trialData.WriteUint32(trial.GoalID)
trialData.WriteUint16(trial.TimesReq)
trialData.WriteUint16(trial.Locale)
trialData.WriteUint16(trial.Reward)
trialData.WriteUint8(0xFF) // Unk
trialData.WriteUint8(0xFF) // MonopolyState
trialData.WriteUint16(0) // Unk
}
bf.WriteUint16(count)
bf.WriteBytes(trialData.Data())
// Static bonus rewards
rewards, _ := hex.DecodeString("001901000007015E05F000000000000100000703E81B6300000000010100000C03E8000000000000000100000D0000000000000000000100000100000000000000000002000007015E05F000000000000200000703E81B6300000000010200000C03E8000000000000000200000D0000000000000000000200000400000000000000000003000007015E05F000000000000300000703E81B6300000000010300000C03E8000000000000000300000D0000000000000000000300000100000000000000000004000007015E05F000000000000400000703E81B6300000000010400000C03E8000000000000000400000D0000000000000000000400000400000000000000000005000007015E05F000000000000500000703E81B6300000000010500000C03E8000000000000000500000D00000000000000000005000001000000000000000000")
bf.WriteBytes(rewards)
bf.WriteUint16(0x0001)
bf.WriteUint32(0xD4C001F4)
categoryWinners := uint16(0) // NYI
bf.WriteUint16(categoryWinners)
for i := uint16(0); i < categoryWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
}
unk := 0 // static rewards?
bf.WriteUint16(uint16(unk))
for i := 0; i < unk; i++ {
bf.WriteUint32(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint32(0)
bf.WriteBool(false)
dailyWinners := uint16(0) // NYI
bf.WriteUint16(dailyWinners)
for i := uint16(0); i < dailyWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
}
d, _ := hex.DecodeString("0001D4C001F4000411B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10003000109E54BE54E89B38F970029FDCE04000400001381818D84836C8352819993A294B091D1818100000811B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100030001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100040001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100050001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100060001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10007000109E54BE54E89B38F9700000000000008000001000000000100001388000007D0000003E800000064012C00C8009600640032")
d, _ := hex.DecodeString("000000000000000100001388000007D0000003E800000064012C00C8009600640032")
bf.WriteBytes(d)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -144,8 +244,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
// state festa (U)ser
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
applicant := false
if guild != nil {
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
}
if err != nil || guild == nil || applicant {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
var souls uint32
s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // souls
bf.WriteUint32(souls)
bf.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -153,80 +264,156 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
// state festa (G)uild
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
applicant := false
if guild != nil {
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // souls
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk, rank?
resp.WriteUint32(1) // unk
if err != nil || guild == nil || applicant {
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0xFFFFFFFF)
resp.WriteUint32(0)
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
resp.WriteUint32(guild.Souls)
resp.WriteUint32(0) // unk
resp.WriteUint32(0) // unk, rank?
resp.WriteUint32(0) // unk
resp.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
members, err := GetGuildMembers(s, guild.ID, false)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(0) // numMembers
// uint16 unk
// uint32 charID
// uint32 souls
bf.WriteUint16(uint16(len(members)))
bf.WriteUint16(0) // Unk
for _, member := range members {
bf.WriteUint32(member.CharID)
bf.WriteUint32(member.Souls)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
bf := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
rand.Seed(time.Now().UnixNano())
bf.WriteUint32(uint32(rand.Intn(2)))
// Update guild table
team := uint32(rand.Intn(2))
switch team {
case 0:
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)
case 1:
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'red')", guild.ID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(team)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
// Update festa state table
s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
// Mark festa as claimed
// Update guild table?
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
// Set prize as claimed
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
// Set prize as claimed
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// uint32 numPrizes
// struct festaPrize
// uint32 prizeID
// uint32 prizeTier (1/2/3, 3 = GR)
// uint32 soulsReq
// uint32 unk (00 00 00 07)
// uint32 itemID
// uint32 numItem
// bool claimed
type Prize struct {
ID uint32 `db:"id"`
Tier uint32 `db:"tier"`
SoulsReq uint32 `db:"souls_req"`
ItemID uint32 `db:"item_id"`
NumItem uint32 `db:"num_item"`
Claimed int `db:"claimed"`
}
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
rows, _ := s.server.db.Queryx("SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = 4) AS claimed FROM festa_prizes fp WHERE type='personal'")
var count uint32
prizeData := byteframe.NewByteFrame()
for rows.Next() {
prize := &Prize{}
err := rows.StructScan(&prize)
if err != nil {
continue
}
count++
prizeData.WriteUint32(prize.ID)
prizeData.WriteUint32(prize.Tier)
prizeData.WriteUint32(prize.SoulsReq)
prizeData.WriteUint32(7) // Unk
prizeData.WriteUint32(prize.ItemID)
prizeData.WriteUint32(prize.NumItem)
prizeData.WriteBool(prize.Claimed > 0)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(count)
bf.WriteBytes(prizeData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
rows, _ := s.server.db.Queryx("SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = 4) AS claimed FROM festa_prizes fp WHERE type='guild'")
var count uint32
prizeData := byteframe.NewByteFrame()
for rows.Next() {
prize := &Prize{}
err := rows.StructScan(&prize)
if err != nil {
continue
}
count++
prizeData.WriteUint32(prize.ID)
prizeData.WriteUint32(prize.Tier)
prizeData.WriteUint32(prize.SoulsReq)
prizeData.WriteUint32(7) // Unk
prizeData.WriteUint32(prize.ItemID)
prizeData.WriteUint32(prize.NumItem)
prizeData.WriteBool(prize.Claimed > 0)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(count)
bf.WriteBytes(prizeData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -56,6 +56,7 @@ type Guild struct {
PugiName3 string `db:"pugi_name_3"`
Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"`
Souls uint32 `db:"souls"`
Rank uint16 `db:"rank"`
AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"`
@@ -121,11 +122,12 @@ SELECT
leader_id,
lc.name as leader_name,
comment,
pugi_name_1,
pugi_name_2,
pugi_name_3,
COALESCE(pugi_name_1, '') AS pugi_name_1,
COALESCE(pugi_name_2, '') AS pugi_name_2,
COALESCE(pugi_name_3, '') AS pugi_name_3,
recruiting,
festival_colour,
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_colour,
(SELECT SUM(souls) FROM guild_characters gc WHERE gc.guild_id = g.id) AS souls,
CASE
WHEN rank_rp <= 48 THEN rank_rp/24
WHEN rank_rp <= 288 THEN rank_rp/48+1
@@ -134,23 +136,14 @@ SELECT
WHEN rank_rp < 1200 THEN 16
ELSE 17
END rank,
CASE WHEN (
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
) IS NULL THEN 0
ELSE (
SELECT id FROM guild_alliances ga WHERE
ga.parent_id = g.id OR
ga.sub1_id = g.id OR
ga.sub2_id = g.id
)
END alliance_id,
), 0) AS alliance_id,
icon,
(
SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id
) AS member_count
(SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id) AS member_count
FROM guilds g
JOIN guild_characters lgc ON lgc.character_id = leader_id
JOIN characters lc on leader_id = lc.id
@@ -158,8 +151,8 @@ SELECT
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, festival_colour=$8, icon=$9 WHERE id=$1
`, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3, guild.FestivalColour, guild.Icon)
UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7, icon=$8, leader_id=$9 WHERE id=$1
`, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3, guild.Icon, guild.GuildLeader.LeaderCharID)
if err != nil {
s.logger.Error("failed to update guild data", zap.Error(err), zap.Uint32("guildID", guild.ID))
@@ -643,6 +636,33 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
case mhfpacket.OPERATE_GUILD_RESIGN:
guildMembers, err := GetGuildMembers(s, guild.ID, false)
success := false
if err == nil {
sort.Slice(guildMembers[:], func(i, j int) bool {
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
})
for i := 1; i < len(guildMembers); i++ {
if !guildMembers[i].AvoidLeadership {
guild.LeaderCharID = guildMembers[i].CharID
guildMembers[0].OrderIndex = guildMembers[i].OrderIndex
guildMembers[i].OrderIndex = 1
guildMembers[0].Save(s)
guildMembers[i].Save(s)
bf.WriteUint32(guildMembers[i].CharID)
success = true
break
}
}
guild.Save(s)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
if !success {
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
return
case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
@@ -667,6 +687,14 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure
response = 0x00
} else {
mail := Mail{
RecipientID: s.charID,
Subject: "Withdrawal",
Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
IsSystemMessage: true,
}
mail.Send(s, nil)
}
bf.WriteUint32(uint32(response))
@@ -790,14 +818,14 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
actorCharacter, err := GetCharacterGuildData(s, s.charID)
if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) {
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -806,41 +834,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Accepted!",
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
IsGuildInvite: false,
RecipientID: pkt.CharID,
Subject: "Accepted!",
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
IsSystemMessage: true,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Rejected",
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
IsGuildInvite: false,
RecipientID: pkt.CharID,
Subject: "Rejected",
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
IsSystemMessage: true,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Kicked",
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
IsGuildInvite: false,
RecipientID: pkt.CharID,
Subject: "Kicked",
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
IsSystemMessage: true,
}
default:
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
}
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
mail.Send(s, nil)
for _, channel := range s.server.Channels {
for _, session := range channel.sessions {
if session.charID == pkt.CharID {
SendMailNotification(s, &mail, session)
}
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
}
func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
@@ -914,24 +946,12 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBytes(guildLeaderName)
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBool(false) // isReturnGuild
bf.WriteBytes([]byte{0x01, 0x02, 0x02}) // Unk
bf.WriteBool(false) // earnedSpecialHall
bf.WriteBytes([]byte{0x02, 0x02}) // Unk
bf.WriteUint32(guild.EventRP)
if guild.PugiName1 == "" {
bf.WriteUint16(0x0100)
} else {
ps.Uint8(bf, guild.PugiName1, true)
}
if guild.PugiName2 == "" {
bf.WriteUint16(0x0100)
} else {
ps.Uint8(bf, guild.PugiName2, true)
}
if guild.PugiName3 == "" {
bf.WriteUint16(0x0100)
} else {
ps.Uint8(bf, guild.PugiName3, true)
}
ps.Uint8(bf, guild.PugiName1, true)
ps.Uint8(bf, guild.PugiName2, true)
ps.Uint8(bf, guild.PugiName3, true)
// probably guild pugi properties, should be status, stamina and luck outfits
bf.WriteBytes([]byte{
@@ -952,7 +972,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint32(alliance.ID)
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
bf.WriteUint16(uint16(alliance.TotalMembers))
bf.WriteUint16(alliance.TotalMembers)
bf.WriteUint16(0) // Unk0
ps.Uint16(bf, alliance.Name, true)
if alliance.SubGuild1ID > 0 {
@@ -1015,7 +1035,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
if err != nil {
if err != nil || characterGuildData.IsApplicant {
bf.WriteUint16(0)
} else {
bf.WriteUint16(uint16(len(applicants)))
@@ -1063,15 +1083,11 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint8(0x00)
}
bf.WriteUint8(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
//// REALLY large/complex format... stubbing it out here for simplicity.
//resp := byteframe.NewByteFrame()
//resp.WriteUint32(0) // Count
//resp.WriteUint8(0) // Unk, read if count == 0.
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 8))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5))
}
}
@@ -1260,6 +1276,14 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByCharacterId(s, s.charID)
}
if guild != nil {
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
if isApplicant {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
if guild == nil && s.prevGuildID > 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
}
@@ -1305,7 +1329,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0x0600)
}
bf.WriteUint8(member.OrderIndex)
ps.Uint16(bf, member.Name, true)
bf.WriteBool(member.AvoidLeadership)
ps.Uint8(bf, member.Name, true)
}
for _, member := range guildMembers {
@@ -1414,12 +1439,8 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByID(s, pkt.GuildID)
}
if err != nil {
s.logger.Warn("failed to find guild", zap.Error(err), zap.Uint32("guildID", pkt.GuildID))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else if guild == nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
if err != nil || guild == nil {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
return
}

View File

@@ -12,6 +12,7 @@ type GuildMember struct {
GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"`
JoinedAt *time.Time `db:"joined_at"`
Souls uint32 `db:"souls"`
Name string `db:"name"`
IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"`
@@ -25,12 +26,25 @@ type GuildMember struct {
WeaponType uint16 `db:"weapon_type"`
}
func (gm *GuildMember) CanRecruit() bool {
if gm.Recruiter {
return true
}
if gm.OrderIndex <= 3 {
return true
}
if gm.IsLeader {
return true
}
return false
}
func (gm *GuildMember) IsSubLeader() bool {
return gm.OrderIndex <= 3 && !gm.AvoidLeadership
return gm.OrderIndex <= 3
}
func (gm *GuildMember) Save(s *Session) error {
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1 WHERE character_id=$2", gm.AvoidLeadership, gm.CharID)
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3", gm.AvoidLeadership, gm.OrderIndex, gm.CharID)
if err != nil {
s.logger.Error(
@@ -48,6 +62,7 @@ const guildMembersSelectSQL = `
SELECT
g.id as guild_id,
joined_at,
coalesce(souls, 0) as souls,
c.name,
character.character_id,
coalesce(gc.order_index, 0) as order_index,

View File

@@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
if actorCharGuildData == nil || !actorCharGuildData.Recruiter {
if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -59,19 +59,13 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
senderName, err := getCharacterName(s, s.charID)
if err != nil {
panic(err)
}
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: "Guild! ヽ(・∀・)ノ",
Subject: "Invitation!",
Body: fmt.Sprintf(
"%s has invited you to join the wonderful guild %s, do you accept this challenge?",
senderName,
"%s has invited you to join 「%s」\nDo you want to accept?",
getCharacterName(s, s.charID),
guildInfo.Name,
),
IsGuildInvite: true,
@@ -104,7 +98,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
if guildCharData == nil || !guildCharData.Recruiter {
if guildCharData == nil || !guildCharData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -128,61 +122,72 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
bf := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil {
panic(err)
}
_, err = guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
if err != nil {
if app == nil || err != nil {
s.logger.Warn(
"could not retrieve guild invitation",
"Guild invite missing, deleted?",
zap.Error(err),
zap.Uint32("guildID", guild.ID),
zap.Uint32("charID", s.charID),
)
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
bf.WriteUint32(7)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
var mail []Mail
if pkt.Answer {
err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: "Success!",
Body: fmt.Sprintf("You successfully joined 「%s」.", guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: "Accepted",
Body: fmt.Sprintf("%s accepted your invitation to join 「%s」.", s.Name, guild.Name),
IsSystemMessage: true,
})
} else {
err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: "Declined",
Body: fmt.Sprintf("You declined the invitation to join 「%s」.", guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: "Declined",
Body: fmt.Sprintf("%s declined your invitation to join 「%s」.", s.Name, guild.Name),
IsSystemMessage: true,
})
}
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
bf.WriteUint32(7)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
bf.WriteUint32(0)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
for _, m := range mail {
m.Send(s, nil)
}
}
senderName, err := getCharacterName(s, pkt.LeaderID)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
successMail := Mail{
SenderID: pkt.LeaderID,
RecipientID: s.charID,
Subject: "Happy days!",
Body: fmt.Sprintf("You successfully joined %s and should be proud of all you have accomplished.", guild.Name),
IsGuildInvite: false,
SenderName: senderName,
}
err = successMail.Send(s, nil)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x81, 0x7e})
}
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
@@ -191,12 +196,12 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if guildInfo == nil && s.prevGuildID == 0 {
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
@@ -210,7 +215,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, nil)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}

View File

@@ -25,21 +25,22 @@ type Mail struct {
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
IsSystemMessage bool `db:"is_sys_message"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
VALUES ($1, $2, $3, $4, $5, $6, $7)
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`
var err error
if transaction == nil {
_, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
_, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
} else {
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
}
if err != nil {
@@ -53,6 +54,7 @@ func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
zap.Bool("isSystemMessage", m.IsSystemMessage),
)
return err
}
@@ -141,6 +143,7 @@ func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
@@ -189,6 +192,7 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
@@ -215,16 +219,10 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
}
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
senderName, err := getCharacterName(s, m.SenderID)
if err != nil {
panic(err)
}
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
SenderName: senderName,
SenderName: getCharacterName(s, m.SenderID),
}
notification.Build(bf)
@@ -241,7 +239,7 @@ func SendMailNotification(s *Session, m *Mail, recipient *Session) {
recipient.QueueSendMHF(castedBinary)
}
func getCharacterName(s *Session, charID uint32) (string, error) {
func getCharacterName(s *Session, charID uint32) string {
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
@@ -249,10 +247,9 @@ func getCharacterName(s *Session, charID uint32) (string, error) {
err := row.Scan(&charName)
if err != nil {
return "", err
return ""
}
return charName, nil
return charName
}
func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
@@ -325,8 +322,9 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
flags |= 0x02
}
// System message, hides ID
// flags |= 0x04
if m.IsSystemMessage {
flags |= 0x04
}
// Workaround until EN mail items are patched
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems {