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

@@ -13,9 +13,8 @@
"LogInboundMessages": false, "LogInboundMessages": false,
"LogOutboundMessages": false, "LogOutboundMessages": false,
"MaxHexdumpLength": 256, "MaxHexdumpLength": 256,
"Event": 0,
"DivaEvent": 0, "DivaEvent": 0,
"FestaEvent": 0, "FestaEvent": -1,
"TournamentEvent": 0, "TournamentEvent": 0,
"MezFesEvent": true, "MezFesEvent": true,
"MezFesAlt": false, "MezFesAlt": false,

View File

@@ -16,11 +16,7 @@ func (m MsgBinMailNotify) Parse(bf *byteframe.ByteFrame) error {
func (m MsgBinMailNotify) Build(bf *byteframe.ByteFrame) error { func (m MsgBinMailNotify) Build(bf *byteframe.ByteFrame) error {
bf.WriteUint8(0x01) // Unk bf.WriteUint8(0x01) // Unk
byteName, _ := stringsupport.ConvertUTF8ToShiftJIS(m.SenderName) bf.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
bf.WriteBytes(byteName)
bf.WriteBytes(make([]byte, 21-len(byteName)))
return nil return nil
} }

311
patch-schema/festa.sql Normal file
View File

@@ -0,0 +1,311 @@
BEGIN;
CREATE TYPE event_type AS ENUM ('festa', 'diva', 'vs', 'mezfes');
DROP TABLE IF EXISTS public.event_week;
ALTER TABLE IF EXISTS public.guild_characters
ADD COLUMN IF NOT EXISTS souls int DEFAULT 0;
ALTER TABLE IF EXISTS public.guilds
DROP COLUMN IF EXISTS festival_colour;
CREATE TABLE IF NOT EXISTS public.events
(
id serial NOT NULL PRIMARY KEY,
event_type event_type NOT NULL,
start_time timestamp without time zone NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS public.festa_registrations
(
guild_id int NOT NULL,
team festival_colour NOT NULL
);
CREATE TABLE IF NOT EXISTS public.festa_trials
(
id serial NOT NULL PRIMARY KEY,
objective int NOT NULL,
goal_id int NOT NULL,
times_req int NOT NULL,
locale_req int NOT NULL DEFAULT 0,
reward int NOT NULL
);
CREATE TYPE prize_type AS ENUM ('personal', 'guild');
CREATE TABLE IF NOT EXISTS public.festa_prizes
(
id serial NOT NULL PRIMARY KEY,
type prize_type NOT NULL,
tier int NOT NULL,
souls_req int NOT NULL,
item_id int NOT NULL,
num_item int NOT NULL
);
CREATE TABLE IF NOT EXISTS public.festa_prizes_accepted
(
prize_id int NOT NULL,
character_id int NOT NULL
);
-- Ripped prizes
INSERT INTO public.festa_prizes
(type, tier, souls_req, item_id, num_item)
VALUES
('personal', 1, 1, 9647, 7),
('personal', 2, 1, 9647, 7),
('personal', 3, 1, 9647, 7),
('personal', 1, 200, 11284, 4),
('personal', 2, 200, 11284, 4),
('personal', 3, 200, 11284, 4),
('personal', 1, 400, 11381, 3),
('personal', 2, 400, 11381, 3),
('personal', 3, 400, 11381, 3),
('personal', 1, 600, 11284, 8),
('personal', 2, 600, 11284, 8),
('personal', 3, 600, 11284, 8),
('personal', 1, 800, 11384, 3),
('personal', 2, 800, 11384, 3),
('personal', 3, 800, 11384, 3),
('personal', 1, 1000, 11284, 12),
('personal', 2, 1000, 11284, 12),
('personal', 3, 1000, 11284, 12),
('personal', 1, 1200, 11381, 5),
('personal', 2, 1200, 11381, 5),
('personal', 3, 1200, 11381, 5),
('personal', 1, 1400, 11284, 16),
('personal', 2, 1400, 11284, 16),
('personal', 3, 1400, 11284, 16),
('personal', 1, 1700, 11384, 5),
('personal', 2, 1700, 11384, 5),
('personal', 3, 1700, 11384, 5),
('personal', 1, 2000, 11284, 16),
('personal', 2, 2000, 11284, 16),
('personal', 3, 2000, 11284, 16),
('personal', 1, 2500, 11382, 4),
('personal', 2, 2500, 11382, 4),
('personal', 3, 2500, 11382, 4),
('personal', 1, 3000, 11284, 24),
('personal', 2, 3000, 11284, 24),
('personal', 3, 3000, 11284, 24),
('personal', 1, 4000, 11385, 4),
('personal', 2, 4000, 11385, 4),
('personal', 3, 4000, 11385, 4),
('personal', 1, 5000, 11381, 11),
('personal', 2, 5000, 11381, 11),
('personal', 3, 5000, 11381, 11),
('personal', 1, 6000, 5177, 5),
('personal', 2, 6000, 5177, 5),
('personal', 3, 6000, 5177, 5),
('personal', 1, 7000, 11384, 11),
('personal', 2, 7000, 11384, 11),
('personal', 3, 7000, 11384, 11),
('personal', 1, 10000, 11382, 8),
('personal', 2, 10000, 11382, 8),
('personal', 3, 10000, 11382, 8),
('personal', 1, 15000, 11385, 4),
('personal', 2, 15000, 11385, 4),
('personal', 3, 15000, 11385, 4),
('personal', 1, 20000, 11381, 13),
('personal', 2, 20000, 11381, 13),
('personal', 3, 20000, 11381, 13),
('personal', 1, 25000, 11385, 4),
('personal', 2, 25000, 11385, 4),
('personal', 3, 25000, 11385, 4),
('personal', 1, 30000, 11383, 1),
('personal', 2, 30000, 11383, 1),
('personal', 3, 30000, 11383, 1);
INSERT INTO public.festa_prizes
(type, tier, souls_req, item_id, num_item)
VALUES
('guild', 1, 100, 7468, 5),
('guild', 2, 100, 7468, 5),
('guild', 3, 100, 7465, 5),
('guild', 1, 300, 7469, 5),
('guild', 2, 300, 7469, 5),
('guild', 3, 300, 7466, 5),
('guild', 1, 700, 7470, 5),
('guild', 2, 700, 7470, 5),
('guild', 3, 700, 7467, 5),
('guild', 1, 1500, 13405, 14),
('guild', 1, 1500, 1520, 3),
('guild', 2, 1500, 13405, 14),
('guild', 2, 1500, 1520, 3),
('guild', 3, 1500, 7011, 3),
('guild', 3, 1500, 13405, 14),
('guild', 1, 3000, 10201, 10),
('guild', 2, 3000, 10201, 10),
('guild', 3, 3000, 10201, 10),
('guild', 1, 6000, 13895, 14),
('guild', 1, 6000, 1520, 6),
('guild', 2, 6000, 13895, 14),
('guild', 2, 6000, 1520, 6),
('guild', 3, 6000, 13895, 14),
('guild', 3, 6000, 7011, 4),
('guild', 1, 12000, 13406, 14),
('guild', 1, 12000, 1520, 9),
('guild', 2, 12000, 13406, 14),
('guild', 2, 12000, 1520, 9),
('guild', 3, 12000, 13406, 14),
('guild', 3, 12000, 7011, 5),
('guild', 1, 25000, 10207, 10),
('guild', 2, 25000, 10207, 10),
('guild', 3, 25000, 10207, 10),
('guild', 1, 50000, 1520, 12),
('guild', 1, 50000, 13896, 14),
('guild', 2, 50000, 1520, 12),
('guild', 2, 50000, 13896, 14),
('guild', 3, 50000, 7011, 6),
('guild', 3, 50000, 13896, 14),
('guild', 1, 100000, 10201, 10),
('guild', 2, 100000, 10201, 10),
('guild', 3, 100000, 10201, 10),
('guild', 1, 200000, 13406, 16),
('guild', 2, 200000, 13406, 16),
('guild', 3, 200000, 13406, 16),
('guild', 1, 300000, 13896, 16),
('guild', 2, 300000, 13896, 16),
('guild', 3, 300000, 13896, 16),
('guild', 1, 400000, 10207, 10),
('guild', 2, 400000, 10207, 10),
('guild', 3, 400000, 10207, 10),
('guild', 1, 500000, 13407, 6),
('guild', 1, 500000, 13897, 6),
('guild', 2, 500000, 13407, 6),
('guild', 2, 500000, 13897, 6),
('guild', 3, 500000, 13407, 6),
('guild', 3, 500000, 13897, 6);
-- Ripped trials
INSERT INTO public.festa_trials
(objective, goal_id, times_req, locale_req, reward)
VALUES
(1,27,1,0,1),
(5,53034,0,0,400),
(5,22042,0,0,89),
(5,23397,0,0,89),
(1,28,1,0,1),
(1,68,1,0,1),
(1,6,1,0,2),
(1,38,1,0,2),
(1,20,1,0,3),
(1,39,1,0,4),
(1,48,1,0,4),
(1,67,1,0,4),
(1,93,1,0,4),
(1,22,1,0,5),
(1,52,1,0,5),
(1,101,1,0,5),
(1,1,1,0,5),
(1,37,1,0,5),
(1,15,1,0,5),
(1,45,1,0,5),
(1,74,1,0,5),
(1,78,1,0,5),
(1,103,1,0,5),
(1,51,1,0,6),
(1,17,1,0,6),
(1,21,1,0,6),
(1,92,1,0,6),
(1,47,1,0,7),
(1,46,1,0,7),
(1,26,1,0,7),
(1,14,1,0,7),
(1,11,1,0,7),
(1,44,1,0,8),
(1,43,1,0,8),
(1,49,1,0,8),
(1,40,1,0,8),
(1,76,1,0,8),
(1,89,1,0,8),
(1,94,1,0,8),
(1,96,1,0,8),
(1,75,1,0,8),
(1,91,1,0,8),
(1,53,1,0,9),
(1,80,1,0,9),
(1,42,1,0,9),
(1,79,1,0,9),
(1,81,1,0,10),
(1,41,1,0,10),
(1,82,1,0,10),
(1,90,1,0,10),
(1,149,1,0,10),
(1,85,1,0,11),
(1,95,1,0,11),
(1,121,1,0,11),
(1,142,1,0,11),
(1,141,1,0,11),
(1,146,1,0,12),
(1,147,1,0,12),
(1,148,1,0,12),
(1,151,1,0,12),
(1,152,1,0,12),
(1,159,1,0,12),
(1,153,1,0,12),
(1,162,1,0,12),
(1,111,1,0,13),
(1,110,1,0,13),
(1,112,1,0,13),
(1,109,1,0,14),
(1,169,1,0,15),
(2,33,1,0,6),
(2,104,1,0,8),
(2,119,1,0,8),
(2,120,1,0,8),
(2,54,1,0,8),
(2,59,1,0,8),
(2,64,1,0,8),
(2,65,1,0,8),
(2,99,1,0,9),
(2,83,1,0,9),
(2,84,1,0,10),
(2,77,1,0,10),
(2,106,1,0,10),
(2,55,1,0,10),
(2,58,1,0,10),
(2,7,1,0,10),
(2,50,1,0,11),
(2,131,1,0,11),
(2,129,1,0,11),
(2,140,1,0,11),
(2,122,1,0,11),
(2,126,1,0,11),
(2,127,1,0,11),
(2,128,1,0,11),
(2,130,1,0,11),
(2,139,1,0,11),
(2,144,1,0,11),
(2,150,1,0,11),
(2,158,1,0,11),
(2,164,1,0,15),
(2,165,1,0,15),
(2,2,1,7,15),
(2,36,1,0,15),
(2,71,1,0,15),
(2,108,1,0,15),
(2,116,1,0,15),
(2,107,1,0,15),
(2,154,1,0,17),
(2,166,1,0,17),
(2,170,1,0,18),
(3,31,1,0,1),
(3,8,1,0,3),
(3,123,1,0,8),
(3,105,1,0,9),
(3,125,1,0,11),
(3,115,1,0,12),
(3,114,1,0,12),
(3,161,1,0,12),
(4,670,1,0,1),
(4,671,1,0,1),
(4,672,1,0,1),
(4,675,1,0,1),
(4,673,1,0,1),
(4,674,1,0,1);
END;

View File

@@ -0,0 +1,13 @@
BEGIN;
ALTER TABLE IF EXISTS public.mail
ADD COLUMN IF NOT EXISTS is_sys_message bool DEFAULT false;
UPDATE mail SET is_sys_message=false;
ALTER TABLE IF EXISTS public.mail
DROP CONSTRAINT IF EXISTS mail_sender_id_fkey;
INSERT INTO public.characters (id, name) VALUES (0, '');
END;

View File

@@ -2,11 +2,116 @@ package channelserver
import ( import (
"encoding/hex" "encoding/hex"
"erupe-ce/common/stringsupport"
"time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket" "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) { func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo) pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
// Temporary canned response // Temporary canned response

View File

@@ -7,7 +7,6 @@ import (
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
timeServerFix "erupe-ce/server/channelserver/timeserver"
) )
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
@@ -234,75 +233,6 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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 handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -2,12 +2,11 @@ package channelserver
import ( import (
"encoding/hex" "encoding/hex"
"math/rand"
"time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"math/rand"
"time"
) )
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
@@ -43,99 +42,200 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
case 1: case 1:
bf.WriteUint32(uint32(midnight.Unix())) bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).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(13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(21 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(20 * 24 * time.Hour).Unix()))
case 2: case 2:
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix())) bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(9 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(16 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
case 3: case 3:
bf.WriteUint32(uint32(midnight.Add(-12 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-9 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(-10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix())) bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix())) bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
default: default:
bf.WriteBytes(make([]byte, 16)) bf.WriteBytes(make([]byte, 16))
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
bf.WriteUint16(1) bf.WriteUint8(3)
bf.WriteUint32(0) bf.WriteBytes(make([]byte, 4))
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return return
} }
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
d, _ := hex.DecodeString("031491E631353089F18CF68EAE8EEB97C291E589EF00001200000A54001000000000ED130D949A96B697B393A294B081490000000A55001000010000ED130D949A96B697B393A294B081490000000A56001000020000ED130D949A96B697B393A294B081490000000A57001000030000ED130D949A96B697B393A294B081490000000A58001000040000ED130D949A96B697B393A294B081490000000A59001000050000ED130D949A96B697B393A294B081490000000A5A001000060000ED130D949A96B697B393A294B081490000000A5B001000070000ED130D949A96B697B393A294B081490000000A5C001000080000ED130D949A96B697B393A294B081490000000A5D001000090000ED130D949A96B697B393A294B081490000000A5E0010000A0000ED130D949A96B697B393A294B081490000000A5F0010000B0000ED130D949A96B697B393A294B081490000000A600010000C0000ED130D949A96B697B393A294B081490000000A610010000D0000ED130D949A96B697B393A294B081490000000A620011FFFF0000ED121582DD82F182C882C5949A96B697B393A294B081490000000A63000600EA0000000009834C838C834183570000000A64000600ED000000000B836E838A837D834F838D0000000A65000600EF0000000011834A834E8354839383668381834C83930003000002390006000600000E8CC2906C208B9091E58B9B94740001617E43303581798BA38B5A93E09765817A0A7E433030834E83478358836782C592DE82C182BD8B9B82CC83548343835982F08BA382A40A7E433034817991CE8FDB8B9B817A0A7E433030834C838C8341835781410A836E838A837D834F838D8141834A834E8354839383668381834C83930A7E433037817993FC8FDC8FDC9569817A0A7E4330308B9B947482CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C5000000023A0011000700001297C292632082668B89E8E891CA935694740000ED7E43303581798BA38B5A93E09765817A0A7E43303081E182DD82F182C882C5949A96B697B393A294B0814981E282F00A93AF82B697C2926382C98F8A91AE82B782E934906C82DC82C582CC0A97C2926388F582C582A282A982C9918182AD834E838A834182B782E982A90A82F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303091E631343789F18EEB906C8DD582CC8DB02831816032303088CA290A0A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C50A000000023B001000070000128CC2906C2082668B89E8E891CA935694740001497E43303581798BA38B5A93E09765817A0A7E43303081E1949A96B697B393A294B0814981E282F00A82A282A982C9918182AD834E838A834182B782E982A982F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303089A48ED282CC8381835F838B283188CA290A2F8CF68EAE82CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C500") bf.WriteUint8(3)
bf.WriteBytes(d) 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()) 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) { func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoFesta) pkt := p.(*mhfpacket.MsgMhfInfoFesta)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.FestaEvent
bf.WriteUint32(0xdeadbeef) // festaID id, start := uint32(0xDEADBEEF), uint32(0)
// Registration Week Start rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
// Introductory Week Start for rows.Next() {
// Totalling Time rows.Scan(&id, &start)
// Reward Festival Start (2.5hrs after totalling) }
// 2 weeks after RewardFes (next fes?)
midnight := Time_Current_Midnight() var timestamps []uint32
switch state { if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
case 1: if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
bf.WriteUint32(uint32(midnight.Unix())) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix())) return
bf.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) }
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 150*time.Minute).Unix())) timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
bf.WriteUint32(uint32(midnight.Add(24*28*time.Hour + 11*time.Hour).Unix())) } else {
case 2: timestamps = generateFestaTimestamps(s, start, false)
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())) if timestamps[0] > uint32(Time_Current_Adjusted().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:
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return 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) bf.WriteUint8(4)
ps.Uint8(bf, "", false) ps.Uint8(bf, "", false)
bf.WriteUint32(0) bf.WriteUint32(0)
bf.WriteUint32(0) // Blue souls bf.WriteUint32(blueSouls)
bf.WriteUint32(0) // Red souls bf.WriteUint32(redSouls)
trials := 0 rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
bf.WriteUint16(uint16(trials)) trialData := byteframe.NewByteFrame()
for i := 0; i < trials; i++ { var count uint16
bf.WriteUint32(uint32(i + 1)) // trialID for rows.Next() {
bf.WriteUint8(0xFF) // unk trial := &Trial{}
bf.WriteUint8(uint8(i)) // objective err := rows.StructScan(&trial)
bf.WriteUint32(0x1B) // monID, itemID if deliver if err != nil {
bf.WriteUint16(1) // huntsRemain? continue
bf.WriteUint16(0) // location }
bf.WriteUint16(1) // numSoulsReward count++
bf.WriteUint8(0xFF) // unk trialData.WriteUint32(trial.ID)
bf.WriteUint8(0xFF) // monopolised trialData.WriteUint8(0) // Unk
bf.WriteUint16(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? dailyWinners := uint16(0) // NYI
bf.WriteUint16(uint16(unk)) bf.WriteUint16(dailyWinners)
for i := 0; i < unk; i++ { for i := uint16(0); i < dailyWinners; i++ {
bf.WriteUint32(0) bf.WriteUint32(0) // Guild ID
bf.WriteUint16(0) bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) bf.WriteUint16(0) // Festa Team
bf.WriteUint32(0) ps.Uint8(bf, "", true) // Guild Name
bf.WriteBool(false)
} }
d, _ := hex.DecodeString("0001D4C001F4000411B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10003000109E54BE54E89B38F970029FDCE04000400001381818D84836C8352819993A294B091D1818100000811B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100030001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100040001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100050001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100060001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10007000109E54BE54E89B38F9700000000000008000001000000000100001388000007D0000003E800000064012C00C8009600640032") d, _ := hex.DecodeString("000000000000000100001388000007D0000003E800000064012C00C8009600640032")
bf.WriteBytes(d) bf.WriteBytes(d)
ps.Uint16(bf, "", false) ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -144,8 +244,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
// state festa (U)ser // state festa (U)ser
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaU) 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 := byteframe.NewByteFrame()
bf.WriteUint32(0) // souls bf.WriteUint32(souls)
bf.WriteUint32(0) // unk bf.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
@@ -153,80 +264,156 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
// state festa (G)uild // state festa (G)uild
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaG) 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 := byteframe.NewByteFrame()
resp.WriteUint32(0) // souls if err != nil || guild == nil || applicant {
resp.WriteUint32(1) // unk resp.WriteUint32(0)
resp.WriteUint32(1) // unk resp.WriteUint32(0)
resp.WriteUint32(1) // unk, rank? resp.WriteUint32(0xFFFFFFFF)
resp.WriteUint32(1) // unk 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()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} }
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember) 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 := byteframe.NewByteFrame()
bf.WriteUint16(0) // numMembers bf.WriteUint16(uint16(len(members)))
// uint16 unk bf.WriteUint16(0) // Unk
// uint32 charID for _, member := range members {
// uint32 souls bf.WriteUint32(member.CharID)
bf.WriteUint32(member.Souls)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta) pkt := p.(*mhfpacket.MsgMhfVoteFesta)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta) 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()) rand.Seed(time.Now().UnixNano())
bf.WriteUint32(uint32(rand.Intn(2))) team := uint32(rand.Intn(2))
// Update guild table 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()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta) 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)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFesta) pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
// Mark festa as claimed
// Update guild table?
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize) 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)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize) 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)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
// uint32 numPrizes type Prize struct {
// struct festaPrize ID uint32 `db:"id"`
// uint32 prizeID Tier uint32 `db:"tier"`
// uint32 prizeTier (1/2/3, 3 = GR) SoulsReq uint32 `db:"souls_req"`
// uint32 soulsReq ItemID uint32 `db:"item_id"`
// uint32 unk (00 00 00 07) NumItem uint32 `db:"num_item"`
// uint32 itemID Claimed int `db:"claimed"`
// uint32 numItem }
// bool claimed
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize) 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) { func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize) 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"` PugiName3 string `db:"pugi_name_3"`
Recruiting bool `db:"recruiting"` Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"` FestivalColour FestivalColour `db:"festival_colour"`
Souls uint32 `db:"souls"`
Rank uint16 `db:"rank"` Rank uint16 `db:"rank"`
AllianceID uint32 `db:"alliance_id"` AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"` Icon *GuildIcon `db:"icon"`
@@ -121,11 +122,12 @@ SELECT
leader_id, leader_id,
lc.name as leader_name, lc.name as leader_name,
comment, comment,
pugi_name_1, COALESCE(pugi_name_1, '') AS pugi_name_1,
pugi_name_2, COALESCE(pugi_name_2, '') AS pugi_name_2,
pugi_name_3, COALESCE(pugi_name_3, '') AS pugi_name_3,
recruiting, 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 CASE
WHEN rank_rp <= 48 THEN rank_rp/24 WHEN rank_rp <= 48 THEN rank_rp/24
WHEN rank_rp <= 288 THEN rank_rp/48+1 WHEN rank_rp <= 288 THEN rank_rp/48+1
@@ -134,23 +136,14 @@ SELECT
WHEN rank_rp < 1200 THEN 16 WHEN rank_rp < 1200 THEN 16
ELSE 17 ELSE 17
END rank, END rank,
CASE WHEN ( COALESCE((
SELECT id FROM guild_alliances ga WHERE SELECT id FROM guild_alliances ga WHERE
ga.parent_id = g.id OR ga.parent_id = g.id OR
ga.sub1_id = g.id OR ga.sub1_id = g.id OR
ga.sub2_id = g.id ga.sub2_id = g.id
) IS NULL THEN 0 ), 0) AS alliance_id,
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,
icon, 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 FROM guilds g
JOIN guild_characters lgc ON lgc.character_id = leader_id JOIN guild_characters lgc ON lgc.character_id = leader_id
JOIN characters lc on leader_id = lc.id JOIN characters lc on leader_id = lc.id
@@ -158,8 +151,8 @@ SELECT
func (guild *Guild) Save(s *Session) error { func (guild *Guild) Save(s *Session) error {
_, err := s.server.db.Exec(` _, 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 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.FestivalColour, guild.Icon) `, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3, guild.Icon, guild.GuildLeader.LeaderCharID)
if err != nil { if err != nil {
s.logger.Error("failed to update guild data", zap.Error(err), zap.Uint32("guildID", guild.ID)) 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)) bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data()) doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return 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: case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil) err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
@@ -667,6 +687,14 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
if err != nil { if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure // All successful acks return 0x01, assuming 0x00 is failure
response = 0x00 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)) bf.WriteUint32(uint32(response))
@@ -790,14 +818,14 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, pkt.CharID) guild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
if err != nil || guild == nil { if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
actorCharacter, err := GetCharacterGuildData(s, s.charID) actorCharacter, err := GetCharacterGuildData(s, s.charID)
if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) { if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
@@ -806,41 +834,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID) err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{ mail = Mail{
SenderID: s.charID, RecipientID: pkt.CharID,
RecipientID: pkt.CharID, Subject: "Accepted!",
Subject: "Accepted!", Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name), IsSystemMessage: true,
IsGuildInvite: false,
} }
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID) err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{ mail = Mail{
SenderID: s.charID, RecipientID: pkt.CharID,
RecipientID: pkt.CharID, Subject: "Rejected",
Subject: "Rejected", Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name), IsSystemMessage: true,
IsGuildInvite: false,
} }
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK: case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID) err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{ mail = Mail{
SenderID: s.charID, RecipientID: pkt.CharID,
RecipientID: pkt.CharID, Subject: "Kicked",
Subject: "Kicked", Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name), IsSystemMessage: true,
IsGuildInvite: false,
} }
default: default:
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action)) s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
} }
if err != nil { if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else { } else {
mail.Send(s, nil) 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) { func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
@@ -914,24 +946,12 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBytes(guildLeaderName) bf.WriteBytes(guildLeaderName)
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBool(false) // isReturnGuild 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) bf.WriteUint32(guild.EventRP)
ps.Uint8(bf, guild.PugiName1, true)
if guild.PugiName1 == "" { ps.Uint8(bf, guild.PugiName2, true)
bf.WriteUint16(0x0100) ps.Uint8(bf, guild.PugiName3, true)
} 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)
}
// probably guild pugi properties, should be status, stamina and luck outfits // probably guild pugi properties, should be status, stamina and luck outfits
bf.WriteBytes([]byte{ bf.WriteBytes([]byte{
@@ -952,7 +972,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else { } else {
bf.WriteUint32(alliance.ID) bf.WriteUint32(alliance.ID)
bf.WriteUint32(uint32(alliance.CreatedAt.Unix())) bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
bf.WriteUint16(uint16(alliance.TotalMembers)) bf.WriteUint16(alliance.TotalMembers)
bf.WriteUint16(0) // Unk0 bf.WriteUint16(0) // Unk0
ps.Uint16(bf, alliance.Name, true) ps.Uint16(bf, alliance.Name, true)
if alliance.SubGuild1ID > 0 { if alliance.SubGuild1ID > 0 {
@@ -1015,7 +1035,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} }
if err != nil { if err != nil || characterGuildData.IsApplicant {
bf.WriteUint16(0) bf.WriteUint16(0)
} else { } else {
bf.WriteUint16(uint16(len(applicants))) bf.WriteUint16(uint16(len(applicants)))
@@ -1063,15 +1083,11 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else { } else {
bf.WriteUint8(0x00) bf.WriteUint8(0x00)
} }
bf.WriteUint8(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else { } else {
//// REALLY large/complex format... stubbing it out here for simplicity. doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5))
//resp := byteframe.NewByteFrame()
//resp.WriteUint32(0) // Count
//resp.WriteUint8(0) // Unk, read if count == 0.
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 8))
} }
} }
@@ -1260,6 +1276,14 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByCharacterId(s, s.charID) 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 { if guild == nil && s.prevGuildID > 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID) guild, err = GetGuildInfoByID(s, s.prevGuildID)
} }
@@ -1305,7 +1329,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0x0600) bf.WriteUint16(0x0600)
} }
bf.WriteUint8(member.OrderIndex) bf.WriteUint8(member.OrderIndex)
ps.Uint16(bf, member.Name, true) bf.WriteBool(member.AvoidLeadership)
ps.Uint8(bf, member.Name, true)
} }
for _, member := range guildMembers { for _, member := range guildMembers {
@@ -1414,12 +1439,8 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByID(s, pkt.GuildID) guild, err = GetGuildInfoByID(s, pkt.GuildID)
} }
if err != nil { if err != nil || guild == nil {
s.logger.Warn("failed to find guild", zap.Error(err), zap.Uint32("guildID", pkt.GuildID)) doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else if guild == nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }

View File

@@ -12,6 +12,7 @@ type GuildMember struct {
GuildID uint32 `db:"guild_id"` GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"` CharID uint32 `db:"character_id"`
JoinedAt *time.Time `db:"joined_at"` JoinedAt *time.Time `db:"joined_at"`
Souls uint32 `db:"souls"`
Name string `db:"name"` Name string `db:"name"`
IsApplicant bool `db:"is_applicant"` IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"` OrderIndex uint8 `db:"order_index"`
@@ -25,12 +26,25 @@ type GuildMember struct {
WeaponType uint16 `db:"weapon_type"` 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 { func (gm *GuildMember) IsSubLeader() bool {
return gm.OrderIndex <= 3 && !gm.AvoidLeadership return gm.OrderIndex <= 3
} }
func (gm *GuildMember) Save(s *Session) error { 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 { if err != nil {
s.logger.Error( s.logger.Error(
@@ -48,6 +62,7 @@ const guildMembersSelectSQL = `
SELECT SELECT
g.id as guild_id, g.id as guild_id,
joined_at, joined_at,
coalesce(souls, 0) as souls,
c.name, c.name,
character.character_id, character.character_id,
coalesce(gc.order_index, 0) as order_index, coalesce(gc.order_index, 0) as order_index,

View File

@@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err) panic(err)
} }
if actorCharGuildData == nil || !actorCharGuildData.Recruiter { if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
@@ -59,19 +59,13 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err) panic(err)
} }
senderName, err := getCharacterName(s, s.charID)
if err != nil {
panic(err)
}
mail := &Mail{ mail := &Mail{
SenderID: s.charID, SenderID: s.charID,
RecipientID: pkt.CharID, RecipientID: pkt.CharID,
Subject: "Guild! ヽ(・∀・)ノ", Subject: "Invitation!",
Body: fmt.Sprintf( Body: fmt.Sprintf(
"%s has invited you to join the wonderful guild %s, do you accept this challenge?", "%s has invited you to join 「%s」\nDo you want to accept?",
senderName, getCharacterName(s, s.charID),
guildInfo.Name, guildInfo.Name,
), ),
IsGuildInvite: true, IsGuildInvite: true,
@@ -104,7 +98,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err) panic(err)
} }
if guildCharData == nil || !guildCharData.Recruiter { if guildCharData == nil || !guildCharData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return return
} }
@@ -128,61 +122,72 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout) pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
bf := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID) guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil { if err != nil {
panic(err) 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( s.logger.Warn(
"could not retrieve guild invitation", "Guild invite missing, deleted?",
zap.Error(err), zap.Error(err),
zap.Uint32("guildID", guild.ID), zap.Uint32("guildID", guild.ID),
zap.Uint32("charID", s.charID), 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 return
} }
var mail []Mail
if pkt.Answer { if pkt.Answer {
err = guild.AcceptApplication(s, s.charID) 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 { } else {
err = guild.RejectApplication(s, s.charID) 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 { if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) bf.WriteUint32(7)
return 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) { func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
@@ -191,12 +196,12 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID) guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if guildInfo == nil && s.prevGuildID == 0 { if guildInfo == nil && s.prevGuildID == 0 {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} else { } else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID) guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil { if guildInfo == nil || err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }
} }
@@ -210,7 +215,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
if err != nil { if err != nil {
s.logger.Error("failed to retrieve scouted characters", zap.Error(err)) s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, nil) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return return
} }

View File

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