diff --git a/config.json b/config.json index c8705a661..48e166824 100644 --- a/config.json +++ b/config.json @@ -157,6 +157,11 @@ "Enabled": false, "Description": "Ban/Temp Ban a user", "Prefix": "ban" + }, { + "Name": "Timer", + "Enabled": true, + "Description": "Toggle the Quest timer", + "Prefix": "timer" } ], "Courses": [ diff --git a/network/mhfpacket/msg_mhf_charge_festa.go b/network/mhfpacket/msg_mhf_charge_festa.go index f5452df73..145c2a3a7 100644 --- a/network/mhfpacket/msg_mhf_charge_festa.go +++ b/network/mhfpacket/msg_mhf_charge_festa.go @@ -1,19 +1,20 @@ package mhfpacket import ( - "errors" + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfChargeFesta represents the MSG_MHF_CHARGE_FESTA type MsgMhfChargeFesta struct { - AckHandle uint32 - FestaID uint32 - GuildID uint32 - Souls int + AckHandle uint32 + FestaID uint32 + GuildID uint32 + Souls []uint16 + Auto bool } // Opcode returns the ID associated with this packet type. @@ -23,15 +24,14 @@ func (m *MsgMhfChargeFesta) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfChargeFesta) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - m.AckHandle = bf.ReadUint32() - m.FestaID = bf.ReadUint32() - m.GuildID = bf.ReadUint32() - m.Souls = 0 - for i := bf.ReadUint16(); i > 0; i-- { - m.Souls += int(bf.ReadUint16()) - } - _ = bf.ReadUint8() // Unk - return nil + m.AckHandle = bf.ReadUint32() + m.FestaID = bf.ReadUint32() + m.GuildID = bf.ReadUint32() + for i := bf.ReadUint16(); i > 0; i-- { + m.Souls = append(m.Souls, bf.ReadUint16()) + } + m.Auto = bf.ReadBool() + return nil } // Build builds a binary packet from the current data. diff --git a/schemas/patch-schema/op-accounts.sql b/schemas/patch-schema/17-op-accounts.sql similarity index 100% rename from schemas/patch-schema/op-accounts.sql rename to schemas/patch-schema/17-op-accounts.sql diff --git a/schemas/patch-schema/18-timer-toggle.sql b/schemas/patch-schema/18-timer-toggle.sql new file mode 100644 index 000000000..c2bff008f --- /dev/null +++ b/schemas/patch-schema/18-timer-toggle.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool; + +END; \ No newline at end of file diff --git a/schemas/patch-schema/19-festa-submissions.sql b/schemas/patch-schema/19-festa-submissions.sql new file mode 100644 index 000000000..d720c587f --- /dev/null +++ b/schemas/patch-schema/19-festa-submissions.sql @@ -0,0 +1,15 @@ +BEGIN; + +CREATE TABLE festa_submissions ( + character_id int NOT NULL, + guild_id int NOT NULL, + trial_type int NOT NULL, + souls int NOT NULL, + timestamp timestamp with time zone NOT NULL +); + +ALTER TABLE guild_characters DROP COLUMN souls; + +ALTER TYPE festival_colour RENAME TO festival_color; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index c219e8a42..67e6e3a3a 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -144,6 +144,19 @@ func parseChatCommand(s *Session, command string) { } else { sendServerChatMessage(s, s.server.i18n.commands.noOp) } + case commands["Timer"].Prefix: + if commands["Timer"].Enabled || s.isOp() { + var state bool + s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&state) + s.server.db.Exec(`UPDATE users u SET timer=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, !state, s.charID) + if state { + sendServerChatMessage(s, s.server.i18n.commands.timer.disabled) + } else { + sendServerChatMessage(s, s.server.i18n.commands.timer.enabled) + } + } else { + sendDisabledCommandMessage(s, commands["Timer"]) + } case commands["PSN"].Prefix: if commands["PSN"].Enabled || s.isOp() { if len(args) > 1 { @@ -413,10 +426,14 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 { if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 { - _ = tmp.ReadBytes(9) - tmp.SetLE() - frame := tmp.ReadUint32() - sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame)) + var timer bool + s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&timer) + if timer { + _ = tmp.ReadBytes(9) + tmp.SetLE() + frame := tmp.ReadUint32() + sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.timer, frame/30/60/60, frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame)) + } } } diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index 9d90cc898..310fa0221 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -97,6 +97,17 @@ func getPointers() map[SavePointer]int { pointers[pGalleryData] = 72064 pointers[pGardenData] = 74424 pointers[pRP] = 74614 + case _config.S6: + pointers[pWeaponID] = 12522 + pointers[pWeaponType] = 12789 + pointers[pHouseTier] = 13900 + pointers[pToreData] = 14228 + pointers[pHRP] = 14550 + pointers[pHouseData] = 14561 + pointers[pBookshelfData] = 9118 // Probably same here + pointers[pGalleryData] = 24064 + pointers[pGardenData] = 26424 + pointers[pRP] = 26614 } if _config.ErupeConfig.RealClientMode == _config.G5 { pointers[lBookshelfData] = 5548 @@ -212,7 +223,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - if _config.ErupeConfig.RealClientMode >= _config.F4 { + if _config.ErupeConfig.RealClientMode >= _config.S6 { save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2]) save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5] save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195] diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 8844dbdd8..fd41e1366 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -1,6 +1,7 @@ package channelserver import ( + "erupe-ce/common/mhfmon" "erupe-ce/common/stringsupport" _config "erupe-ce/config" "fmt" @@ -1042,34 +1043,34 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) { {1105, 1, 10, 500, 0, 0, 0}, {1105, 2, 10, 500, 0, 0, 0}, // setServerBoss - {2001, 1, 17, 58, 0, 6, 700}, - {2001, 1, 20, 58, 0, 3, 200}, - {2001, 1, 22, 58, 0, 7, 250}, - {2001, 1, 27, 58, 0, 1, 100}, - {2001, 1, 53, 58, 0, 8, 1000}, - {2001, 1, 67, 58, 0, 9, 500}, - {2001, 1, 68, 58, 0, 2, 150}, - {2001, 1, 74, 58, 0, 4, 200}, - {2001, 1, 75, 58, 0, 5, 500}, - {2001, 1, 76, 58, 0, 10, 800}, - {2001, 1, 80, 58, 0, 11, 900}, - {2001, 1, 89, 58, 0, 12, 600}, - {2001, 2, 17, 60, 0, 6, 700}, - {2001, 2, 20, 60, 0, 3, 200}, - {2001, 2, 22, 60, 0, 7, 350}, - {2001, 2, 27, 60, 0, 1, 100}, - {2001, 2, 39, 60, 0, 13, 200}, - {2001, 2, 40, 60, 0, 15, 600}, - {2001, 2, 53, 60, 0, 8, 1000}, - {2001, 2, 67, 60, 0, 2, 500}, - {2001, 2, 68, 60, 0, 9, 150}, - {2001, 2, 74, 60, 0, 4, 200}, - {2001, 2, 75, 60, 0, 5, 500}, - {2001, 2, 76, 60, 0, 10, 800}, - {2001, 2, 80, 60, 0, 11, 900}, - {2001, 2, 81, 60, 0, 14, 900}, - {2001, 2, 89, 60, 0, 12, 600}, - {2001, 2, 94, 60, 0, 16, 1000}, + {2001, 1, mhfmon.Gravios, 58, 0, 6, 700}, + {2001, 1, mhfmon.Gypceros, 58, 0, 3, 200}, + {2001, 1, mhfmon.Basarios, 58, 0, 7, 250}, + {2001, 1, mhfmon.Velocidrome, 58, 0, 1, 100}, + {2001, 1, mhfmon.Rajang, 58, 0, 8, 1000}, + {2001, 1, mhfmon.ShogunCeanataur, 58, 0, 9, 500}, + {2001, 1, mhfmon.Bulldrome, 58, 0, 2, 150}, + {2001, 1, mhfmon.Hypnocatrice, 58, 0, 4, 200}, + {2001, 1, mhfmon.Lavasioth, 58, 0, 5, 500}, + {2001, 1, mhfmon.Tigrex, 58, 0, 10, 800}, + {2001, 1, mhfmon.Espinas, 58, 0, 11, 900}, + {2001, 1, mhfmon.Pariapuria, 58, 0, 12, 600}, + {2001, 2, mhfmon.Gravios, 60, 0, 6, 700}, + {2001, 2, mhfmon.Gypceros, 60, 0, 3, 200}, + {2001, 2, mhfmon.Basarios, 60, 0, 7, 350}, + {2001, 2, mhfmon.Velocidrome, 60, 0, 1, 100}, + {2001, 2, mhfmon.PurpleGypceros, 60, 0, 13, 200}, + {2001, 2, mhfmon.YianGaruga, 60, 0, 15, 600}, + {2001, 2, mhfmon.Rajang, 60, 0, 8, 1000}, + {2001, 2, mhfmon.ShogunCeanataur, 60, 0, 2, 500}, + {2001, 2, mhfmon.Bulldrome, 60, 0, 9, 150}, + {2001, 2, mhfmon.Hypnocatrice, 60, 0, 4, 200}, + {2001, 2, mhfmon.Lavasioth, 60, 0, 5, 500}, + {2001, 2, mhfmon.Tigrex, 60, 0, 10, 800}, + {2001, 2, mhfmon.Espinas, 60, 0, 11, 900}, + {2001, 2, mhfmon.BurningEspinas, 60, 0, 14, 900}, + {2001, 2, mhfmon.Pariapuria, 60, 0, 12, 600}, + {2001, 2, mhfmon.Dyuragaua, 60, 0, 16, 1000}, } case 6: paperData = []PaperData{ diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go index 4fc5370e1..f833a1f4e 100644 --- a/server/channelserver/handlers_festa.go +++ b/server/channelserver/handlers_festa.go @@ -95,8 +95,9 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) { 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_submissions") s.server.db.Exec("DELETE FROM festa_prizes_accepted") - s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL") + s.server.db.Exec("UPDATE guild_characters SET trial_vote=NULL") } func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { @@ -141,13 +142,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 { } type FestaTrial struct { - ID uint32 `db:"id"` - Objective uint16 `db:"objective"` - GoalID uint32 `db:"goal_id"` - TimesReq uint16 `db:"times_req"` - Locale uint16 `db:"locale_req"` - Reward uint16 `db:"reward"` - Monopoly FestivalColour `db:"monopoly"` + ID uint32 `db:"id"` + Objective uint16 `db:"objective"` + GoalID uint32 `db:"goal_id"` + TimesReq uint16 `db:"times_req"` + Locale uint16 `db:"locale_req"` + Reward uint16 `db:"reward"` + Monopoly FestivalColor `db:"monopoly"` Unk uint16 } @@ -189,8 +190,8 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } 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) + s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'blue'`).Scan(&blueSouls) + s.server.db.QueryRow(`SELECT COALESCE(SUM(fs.souls), 0) AS souls FROM festa_registrations fr LEFT JOIN festa_submissions fs ON fr.guild_id = fs.guild_id AND fr.team = 'red'`).Scan(&redSouls) bf.WriteUint32(id) for _, timestamp := range timestamps { @@ -209,11 +210,11 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { COALESCE(CASE WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) > COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) - THEN CAST('blue' AS public.festival_colour) + THEN CAST('blue' AS public.festival_color) WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) > COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) - THEN CAST('red' AS public.festival_colour) - END, CAST('none' AS public.festival_colour)) AS monopoly + THEN CAST('red' AS public.festival_color) + END, CAST('none' AS public.festival_color)) AS monopoly FROM public.festa_trials ft LEFT JOIN public.guild_characters gc ON ft.id = gc.trial_vote LEFT JOIN public.festa_registrations fr ON gc.guild_id = fr.guild_id @@ -233,8 +234,10 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(trial.TimesReq) bf.WriteUint16(trial.Locale) bf.WriteUint16(trial.Reward) - bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly])) - bf.WriteUint16(trial.Unk) + bf.WriteInt16(FestivalColorCodes[trial.Monopoly]) + if _config.ErupeConfig.RealClientMode >= _config.F4 { // Not in S6.0 + bf.WriteUint16(trial.Unk) + } } // The Winner and Loser Armor IDs are missing @@ -291,22 +294,45 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint16(500) - 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 + var temp uint32 + bf.WriteUint16(4) + for i := uint16(0); i < 4; i++ { + var guildID uint32 + var guildName string + var guildTeam = FestivalColorNone + s.server.db.QueryRow(` + SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _ + FROM festa_submissions fs + LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id + LEFT JOIN guilds g ON fs.guild_id = g.id + WHERE fs.trial_type = $1 + GROUP BY fs.guild_id, g.name, fr.team + ORDER BY _ DESC LIMIT 1 + `, i+1).Scan(&guildID, &guildName, &guildTeam, &temp) + bf.WriteUint32(guildID) + bf.WriteUint16(i + 1) + bf.WriteInt16(FestivalColorCodes[guildTeam]) + ps.Uint8(bf, guildName, true) } - - 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 + bf.WriteUint16(7) + for i := uint16(0); i < 7; i++ { + var guildID uint32 + var guildName string + var guildTeam = FestivalColorNone + offset := 86400 * uint32(i) + s.server.db.QueryRow(` + SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _ + FROM festa_submissions fs + LEFT JOIN festa_registrations fr ON fs.guild_id = fr.guild_id + LEFT JOIN guilds g ON fs.guild_id = g.id + WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2 + GROUP BY fs.guild_id, g.name, fr.team + ORDER BY _ DESC LIMIT 1 + `, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp) + bf.WriteUint32(guildID) + bf.WriteUint16(i + 1) + bf.WriteInt16(FestivalColorCodes[guildTeam]) + ps.Uint8(bf, guildName, true) } bf.WriteUint32(0) // Clan goal @@ -321,7 +347,9 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(100) // Normal rate bf.WriteUint16(50) // 50% penalty - ps.Uint16(bf, "", false) + if _config.ErupeConfig.RealClientMode >= _config.G52 { + ps.Uint16(bf, "", false) + } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -338,7 +366,7 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) { return } var souls, exists uint32 - s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls) + s.server.db.QueryRow(`SELECT COALESCE((SELECT SUM(souls) FROM festa_submissions WHERE character_id=$1), 0)`, s.charID).Scan(&souls) err = s.server.db.QueryRow("SELECT prize_id FROM festa_prizes_accepted WHERE prize_id=0 AND character_id=$1", s.charID).Scan(&exists) bf := byteframe.NewByteFrame() bf.WriteUint32(souls) @@ -371,10 +399,10 @@ func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) { return } resp.WriteUint32(guild.Souls) - resp.WriteInt32(0) // unk + resp.WriteInt32(1) // unk resp.WriteInt32(1) // unk, rank? - resp.WriteInt32(0) // unk - resp.WriteInt32(0) // unk + resp.WriteInt32(1) // unk + resp.WriteInt32(1) // unk doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } @@ -404,7 +432,12 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(0) // Unk for _, member := range validMembers { bf.WriteUint32(member.CharID) - bf.WriteUint32(member.Souls) + if _config.ErupeConfig.RealClientMode <= _config.Z1 { + bf.WriteUint16(uint16(member.Souls)) + bf.WriteUint16(0) + } else { + bf.WriteUint32(member.Souls) + } } doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } @@ -436,7 +469,14 @@ func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfChargeFesta) - s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID) + tx, _ := s.server.db.Begin() + for i := range pkt.Souls { + if pkt.Souls[i] == 0 { + continue + } + _, _ = tx.Exec(`INSERT INTO festa_submissions VALUES ($1, $2, $3, $4, now())`, s.charID, pkt.GuildID, i, pkt.Souls[i]) + } + _ = tx.Commit() doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 656b6a222..f24506501 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -21,18 +21,18 @@ import ( "go.uber.org/zap" ) -type FestivalColour string +type FestivalColor string const ( - FestivalColourNone FestivalColour = "none" - FestivalColourBlue FestivalColour = "blue" - FestivalColourRed FestivalColour = "red" + FestivalColorNone FestivalColor = "none" + FestivalColorBlue FestivalColor = "blue" + FestivalColorRed FestivalColor = "red" ) -var FestivalColourCodes = map[FestivalColour]int8{ - FestivalColourNone: -1, - FestivalColourBlue: 0, - FestivalColourRed: 1, +var FestivalColorCodes = map[FestivalColor]int16{ + FestivalColorNone: -1, + FestivalColorBlue: 0, + FestivalColorRed: 1, } type GuildApplicationType string @@ -43,27 +43,27 @@ const ( ) type Guild struct { - ID uint32 `db:"id"` - Name string `db:"name"` - MainMotto uint8 `db:"main_motto"` - SubMotto uint8 `db:"sub_motto"` - CreatedAt time.Time `db:"created_at"` - MemberCount uint16 `db:"member_count"` - RankRP uint32 `db:"rank_rp"` - EventRP uint32 `db:"event_rp"` - Comment string `db:"comment"` - PugiName1 string `db:"pugi_name_1"` - PugiName2 string `db:"pugi_name_2"` - PugiName3 string `db:"pugi_name_3"` - PugiOutfit1 uint8 `db:"pugi_outfit_1"` - PugiOutfit2 uint8 `db:"pugi_outfit_2"` - PugiOutfit3 uint8 `db:"pugi_outfit_3"` - PugiOutfits uint32 `db:"pugi_outfits"` - Recruiting bool `db:"recruiting"` - FestivalColour FestivalColour `db:"festival_colour"` - Souls uint32 `db:"souls"` - AllianceID uint32 `db:"alliance_id"` - Icon *GuildIcon `db:"icon"` + ID uint32 `db:"id"` + Name string `db:"name"` + MainMotto uint8 `db:"main_motto"` + SubMotto uint8 `db:"sub_motto"` + CreatedAt time.Time `db:"created_at"` + MemberCount uint16 `db:"member_count"` + RankRP uint32 `db:"rank_rp"` + EventRP uint32 `db:"event_rp"` + Comment string `db:"comment"` + PugiName1 string `db:"pugi_name_1"` + PugiName2 string `db:"pugi_name_2"` + PugiName3 string `db:"pugi_name_3"` + PugiOutfit1 uint8 `db:"pugi_outfit_1"` + PugiOutfit2 uint8 `db:"pugi_outfit_2"` + PugiOutfit3 uint8 `db:"pugi_outfit_3"` + PugiOutfits uint32 `db:"pugi_outfits"` + Recruiting bool `db:"recruiting"` + FestivalColor FestivalColor `db:"festival_color"` + Souls uint32 `db:"souls"` + AllianceID uint32 `db:"alliance_id"` + Icon *GuildIcon `db:"icon"` GuildLeader } @@ -157,7 +157,7 @@ SELECT sub_motto, created_at, leader_id, - lc.name as leader_name, + c.name AS leader_name, comment, COALESCE(pugi_name_1, '') AS pugi_name_1, COALESCE(pugi_name_2, '') AS pugi_name_2, @@ -167,8 +167,8 @@ SELECT pugi_outfit_3, pugi_outfits, recruiting, - 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, + COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_color, + COALESCE((SELECT SUM(fs.souls) FROM festa_submissions fs WHERE fs.guild_id=g.id), 0) AS souls, COALESCE(( SELECT id FROM guild_alliances ga WHERE ga.parent_id = g.id OR @@ -178,8 +178,8 @@ SELECT icon, (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 + JOIN guild_characters gc ON gc.character_id = leader_id + JOIN characters c on leader_id = c.id ` func (guild *Guild) Save(s *Session) error { @@ -967,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint8(uint8(len(guildLeaderName))) bf.WriteBytes(guildName) bf.WriteBytes(guildComment) - bf.WriteInt8(FestivalColourCodes[guild.FestivalColour]) + bf.WriteInt8(int8(FestivalColorCodes[guild.FestivalColor])) bf.WriteUint32(guild.RankRP) bf.WriteBytes(guildLeaderName) bf.WriteUint32(0) // Unk diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go index e053e2498..ac64e892a 100644 --- a/server/channelserver/handlers_guild_member.go +++ b/server/channelserver/handlers_guild_member.go @@ -61,41 +61,35 @@ func (gm *GuildMember) Save(s *Session) error { } const guildMembersSelectSQL = ` -SELECT - g.id as guild_id, - joined_at, - coalesce(souls, 0) as souls, - COALESCE(rp_today, 0) AS rp_today, - COALESCE(rp_yesterday, 0) AS rp_yesterday, - c.name, - character.character_id, - coalesce(gc.order_index, 0) as order_index, - c.last_login, - coalesce(gc.recruiter, false) as recruiter, - coalesce(gc.avoid_leadership, false) as avoid_leadership, - c.hrp, - c.gr, - c.weapon_id, - c.weapon_type, - character.is_applicant, - CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader - FROM ( - SELECT character_id, true as is_applicant, guild_id - FROM guild_applications ga - WHERE ga.application_type = 'applied' - UNION - SELECT character_id, false as is_applicant, guild_id +SELECT * FROM ( + SELECT + g.id AS guild_id, + joined_at, + COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls, + COALESCE(rp_today, 0) AS rp_today, + COALESCE(rp_yesterday, 0) AS rp_yesterday, + c.name, + c.id AS character_id, + COALESCE(order_index, 0) AS order_index, + c.last_login, + COALESCE(recruiter, false) AS recruiter, + COALESCE(avoid_leadership, false) AS avoid_leadership, + c.hrp, + c.gr, + c.weapon_id, + c.weapon_type, + EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant, + CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader FROM guild_characters gc - ) character - JOIN characters c on character.character_id = c.id - LEFT JOIN guild_characters gc ON gc.character_id = character.character_id - JOIN guilds g ON g.id = character.guild_id + LEFT JOIN characters c ON c.id = gc.character_id + LEFT JOIN guilds g ON g.id = gc.guild_id +) AS subquery ` func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) { rows, err := s.server.db.Queryx(fmt.Sprintf(` %s - WHERE character.guild_id = $1 AND is_applicant = $2 + WHERE guild_id = $1 AND is_applicant = $2 `, guildMembersSelectSQL), guildID, applicants) if err != nil { @@ -121,7 +115,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe } func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) { - rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID) + rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID) if err != nil { s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID)) diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 148908dbf..753e59040 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -22,6 +22,32 @@ type tuneValue struct { Value uint16 } +func findSubSliceIndices(data []byte, sub []byte) []int { + var indices []int + lenSub := len(sub) + for i := 0; i < len(data); i++ { + if i+lenSub > len(data) { + break + } + if equal(data[i:i+lenSub], sub) { + indices = append(indices, i) + } + } + return indices +} + +func equal(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + func BackportQuest(data []byte) []byte { wp := binary.LittleEndian.Uint32(data[0:4]) + 96 rp := wp + 4 @@ -32,7 +58,33 @@ func BackportQuest(data []byte) []byte { } copy(data[wp:wp+4], data[rp:rp+4]) } - copy(data[wp:wp+180], data[rp:rp+180]) + + fillLength := uint32(108) + if _config.ErupeConfig.RealClientMode <= _config.S6 { + fillLength = 44 + } else if _config.ErupeConfig.RealClientMode <= _config.F5 { + fillLength = 52 + } else if _config.ErupeConfig.RealClientMode <= _config.G101 { + fillLength = 76 + } + + copy(data[wp:wp+fillLength], data[rp:rp+fillLength]) + if _config.ErupeConfig.RealClientMode <= _config.G91 { + patterns := [][]byte{ + {0x0A, 0x00, 0x01, 0x33, 0xD7, 0x00}, // 10% Armor Sphere -> Stone + {0x06, 0x00, 0x02, 0x33, 0xD8, 0x00}, // 6% Armor Sphere+ -> Iron Ore + {0x0A, 0x00, 0x03, 0x33, 0xD7, 0x00}, // 10% Adv Armor Sphere -> Stone + {0x06, 0x00, 0x04, 0x33, 0xDB, 0x00}, // 6% Hard Armor Sphere -> Dragonite Ore + {0x0A, 0x00, 0x05, 0x33, 0xD9, 0x00}, // 10% Heaven Armor Sphere -> Earth Crystal + {0x06, 0x00, 0x06, 0x33, 0xDB, 0x00}, // 6% True Armor Sphere -> Dragonite Ore + } + for i := range patterns { + j := findSubSliceIndices(data, patterns[i][0:4]) + for k := range j { + copy(data[j[k]+2:j[k]+4], patterns[i][4:6]) + } + } + } return data } @@ -149,21 +201,32 @@ func loadQuestFile(s *Session, questId int) []byte { fileBytes.SetLE() fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) - // The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. - questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320)) + bodyLength := 320 + if _config.ErupeConfig.RealClientMode <= _config.S6 { + bodyLength = 160 + } else if _config.ErupeConfig.RealClientMode <= _config.F5 { + bodyLength = 168 + } else if _config.ErupeConfig.RealClientMode <= _config.G101 { + bodyLength = 192 + } else if _config.ErupeConfig.RealClientMode <= _config.Z1 { + bodyLength = 224 + } + + // The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. + questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength))) questBody.SetLE() // Find the master quest string pointer questBody.Seek(40, 0) fileBytes.Seek(int64(questBody.ReadUint32()), 0) questBody.Seek(40, 0) // Overwrite it - questBody.WriteUint32(320) + questBody.WriteUint32(uint32(bodyLength)) questBody.Seek(0, 2) // Rewrite the quest strings and their pointers var tempString []byte newStrings := byteframe.NewByteFrame() - tempPointer := 352 + tempPointer := bodyLength + 32 for i := 0; i < 8; i++ { questBody.WriteUint32(uint32(tempPointer)) temp := int64(fileBytes.Index()) @@ -506,8 +569,13 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tuneValues = append(tuneValues, getTuneValueRange(3299, 200)...) tuneValues = append(tuneValues, getTuneValueRange(3325, 300)...) - offset := uint16(time.Now().Unix()) - bf.WriteUint16(offset) + var temp []tuneValue + for i := range tuneValues { + if tuneValues[i].Value > 0 { + temp = append(temp, tuneValues[i]) + } + } + tuneValues = temp tuneLimit := 770 if _config.ErupeConfig.RealClientMode <= _config.F5 { @@ -533,6 +601,9 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { tuneValues = tuneValues[:tuneLimit] } + offset := uint16(time.Now().Unix()) + bf.WriteUint16(offset) + bf.WriteUint16(uint16(len(tuneValues))) for i := range tuneValues { bf.WriteUint16(tuneValues[i].ID ^ offset) diff --git a/server/channelserver/sys_language.go b/server/channelserver/sys_language.go index badbf754e..914dcc6ca 100644 --- a/server/channelserver/sys_language.go +++ b/server/channelserver/sys_language.go @@ -10,6 +10,7 @@ type i18n struct { cafe struct { reset string } + timer string commands struct { noOp string disabled string @@ -51,6 +52,10 @@ type i18n struct { error string length string } + timer struct { + enabled string + disabled string + } ravi struct { noCommand string start struct { @@ -112,6 +117,7 @@ func getLangStrings(s *Server) i18n { case "jp": i.language = "日本語" i.cafe.reset = "%d/%dにリセット" + i.timer = "タイマー:%02d'%02d\"%02d.%03d (%df)" i.diva.prayer.beads = []Bead{ {id: 1, name: "暴風の祈珠", description: "ーあらしまかぜのきじゅー\n暴風とは猛る思い。\n聞く者に勇気を与える。"}, @@ -215,6 +221,7 @@ func getLangStrings(s *Server) i18n { {id: 24, name: "Bead of Thunderproof", description: "ーふうらいのきじゅー"}, {id: 25, name: "Bead of Immunity", description: "ーふうぞくのきじゅー"}, } + i.timer = "Time: %02d:%02d:%02d.%03d (%df)" i.commands.noOp = "You don't have permission to use this command" i.commands.disabled = "%s command is disabled" @@ -243,6 +250,9 @@ func getLangStrings(s *Server) i18n { i.commands.ban.error = "Error in command. Format: %s [length]" i.commands.ban.length = " until %s" + i.commands.timer.enabled = "Quest timer enabled" + i.commands.timer.disabled = "Quest timer disabled" + i.commands.ravi.noCommand = "No Raviente command specified!" i.commands.ravi.start.success = "The Great Slaying will begin in a moment" i.commands.ravi.start.error = "The Great Slaying has already begun!" diff --git a/server/signserver/session.go b/server/signserver/session.go index 164ab70e2..e4cbd5537 100644 --- a/server/signserver/session.go +++ b/server/signserver/session.go @@ -54,7 +54,7 @@ func (s *Session) handlePacket(pkt []byte) error { bf := byteframe.NewByteFrameFromBytes(pkt) reqType := string(bf.ReadNullTerminatedBytes()) switch reqType[:len(reqType)-3] { - case "DLTSKEYSIGN:", "DSGN:": + case "DLTSKEYSIGN:", "DSGN:", "SIGN:": s.handleDSGN(bf) case "PS3SGN:": s.client = PS3