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
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.
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.ItemID = bf.ReadUint16()
m.Quant = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
if _config.ErupeConfig.RealClientMode >= _config.G6 {
m.PointCost = bf.ReadUint32()
} else {
m.PointCost = uint32(bf.ReadUint16())

View File

@@ -11,7 +11,9 @@ import (
// MsgMhfAcquireItem represents the MSG_MHF_ACQUIRE_ITEM
type MsgMhfAcquireItem struct {
AckHandle uint32
RewardIDs []uint32
Unk0 uint16
Length uint16
Unk1 []uint32
}
// 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
func (m *MsgMhfAcquireItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
bf.ReadUint16() // Zeroed
ids := bf.ReadUint16()
for i := uint16(0); i < ids; i++ {
m.RewardIDs = append(m.RewardIDs, bf.ReadUint32())
m.Unk0 = bf.ReadUint16()
m.Length = bf.ReadUint16()
for i := 0; i < int(m.Length); i++ {
m.Unk1 = append(m.Unk1, bf.ReadUint32())
}
return nil
}

View File

@@ -2,7 +2,6 @@ package mhfpacket
import (
"errors"
"erupe-ce/common/bfutil"
"erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
@@ -10,9 +9,10 @@ import (
// MsgMhfApplyCampaign represents the MSG_MHF_APPLY_CAMPAIGN
type MsgMhfApplyCampaign struct {
AckHandle uint32
CampaignID uint32
Code string
AckHandle uint32
Unk0 uint32
Unk1 uint16
Unk2 []byte
}
// 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
func (m *MsgMhfApplyCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.CampaignID = bf.ReadUint32()
bf.ReadUint16() // Zeroed
m.Code = string(bfutil.UpToNull(bf.ReadBytes(16)))
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadBytes(16)
return nil
}

View File

@@ -8,9 +8,9 @@ import (
// MsgMhfEnumerateCampaign represents the MSG_MHF_ENUMERATE_CAMPAIGN
type MsgMhfEnumerateCampaign struct {
AckHandle uint32
NullPadding1 uint16 // 0 in z2
NullPadding2 uint16 // 0 in z2
AckHandle uint32
Unk0 uint16
Unk1 uint16
}
// 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
func (m *MsgMhfEnumerateCampaign) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.NullPadding1 = bf.ReadUint16()
m.NullPadding2 = bf.ReadUint16()
m.Unk0 = bf.ReadUint16()
m.Unk1 = bf.ReadUint16()
return nil
}
// Build builds a binary packet from the current data.
func (m *MsgMhfEnumerateCampaign) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle)
bf.WriteUint16(m.NullPadding1)
bf.WriteUint16(m.NullPadding2)
bf.WriteUint16(m.Unk0)
bf.WriteUint16(m.Unk1)
return nil
}

View File

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

View File

@@ -10,9 +10,9 @@ import (
// MsgMhfStateCampaign represents the MSG_MHF_STATE_CAMPAIGN
type MsgMhfStateCampaign struct {
AckHandle uint32
CampaignID uint32
NullPadding uint16
AckHandle uint32
CampaignID uint32
Unk1 uint16
}
// 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 {
m.AckHandle = bf.ReadUint32()
m.CampaignID = bf.ReadUint32()
m.NullPadding = bf.ReadUint16() //0 in Z2
m.Unk1 = bf.ReadUint16()
return nil
}

View File

@@ -11,9 +11,12 @@ import (
// MsgMhfTransferItem represents the MSG_MHF_TRANSFER_ITEM
type MsgMhfTransferItem struct {
AckHandle uint32
QuestID uint32
ItemType uint8
Quantity uint16
// looking at packets, these were static across sessions and did not actually
// correlate with any item IDs that would make sense to get after quests so
// I have no idea what this actually does
Unk0 uint32
Unk1 uint8
Unk2 uint16
}
// 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
func (m *MsgMhfTransferItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
m.QuestID = bf.ReadUint32()
m.ItemType = bf.ReadUint8()
m.Unk0 = bf.ReadUint32()
m.Unk1 = bf.ReadUint8()
bf.ReadUint8() // Zeroed
m.Quantity = bf.ReadUint16()
m.Unk2 = bf.ReadUint16()
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

@@ -3,4 +3,4 @@ BEGIN;
ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked;
ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked;
END;
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 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) {
pkt := p.(*mhfpacket.MsgMhfEnumeratePrice)
bf := byteframe.NewByteFrame()

View File

@@ -10,68 +10,51 @@ import (
)
type CampaignEvent struct {
ID uint32 `db:"id"`
MinHR int16 `db:"min_hr"`
MaxHR int16 `db:"max_hr"`
MinSR int16 `db:"min_sr"`
MaxSR int16 `db:"max_sr"`
MinGR int16 `db:"min_gr"`
MaxGR int16 `db:"max_gr"`
RewardType uint16 `db:"reward_type"`
Stamps uint8 `db:"stamps"`
Unk uint8 `db:"unk"`
BackgroundID uint16 `db:"background_id"`
Start time.Time `db:"start_time"`
End time.Time `db:"end_time"`
Title string `db:"title"`
Reward string `db:"reward"`
Link string `db:"link"`
Prefix string `db:"code_prefix"`
ID uint32
Unk0 uint32
MinHR int16
MaxHR int16
MinSR int16
MaxSR int16
MinGR int16
MaxGR int16
Unk1 uint16
Unk2 uint8
Unk3 uint8
Unk4 uint16
Unk5 uint16
Start time.Time
End time.Time
Unk6 uint8
String0 string
String1 string
String2 string
String3 string
Link string
Prefix string
Categories []uint16
}
type CampaignCategory struct {
ID uint16 `db:"id"`
Type uint8 `db:"type"`
Title string `db:"title"`
Description string `db:"description"`
ID uint16
Type uint8
Title string
Description string
}
type CampaignLink struct {
CategoryID uint16 `db:"category_id"`
CampaignID uint32 `db:"campaign_id"`
}
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"`
CategoryID uint16
CampaignID uint32
}
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
bf := byteframe.NewByteFrame()
var events []CampaignEvent
var categories []CampaignCategory
events := []CampaignEvent{}
categories := []CampaignCategory{}
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 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(events)))
@@ -80,7 +63,7 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
}
for _, event := range events {
bf.WriteUint32(event.ID)
bf.WriteUint32(0)
bf.WriteUint32(event.Unk0)
bf.WriteInt16(event.MinHR)
bf.WriteInt16(event.MaxHR)
bf.WriteInt16(event.MinSR)
@@ -89,23 +72,22 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
bf.WriteInt16(event.MinGR)
bf.WriteInt16(event.MaxGR)
}
bf.WriteUint16(event.RewardType)
bf.WriteUint8(event.Stamps)
bf.WriteUint8(event.Unk) // Related to stamp count
bf.WriteUint16(event.BackgroundID)
bf.WriteUint16(0)
bf.WriteUint16(event.Unk1)
bf.WriteUint8(event.Unk2)
bf.WriteUint8(event.Unk3)
bf.WriteUint16(event.Unk4)
bf.WriteUint16(event.Unk5)
bf.WriteUint32(uint32(event.Start.Unix()))
bf.WriteUint32(uint32(event.End.Unix()))
if event.End.After(time.Now()) {
bf.WriteBool(true)
} else {
bf.WriteBool(false)
}
ps.Uint8(bf, event.Title, true)
ps.Uint8(bf, event.Reward, true)
ps.Uint8(bf, "", false)
ps.Uint8(bf, "", false)
bf.WriteUint8(event.Unk6)
ps.Uint8(bf, event.String0, true)
ps.Uint8(bf, event.String1, true)
ps.Uint8(bf, event.String2, true)
ps.Uint8(bf, event.String3, 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 {
@@ -116,7 +98,7 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
}
for _, event := range events {
bf.WriteUint32(event.ID)
bf.WriteUint8(1) // Related to stamp count
bf.WriteUint8(1) // Always 1?
bf.WriteBytes([]byte(event.Prefix))
}
@@ -153,156 +135,42 @@ func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
bf := byteframe.NewByteFrame()
var required int
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)
} else if len(stamps) >= required || deadline.After(time.Now()) {
bf.WriteUint16(2)
}
for _, v := range stamps {
bf.WriteUint32(v)
}
bf.WriteUint16(1)
bf.WriteUint16(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
// Check if the code exists and check if it's a multi-code
var multi bool
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))
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateItem)
items := []struct {
Unk0 uint32
Unk1 uint16
Unk2 uint16
Unk3 uint16
Unk4 uint32
Unk5 uint32
}{}
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)))
for _, item := range items {
bf.WriteUint32(item.ID)
bf.WriteUint16(item.ItemType)
bf.WriteUint16(item.Quantity)
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(0) //Unk4, gets cast to uint8
bf.WriteUint32(0) //Unk5
bf.WriteUint32(uint32(deadline.Unix()))
}
if len(items) == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
bf.WriteUint16(uint16(len(items)))
for _, item := range items {
bf.WriteUint32(item.Unk0)
bf.WriteUint16(item.Unk1)
bf.WriteUint16(item.Unk2)
bf.WriteUint16(item.Unk3)
bf.WriteUint32(item.Unk4)
bf.WriteUint32(item.Unk5)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {
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))
}

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
}
@@ -277,33 +281,7 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
}
bf.WriteUint8(questType)
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 {
bf.WriteBool(true)
}