mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-14 07:55:33 +01:00
Merge branch 'main' into feature/diva
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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.
|
||||
|
||||
5
schemas/patch-schema/18-timer-toggle.sql
Normal file
5
schemas/patch-schema/18-timer-toggle.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool;
|
||||
|
||||
END;
|
||||
15
schemas/patch-schema/19-festa-submissions.sql
Normal file
15
schemas/patch-schema/19-festa-submissions.sql
Normal file
@@ -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;
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <id> [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!"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user