6 Commits

Author SHA1 Message Date
wish
8f3624d589 Update README.md 2025-05-16 18:10:13 +10:00
wish
8d1c6a79e2 S6 compatibility fix 2025-05-04 01:47:30 +10:00
wish
d83c784452 Create 26-fix-mail.sql 2025-04-10 23:35:59 +10:00
wish
ee7b099deb Create 25-fix-rasta-id.sql 2025-04-10 23:33:40 +10:00
wish
ef3212da11 Rename fix-weekly-stamps.sql to 24-fix-weekly-stamps.sql 2025-04-10 23:33:08 +10:00
wish
3d0114ce22 fix MhfAcquireCafeItem cost in G1-G5.2 2025-04-01 20:58:33 +11:00
16 changed files with 129 additions and 5466 deletions

View File

@@ -28,7 +28,7 @@ If you want to modify or compile Erupe yourself, please read on.
## Installation ## Installation
1. Bring up a fresh database by using the [backup file attached with the latest release](https://github.com/ZeruLight/Erupe/releases/latest/download/SCHEMA.sql). 1. Bring up a fresh database by using the [backup file attached with the latest release](https://github.com/ZeruLight/Erupe/releases/latest/download/SCHEMA.sql).
2. Run each script under [patch-schema](./patch-schema) as they introduce newer schema. 2. Run each script under [patch-schema](./schemas/patch-schema) as they introduce newer schema.
3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup. 3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup.
4. Run `go build` or `go run .` to compile Erupe. 4. Run `go build` or `go run .` to compile Erupe.

View File

@@ -31,7 +31,7 @@ func (m *MsgMhfAcquireCafeItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl
m.ItemType = bf.ReadUint16() m.ItemType = bf.ReadUint16()
m.ItemID = bf.ReadUint16() m.ItemID = bf.ReadUint16()
m.Quant = bf.ReadUint16() m.Quant = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 { if _config.ErupeConfig.RealClientMode >= _config.G6 {
m.PointCost = bf.ReadUint32() m.PointCost = bf.ReadUint32()
} else { } else {
m.PointCost = uint32(bf.ReadUint16()) m.PointCost = uint32(bf.ReadUint16())

View File

@@ -11,7 +11,9 @@ import (
// MsgMhfAcquireItem represents the MSG_MHF_ACQUIRE_ITEM // MsgMhfAcquireItem represents the MSG_MHF_ACQUIRE_ITEM
type MsgMhfAcquireItem struct { type MsgMhfAcquireItem struct {
AckHandle uint32 AckHandle uint32
RewardIDs []uint32 Unk0 uint16
Length uint16
Unk1 []uint32
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -22,10 +24,10 @@ func (m *MsgMhfAcquireItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfAcquireItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfAcquireItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
bf.ReadUint16() // Zeroed m.Unk0 = bf.ReadUint16()
ids := bf.ReadUint16() m.Length = bf.ReadUint16()
for i := uint16(0); i < ids; i++ { for i := 0; i < int(m.Length); i++ {
m.RewardIDs = append(m.RewardIDs, bf.ReadUint32()) m.Unk1 = append(m.Unk1, bf.ReadUint32())
} }
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/bfutil"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
@@ -11,8 +10,9 @@ import (
// MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN // MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN
type MsgMhfApplyCampaign struct { type MsgMhfApplyCampaign struct {
AckHandle uint32 AckHandle uint32
CampaignID uint32 Unk0 uint32
Code string Unk1 uint16
Unk2 []byte
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -23,9 +23,9 @@ func (m *MsgMhfApplyCampaign) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfApplyCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfApplyCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.CampaignID = bf.ReadUint32() m.Unk0 = bf.ReadUint32()
bf.ReadUint16() // Zeroed m.Unk1 = bf.ReadUint16()
m.Code = string(bfutil.UpToNull(bf.ReadBytes(16))) m.Unk2 = bf.ReadBytes(16)
return nil return nil
} }

View File

@@ -9,8 +9,8 @@ import (
// MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN // MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN
type MsgMhfEnumerateCampaign struct { type MsgMhfEnumerateCampaign struct {
AckHandle uint32 AckHandle uint32
NullPadding1 uint16 // 0 in z2 Unk0 uint16
NullPadding2 uint16 // 0 in z2 Unk1 uint16
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -21,15 +21,15 @@ func (m *MsgMhfEnumerateCampaign) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.NullPadding1 = bf.ReadUint16() m.Unk0 = bf.ReadUint16()
m.NullPadding2 = bf.ReadUint16() m.Unk1 = bf.ReadUint16()
return nil return nil
} }
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) bf.WriteUint32(m.AckHandle)
bf.WriteUint16(m.NullPadding1) bf.WriteUint16(m.Unk0)
bf.WriteUint16(m.NullPadding2) bf.WriteUint16(m.Unk1)
return nil return nil
} }

View File

@@ -11,6 +11,8 @@ import (
// MsgMhfEnumerateItem represents the MSG_MHF_ENUMERATE_ITEM // MsgMhfEnumerateItem represents the MSG_MHF_ENUMERATE_ITEM
type MsgMhfEnumerateItem struct { type MsgMhfEnumerateItem struct {
AckHandle uint32 AckHandle uint32
Unk0 uint16
Unk1 uint16
CampaignID uint32 CampaignID uint32
} }
@@ -22,8 +24,8 @@ func (m *MsgMhfEnumerateItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
bf.ReadUint16() // Zeroed m.Unk0 = bf.ReadUint16()
bf.ReadUint16() // Always 2 m.Unk1 = bf.ReadUint16()
m.CampaignID = bf.ReadUint32() m.CampaignID = bf.ReadUint32()
return nil return nil
} }

View File

@@ -12,7 +12,7 @@ import (
type MsgMhfStateCampaign struct { type MsgMhfStateCampaign struct {
AckHandle uint32 AckHandle uint32
CampaignID uint32 CampaignID uint32
NullPadding uint16 Unk1 uint16
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -24,7 +24,7 @@ func (m *MsgMhfStateCampaign) Opcode() network.PacketID {
func (m *MsgMhfStateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfStateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.CampaignID = bf.ReadUint32() m.CampaignID = bf.ReadUint32()
m.NullPadding = bf.ReadUint16() //0 in Z2 m.Unk1 = bf.ReadUint16()
return nil return nil
} }

View File

@@ -11,9 +11,12 @@ import (
// MsgMhfTransferItem represents the MSG_MHF_TRANSFER_ITEM // MsgMhfTransferItem represents the MSG_MHF_TRANSFER_ITEM
type MsgMhfTransferItem struct { type MsgMhfTransferItem struct {
AckHandle uint32 AckHandle uint32
QuestID uint32 // looking at packets, these were static across sessions and did not actually
ItemType uint8 // correlate with any item IDs that would make sense to get after quests so
Quantity uint16 // I have no idea what this actually does
Unk0 uint32
Unk1 uint8
Unk2 uint16
} }
// Opcode returns the ID associated with this packet type. // Opcode returns the ID associated with this packet type.
@@ -24,10 +27,10 @@ func (m *MsgMhfTransferItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfTransferItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfTransferItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.QuestID = bf.ReadUint32() m.Unk0 = bf.ReadUint32()
m.ItemType = bf.ReadUint8() m.Unk1 = bf.ReadUint8()
bf.ReadUint8() // Zeroed bf.ReadUint8() // Zeroed
m.Quantity = bf.ReadUint16() m.Unk2 = bf.ReadUint16()
return nil return nil
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
BEGIN;
CREATE TABLE IF NOT EXISTS public.campaigns (
id SERIAL PRIMARY KEY,
min_hr INTEGER,
max_hr INTEGER,
min_sr INTEGER,
max_sr INTEGER,
min_gr INTEGER,
max_gr INTEGER,
reward_type INTEGER,
stamps INTEGER,
unk INTEGER,
background_id INTEGER,
start_time TIMESTAMP WITH TIME ZONE,
end_time TIMESTAMP WITH TIME ZONE,
title TEXT,
reward TEXT,
link TEXT,
code_prefix TEXT
);
CREATE TABLE IF NOT EXISTS public.campaign_categories (
id SERIAL PRIMARY KEY,
type INTEGER,
title TEXT,
description TEXT
);
CREATE TABLE IF NOT EXISTS public.campaign_category_links (
id SERIAL PRIMARY KEY,
campaign_id INTEGER,
category_id INTEGER
);
CREATE TABLE IF NOT EXISTS public.campaign_rewards (
id SERIAL PRIMARY KEY,
campaign_id INTEGER,
item_type INTEGER,
quantity INTEGER,
item_id INTEGER
);
CREATE TABLE IF NOT EXISTS public.campaign_rewards_claimed (
character_id INTEGER,
reward_id INTEGER
);
CREATE TABLE IF NOT EXISTS public.campaign_state (
id SERIAL PRIMARY KEY,
campaign_id INTEGER,
character_id INTEGER,
code TEXT
);
CREATE TABLE IF NOT EXISTS public.campaign_codes (
code TEXT,
multi BOOLEAN
);
CREATE TABLE IF NOT EXISTS public.campaign_quest (
campaign_id INTEGER,
character_id INTEGER
);
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE mail ADD COLUMN IF NOT EXISTS is_sys_message BOOLEAN NOT NULL DEFAULT false;
END;

View File

@@ -640,6 +640,11 @@ func handleMsgSysInfokyserver(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetCaUniqueID(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetCaUniqueID(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfTransferItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumeratePrice) pkt := p.(*mhfpacket.MsgMhfEnumeratePrice)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()

View File

@@ -10,68 +10,51 @@ import (
) )
type CampaignEvent struct { type CampaignEvent struct {
ID uint32 `db:"id"` ID uint32
MinHR int16 `db:"min_hr"` Unk0 uint32
MaxHR int16 `db:"max_hr"` MinHR int16
MinSR int16 `db:"min_sr"` MaxHR int16
MaxSR int16 `db:"max_sr"` MinSR int16
MinGR int16 `db:"min_gr"` MaxSR int16
MaxGR int16 `db:"max_gr"` MinGR int16
RewardType uint16 `db:"reward_type"` MaxGR int16
Stamps uint8 `db:"stamps"` Unk1 uint16
Unk uint8 `db:"unk"` Unk2 uint8
BackgroundID uint16 `db:"background_id"` Unk3 uint8
Start time.Time `db:"start_time"` Unk4 uint16
End time.Time `db:"end_time"` Unk5 uint16
Title string `db:"title"` Start time.Time
Reward string `db:"reward"` End time.Time
Link string `db:"link"` Unk6 uint8
Prefix string `db:"code_prefix"` String0 string
String1 string
String2 string
String3 string
Link string
Prefix string
Categories []uint16
} }
type CampaignCategory struct { type CampaignCategory struct {
ID uint16 `db:"id"` ID uint16
Type uint8 `db:"type"` Type uint8
Title string `db:"title"` Title string
Description string `db:"description"` Description string
} }
type CampaignLink struct { type CampaignLink struct {
CategoryID uint16 `db:"category_id"` CategoryID uint16
CampaignID uint32 `db:"campaign_id"` CampaignID uint32
}
type CampaignReward struct {
ID uint32 `db:"id"`
ItemType uint16 `db:"item_type"`
Quantity uint16 `db:"quantity"`
ItemID uint16 `db:"item_id"`
Deadline time.Time `db:"deadline"`
} }
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign) pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
var events []CampaignEvent events := []CampaignEvent{}
var categories []CampaignCategory categories := []CampaignCategory{}
var campaignLinks []CampaignLink var campaignLinks []CampaignLink
err := s.server.db.Select(&events, "SELECT id,min_hr,max_hr,min_sr,max_sr,min_gr,max_gr,reward_type,stamps,unk,background_id,start_time,end_time,title,reward,link,code_prefix FROM campaigns")
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = s.server.db.Select(&categories, "SELECT id, type, title, description FROM campaign_categories")
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = s.server.db.Select(&campaignLinks, "SELECT campaign_id, category_id FROM campaign_category_links")
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
if len(events) > 255 { if len(events) > 255 {
bf.WriteUint8(255) bf.WriteUint8(255)
bf.WriteUint16(uint16(len(events))) bf.WriteUint16(uint16(len(events)))
@@ -80,7 +63,7 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
} }
for _, event := range events { for _, event := range events {
bf.WriteUint32(event.ID) bf.WriteUint32(event.ID)
bf.WriteUint32(0) bf.WriteUint32(event.Unk0)
bf.WriteInt16(event.MinHR) bf.WriteInt16(event.MinHR)
bf.WriteInt16(event.MaxHR) bf.WriteInt16(event.MaxHR)
bf.WriteInt16(event.MinSR) bf.WriteInt16(event.MinSR)
@@ -89,23 +72,22 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
bf.WriteInt16(event.MinGR) bf.WriteInt16(event.MinGR)
bf.WriteInt16(event.MaxGR) bf.WriteInt16(event.MaxGR)
} }
bf.WriteUint16(event.RewardType) bf.WriteUint16(event.Unk1)
bf.WriteUint8(event.Stamps) bf.WriteUint8(event.Unk2)
bf.WriteUint8(event.Unk) // Related to stamp count bf.WriteUint8(event.Unk3)
bf.WriteUint16(event.BackgroundID) bf.WriteUint16(event.Unk4)
bf.WriteUint16(0) bf.WriteUint16(event.Unk5)
bf.WriteUint32(uint32(event.Start.Unix())) bf.WriteUint32(uint32(event.Start.Unix()))
bf.WriteUint32(uint32(event.End.Unix())) bf.WriteUint32(uint32(event.End.Unix()))
if event.End.After(time.Now()) { bf.WriteUint8(event.Unk6)
bf.WriteBool(true) ps.Uint8(bf, event.String0, true)
} else { ps.Uint8(bf, event.String1, true)
bf.WriteBool(false) ps.Uint8(bf, event.String2, true)
} ps.Uint8(bf, event.String3, true)
ps.Uint8(bf, event.Title, true)
ps.Uint8(bf, event.Reward, true)
ps.Uint8(bf, "", false)
ps.Uint8(bf, "", false)
ps.Uint8(bf, event.Link, true) ps.Uint8(bf, event.Link, true)
for i := range event.Categories {
campaignLinks = append(campaignLinks, CampaignLink{event.Categories[i], event.ID})
}
} }
if len(events) > 255 { if len(events) > 255 {
@@ -116,7 +98,7 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
} }
for _, event := range events { for _, event := range events {
bf.WriteUint32(event.ID) bf.WriteUint32(event.ID)
bf.WriteUint8(1) // Related to stamp count bf.WriteUint8(1) // Always 1?
bf.WriteBytes([]byte(event.Prefix)) bf.WriteBytes([]byte(event.Prefix))
} }
@@ -153,156 +135,42 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateCampaign) pkt := p.(*mhfpacket.MsgMhfStateCampaign)
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
var required int bf.WriteUint16(1)
var deadline time.Time
var stamps []uint32
err := s.server.db.Select(&stamps, "SELECT id FROM campaign_state WHERE campaign_id = $1 AND character_id = $2", pkt.CampaignID, s.charID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = s.server.db.QueryRow(`SELECT stamps, end_time FROM campaigns WHERE id = $1`, pkt.CampaignID).Scan(&required, &deadline)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint16(uint16(len(stamps) + 1))
if required == 0 {
required = 1 // TODO: I don't understand how this is supposed to work
}
if len(stamps) < required {
bf.WriteUint16(0) bf.WriteUint16(0)
} else if len(stamps) >= required || deadline.After(time.Now()) {
bf.WriteUint16(2)
}
for _, v := range stamps {
bf.WriteUint32(v)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyCampaign) pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
bf := byteframe.NewByteFrame()
// Check if the code exists and check if it's a multi-code bf.WriteUint32(1)
var multi bool doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
err := s.server.db.QueryRow(`SELECT multi FROM public.campaign_codes WHERE code = $1 GROUP BY multi`, pkt.Code).Scan(&multi)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
// Check if the code is already used
var exists bool
if multi {
s.server.db.QueryRow(`SELECT COUNT(*) FROM public.campaign_state WHERE code = $1 AND character_id = $2`, pkt.Code, s.charID).Scan(&exists)
} else {
s.server.db.QueryRow(`SELECT COUNT(*) FROM public.campaign_state WHERE code = $1`, pkt.Code).Scan(&exists)
}
if exists {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
s.server.db.Exec(`INSERT INTO public.campaign_state (code, campaign_id, character_id) VALUES ($1, $2, $3)`, pkt.Code, pkt.CampaignID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }
func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateItem) pkt := p.(*mhfpacket.MsgMhfEnumerateItem)
items := []struct {
Unk0 uint32
Unk1 uint16
Unk2 uint16
Unk3 uint16
Unk4 uint32
Unk5 uint32
}{}
bf := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
var stamps, required, rewardType uint16
var deadline time.Time
err := s.server.db.QueryRow(`SELECT COUNT(*) FROM campaign_state WHERE campaign_id = $1 AND character_id = $2`, pkt.CampaignID, s.charID).Scan(&stamps)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
err = s.server.db.QueryRow(`SELECT stamps, reward_type, end_time FROM campaigns WHERE id = $1`, pkt.CampaignID).Scan(&required, &rewardType, &deadline)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
if required == 0 {
required = 1 // TODO: I don't understand how this is supposed to work
}
if stamps >= required {
var items []CampaignReward
if rewardType == 2 {
var exists int
s.server.db.QueryRow(`SELECT COUNT(*) FROM campaign_quest WHERE campaign_id = $1 AND character_id = $2`, pkt.CampaignID, s.charID).Scan(&exists)
if exists > 0 {
err = s.server.db.Select(&items, `
SELECT id, item_type, quantity, item_id, TO_TIMESTAMP(0) AS deadline FROM campaign_rewards
WHERE campaign_id = $1 AND item_type != 9
AND NOT EXISTS (SELECT 1 FROM campaign_rewards_claimed WHERE reward_id = campaign_rewards.id AND character_id = $2)
`, pkt.CampaignID, s.charID)
} else {
err = s.server.db.Select(&items, `
SELECT cr.id, cr.item_type, cr.quantity, cr.item_id, COALESCE(c.end_time, TO_TIMESTAMP(0)) AS deadline FROM campaign_rewards cr
JOIN campaigns c ON cr.campaign_id = c.id
WHERE campaign_id = $1 AND item_type = 9`, pkt.CampaignID)
}
} else {
err = s.server.db.Select(&items, `
SELECT id, item_type, quantity, item_id, TO_TIMESTAMP(0) AS deadline FROM campaign_rewards
WHERE campaign_id = $1
AND NOT EXISTS (SELECT 1 FROM campaign_rewards_claimed WHERE reward_id = campaign_rewards.id AND character_id = $2)
`, pkt.CampaignID, s.charID)
}
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint16(uint16(len(items))) bf.WriteUint16(uint16(len(items)))
for _, item := range items { for _, item := range items {
bf.WriteUint32(item.ID) bf.WriteUint32(item.Unk0)
bf.WriteUint16(item.ItemType) bf.WriteUint16(item.Unk1)
bf.WriteUint16(item.Quantity) bf.WriteUint16(item.Unk2)
bf.WriteUint16(item.ItemID) //HACK:placed quest id in this field to fit with Item No pattern. however it could be another field... possibly the other unks. bf.WriteUint16(item.Unk3)
bf.WriteUint16(0) //Unk4, gets cast to uint8 bf.WriteUint32(item.Unk4)
bf.WriteUint32(0) //Unk5 bf.WriteUint32(item.Unk5)
bf.WriteUint32(uint32(deadline.Unix()))
} }
if len(items) == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckBufSucceed(s, pkt.AckHandle, bf.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
} }
func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireItem) pkt := p.(*mhfpacket.MsgMhfAcquireItem)
for _, id := range pkt.RewardIDs {
s.server.db.Exec(`INSERT INTO campaign_rewards_claimed (reward_id, character_id) VALUES ($1, $2)`, id, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfTransferItem)
if pkt.ItemType == 9 {
var campaignID uint32
err := s.server.db.QueryRow(`
SELECT ce.campaign_id FROM campaign_rewards ce
JOIN event_quests eq ON ce.item_id = eq.quest_id
WHERE eq.id = $1
`, pkt.QuestID).Scan(&campaignID)
if err == nil {
s.server.db.Exec(`INSERT INTO campaign_quest (campaign_id, character_id) VALUES ($1, $2)`, campaignID, s.charID)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} }

View File

@@ -85,6 +85,10 @@ func BackportQuest(data []byte) []byte {
} }
} }
} }
if _config.ErupeConfig.RealClientMode <= _config.S6 {
binary.LittleEndian.PutUint32(data[16:20], binary.LittleEndian.Uint32(data[8:12]))
}
return data return data
} }
@@ -277,33 +281,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
} }
bf.WriteUint8(questType) bf.WriteUint8(questType)
if questType == 9 { if questType == 9 {
var stamps, required int
var deadline time.Time
err := s.server.db.QueryRow(`SELECT COUNT(*) FROM campaign_state WHERE campaign_id = (
SELECT campaign_id
FROM campaign_rewards
WHERE item_type = 9
AND item_id = $1
) AND character_id = $2`, questId, s.charID).Scan(&stamps)
err2 := s.server.db.QueryRow(`SELECT stamps, end_time
FROM campaigns
WHERE id = (
SELECT campaign_id
FROM campaign_rewards
WHERE item_type = 9
AND item_id = $1
)`, questId).Scan(&required, &deadline)
if required == 0 {
required = 1 // TODO: I don't understand how this is supposed to work
}
// Check if there are enough stamps to activate the quest, the deadline hasn't passed, and there are no errors
if stamps >= required && deadline.After(time.Now()) && err == nil && err2 == nil {
bf.WriteBool(true)
} else {
bf.WriteBool(false) bf.WriteBool(false)
}
} else { } else {
bf.WriteBool(true) bf.WriteBool(true)
} }