Merge branch 'main' into feature/diva

This commit is contained in:
stratic-dev
2024-02-20 21:01:41 +00:00
14 changed files with 328 additions and 159 deletions

View File

@@ -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": [

View File

@@ -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.

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE users ADD COLUMN IF NOT EXISTS timer bool;
END;

View 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;

View File

@@ -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))
}
}
}

View File

@@ -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]

View File

@@ -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{

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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!"

View File

@@ -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