diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
new file mode 100644
index 000000000..375aa424a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -0,0 +1,21 @@
+---
+name: Bug
+about: Bug
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Description**:
+A clear and concise description of what the bug is.
+
+**Reproduction Steps**:
+1.
+2.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md
new file mode 100644
index 000000000..1d8650d16
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.md
@@ -0,0 +1,17 @@
+---
+name: Feature
+about: Feature
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+**Is your feature request related to an existing issue?**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/common/pascalstring/pascalstring.go b/common/pascalstring/pascalstring.go
index 3640db22d..8ad332018 100644
--- a/common/pascalstring/pascalstring.go
+++ b/common/pascalstring/pascalstring.go
@@ -11,7 +11,8 @@ func Uint8(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
- panic(err)
+ bf.WriteUint8(0)
+ return
}
x = xt
}
@@ -24,7 +25,8 @@ func Uint16(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
- panic(err)
+ bf.WriteUint16(0)
+ return
}
x = xt
}
@@ -37,7 +39,8 @@ func Uint32(bf *byteframe.ByteFrame, x string, t bool) {
e := japanese.ShiftJIS.NewEncoder()
xt, _, err := transform.String(e, x)
if err != nil {
- panic(err)
+ bf.WriteUint32(0)
+ return
}
x = xt
}
diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go
index 84574375b..ab91311ac 100644
--- a/common/stringsupport/string_convert.go
+++ b/common/stringsupport/string_convert.go
@@ -120,7 +120,11 @@ func CSVAdd(csv string, v int) string {
if len(csv) == 0 {
return strconv.Itoa(v)
}
- return csv + "," + strconv.Itoa(v)
+ if CSVContains(csv, v) {
+ return csv
+ } else {
+ return csv + "," + strconv.Itoa(v)
+ }
}
func CSVRemove(csv string, v int) string {
diff --git a/config.json b/config.json
index 29af1fb8d..00e541adf 100644
--- a/config.json
+++ b/config.json
@@ -5,16 +5,16 @@
"devmode": true,
"devmodeoptions": {
"serverName" : "",
+ "EnableLauncherServer": false,
"hideLoginNotice": false,
- "loginNotice": "
Welcome to Erupe SU9 (Patch 1)!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!
■Report bugs on Discord!
■Test everything!
■Don't talk to softlocking NPCs!
■Fork the code on GitHub!
Thank you to all of the contributors,
this wouldn't exist without you.",
+ "loginNotice": "Welcome to Erupe SU9.1 Beta!
Erupe is experimental software, we are not liable for any
issues caused by installing the software!
■Report bugs on Discord!
■Test everything!
■Don't talk to softlocking NPCs!
■Fork the code on GitHub!
Thank you to all of the contributors,
this wouldn't exist without you.",
"cleandb": false,
"maxlauncherhr": false,
"LogInboundMessages": false,
"LogOutboundMessages": false,
"MaxHexdumpLength": 256,
- "Event": 0,
"DivaEvent": 0,
- "FestaEvent": 0,
+ "FestaEvent": -1,
"TournamentEvent": 0,
"MezFesEvent": true,
"MezFesAlt": false,
@@ -28,10 +28,7 @@
"discord": {
"enabled": false,
"bottoken": "",
- "realtimeChannelID": "",
- "serverId": "",
- "devRoles": [],
- "devMode": false
+ "realtimeChannelID": ""
},
"database": {
"host": "localhost",
diff --git a/config/config.go b/config/config.go
index b025e7a0a..bfbf8086a 100644
--- a/config/config.go
+++ b/config/config.go
@@ -24,23 +24,24 @@ type Config struct {
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
type DevModeOptions struct {
- ServerName string // To get specific instance server about (Current Players/Event Week)
- HideLoginNotice bool // Hide the Erupe notice on login
- LoginNotice string // MHFML string of the login notice displayed
- CleanDB bool // Automatically wipes the DB on server reset.
- MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds.
- FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages
- LogInboundMessages bool // Log all messages sent to the server
- LogOutboundMessages bool // Log all messages sent to the clients
- MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
- DivaEvent int // Diva Defense event status
- FestaEvent int // Hunter's Festa event status
- TournamentEvent int // VS Tournament event status
- MezFesEvent bool // MezFes status
- MezFesAlt bool // Swaps out Volpakkun for Tokotoko
- DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
- DisableMailItems bool // Hack to prevent english versions of MHF from crashing
- SaveDumps SaveDumpOptions
+ ServerName string // To get specific instance server about (Current Players/Event Week)
+ EnableLauncherServer bool // Enables the launcher server to be served on port 80
+ HideLoginNotice bool // Hide the Erupe notice on login
+ LoginNotice string // MHFML string of the login notice displayed
+ CleanDB bool // Automatically wipes the DB on server reset.
+ MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds.
+ FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages
+ LogInboundMessages bool // Log all messages sent to the server
+ LogOutboundMessages bool // Log all messages sent to the clients
+ MaxHexdumpLength int // Maximum number of bytes printed when logs are enabled
+ DivaEvent int // Diva Defense event status
+ FestaEvent int // Hunter's Festa event status
+ TournamentEvent int // VS Tournament event status
+ MezFesEvent bool // MezFes status
+ MezFesAlt bool // Swaps out Volpakkun for Tokotoko
+ DisableTokenCheck bool // Disables checking login token exists in the DB (security risk!)
+ DisableMailItems bool // Hack to prevent english versions of MHF from crashing
+ SaveDumps SaveDumpOptions
}
type SaveDumpOptions struct {
diff --git a/go.mod b/go.mod
index 59d5c7eb9..8107625a2 100644
--- a/go.mod
+++ b/go.mod
@@ -4,16 +4,13 @@ go 1.16
require (
github.com/bwmarrin/discordgo v0.23.2
- github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jmoiron/sqlx v1.3.4
github.com/lib/pq v1.10.4
- github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
- github.com/pkg/errors v0.9.1 // indirect
github.com/sachaos/lottery v0.0.0-20180520074626-61949d99bd96
github.com/spf13/viper v1.8.1
go.uber.org/atomic v1.9.0 // indirect
@@ -23,5 +20,4 @@ require (
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/text v0.3.7
golang.org/x/tools v0.1.8 // indirect
- gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
diff --git a/main.go b/main.go
index c5ac726b3..b9dc62538 100644
--- a/main.go
+++ b/main.go
@@ -2,7 +2,6 @@ package main
import (
"fmt"
- "math/rand"
"net"
"os"
"os/signal"
@@ -66,7 +65,7 @@ func main() {
var discordBot *discordbot.DiscordBot = nil
if erupeConfig.Discord.Enabled {
- bot, err := discordbot.NewDiscordBot(discordbot.DiscordBotOptions{
+ bot, err := discordbot.NewDiscordBot(discordbot.Options{
Logger: logger,
Config: erupeConfig,
})
@@ -83,6 +82,7 @@ func main() {
}
discordBot = bot
+ logger.Info("Discord bot is enabled")
} else {
logger.Info("Discord bot is disabled")
}
@@ -109,9 +109,11 @@ func main() {
}
logger.Info("Connected to database")
- // Clear existing tokens
+ // Clear stale data
_ = db.MustExec("DELETE FROM sign_sessions")
_ = db.MustExec("DELETE FROM servers")
+ _ = db.MustExec("DELETE FROM cafe_accepted")
+ _ = db.MustExec("UPDATE characters SET cafe_time=0")
// Clean the DB if the option is on.
if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB {
@@ -123,18 +125,21 @@ func main() {
// Now start our server(s).
// Launcher HTTP server.
- launcherServer := launcherserver.NewServer(
- &launcherserver.Config{
- Logger: logger.Named("launcher"),
- ErupeConfig: erupeConfig,
- DB: db,
- UseOriginalLauncherFiles: erupeConfig.Launcher.UseOriginalLauncherFiles,
- })
- err = launcherServer.Start()
- if err != nil {
- preventClose(fmt.Sprintf("Failed to start launcher server: %s", err.Error()))
+ var launcherServer *launcherserver.Server
+ if erupeConfig.DevMode && erupeConfig.DevModeOptions.EnableLauncherServer {
+ launcherServer = launcherserver.NewServer(
+ &launcherserver.Config{
+ Logger: logger.Named("launcher"),
+ ErupeConfig: erupeConfig,
+ DB: db,
+ UseOriginalLauncherFiles: erupeConfig.Launcher.UseOriginalLauncherFiles,
+ })
+ err = launcherServer.Start()
+ if err != nil {
+ preventClose(fmt.Sprintf("Failed to start launcher server: %s", err.Error()))
+ }
+ logger.Info("Started launcher server")
}
- logger.Info("Started launcher server")
// Entrance server.
entranceServer := entranceserver.NewServer(
@@ -168,9 +173,6 @@ func main() {
ci := 0
count := 1
for _, ee := range erupeConfig.Entrance.Entries {
- rand.Seed(time.Now().UnixNano())
- // Randomly generate a season for the World
- season := rand.Intn(3) + 1
for _, ce := range ee.Channels {
sid := (4096 + si*256) + (16 + ci)
c := *channelserver.NewServer(&channelserver.Config{
@@ -190,7 +192,7 @@ func main() {
if err != nil {
preventClose(fmt.Sprintf("Failed to start channel server: %s", err.Error()))
} else {
- channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, season)
+ channelQuery += fmt.Sprintf("INSERT INTO servers (server_id, season, current_players) VALUES (%d, %d, 0);", sid, si%3)
channels = append(channels, &c)
logger.Info(fmt.Sprintf("Started channel server %d on port %d", count, ce.Port))
ci++
@@ -220,7 +222,9 @@ func main() {
}
signServer.Shutdown()
entranceServer.Shutdown()
- launcherServer.Shutdown()
+ if erupeConfig.DevModeOptions.EnableLauncherServer {
+ launcherServer.Shutdown()
+ }
time.Sleep(1 * time.Second)
}
diff --git a/network/binpacket/msg_bin_mail_notify.go b/network/binpacket/msg_bin_mail_notify.go
index 5e1687512..125dc57ef 100644
--- a/network/binpacket/msg_bin_mail_notify.go
+++ b/network/binpacket/msg_bin_mail_notify.go
@@ -16,11 +16,7 @@ func (m MsgBinMailNotify) Parse(bf *byteframe.ByteFrame) error {
func (m MsgBinMailNotify) Build(bf *byteframe.ByteFrame) error {
bf.WriteUint8(0x01) // Unk
- byteName, _ := stringsupport.ConvertUTF8ToShiftJIS(m.SenderName)
-
- bf.WriteBytes(byteName)
- bf.WriteBytes(make([]byte, 21-len(byteName)))
-
+ bf.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
return nil
}
diff --git a/network/mhfpacket/msg_mhf_acquire_monthly_item.go b/network/mhfpacket/msg_mhf_acquire_monthly_item.go
index a8d8e2f04..acc10b42a 100644
--- a/network/mhfpacket/msg_mhf_acquire_monthly_item.go
+++ b/network/mhfpacket/msg_mhf_acquire_monthly_item.go
@@ -1,15 +1,21 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfAcquireMonthlyItem represents the MSG_MHF_ACQUIRE_MONTHLY_ITEM
-type MsgMhfAcquireMonthlyItem struct{}
+type MsgMhfAcquireMonthlyItem struct {
+ AckHandle uint32
+ Unk0 uint16
+ Unk1 uint16
+ Unk2 uint32
+ Unk3 uint32
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID {
@@ -18,7 +24,12 @@ func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfAcquireMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk0 = bf.ReadUint16()
+ m.Unk1 = bf.ReadUint16()
+ m.Unk2 = bf.ReadUint32()
+ m.Unk3 = bf.ReadUint32()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_acquire_title.go b/network/mhfpacket/msg_mhf_acquire_title.go
index e521734f8..fe3a5ca95 100644
--- a/network/mhfpacket/msg_mhf_acquire_title.go
+++ b/network/mhfpacket/msg_mhf_acquire_title.go
@@ -1,15 +1,20 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfAcquireTitle represents the MSG_MHF_ACQUIRE_TITLE
-type MsgMhfAcquireTitle struct{}
+type MsgMhfAcquireTitle struct {
+ AckHandle uint32
+ Unk0 uint16
+ Unk1 uint16
+ TitleID uint16
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireTitle) Opcode() network.PacketID {
@@ -18,7 +23,11 @@ func (m *MsgMhfAcquireTitle) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfAcquireTitle) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk0 = bf.ReadUint16()
+ m.Unk1 = bf.ReadUint16()
+ m.TitleID = bf.ReadUint16()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_check_monthly_item.go b/network/mhfpacket/msg_mhf_check_monthly_item.go
index 2e04c3226..d79c65240 100644
--- a/network/mhfpacket/msg_mhf_check_monthly_item.go
+++ b/network/mhfpacket/msg_mhf_check_monthly_item.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfCheckMonthlyItem represents the MSG_MHF_CHECK_MONTHLY_ITEM
-type MsgMhfCheckMonthlyItem struct{}
+type MsgMhfCheckMonthlyItem struct {
+ AckHandle uint32
+ Unk uint32
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfCheckMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk = bf.ReadUint32()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_check_weekly_stamp.go b/network/mhfpacket/msg_mhf_check_weekly_stamp.go
index 83f6e5ce8..f03b1d1e7 100644
--- a/network/mhfpacket/msg_mhf_check_weekly_stamp.go
+++ b/network/mhfpacket/msg_mhf_check_weekly_stamp.go
@@ -1,15 +1,16 @@
package mhfpacket
import (
+ "errors"
+ "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
- "erupe-ce/common/byteframe"
)
// MsgMhfCheckWeeklyStamp represents the MSG_MHF_CHECK_WEEKLY_STAMP
type MsgMhfCheckWeeklyStamp struct {
AckHandle uint32
- Unk0 uint8
+ StampType string
Unk1 bool
Unk2 uint16 // Hardcoded 0 in the binary
}
@@ -22,7 +23,13 @@ func (m *MsgMhfCheckWeeklyStamp) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
- m.Unk0 = bf.ReadUint8()
+ stampType := bf.ReadUint8()
+ switch stampType {
+ case 1:
+ m.StampType = "hl"
+ case 2:
+ m.StampType = "ex"
+ }
m.Unk1 = bf.ReadBool()
m.Unk2 = bf.ReadUint16()
return nil
@@ -30,9 +37,5 @@ func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.C
// Build builds a binary packet from the current data.
func (m *MsgMhfCheckWeeklyStamp) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- bf.WriteUint32(m.AckHandle)
- bf.WriteUint8(m.Unk0)
- bf.WriteBool(m.Unk1)
- bf.WriteUint16(m.Unk2)
- return nil
+ return errors.New("NOT IMPLEMENTED")
}
diff --git a/network/mhfpacket/msg_mhf_entry_rookie_guild.go b/network/mhfpacket/msg_mhf_entry_rookie_guild.go
index d210be01a..e3f12660f 100644
--- a/network/mhfpacket/msg_mhf_entry_rookie_guild.go
+++ b/network/mhfpacket/msg_mhf_entry_rookie_guild.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfEntryRookieGuild represents the MSG_MHF_ENTRY_ROOKIE_GUILD
-type MsgMhfEntryRookieGuild struct{}
+type MsgMhfEntryRookieGuild struct {
+ AckHandle uint32
+ Unk uint32
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfEntryRookieGuild) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfEntryRookieGuild) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEntryRookieGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk = bf.ReadUint32()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go b/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go
index d35bc7227..21817237d 100644
--- a/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go
+++ b/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go
@@ -1,19 +1,19 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfEnumerateRengokuRanking represents the MSG_MHF_ENUMERATE_RENGOKU_RANKING
type MsgMhfEnumerateRengokuRanking struct {
- AckHandle uint32
- Unk0 uint32
- Unk1 uint16 // Hardcoded 0 in the binary
- Unk2 uint16 // Hardcoded 00 01 in the binary
+ AckHandle uint32
+ Leaderboard uint32
+ Unk1 uint16 // Hardcoded 0 in the binary
+ Unk2 uint16 // Hardcoded 00 01 in the binary
}
// Opcode returns the ID associated with this packet type.
@@ -24,7 +24,7 @@ func (m *MsgMhfEnumerateRengokuRanking) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateRengokuRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
- m.Unk0 = bf.ReadUint32()
+ m.Leaderboard = bf.ReadUint32()
m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadUint16()
return nil
diff --git a/network/mhfpacket/msg_mhf_enumerate_warehouse.go b/network/mhfpacket/msg_mhf_enumerate_warehouse.go
index f567e8bcb..3f1358045 100644
--- a/network/mhfpacket/msg_mhf_enumerate_warehouse.go
+++ b/network/mhfpacket/msg_mhf_enumerate_warehouse.go
@@ -1,15 +1,19 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE
-type MsgMhfEnumerateWarehouse struct{}
+type MsgMhfEnumerateWarehouse struct {
+ AckHandle uint32
+ BoxType string
+ BoxIndex uint8
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
@@ -18,7 +22,17 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ boxType := bf.ReadUint8()
+ switch boxType {
+ case 0:
+ m.BoxType = "item"
+ case 1:
+ m.BoxType = "equip"
+ }
+ m.BoxIndex = bf.ReadUint8()
+ _ = bf.ReadUint16()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go
index 6cce19147..918a870ac 100644
--- a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go
+++ b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go
@@ -1,15 +1,20 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfExchangeWeeklyStamp represents the MSG_MHF_EXCHANGE_WEEKLY_STAMP
-type MsgMhfExchangeWeeklyStamp struct{}
+type MsgMhfExchangeWeeklyStamp struct {
+ AckHandle uint32
+ StampType string
+ Unk1 uint8
+ Unk2 uint16
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID {
@@ -18,7 +23,17 @@ func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfExchangeWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ stampType := bf.ReadUint8()
+ switch stampType {
+ case 1:
+ m.StampType = "hl"
+ case 2:
+ m.StampType = "ex"
+ }
+ m.Unk1 = bf.ReadUint8()
+ m.Unk2 = bf.ReadUint16()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_get_achievement.go b/network/mhfpacket/msg_mhf_get_achievement.go
index 4b41ce72b..afa49d0d4 100644
--- a/network/mhfpacket/msg_mhf_get_achievement.go
+++ b/network/mhfpacket/msg_mhf_get_achievement.go
@@ -1,18 +1,18 @@
package mhfpacket
import (
- "errors"
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfGetAchievement represents the MSG_MHF_GET_ACHIEVEMENT
-type MsgMhfGetAchievement struct{
- AckHandle uint32
- Unk0 uint32 // id?
- Unk1 uint32 // char?
+type MsgMhfGetAchievement struct {
+ AckHandle uint32
+ CharID uint32
+ Unk1 uint32 // char?
}
// Opcode returns the ID associated with this packet type.
@@ -22,8 +22,8 @@ func (m *MsgMhfGetAchievement) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfGetAchievement) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- m.AckHandle = bf.ReadUint32()
- m.Unk0 = bf.ReadUint32()
+ m.AckHandle = bf.ReadUint32()
+ m.CharID = bf.ReadUint32()
m.Unk1 = bf.ReadUint32()
return nil
}
diff --git a/network/mhfpacket/msg_mhf_operate_warehouse.go b/network/mhfpacket/msg_mhf_operate_warehouse.go
index 9df4e3770..ba0ee7b78 100644
--- a/network/mhfpacket/msg_mhf_operate_warehouse.go
+++ b/network/mhfpacket/msg_mhf_operate_warehouse.go
@@ -1,15 +1,22 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
+ "erupe-ce/common/stringsupport"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfOperateWarehouse represents the MSG_MHF_OPERATE_WAREHOUSE
-type MsgMhfOperateWarehouse struct{}
+type MsgMhfOperateWarehouse struct {
+ AckHandle uint32
+ Operation uint8
+ BoxType string
+ BoxIndex uint8
+ Name string
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
@@ -18,7 +25,20 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Operation = bf.ReadUint8()
+ boxType := bf.ReadUint8()
+ switch boxType {
+ case 0:
+ m.BoxType = "item"
+ case 1:
+ m.BoxType = "equip"
+ }
+ m.BoxIndex = bf.ReadUint8()
+ _ = bf.ReadUint8() // lenName
+ _ = bf.ReadUint16() // Unk
+ m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_operation_inv_guild.go b/network/mhfpacket/msg_mhf_operation_inv_guild.go
index b07311c9d..bcd0b45b8 100644
--- a/network/mhfpacket/msg_mhf_operation_inv_guild.go
+++ b/network/mhfpacket/msg_mhf_operation_inv_guild.go
@@ -1,15 +1,22 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfOperationInvGuild represents the MSG_MHF_OPERATION_INV_GUILD
-type MsgMhfOperationInvGuild struct{}
+type MsgMhfOperationInvGuild struct {
+ AckHandle uint32
+ Operation uint8
+ ActiveHours uint8
+ DaysActive uint8
+ PlayStyle uint8
+ GuildRequest uint8
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID {
@@ -18,7 +25,13 @@ func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfOperationInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Operation = bf.ReadUint8()
+ m.ActiveHours = bf.ReadUint8()
+ m.DaysActive = bf.ReadUint8()
+ m.PlayStyle = bf.ReadUint8()
+ m.GuildRequest = bf.ReadUint8()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go b/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go
index c89159c0c..baa102f61 100644
--- a/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go
+++ b/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfPostCafeDurationBonusReceived represents the MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED
-type MsgMhfPostCafeDurationBonusReceived struct{}
+type MsgMhfPostCafeDurationBonusReceived struct {
+ AckHandle uint32
+ CafeBonusID []uint32
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID {
@@ -18,7 +21,12 @@ func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfPostCafeDurationBonusReceived) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ ids := int(bf.ReadUint32())
+ for i := 0; i < ids; i++ {
+ m.CafeBonusID = append(m.CafeBonusID, bf.ReadUint32())
+ }
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_read_mercenary_w.go b/network/mhfpacket/msg_mhf_read_mercenary_w.go
index 7cb0117e7..c80afee14 100644
--- a/network/mhfpacket/msg_mhf_read_mercenary_w.go
+++ b/network/mhfpacket/msg_mhf_read_mercenary_w.go
@@ -1,17 +1,17 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfReadMercenaryW represents the MSG_MHF_READ_MERCENARY_W
type MsgMhfReadMercenaryW struct {
AckHandle uint32
- Unk0 uint8
+ Unk0 bool
Unk1 uint8
Unk2 uint16 // Hardcoded 0 in the binary
}
@@ -24,7 +24,7 @@ func (m *MsgMhfReadMercenaryW) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfReadMercenaryW) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
- m.Unk0 = bf.ReadUint8()
+ m.Unk0 = bf.ReadBool()
m.Unk1 = bf.ReadUint8()
m.Unk2 = bf.ReadUint16()
return nil
diff --git a/network/mhfpacket/msg_mhf_save_mercenary.go b/network/mhfpacket/msg_mhf_save_mercenary.go
index a52c973ab..3aa2b0311 100644
--- a/network/mhfpacket/msg_mhf_save_mercenary.go
+++ b/network/mhfpacket/msg_mhf_save_mercenary.go
@@ -1,18 +1,20 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfSaveMercenary represents the MSG_MHF_SAVE_MERCENARY
-type MsgMhfSaveMercenary struct{
- AckHandle uint32
- DataSize uint32
- RawDataPayload []byte
+type MsgMhfSaveMercenary struct {
+ AckHandle uint32
+ GCP uint32
+ Unk0 uint32
+ MercData []byte
+ Unk1 uint32
}
// Opcode returns the ID associated with this packet type.
@@ -23,8 +25,11 @@ func (m *MsgMhfSaveMercenary) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfSaveMercenary) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32()
- m.DataSize = bf.ReadUint32()
- m.RawDataPayload = bf.ReadBytes(uint(m.DataSize))
+ bf.ReadUint32() // lenData
+ m.GCP = bf.ReadUint32()
+ m.Unk0 = bf.ReadUint32()
+ m.MercData = bf.ReadBytes(uint(bf.ReadUint32()))
+ m.Unk1 = bf.ReadUint32()
return nil
}
diff --git a/network/mhfpacket/msg_mhf_start_boost_time.go b/network/mhfpacket/msg_mhf_start_boost_time.go
index ff424aca9..583ab3ab0 100644
--- a/network/mhfpacket/msg_mhf_start_boost_time.go
+++ b/network/mhfpacket/msg_mhf_start_boost_time.go
@@ -1,15 +1,18 @@
package mhfpacket
-import (
- "errors"
+import (
+ "errors"
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
// MsgMhfStartBoostTime represents the MSG_MHF_START_BOOST_TIME
-type MsgMhfStartBoostTime struct{}
+type MsgMhfStartBoostTime struct {
+ AckHandle uint32
+ Unk0 uint32
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfStartBoostTime) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfStartBoostTime) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfStartBoostTime) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ m.Unk0 = bf.ReadUint32()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_mhf_update_warehouse.go b/network/mhfpacket/msg_mhf_update_warehouse.go
index ba0321910..962906988 100644
--- a/network/mhfpacket/msg_mhf_update_warehouse.go
+++ b/network/mhfpacket/msg_mhf_update_warehouse.go
@@ -1,15 +1,28 @@
package mhfpacket
-import (
- "errors"
-
- "erupe-ce/network/clientctx"
- "erupe-ce/network"
+import (
+ "errors"
"erupe-ce/common/byteframe"
+ "erupe-ce/network"
+ "erupe-ce/network/clientctx"
)
+type WarehouseStack struct {
+ ID uint32
+ Index uint16
+ EquipType uint16
+ ItemID uint16
+ Quantity uint16
+ Data []byte
+}
+
// MsgMhfUpdateWarehouse represents the MSG_MHF_UPDATE_WAREHOUSE
-type MsgMhfUpdateWarehouse struct{}
+type MsgMhfUpdateWarehouse struct {
+ AckHandle uint32
+ BoxType string
+ BoxIndex uint8
+ Updates []WarehouseStack
+}
// Opcode returns the ID associated with this packet type.
func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
@@ -18,7 +31,37 @@ func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary
func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
- return errors.New("NOT IMPLEMENTED")
+ m.AckHandle = bf.ReadUint32()
+ boxType := bf.ReadUint8()
+ switch boxType {
+ case 0:
+ m.BoxType = "item"
+ case 1:
+ m.BoxType = "equip"
+ }
+ m.BoxIndex = bf.ReadUint8()
+ changes := int(bf.ReadUint16())
+ var stackUpdate WarehouseStack
+ for i := 0; i < changes; i++ {
+ switch boxType {
+ case 0:
+ stackUpdate.ID = bf.ReadUint32()
+ stackUpdate.Index = bf.ReadUint16()
+ stackUpdate.ItemID = bf.ReadUint16()
+ stackUpdate.Quantity = bf.ReadUint16()
+ _ = bf.ReadUint16() // Unk
+ m.Updates = append(m.Updates, stackUpdate)
+ case 1:
+ stackUpdate.ID = bf.ReadUint32()
+ stackUpdate.Index = bf.ReadUint16()
+ stackUpdate.EquipType = bf.ReadUint16()
+ stackUpdate.ItemID = bf.ReadUint16()
+ stackUpdate.Data = bf.ReadBytes(56)
+ m.Updates = append(m.Updates, stackUpdate)
+ }
+ }
+ _ = bf.ReadUint16()
+ return nil
}
// Build builds a binary packet from the current data.
diff --git a/network/mhfpacket/msg_sys_update_right.go b/network/mhfpacket/msg_sys_update_right.go
index 3a7c95f4e..b343dd0c4 100644
--- a/network/mhfpacket/msg_sys_update_right.go
+++ b/network/mhfpacket/msg_sys_update_right.go
@@ -37,7 +37,7 @@ type ClientRight struct {
// MsgSysUpdateRight represents the MSG_SYS_UPDATE_RIGHT
type MsgSysUpdateRight struct {
ClientRespAckHandle uint32 // If non-0, requests the client to send back a MSG_SYS_ACK packet with this value.
- Unk1 uint32
+ Bitfield uint32
Rights []ClientRight
UnkSize uint16 // Count of some buf up to 0x800 bytes following it.
}
@@ -55,7 +55,7 @@ func (m *MsgSysUpdateRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
// Build builds a binary packet from the current data.
func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.ClientRespAckHandle)
- bf.WriteUint32(m.Unk1)
+ bf.WriteUint32(m.Bitfield)
bf.WriteUint16(uint16(len(m.Rights)))
bf.WriteUint16(0)
for _, v := range m.Rights {
diff --git a/patch-schema/achievements.sql b/patch-schema/achievements.sql
new file mode 100644
index 000000000..333ae5ed6
--- /dev/null
+++ b/patch-schema/achievements.sql
@@ -0,0 +1,41 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS public.achievements
+(
+ id int NOT NULL PRIMARY KEY ,
+ ach0 int DEFAULT 0,
+ ach1 int DEFAULT 0,
+ ach2 int DEFAULT 0,
+ ach3 int DEFAULT 0,
+ ach4 int DEFAULT 0,
+ ach5 int DEFAULT 0,
+ ach6 int DEFAULT 0,
+ ach7 int DEFAULT 0,
+ ach8 int DEFAULT 0,
+ ach9 int DEFAULT 0,
+ ach10 int DEFAULT 0,
+ ach11 int DEFAULT 0,
+ ach12 int DEFAULT 0,
+ ach13 int DEFAULT 0,
+ ach14 int DEFAULT 0,
+ ach15 int DEFAULT 0,
+ ach16 int DEFAULT 0,
+ ach17 int DEFAULT 0,
+ ach18 int DEFAULT 0,
+ ach19 int DEFAULT 0,
+ ach20 int DEFAULT 0,
+ ach21 int DEFAULT 0,
+ ach22 int DEFAULT 0,
+ ach23 int DEFAULT 0,
+ ach24 int DEFAULT 0,
+ ach25 int DEFAULT 0,
+ ach26 int DEFAULT 0,
+ ach27 int DEFAULT 0,
+ ach28 int DEFAULT 0,
+ ach29 int DEFAULT 0,
+ ach30 int DEFAULT 0,
+ ach31 int DEFAULT 0,
+ ach32 int DEFAULT 0
+);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/festa.sql b/patch-schema/festa.sql
new file mode 100644
index 000000000..ce7d03594
--- /dev/null
+++ b/patch-schema/festa.sql
@@ -0,0 +1,311 @@
+BEGIN;
+
+CREATE TYPE event_type AS ENUM ('festa', 'diva', 'vs', 'mezfes');
+
+DROP TABLE IF EXISTS public.event_week;
+
+ALTER TABLE IF EXISTS public.guild_characters
+ ADD COLUMN IF NOT EXISTS souls int DEFAULT 0;
+
+ALTER TABLE IF EXISTS public.guilds
+ DROP COLUMN IF EXISTS festival_colour;
+
+CREATE TABLE IF NOT EXISTS public.events
+(
+ id serial NOT NULL PRIMARY KEY,
+ event_type event_type NOT NULL,
+ start_time timestamp without time zone NOT NULL DEFAULT now()
+);
+
+CREATE TABLE IF NOT EXISTS public.festa_registrations
+(
+ guild_id int NOT NULL,
+ team festival_colour NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS public.festa_trials
+(
+ id serial NOT NULL PRIMARY KEY,
+ objective int NOT NULL,
+ goal_id int NOT NULL,
+ times_req int NOT NULL,
+ locale_req int NOT NULL DEFAULT 0,
+ reward int NOT NULL
+);
+
+CREATE TYPE prize_type AS ENUM ('personal', 'guild');
+
+CREATE TABLE IF NOT EXISTS public.festa_prizes
+(
+ id serial NOT NULL PRIMARY KEY,
+ type prize_type NOT NULL,
+ tier int NOT NULL,
+ souls_req int NOT NULL,
+ item_id int NOT NULL,
+ num_item int NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS public.festa_prizes_accepted
+(
+ prize_id int NOT NULL,
+ character_id int NOT NULL
+);
+
+-- Ripped prizes
+INSERT INTO public.festa_prizes
+ (type, tier, souls_req, item_id, num_item)
+VALUES
+ ('personal', 1, 1, 9647, 7),
+ ('personal', 2, 1, 9647, 7),
+ ('personal', 3, 1, 9647, 7),
+ ('personal', 1, 200, 11284, 4),
+ ('personal', 2, 200, 11284, 4),
+ ('personal', 3, 200, 11284, 4),
+ ('personal', 1, 400, 11381, 3),
+ ('personal', 2, 400, 11381, 3),
+ ('personal', 3, 400, 11381, 3),
+ ('personal', 1, 600, 11284, 8),
+ ('personal', 2, 600, 11284, 8),
+ ('personal', 3, 600, 11284, 8),
+ ('personal', 1, 800, 11384, 3),
+ ('personal', 2, 800, 11384, 3),
+ ('personal', 3, 800, 11384, 3),
+ ('personal', 1, 1000, 11284, 12),
+ ('personal', 2, 1000, 11284, 12),
+ ('personal', 3, 1000, 11284, 12),
+ ('personal', 1, 1200, 11381, 5),
+ ('personal', 2, 1200, 11381, 5),
+ ('personal', 3, 1200, 11381, 5),
+ ('personal', 1, 1400, 11284, 16),
+ ('personal', 2, 1400, 11284, 16),
+ ('personal', 3, 1400, 11284, 16),
+ ('personal', 1, 1700, 11384, 5),
+ ('personal', 2, 1700, 11384, 5),
+ ('personal', 3, 1700, 11384, 5),
+ ('personal', 1, 2000, 11284, 16),
+ ('personal', 2, 2000, 11284, 16),
+ ('personal', 3, 2000, 11284, 16),
+ ('personal', 1, 2500, 11382, 4),
+ ('personal', 2, 2500, 11382, 4),
+ ('personal', 3, 2500, 11382, 4),
+ ('personal', 1, 3000, 11284, 24),
+ ('personal', 2, 3000, 11284, 24),
+ ('personal', 3, 3000, 11284, 24),
+ ('personal', 1, 4000, 11385, 4),
+ ('personal', 2, 4000, 11385, 4),
+ ('personal', 3, 4000, 11385, 4),
+ ('personal', 1, 5000, 11381, 11),
+ ('personal', 2, 5000, 11381, 11),
+ ('personal', 3, 5000, 11381, 11),
+ ('personal', 1, 6000, 5177, 5),
+ ('personal', 2, 6000, 5177, 5),
+ ('personal', 3, 6000, 5177, 5),
+ ('personal', 1, 7000, 11384, 11),
+ ('personal', 2, 7000, 11384, 11),
+ ('personal', 3, 7000, 11384, 11),
+ ('personal', 1, 10000, 11382, 8),
+ ('personal', 2, 10000, 11382, 8),
+ ('personal', 3, 10000, 11382, 8),
+ ('personal', 1, 15000, 11385, 4),
+ ('personal', 2, 15000, 11385, 4),
+ ('personal', 3, 15000, 11385, 4),
+ ('personal', 1, 20000, 11381, 13),
+ ('personal', 2, 20000, 11381, 13),
+ ('personal', 3, 20000, 11381, 13),
+ ('personal', 1, 25000, 11385, 4),
+ ('personal', 2, 25000, 11385, 4),
+ ('personal', 3, 25000, 11385, 4),
+ ('personal', 1, 30000, 11383, 1),
+ ('personal', 2, 30000, 11383, 1),
+ ('personal', 3, 30000, 11383, 1);
+
+INSERT INTO public.festa_prizes
+(type, tier, souls_req, item_id, num_item)
+VALUES
+ ('guild', 1, 100, 7468, 5),
+ ('guild', 2, 100, 7468, 5),
+ ('guild', 3, 100, 7465, 5),
+ ('guild', 1, 300, 7469, 5),
+ ('guild', 2, 300, 7469, 5),
+ ('guild', 3, 300, 7466, 5),
+ ('guild', 1, 700, 7470, 5),
+ ('guild', 2, 700, 7470, 5),
+ ('guild', 3, 700, 7467, 5),
+ ('guild', 1, 1500, 13405, 14),
+ ('guild', 1, 1500, 1520, 3),
+ ('guild', 2, 1500, 13405, 14),
+ ('guild', 2, 1500, 1520, 3),
+ ('guild', 3, 1500, 7011, 3),
+ ('guild', 3, 1500, 13405, 14),
+ ('guild', 1, 3000, 10201, 10),
+ ('guild', 2, 3000, 10201, 10),
+ ('guild', 3, 3000, 10201, 10),
+ ('guild', 1, 6000, 13895, 14),
+ ('guild', 1, 6000, 1520, 6),
+ ('guild', 2, 6000, 13895, 14),
+ ('guild', 2, 6000, 1520, 6),
+ ('guild', 3, 6000, 13895, 14),
+ ('guild', 3, 6000, 7011, 4),
+ ('guild', 1, 12000, 13406, 14),
+ ('guild', 1, 12000, 1520, 9),
+ ('guild', 2, 12000, 13406, 14),
+ ('guild', 2, 12000, 1520, 9),
+ ('guild', 3, 12000, 13406, 14),
+ ('guild', 3, 12000, 7011, 5),
+ ('guild', 1, 25000, 10207, 10),
+ ('guild', 2, 25000, 10207, 10),
+ ('guild', 3, 25000, 10207, 10),
+ ('guild', 1, 50000, 1520, 12),
+ ('guild', 1, 50000, 13896, 14),
+ ('guild', 2, 50000, 1520, 12),
+ ('guild', 2, 50000, 13896, 14),
+ ('guild', 3, 50000, 7011, 6),
+ ('guild', 3, 50000, 13896, 14),
+ ('guild', 1, 100000, 10201, 10),
+ ('guild', 2, 100000, 10201, 10),
+ ('guild', 3, 100000, 10201, 10),
+ ('guild', 1, 200000, 13406, 16),
+ ('guild', 2, 200000, 13406, 16),
+ ('guild', 3, 200000, 13406, 16),
+ ('guild', 1, 300000, 13896, 16),
+ ('guild', 2, 300000, 13896, 16),
+ ('guild', 3, 300000, 13896, 16),
+ ('guild', 1, 400000, 10207, 10),
+ ('guild', 2, 400000, 10207, 10),
+ ('guild', 3, 400000, 10207, 10),
+ ('guild', 1, 500000, 13407, 6),
+ ('guild', 1, 500000, 13897, 6),
+ ('guild', 2, 500000, 13407, 6),
+ ('guild', 2, 500000, 13897, 6),
+ ('guild', 3, 500000, 13407, 6),
+ ('guild', 3, 500000, 13897, 6);
+
+-- Ripped trials
+INSERT INTO public.festa_trials
+ (objective, goal_id, times_req, locale_req, reward)
+VALUES
+ (1,27,1,0,1),
+ (5,53034,0,0,400),
+ (5,22042,0,0,89),
+ (5,23397,0,0,89),
+ (1,28,1,0,1),
+ (1,68,1,0,1),
+ (1,6,1,0,2),
+ (1,38,1,0,2),
+ (1,20,1,0,3),
+ (1,39,1,0,4),
+ (1,48,1,0,4),
+ (1,67,1,0,4),
+ (1,93,1,0,4),
+ (1,22,1,0,5),
+ (1,52,1,0,5),
+ (1,101,1,0,5),
+ (1,1,1,0,5),
+ (1,37,1,0,5),
+ (1,15,1,0,5),
+ (1,45,1,0,5),
+ (1,74,1,0,5),
+ (1,78,1,0,5),
+ (1,103,1,0,5),
+ (1,51,1,0,6),
+ (1,17,1,0,6),
+ (1,21,1,0,6),
+ (1,92,1,0,6),
+ (1,47,1,0,7),
+ (1,46,1,0,7),
+ (1,26,1,0,7),
+ (1,14,1,0,7),
+ (1,11,1,0,7),
+ (1,44,1,0,8),
+ (1,43,1,0,8),
+ (1,49,1,0,8),
+ (1,40,1,0,8),
+ (1,76,1,0,8),
+ (1,89,1,0,8),
+ (1,94,1,0,8),
+ (1,96,1,0,8),
+ (1,75,1,0,8),
+ (1,91,1,0,8),
+ (1,53,1,0,9),
+ (1,80,1,0,9),
+ (1,42,1,0,9),
+ (1,79,1,0,9),
+ (1,81,1,0,10),
+ (1,41,1,0,10),
+ (1,82,1,0,10),
+ (1,90,1,0,10),
+ (1,149,1,0,10),
+ (1,85,1,0,11),
+ (1,95,1,0,11),
+ (1,121,1,0,11),
+ (1,142,1,0,11),
+ (1,141,1,0,11),
+ (1,146,1,0,12),
+ (1,147,1,0,12),
+ (1,148,1,0,12),
+ (1,151,1,0,12),
+ (1,152,1,0,12),
+ (1,159,1,0,12),
+ (1,153,1,0,12),
+ (1,162,1,0,12),
+ (1,111,1,0,13),
+ (1,110,1,0,13),
+ (1,112,1,0,13),
+ (1,109,1,0,14),
+ (1,169,1,0,15),
+ (2,33,1,0,6),
+ (2,104,1,0,8),
+ (2,119,1,0,8),
+ (2,120,1,0,8),
+ (2,54,1,0,8),
+ (2,59,1,0,8),
+ (2,64,1,0,8),
+ (2,65,1,0,8),
+ (2,99,1,0,9),
+ (2,83,1,0,9),
+ (2,84,1,0,10),
+ (2,77,1,0,10),
+ (2,106,1,0,10),
+ (2,55,1,0,10),
+ (2,58,1,0,10),
+ (2,7,1,0,10),
+ (2,50,1,0,11),
+ (2,131,1,0,11),
+ (2,129,1,0,11),
+ (2,140,1,0,11),
+ (2,122,1,0,11),
+ (2,126,1,0,11),
+ (2,127,1,0,11),
+ (2,128,1,0,11),
+ (2,130,1,0,11),
+ (2,139,1,0,11),
+ (2,144,1,0,11),
+ (2,150,1,0,11),
+ (2,158,1,0,11),
+ (2,164,1,0,15),
+ (2,165,1,0,15),
+ (2,2,1,7,15),
+ (2,36,1,0,15),
+ (2,71,1,0,15),
+ (2,108,1,0,15),
+ (2,116,1,0,15),
+ (2,107,1,0,15),
+ (2,154,1,0,17),
+ (2,166,1,0,17),
+ (2,170,1,0,18),
+ (3,31,1,0,1),
+ (3,8,1,0,3),
+ (3,123,1,0,8),
+ (3,105,1,0,9),
+ (3,125,1,0,11),
+ (3,115,1,0,12),
+ (3,114,1,0,12),
+ (3,161,1,0,12),
+ (4,670,1,0,1),
+ (4,671,1,0,1),
+ (4,672,1,0,1),
+ (4,675,1,0,1),
+ (4,673,1,0,1),
+ (4,674,1,0,1);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/mail-system-messages.sql b/patch-schema/mail-system-messages.sql
new file mode 100644
index 000000000..4ce8dfaf6
--- /dev/null
+++ b/patch-schema/mail-system-messages.sql
@@ -0,0 +1,13 @@
+BEGIN;
+
+ALTER TABLE IF EXISTS public.mail
+ ADD COLUMN IF NOT EXISTS is_sys_message bool DEFAULT false;
+
+UPDATE mail SET is_sys_message=false;
+
+ALTER TABLE IF EXISTS public.mail
+ DROP CONSTRAINT IF EXISTS mail_sender_id_fkey;
+
+INSERT INTO public.characters (id, name) VALUES (0, '');
+
+END;
\ No newline at end of file
diff --git a/patch-schema/mercenary.sql b/patch-schema/mercenary.sql
new file mode 100644
index 000000000..9000db9f3
--- /dev/null
+++ b/patch-schema/mercenary.sql
@@ -0,0 +1,7 @@
+BEGIN;
+
+CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
+
+UPDATE characters SET savemercenary=NULL;
+
+END;
\ No newline at end of file
diff --git a/patch-schema/netcafe.sql b/patch-schema/netcafe.sql
new file mode 100644
index 000000000..e284742ce
--- /dev/null
+++ b/patch-schema/netcafe.sql
@@ -0,0 +1,40 @@
+BEGIN;
+
+ALTER TABLE IF EXISTS public.characters
+ ADD COLUMN IF NOT EXISTS cafe_time integer DEFAULT 0;
+
+ALTER TABLE IF EXISTS public.characters
+ DROP COLUMN IF EXISTS netcafe_points;
+
+ALTER TABLE IF EXISTS public.characters
+ ADD COLUMN IF NOT EXISTS netcafe_points int DEFAULT 0;
+
+ALTER TABLE IF EXISTS public.characters
+ ADD COLUMN IF NOT EXISTS boost_time timestamp without time zone;
+
+CREATE TABLE IF NOT EXISTS public.cafebonus
+(
+ id serial NOT NULL PRIMARY KEY,
+ time_req integer NOT NULL,
+ item_type integer NOT NULL,
+ item_id integer NOT NULL,
+ quantity integer NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS public.cafe_accepted
+(
+ cafe_id integer NOT NULL,
+ character_id integer NOT NULL
+);
+
+INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity)
+VALUES
+ (1800, 17, 0, 250),
+ (3600, 17, 0, 500),
+ (7200, 17, 0, 1000),
+ (10800, 17, 0, 1500),
+ (18000, 17, 0, 1750),
+ (28800, 17, 0, 3000),
+ (43200, 17, 0, 4000);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/return.sql b/patch-schema/return.sql
new file mode 100644
index 000000000..4e09d7e47
--- /dev/null
+++ b/patch-schema/return.sql
@@ -0,0 +1,9 @@
+BEGIN;
+
+ALTER TABLE IF EXISTS public.users
+ ADD COLUMN IF NOT EXISTS last_login timestamp without time zone;
+
+ALTER TABLE IF EXISTS public.users
+ ADD COLUMN IF NOT EXISTS return_expires timestamp without time zone;
+
+END;
\ No newline at end of file
diff --git a/patch-schema/road-leaderboard.sql b/patch-schema/road-leaderboard.sql
new file mode 100644
index 000000000..8dfea2875
--- /dev/null
+++ b/patch-schema/road-leaderboard.sql
@@ -0,0 +1,11 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS rengoku_score (
+ character_id integer PRIMARY KEY,
+ max_stages_mp integer,
+ max_points_mp integer,
+ max_stages_sp integer,
+ max_points_sp integer
+);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/stamps.sql b/patch-schema/stamps.sql
new file mode 100644
index 000000000..2b940fa8c
--- /dev/null
+++ b/patch-schema/stamps.sql
@@ -0,0 +1,13 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS public.stamps (
+ character_id integer PRIMARY KEY,
+ hl_total integer DEFAULT 0,
+ hl_redeemed integer DEFAULT 0,
+ hl_next timestamp without time zone,
+ ex_total integer DEFAULT 0,
+ ex_redeemed integer DEFAULT 0,
+ ex_next timestamp without time zone
+);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/titles.sql b/patch-schema/titles.sql
new file mode 100644
index 000000000..e4a87dc86
--- /dev/null
+++ b/patch-schema/titles.sql
@@ -0,0 +1,11 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS public.titles
+(
+ id int NOT NULL,
+ char_id int NOT NULL,
+ unlocked_at timestamp without time zone,
+ updated_at timestamp without time zone
+);
+
+END;
\ No newline at end of file
diff --git a/patch-schema/warehouse.sql b/patch-schema/warehouse.sql
new file mode 100644
index 000000000..2f2a5adde
--- /dev/null
+++ b/patch-schema/warehouse.sql
@@ -0,0 +1,49 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS public.warehouse (
+ character_id integer PRIMARY KEY,
+ item0 bytea,
+ item1 bytea,
+ item2 bytea,
+ item3 bytea,
+ item4 bytea,
+ item5 bytea,
+ item6 bytea,
+ item7 bytea,
+ item8 bytea,
+ item9 bytea,
+ item10 bytea,
+ item0name text,
+ item1name text,
+ item2name text,
+ item3name text,
+ item4name text,
+ item5name text,
+ item6name text,
+ item7name text,
+ item8name text,
+ item9name text,
+ equip0 bytea,
+ equip1 bytea,
+ equip2 bytea,
+ equip3 bytea,
+ equip4 bytea,
+ equip5 bytea,
+ equip6 bytea,
+ equip7 bytea,
+ equip8 bytea,
+ equip9 bytea,
+ equip10 bytea,
+ equip0name text,
+ equip1name text,
+ equip2name text,
+ equip3name text,
+ equip4name text,
+ equip5name text,
+ equip6name text,
+ equip7name text,
+ equip8name text,
+ equip9name text
+);
+
+END;
\ No newline at end of file
diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go
index 1cfe9e3ff..6c49ba0af 100644
--- a/server/channelserver/handlers.go
+++ b/server/channelserver/handlers.go
@@ -1,25 +1,21 @@
package channelserver
import (
- "bytes"
"encoding/binary"
"encoding/hex"
"erupe-ce/common/stringsupport"
"fmt"
"io"
+ "math"
"net"
"strings"
-
- "io/ioutil"
- "math/bits"
- "math/rand"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
- "golang.org/x/text/encoding/japanese"
- "golang.org/x/text/transform"
+ "math/bits"
+ "math/rand"
)
// Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet
@@ -78,40 +74,32 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) {
}
func updateRights(s *Session) {
+ s.rights = uint32(0x0E)
+ s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&s.rights)
+
+ rights := make([]mhfpacket.ClientRight, 0)
+ tempRights := s.rights
+ for i := 30; i > 0; i-- {
+ right := uint32(math.Pow(2, float64(i)))
+ if tempRights-right < 0x80000000 {
+ if i == 1 {
+ continue
+ }
+ rights = append(rights, mhfpacket.ClientRight{ID: uint16(i), Timestamp: 0x70DB59F0})
+ tempRights -= right
+ }
+ }
+ rights = append(rights, mhfpacket.ClientRight{ID: 1, Timestamp: 0})
+
update := &mhfpacket.MsgSysUpdateRight{
ClientRespAckHandle: 0,
- Unk1: s.rights,
- Rights: []mhfpacket.ClientRight{
- {
- ID: 1,
- Timestamp: 0,
- },
- {
- ID: 2,
- Timestamp: 0x5FEA1781,
- },
- {
- ID: 3,
- Timestamp: 0x5FEA1781,
- },
- },
- UnkSize: 0,
+ Bitfield: s.rights,
+ Rights: rights,
+ UnkSize: 0,
}
s.QueueSendMHF(update)
}
-func fixedSizeShiftJIS(text string, size int) []byte {
- r := bytes.NewBuffer([]byte(text))
- encoded, err := ioutil.ReadAll(transform.NewReader(r, japanese.ShiftJIS.NewEncoder()))
- if err != nil {
- panic(err)
- }
-
- out := make([]byte, size)
- copy(out, encoded)
- out[len(out)-1] = 0
- return out
-}
func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysExtendThreshold(s *Session, p mhfpacket.MHFPacket) {
@@ -149,14 +137,11 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
}
}
- rights := uint32(0x0E)
- s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", pkt.CharID0).Scan(&rights)
-
s.Lock()
s.charID = pkt.CharID0
- s.rights = rights
s.token = pkt.LoginTokenString
s.Unlock()
+ updateRights(s)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // Unix timestamp
@@ -192,8 +177,12 @@ func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {
}
func logoutPlayer(s *Session) {
- delete(s.server.sessions, s.rawConn)
- s.rawConn.Close()
+ if _, exists := s.server.sessions[s.rawConn]; exists {
+ delete(s.server.sessions, s.rawConn)
+ s.rawConn.Close()
+ } else {
+ return // Prevent re-running logout logic on real logouts
+ }
_, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL, char_id=NULL WHERE token=$1", s.token)
if err != nil {
@@ -206,13 +195,13 @@ func logoutPlayer(s *Session) {
}
var timePlayed int
+ var sessionTime int
_ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed)
-
- timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed
+ sessionTime = int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)
+ timePlayed += sessionTime
var rpGained int
-
- if s.rights > 0x40000000 { // N Course
+ if s.rights >= 0x40000000 { // N Course
rpGained = timePlayed / 900
timePlayed = timePlayed % 900
} else {
@@ -220,10 +209,10 @@ func logoutPlayer(s *Session) {
timePlayed = timePlayed % 1800
}
- _, err = s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
- if err != nil {
- panic(err)
- }
+ s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
+ s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
+
+ treasureHuntUnregister(s)
if s.stage == nil {
return
@@ -243,7 +232,6 @@ func logoutPlayer(s *Session) {
removeSessionFromSemaphore(s)
removeSessionFromStage(s)
- treasureHuntUnregister(s)
saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil {
@@ -421,6 +409,9 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
}
for session := range stage.clients {
count++
+ hrp := uint16(1)
+ gr := uint16(0)
+ s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr)
sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
sessionName := stringsupport.UTF8ToSJIS(session.Name)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
@@ -433,8 +424,8 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
resp.WriteBytes(make([]byte, 48))
resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteNullTerminatedBytes(sessionName)
- resp.WriteUint16(999) // HR
- resp.WriteUint16(999) // GR
+ resp.WriteUint16(hrp)
+ resp.WriteUint16(gr)
resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk
}
}
@@ -443,12 +434,23 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
}
case 4: // Find Party
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
- bf.ReadUint8()
+ setting := bf.ReadUint8()
maxResults := bf.ReadUint16()
- bf.ReadUint8()
- bf.ReadUint8()
+ bf.Seek(2, 1)
partyType := bf.ReadUint16()
- _ = bf.DataFromCurrent() // Restrictions
+ rankRestriction := uint16(0)
+ if setting >= 2 {
+ bf.Seek(2, 1)
+ rankRestriction = bf.ReadUint16()
+ }
+ targets := make([]uint16, 4)
+ if setting >= 3 {
+ bf.Seek(1, 1)
+ lenTargets := int(bf.ReadUint8())
+ for i := 0; i < lenTargets; i++ {
+ targets[i] = bf.ReadUint16()
+ }
+ }
var stagePrefix string
switch partyType {
case 0: // Public Bar
@@ -468,31 +470,41 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
break
}
if strings.HasPrefix(stage.id, stagePrefix) {
+ sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}])
+ sb3.Seek(4, 0)
+ stageRankRestriction := sb3.ReadUint16()
+ stageTarget := sb3.ReadUint16()
+ if rankRestriction != 0xFFFF && stageRankRestriction < rankRestriction {
+ continue
+ }
count++
sessionStage := stringsupport.UTF8ToSJIS(stage.id)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port)
-
- // TODO: This is half right, could be trimmed
- resp.WriteUint16(0)
- resp.WriteUint16(uint16(len(stage.clients)))
+ resp.WriteUint16(0) // Static?
+ resp.WriteUint16(0) // Unk
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(stage.maxPlayers)
- resp.WriteUint16(0)
- resp.WriteUint16(uint16(len(stage.clients)))
- //
-
- resp.WriteUint16(uint16(len(sessionStage) + 1))
- resp.WriteUint8(1)
+ resp.WriteUint16(0) // Num clients entered from stage
+ resp.WriteUint16(stage.maxPlayers)
+ resp.WriteUint8(1) // Static?
+ resp.WriteUint8(uint8(len(sessionStage) + 1))
+ resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}])))
resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}])))
- resp.WriteBytes(make([]byte, 16))
+ resp.WriteUint16(stageRankRestriction)
+ resp.WriteUint16(stageTarget)
+ resp.WriteBytes(make([]byte, 12))
resp.WriteNullTerminatedBytes(sessionStage)
- resp.WriteBytes([]byte{0x00})
+ resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}])
resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}])
}
}
}
}
+ if (pkt.SearchType == 1 || pkt.SearchType == 3) && count == 0 {
+ doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
resp.Seek(0, io.SeekStart)
resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
@@ -500,8 +512,6 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
func handleMsgCaExchangeItem(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
-
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {}
@@ -556,8 +566,6 @@ func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfGetExtraInfo(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {}
-
func handleMsgMhfEnumerateUnionItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateUnionItem)
var boxContents []byte
@@ -645,86 +653,57 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
-func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
- var netcafe_points int
- err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafe_points)
- if err != nil {
- s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
- }
- resp := byteframe.NewByteFrame()
- resp.WriteUint32(uint32(netcafe_points))
- doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
-}
-
-func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
- var netcafe_points int
- err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafe_points)
- if err != nil {
- s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
- }
- resp := byteframe.NewByteFrame()
- resp.WriteUint32(0)
- resp.WriteUint32(uint32(netcafe_points))
- doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
-}
-
-func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
-
- // I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes
- // 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client
- // available once after midday every day
-
- // get next midday
- var t = Time_static()
- year, month, day := t.Date()
- midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
- if t.After(midday) {
- midday = midday.Add(24 * time.Hour)
- }
-
- // get time after which daily claiming would be valid from db
- var dailyTime time.Time
- err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
- if err != nil {
- s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err))
- }
-
- if t.After(dailyTime) {
- // +5 netcafe points and setting next valid window
- _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points::int + 5 WHERE id=$2", midday, s.charID)
- if err != nil {
- s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err))
- }
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01})
- } else {
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
- }
-}
-
func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {}
-
func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
-
- resp := byteframe.NewByteFrame()
- resp.WriteUint16(0x000E)
- resp.WriteUint16(0x0001)
- resp.WriteUint16(0x0000)
- resp.WriteUint16(0x0000) // 0x0000 stops the vaguely annoying log in pop up
- resp.WriteUint32(0)
- resp.WriteUint32(0x5dddcbb3) // Timestamp
-
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
+ weekCurrentStart := TimeWeekStart()
+ weekNextStart := TimeWeekNext()
+ var total, redeemed, updated uint16
+ var nextClaim time.Time
+ err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim)
+ if err != nil {
+ s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart)
+ nextClaim = weekNextStart
+ }
+ if nextClaim.Before(weekCurrentStart) {
+ s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1, %s_next=$1 WHERE character_id=$2", pkt.StampType, pkt.StampType, pkt.StampType), weekNextStart, s.charID)
+ updated = 1
+ }
+ s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint16(total)
+ bf.WriteUint16(redeemed)
+ bf.WriteUint16(updated)
+ bf.WriteUint32(0) // Unk
+ bf.WriteUint32(uint32(weekCurrentStart.Unix()))
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
-func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp)
+ var total, redeemed uint16
+ var tktStack mhfpacket.WarehouseStack
+ if pkt.Unk1 == 0xA { // Yearly Sub Ex
+ s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed)
+ tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1}
+ } else {
+ s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed)
+ if pkt.StampType == "hl" {
+ tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5}
+ } else {
+ tktStack = mhfpacket.WarehouseStack{ItemID: 0x065F, Quantity: 5}
+ }
+ }
+ addWarehouseGift(s, "item", tktStack)
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint16(total)
+ bf.WriteUint16(redeemed)
+ bf.WriteUint16(0)
+ bf.WriteUint32(0) // Unk, but has possible values
+ bf.WriteUint32(uint32(TimeWeekStart().Unix()))
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
func getGookData(s *Session, cid uint32) (uint16, []byte) {
var data []byte
diff --git a/server/channelserver/handlers_achievement.go b/server/channelserver/handlers_achievement.go
index 7887ab71d..5db3853e1 100644
--- a/server/channelserver/handlers_achievement.go
+++ b/server/channelserver/handlers_achievement.go
@@ -3,86 +3,139 @@ package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
+ "fmt"
+ "io"
)
+var achievementCurves = [][]int32{
+ // 0: HR weapon use, Class use, Tore dailies
+ {5, 15, 30, 50, 100, 150, 200, 300},
+ // 1: Weapon collector, G wep enhances
+ {1, 5, 10, 15, 30, 50, 75, 100},
+ // 2: Festa wins
+ {1, 2, 3, 4, 5, 6, 7, 8},
+ // 3: GR weapon use, Sigil crafts
+ {10, 50, 100, 200, 350, 500, 750, 999},
+}
+
+var achievementCurveMap = map[uint8][]int32{
+ 0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0],
+ 4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1],
+ 8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0],
+ 12: achievementCurves[0], 13: achievementCurves[0], 14: achievementCurves[0], 15: achievementCurves[0],
+ 16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3],
+ 20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3],
+ 24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1],
+ 28: achievementCurves[1], 29: achievementCurves[3], 30: achievementCurves[3], 31: achievementCurves[3],
+ 32: achievementCurves[3],
+}
+
+type Achievement struct {
+ Level uint8
+ Value uint32
+ NextValue uint16
+ Required uint32
+ Updated bool
+ Progress uint32
+ Trophy uint8
+}
+
+func GetAchData(id uint8, score int32) Achievement {
+ curve := achievementCurveMap[id]
+ var ach Achievement
+ for i, v := range curve {
+ temp := score - v
+ if temp < 0 {
+ ach.Progress = uint32(score)
+ ach.Required = uint32(curve[i])
+ switch ach.Level {
+ case 0:
+ ach.NextValue = 5
+ case 1, 2, 3:
+ ach.NextValue = 10
+ case 4, 5:
+ ach.NextValue = 15
+ case 6:
+ ach.NextValue = 15
+ ach.Trophy = 0x40
+ case 7:
+ ach.NextValue = 20
+ ach.Trophy = 0x60
+ }
+ return ach
+ } else {
+ score = temp
+ ach.Level++
+ switch ach.Level {
+ case 1:
+ ach.Value += 5
+ case 2, 3, 4:
+ ach.Value += 10
+ case 5, 6, 7:
+ ach.Value += 15
+ case 8:
+ ach.Value += 20
+ }
+ }
+ }
+ ach.Required = uint32(curve[7])
+ ach.Trophy = 0x7F
+ ach.Progress = ach.Required
+ return ach
+}
+
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
- achievementStruct := []struct {
- ID uint8 // Main ID
- Unk0 uint8 // always FF
- Unk1 uint16 // 0x05 0x00
- Unk2 uint32 // 0x01 0x0A 0x05 0x00
- }{
- {ID: 0, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 1, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 2, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 3, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 4, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 5, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 6, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 7, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 8, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 9, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 10, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 11, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 12, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 13, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 14, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 15, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 16, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 17, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 18, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 19, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 20, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 21, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 22, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 23, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 24, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 25, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 26, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 27, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 28, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 29, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 30, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 31, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 32, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 33, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 34, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 35, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 36, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 37, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 38, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 39, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 40, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 41, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 42, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 43, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 44, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 45, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 46, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 47, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 48, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 49, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 50, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 51, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 52, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 53, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 54, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 55, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 56, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 57, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 58, Unk0: 0xFF, Unk1: 0, Unk2: 0},
- {ID: 59, Unk0: 0xFF, Unk1: 0, Unk2: 0},
+ var exists int
+ err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists)
+ if err != nil {
+ s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID)
}
+
+ var scores [33]int32
+ err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0],
+ &scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
+ &scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
+ &scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
+ &scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
+ if err != nil {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
+ return
+ }
+
resp := byteframe.NewByteFrame()
- resp.WriteUint8(uint8(len(achievementStruct))) // Entry count
- for _, entry := range achievementStruct {
- resp.WriteUint8(entry.ID)
- resp.WriteUint8(entry.Unk0)
- resp.WriteUint16(entry.Unk1)
- resp.WriteUint32(entry.Unk2)
+ var points uint32
+ resp.WriteBytes(make([]byte, 16))
+ resp.WriteBytes([]byte{0x02, 0x00, 0x00}) // Unk
+
+ var id uint8
+ entries := uint8(33)
+ resp.WriteUint8(entries) // Entry count
+ for id = 0; id < entries; id++ {
+ achData := GetAchData(id, scores[id])
+ points += achData.Value
+ resp.WriteUint8(id)
+ resp.WriteUint8(achData.Level)
+ resp.WriteUint16(achData.NextValue)
+ resp.WriteUint32(achData.Required)
+ resp.WriteBool(false) // TODO: Notify on rank increase since last checked, see MhfDisplayedAchievement
+ resp.WriteUint8(achData.Trophy)
+ /* Trophy bitfield
+ 0000 0000
+ abcd efgh
+ B - Bronze (0x40)
+ B-C - Silver (0x60)
+ B-H - Gold (0x7F)
+ */
+ resp.WriteUint16(0) // Unk
+ resp.WriteUint32(achData.Progress)
}
+ resp.Seek(0, io.SeekStart)
+ resp.WriteUint32(points)
+ resp.WriteUint32(points)
+ resp.WriteUint32(points)
+ resp.WriteUint32(points)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
@@ -93,11 +146,23 @@ func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfAddAchievement)
+
+ var exists int
+ err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists)
+ if err != nil {
+ s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID)
+ }
+
+ s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID)
+}
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {
+ // This is how you would figure out if the rank-up notification needs to occur
+}
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go
new file mode 100644
index 000000000..c7d826429
--- /dev/null
+++ b/server/channelserver/handlers_cafe.go
@@ -0,0 +1,239 @@
+package channelserver
+
+import (
+ "erupe-ce/common/byteframe"
+ ps "erupe-ce/common/pascalstring"
+ "erupe-ce/network/mhfpacket"
+ "go.uber.org/zap"
+ "io"
+ "time"
+)
+
+func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
+ var netcafePoints uint32
+ err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints)
+ if err != nil {
+ s.logger.Fatal("Failed to get netcafe points from db", zap.Error(err))
+ }
+ resp := byteframe.NewByteFrame()
+ resp.WriteUint32(netcafePoints)
+ doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
+}
+
+func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
+ var netcafePoints uint32
+ err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints)
+ if err != nil {
+ s.logger.Fatal("Failed to get netcate points from db", zap.Error(err))
+ }
+ resp := byteframe.NewByteFrame()
+ resp.WriteUint32(netcafePoints)
+ doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
+}
+
+func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
+
+ // I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes
+ // 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client
+ // available once after midday every day
+
+ // get next midday
+ var t = Time_static()
+ year, month, day := t.Date()
+ midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
+ if t.After(midday) {
+ midday = midday.Add(24 * time.Hour)
+ }
+
+ // get time after which daily claiming would be valid from db
+ var dailyTime time.Time
+ err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
+ if err != nil {
+ s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err))
+ }
+
+ if t.After(dailyTime) {
+ // +5 netcafe points and setting next valid window
+ _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points+5 WHERE id=$2", midday, s.charID)
+ if err != nil {
+ s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err))
+ }
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01})
+ } else {
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
+ }
+}
+
+func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
+ bf := byteframe.NewByteFrame()
+
+ var cafeTime uint32
+ err := s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
+ if err != nil {
+ panic(err)
+ }
+ cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime
+ bf.WriteUint32(cafeTime) // Total cafe time
+ bf.WriteUint16(0)
+ ps.Uint16(bf, "Resets at next maintenance", true)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+type CafeBonus struct {
+ ID uint32 `db:"id"`
+ TimeReq uint32 `db:"time_req"`
+ ItemType uint32 `db:"item_type"`
+ ItemID uint32 `db:"item_id"`
+ Quantity uint32 `db:"quantity"`
+ Claimed bool `db:"claimed"`
+}
+
+func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo)
+ bf := byteframe.NewByteFrame()
+
+ var count uint32
+ rows, err := s.server.db.Queryx(`
+ SELECT cb.id, time_req, item_type, item_id, quantity,
+ (
+ SELECT count(*)
+ FROM cafe_accepted ca
+ WHERE cb.id = ca.cafe_id AND ca.character_id = $1
+ )::int::bool AS claimed
+ FROM cafebonus cb ORDER BY id ASC;`, s.charID)
+ if err != nil {
+ s.logger.Error("Error getting cafebonus", zap.Error(err))
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ } else {
+ for rows.Next() {
+ count++
+ cafeBonus := &CafeBonus{}
+ err = rows.StructScan(&cafeBonus)
+ if err != nil {
+ s.logger.Error("Error scanning cafebonus", zap.Error(err))
+ }
+ bf.WriteUint32(cafeBonus.TimeReq)
+ bf.WriteUint32(cafeBonus.ItemType)
+ bf.WriteUint32(cafeBonus.ItemID)
+ bf.WriteUint32(cafeBonus.Quantity)
+ bf.WriteBool(cafeBonus.Claimed)
+ }
+ resp := byteframe.NewByteFrame()
+ resp.WriteUint32(0)
+ resp.WriteUint32(uint32(time.Now().Unix()))
+ resp.WriteUint32(count)
+ resp.WriteBytes(bf.Data())
+ doAckBufSucceed(s, pkt.AckHandle, resp.Data())
+ }
+}
+
+func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus)
+ bf := byteframe.NewByteFrame()
+ var count uint32
+ bf.WriteUint32(0)
+ rows, err := s.server.db.Queryx(`
+ SELECT c.id, time_req, item_type, item_id, quantity
+ FROM cafebonus c
+ WHERE (
+ SELECT count(*)
+ FROM cafe_accepted ca
+ WHERE c.id = ca.cafe_id AND ca.character_id = $1
+ ) < 1 AND (
+ SELECT ch.cafe_time + $2
+ FROM characters ch
+ WHERE ch.id = $1
+ ) >= time_req`, s.charID, Time_Current_Adjusted().Unix()-s.sessionStart)
+ if err != nil {
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ } else {
+ for rows.Next() {
+ cafeBonus := &CafeBonus{}
+ err = rows.StructScan(cafeBonus)
+ if err != nil {
+ continue
+ }
+ count++
+ bf.WriteUint32(cafeBonus.ID)
+ bf.WriteUint32(cafeBonus.ItemType)
+ bf.WriteUint32(cafeBonus.ItemID)
+ bf.WriteUint32(cafeBonus.Quantity)
+ }
+ bf.Seek(0, io.SeekStart)
+ bf.WriteUint32(count)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ }
+}
+
+func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
+ var cafeBonus CafeBonus
+ for _, cbID := range pkt.CafeBonusID {
+ err := s.server.db.QueryRow(`
+ SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1
+ `, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity)
+ if err == nil {
+ if cafeBonus.ItemType == 17 {
+ s.server.db.Exec("UPDATE characters SET netcafe_points=netcafe_points+$1 WHERE id=$2", cafeBonus.Quantity, s.charID)
+ }
+ }
+ s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID)
+ }
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
+}
+
+func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
+ bf := byteframe.NewByteFrame()
+ boostLimit := Time_Current_Adjusted().Add(100 * time.Minute)
+ s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID)
+ bf.WriteUint32(uint32(boostLimit.Unix()))
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
+ doAckBufSucceed(s, pkt.AckHandle, []byte{})
+}
+
+func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
+ bf := byteframe.NewByteFrame()
+ var boostLimit time.Time
+ err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
+ if err != nil {
+ bf.WriteUint32(0)
+ } else {
+ bf.WriteUint32(uint32(boostLimit.Unix()))
+ }
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
+}
+
+func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
+ var boostLimit time.Time
+ err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
+ if err != nil {
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
+ return
+ }
+ if boostLimit.Unix() > Time_Current_Adjusted().Unix() {
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
+ } else {
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
+ }
+}
+
+func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
+ doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
+}
+
+func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
+
+func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go
index 304369295..35538e0fb 100644
--- a/server/channelserver/handlers_cast_binary.go
+++ b/server/channelserver/handlers_cast_binary.go
@@ -16,6 +16,7 @@ import (
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
+ BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
@@ -164,6 +165,66 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
fmt.Printf("Got chat message: %+v\n", chatMessage)
+ // Flush all objects and users and reload
+ if strings.HasPrefix(chatMessage.Message, "!reload") {
+ sendServerChatMessage(s, "Reloading players...")
+ var temp mhfpacket.MHFPacket
+ deleteNotif := byteframe.NewByteFrame()
+ for _, object := range s.stage.objects {
+ if object.ownerCharID == s.charID {
+ continue
+ }
+ temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
+ deleteNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(deleteNotif, s.clientContext)
+ }
+ for _, session := range s.server.sessions {
+ if s == session {
+ continue
+ }
+ temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
+ deleteNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(deleteNotif, s.clientContext)
+ }
+ deleteNotif.WriteUint16(0x0010)
+ s.QueueSend(deleteNotif.Data())
+ time.Sleep(500 * time.Millisecond)
+ reloadNotif := byteframe.NewByteFrame()
+ for _, session := range s.server.sessions {
+ if s == session {
+ continue
+ }
+ temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
+ reloadNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(reloadNotif, s.clientContext)
+ for i := 0; i < 3; i++ {
+ temp = &mhfpacket.MsgSysNotifyUserBinary{
+ CharID: session.charID,
+ BinaryType: uint8(i + 1),
+ }
+ reloadNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(reloadNotif, s.clientContext)
+ }
+ }
+ for _, obj := range s.stage.objects {
+ if obj.ownerCharID == s.charID {
+ continue
+ }
+ temp = &mhfpacket.MsgSysDuplicateObject{
+ ObjID: obj.id,
+ X: obj.x,
+ Y: obj.y,
+ Z: obj.z,
+ Unk0: 0,
+ OwnerCharID: obj.ownerCharID,
+ }
+ reloadNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(reloadNotif, s.clientContext)
+ }
+ reloadNotif.WriteUint16(0x0010)
+ s.QueueSend(reloadNotif.Data())
+ }
+
// Set account rights
if strings.HasPrefix(chatMessage.Message, "!rights") {
var v uint32
@@ -179,7 +240,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
}
// Discord integration
- if chatMessage.Type == binpacket.ChatTypeLocal || chatMessage.Type == binpacket.ChatTypeParty {
+ if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
}
diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go
index 1f18fe262..8ed55c54a 100644
--- a/server/channelserver/handlers_data.go
+++ b/server/channelserver/handlers_data.go
@@ -56,7 +56,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
s.logger.Fatal("Failed to update savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed savedata back to DB.")
- dumpSaveData(s, pkt.RawDataPayload, "")
+ dumpSaveData(s, pkt.RawDataPayload, "savedata")
_, err = s.server.db.Exec("UPDATE characters SET weapon_type=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID)
if err != nil {
@@ -71,8 +71,8 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
s.myseries.toreData = decompressedData[130228:130468] // 0x1FCB4 + 240
s.myseries.gardenData = decompressedData[142424:142492] // 0x22C58 + 68
- isMale := uint8(decompressedData[80]) // 0x50
- if isMale == 1 {
+ isFemale := decompressedData[81] // 0x51
+ if isFemale == 1 {
_, err = s.server.db.Exec("UPDATE characters SET is_female=true WHERE id=$1", s.charID)
} else {
_, err = s.server.db.Exec("UPDATE characters SET is_female=false WHERE id=$1", s.charID)
@@ -275,8 +275,8 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
return
} else {
- dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name))
- path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name), fmt.Sprintf("%d_%s_%s%s.bin", s.charID, s.Name, Time_Current().Format("2006-01-02_15.04.05"), suffix))
+ dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name))
+ path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name), fmt.Sprintf("%d_%s_%s.bin", s.charID, s.Name, suffix))
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, os.ModeDir)
@@ -297,10 +297,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
}
var data []byte
-
err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data)
- if err != nil {
- s.logger.Fatal("Failed to get savedata from db", zap.Error(err))
+ if err != nil || len(data) == 0 {
+ s.logger.Warn(fmt.Sprintf("Failed to load savedata (CID: %d)", s.charID), zap.Error(err))
+ s.rawConn.Close() // Terminate the connection
+ return
}
doAckBufSucceed(s, pkt.AckHandle, data)
@@ -310,11 +311,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
}
bf := byteframe.NewByteFrameFromBytes(decompSaveData)
bf.Seek(88, io.SeekStart)
- binary1 := bf.ReadNullTerminatedBytes()
+ name := bf.ReadNullTerminatedBytes()
s.server.userBinaryPartsLock.Lock()
- s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(binary1, []byte{0x00}...)
+ s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(name, []byte{0x00}...)
s.server.userBinaryPartsLock.Unlock()
- s.Name = stringsupport.SJISToUTF8(binary1)
+ s.Name = stringsupport.SJISToUTF8(name)
}
func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) {
diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go
index 38619e079..1f3398d18 100644
--- a/server/channelserver/handlers_discord.go
+++ b/server/channelserver/handlers_discord.go
@@ -2,87 +2,45 @@ package channelserver
import (
"fmt"
+ "github.com/bwmarrin/discordgo"
"sort"
"strings"
-
- "github.com/bwmarrin/discordgo"
+ "unicode"
)
-type Character struct {
- ID uint32 `db:"id"`
- IsFemale bool `db:"is_female"`
- IsNewCharacter bool `db:"is_new_character"`
- Name string `db:"name"`
- UnkDescString string `db:"unk_desc_string"`
- HRP uint16 `db:"hrp"`
- GR uint16 `db:"gr"`
- WeaponType uint16 `db:"weapon_type"`
- LastLogin uint32 `db:"last_login"`
+type Player struct {
+ CharName string
+ QuestID int
}
-var weapons = []string{
- "<:gs:970861408227049477>",
- "<:hbg:970861408281563206>",
- "<:hm:970861408239628308>",
- "<:lc:970861408298352660>",
- "<:sns:970861408319315988>",
- "<:lbg:970861408327725137>",
- "<:ds:970861408277368883>",
- "<:ls:970861408319311872>",
- "<:hh:970861408222863400>",
- "<:gl:970861408327720980>",
- "<:bw:970861408294174780>",
- "<:tf:970861408424177744>",
- "<:sw:970861408340283472>",
- "<:ms:970861408411594842>",
-}
+func getPlayerSlice(s *Server) []Player {
+ var p []Player
+ var questIndex int
-func (s *Server) getCharacterForUser(uid int) (*Character, error) {
- character := Character{}
- err := s.db.Get(&character, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE id = $1", uid)
- if err != nil {
- return nil, err
+ for _, channel := range s.Channels {
+ for _, stage := range channel.stages {
+ if len(stage.clients) == 0 {
+ continue
+ }
+ questID := 0
+ if stage.isQuest() {
+ questIndex++
+ questID = questIndex
+ }
+ for client := range stage.clients {
+ p = append(p, Player{
+ CharName: client.Name,
+ QuestID: questID,
+ })
+ }
+ }
}
-
- return &character, nil
+ return p
}
-func CountChars(s *Server) string {
- count := 0
- for _, stage := range s.stages {
- count += len(stage.clients)
- }
-
- message := fmt.Sprintf("Server [%s]: %d players;", s.name, count)
-
- return message
-}
-
-type ListPlayer struct {
- CharName string
- InQuest bool
- WeaponEmoji string
- QuestEmoji string
- StageName string
-}
-
-func (p *ListPlayer) toString(length int) string {
- status := ""
- if p.InQuest {
- status = "(in quest " + p.QuestEmoji + ")"
- } else {
- status = p.StageName
- }
-
- missingSpace := length - len(p.CharName)
- whitespaces := strings.Repeat(" ", missingSpace+5)
-
- return fmt.Sprintf("%s %s %s %s", p.WeaponEmoji, p.CharName, whitespaces, status)
-}
-
-func getPlayerList(s *Server) ([]ListPlayer, int) {
- list := []ListPlayer{}
+func getCharacterList(s *Server) string {
questEmojis := []string{
+ ":person_in_lotus_position:",
":white_circle:",
":red_circle:",
":blue_circle:",
@@ -91,255 +49,41 @@ func getPlayerList(s *Server) ([]ListPlayer, int) {
":purple_circle:",
":yellow_circle:",
":orange_circle:",
+ ":black_circle:",
}
- bigNameLen := 0
+ playerSlice := getPlayerSlice(s)
- for _, stage := range s.stages {
- if len(stage.clients) == 0 {
- continue
- }
-
- questEmoji := ":black_circle:"
-
- if len(questEmojis) > 0 {
- questEmoji = questEmojis[len(questEmojis)-1]
- questEmojis = questEmojis[:len(questEmojis)-1]
- }
-
- isQuest := stage.isQuest()
- for client := range stage.clients {
- char, err := s.getCharacterForUser(int(client.charID))
- if err == nil {
- if len(char.Name) > bigNameLen {
- bigNameLen = len(char.Name)
- }
-
- list = append(list, ListPlayer{
- CharName: char.Name,
- InQuest: isQuest,
- QuestEmoji: questEmoji,
- WeaponEmoji: weapons[char.WeaponType],
- StageName: stage.GetName(),
- })
-
- }
- }
- }
-
- return list, bigNameLen
-}
-
-func PlayerList(s *Server) string {
- list := ""
- count := 0
- listPlayers, bigNameLen := getPlayerList(s)
-
- sort.SliceStable(listPlayers, func(i, j int) bool {
- return listPlayers[i].CharName < listPlayers[j].CharName
+ sort.SliceStable(playerSlice, func(i, j int) bool {
+ return playerSlice[i].QuestID < playerSlice[j].QuestID
})
- for _, lp := range listPlayers {
- list += lp.toString(bigNameLen) + "\n"
- count++
+ message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice))
+ for _, player := range playerSlice {
+ message += fmt.Sprintf("%s %s", questEmojis[player.QuestID], player.CharName)
}
- message := fmt.Sprintf("<:SnS:822963937360347148> Frontier Hunters Online: [%s ] <:switcha:822963906401533992> \n============== Total %d =============\n", s.name, count)
- message += list
-
return message
}
-func debug(s *Server) string {
- list := ""
-
- for _, stage := range s.stages {
- if !stage.isQuest() && len(stage.objects) == 0 {
- continue
- }
-
- list += fmt.Sprintf(" -> Stage: %s StageId: %s\n", stage.GetName(), stage.id)
- isQuest := "false"
- hasDeparted := "false"
-
- if stage.isQuest() {
- isQuest = "true"
- }
-
- list += fmt.Sprintf(" '-> isQuest: %s\n", isQuest)
-
- if stage.isQuest() {
- if len(stage.clients) > 0 {
- hasDeparted = "true"
- }
-
- list += fmt.Sprintf(" '-> isDeparted: %s\n", hasDeparted)
- list += fmt.Sprintf(" '-> reserveSlots (%d/%d)\n", len(stage.reservedClientSlots), stage.maxPlayers)
-
- for charid, _ := range stage.reservedClientSlots {
- char, err := s.getCharacterForUser(int(charid))
- if err == nil {
- list += fmt.Sprintf(" '-> %s\n", char.Name)
- }
- }
- }
-
- list += " '-> objects: \n"
- for _, obj := range stage.objects {
- objInfo := fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
- list += fmt.Sprintf(" '-> ObjectId: %d - %s\n", obj.id, objInfo)
- }
- }
-
- message := fmt.Sprintf("Objects in Server: [%s ]\n", s.name)
- message += list
-
- return message
-}
-
-func questlist(s *Server) string {
- list := ""
-
- for _, stage := range s.stages {
- if !stage.isQuest() {
- continue
- }
-
- hasDeparted := ""
- if len(stage.clients) > 0 {
- hasDeparted = " - departed"
- }
- list += fmt.Sprintf(" '-> StageId: %s (%d/%d) %s - %s\n", stage.id, len(stage.reservedClientSlots), stage.maxPlayers, hasDeparted, stage.createdAt)
-
- for charid, _ := range stage.reservedClientSlots {
- char, err := s.getCharacterForUser(int(charid))
- if err == nil {
- list += fmt.Sprintf(" '-> %s\n", char.Name)
- }
- }
- }
-
- message := fmt.Sprintf("Quests in Server: [%s ]\n", s.name)
- message += list
-
- return message
-}
-
-func removeStageById(s *Server, stageId string) string {
- if s.stages[stageId] != nil {
- delete(s.stages, stageId)
- return "Stage deleted!"
- }
-
- return "Stage not found!"
-}
-
-func cleanStr(str string) string {
- return strings.ToLower(strings.Trim(str, " "))
-}
-
-func getCharInfo(server *Server, charName string) string {
- var s *Stage
- var c *Session
-
- for _, stage := range server.stages {
- for client := range stage.clients {
-
- if client.Name == "" {
- continue
- }
-
- if cleanStr(client.Name) == cleanStr(charName) {
- s = stage
- c = client
- }
-
- }
- }
-
- if s == nil {
- return "Character not found"
- }
-
- objInfo := ""
-
- obj := server.FindObjectByChar(c.charID)
- // server.logger.Info("Found object: %+v", zap.Object("obj", obj))
-
- if obj != nil {
- objInfo = fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
- }
-
- return fmt.Sprintf("Character: %s\nStage: %s\nStageId: %s\n%s", c.Name, s.GetName(), s.id, objInfo)
-}
-
-func (s *Server) isDiscordAdmin(ds *discordgo.Session, m *discordgo.MessageCreate) bool {
- for _, role := range m.Member.Roles {
- for _, id := range s.erupeConfig.Discord.DevRoles {
- if id == role {
- return true
- }
- }
- }
-
- return false
-}
-
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel.
- if m.Author.ID == ds.State.User.ID || !s.enable {
+ if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
return
}
- // Ignore other channels in devMode
- if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
- return
- }
-
- args := strings.Split(m.Content, " ")
- commandName := args[0]
-
- // Move to slash commadns
- if commandName == "!players" {
- ds.ChannelMessageSend(m.ChannelID, PlayerList(s))
- return
- }
-
- if commandName == "-char" {
- if len(args) < 2 {
- ds.ChannelMessageSend(m.ChannelID, "Usage: !char ")
- return
+ paddedName := strings.TrimSpace(strings.Map(func(r rune) rune {
+ if r > unicode.MaxASCII {
+ return -1
}
+ return r
+ }, m.Author.Username))
- charName := strings.Join(args[1:], " ")
- ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName))
- return
+ for i := 0; i < 8-len(m.Author.Username); i++ {
+ paddedName += " "
}
- if commandName == "!debug" && s.isDiscordAdmin(ds, m) {
- ds.ChannelMessageSend(m.ChannelID, debug(s))
- return
- }
-
- if commandName == "!questlist" && s.isDiscordAdmin(ds, m) {
- ds.ChannelMessageSend(m.ChannelID, questlist(s))
- return
- }
-
- if commandName == "!remove-stage" && s.isDiscordAdmin(ds, m) {
- if len(args) < 2 {
- ds.ChannelMessageSend(m.ChannelID, "Usage: !remove-stage ")
- return
- }
-
- stageId := strings.Join(args[1:], " ")
- ds.ChannelMessageSend(m.ChannelID, removeStageById(s, stageId))
- return
- }
-
- if m.ChannelID == s.erupeConfig.Discord.RealtimeChannelID {
- message := fmt.Sprintf("[DISCORD] %s: %s", m.Author.Username, m.Content)
- s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
- }
+ message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
+ s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
}
diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go
index 8927cd792..48e6dcecf 100644
--- a/server/channelserver/handlers_diva.go
+++ b/server/channelserver/handlers_diva.go
@@ -2,11 +2,116 @@ package channelserver
import (
"encoding/hex"
+ "erupe-ce/common/stringsupport"
+ "time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
+func cleanupDiva(s *Session) {
+ s.server.db.Exec("DELETE FROM events WHERE event_type='diva'")
+}
+
+func generateDivaTimestamps(s *Session, start uint32, debug bool) []uint32 {
+ timestamps := make([]uint32, 6)
+ midnight := Time_Current_Midnight()
+ if debug && start <= 3 {
+ midnight := uint32(midnight.Unix())
+ switch start {
+ case 1:
+ timestamps[0] = midnight
+ timestamps[1] = timestamps[0] + 601200
+ timestamps[2] = timestamps[1] + 3900
+ timestamps[3] = timestamps[1] + 604800
+ timestamps[4] = timestamps[3] + 3900
+ timestamps[5] = timestamps[3] + 604800
+ case 2:
+ timestamps[0] = midnight - 605100
+ timestamps[1] = midnight - 3900
+ timestamps[2] = midnight
+ timestamps[3] = timestamps[1] + 604800
+ timestamps[4] = timestamps[3] + 3900
+ timestamps[5] = timestamps[3] + 604800
+ case 3:
+ timestamps[0] = midnight - 1213800
+ timestamps[1] = midnight - 608700
+ timestamps[2] = midnight - 604800
+ timestamps[3] = midnight - 3900
+ timestamps[4] = midnight
+ timestamps[5] = timestamps[3] + 604800
+ }
+ return timestamps
+ }
+ if start == 0 || Time_Current_Adjusted().Unix() > int64(start)+2977200 {
+ cleanupDiva(s)
+ // Generate a new diva defense, starting midnight tomorrow
+ start = uint32(midnight.Add(24 * time.Hour).Unix())
+ s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('diva', to_timestamp($1)::timestamp without time zone)", start)
+ }
+ timestamps[0] = start
+ timestamps[1] = timestamps[0] + 601200
+ timestamps[2] = timestamps[1] + 3900
+ timestamps[3] = timestamps[1] + 604800
+ timestamps[4] = timestamps[3] + 3900
+ timestamps[5] = timestamps[3] + 604800
+ return timestamps
+}
+
+func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
+ bf := byteframe.NewByteFrame()
+
+ id, start := uint32(0xCAFEBEEF), uint32(0)
+ rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
+ for rows.Next() {
+ rows.Scan(&id, &start)
+ }
+
+ var timestamps []uint32
+ if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
+ if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
+ return
+ }
+ timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
+ } else {
+ timestamps = generateDivaTimestamps(s, start, false)
+ }
+
+ bf.WriteUint32(id)
+ for _, timestamp := range timestamps {
+ bf.WriteUint32(timestamp)
+ }
+
+ bf.WriteUint16(0x19) // Unk 00011001
+ bf.WriteUint16(0x2D) // Unk 00101101
+ bf.WriteUint16(0x02) // Unk 00000010
+ bf.WriteUint16(0x02) // Unk 00000010
+
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
+ // Message that appears on the Diva Defense NPC and triggers the green exclamation mark
+ udInfos := []struct {
+ Text string
+ StartTime time.Time
+ EndTime time.Time
+ }{}
+
+ resp := byteframe.NewByteFrame()
+ resp.WriteUint8(uint8(len(udInfos)))
+ for _, udInfo := range udInfos {
+ resp.WriteBytes(stringsupport.PaddedString(udInfo.Text, 1024, true))
+ resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
+ resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
+ }
+
+ doAckBufSucceed(s, pkt.AckHandle, resp.Data())
+}
+
func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
// Temporary canned response
diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go
index af16799c7..b6765f998 100644
--- a/server/channelserver/handlers_event.go
+++ b/server/channelserver/handlers_event.go
@@ -7,7 +7,6 @@ import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
- timeServerFix "erupe-ce/server/channelserver/timeserver"
)
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
@@ -234,103 +233,6 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
-func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
- var t = timeServerFix.Tstatic_midnight()
- var event int = s.server.erupeConfig.DevModeOptions.DivaEvent
-
- year, month, day := t.Date()
- midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location())
- // Events with time limits are Festival with Sign up, Soul Week and Winners Weeks
- // Diva Defense with Prayer, Interception and Song weeks
- // Mezeporta Festival with simply 'available' being a weekend thing
- resp := byteframe.NewByteFrame()
- resp.WriteUint32(0x1d5fda5c) // Unk (1d5fda5c, 0b5397df)
-
- if event == 1 {
- resp.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
- } else {
- resp.WriteUint32(uint32(midnight.Add(-24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
- }
-
- if event == 2 {
- resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
- resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
- } else {
- resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
- resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
- }
-
- if event == 3 {
- resp.WriteUint32(uint32(midnight.Add((24) * 7 * time.Hour).Unix())) // Diva Defense Interception
- resp.WriteUint32(uint32(midnight.Add((24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
- } else {
- resp.WriteUint32(uint32(midnight.Add((-24) * 7 * time.Hour).Unix())) // Diva Defense Interception
- resp.WriteUint32(uint32(midnight.Add((-24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
- }
-
- resp.WriteUint16(0x19) // Unk 00011001
- resp.WriteUint16(0x2d) // Unk 00101101
- resp.WriteUint16(0x02) // Unk 00000010
- resp.WriteUint16(0x02) // Unk 00000010
-
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
-}
-
-func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
- // Message that appears on the Diva Defense NPC and triggers the green exclamation mark
- udInfos := []struct {
- Text string
- StartTime time.Time
- EndTime time.Time
- }{
- /*{
- Text: " ~C17【Erupe】 is dead event!\n\n■Features\n~C18 Dont bother walking around!\n~C17 Take down your DB by doing \n~C17 nearly anything!",
- StartTime: Time_static().Add(time.Duration(-5) * time.Minute), // Event started 5 minutes ago,
- EndTime: Time_static().Add(time.Duration(24) * time.Hour), // Event ends in 5 minutes,
- }, */
- }
-
- resp := byteframe.NewByteFrame()
- resp.WriteUint8(uint8(len(udInfos)))
- for _, udInfo := range udInfos {
- resp.WriteBytes(fixedSizeShiftJIS(udInfo.Text, 1024))
- resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
- resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
- }
-
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
-}
-
-func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
-
- doAckBufSucceed(s, pkt.AckHandle, []byte{})
- updateRights(s)
-}
-
-func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
-}
-
-func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
-}
-
-func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
- doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
-}
-
-func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
-
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {
diff --git a/server/channelserver/handlers_festa.go b/server/channelserver/handlers_festa.go
index c1f3cecfb..6e5080ac3 100644
--- a/server/channelserver/handlers_festa.go
+++ b/server/channelserver/handlers_festa.go
@@ -2,12 +2,11 @@ package channelserver
import (
"encoding/hex"
- "math/rand"
- "time"
-
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
+ "math/rand"
+ "time"
)
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
@@ -43,99 +42,200 @@ func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(12 * 24 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(21 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(13 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(20 * 24 * time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
- bf.WriteUint32(uint32(midnight.Add(9 * 24 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(16 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(10 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
case 3:
- bf.WriteUint32(uint32(midnight.Add(-12 * 24 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(-9 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
+ bf.WriteUint32(uint32(midnight.Add(-10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
default:
bf.WriteBytes(make([]byte, 16))
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
- bf.WriteUint16(1)
- bf.WriteUint32(0)
+ bf.WriteUint8(3)
+ bf.WriteBytes(make([]byte, 4))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
- d, _ := hex.DecodeString("031491E631353089F18CF68EAE8EEB97C291E589EF00001200000A54001000000000ED130D949A96B697B393A294B081490000000A55001000010000ED130D949A96B697B393A294B081490000000A56001000020000ED130D949A96B697B393A294B081490000000A57001000030000ED130D949A96B697B393A294B081490000000A58001000040000ED130D949A96B697B393A294B081490000000A59001000050000ED130D949A96B697B393A294B081490000000A5A001000060000ED130D949A96B697B393A294B081490000000A5B001000070000ED130D949A96B697B393A294B081490000000A5C001000080000ED130D949A96B697B393A294B081490000000A5D001000090000ED130D949A96B697B393A294B081490000000A5E0010000A0000ED130D949A96B697B393A294B081490000000A5F0010000B0000ED130D949A96B697B393A294B081490000000A600010000C0000ED130D949A96B697B393A294B081490000000A610010000D0000ED130D949A96B697B393A294B081490000000A620011FFFF0000ED121582DD82F182C882C5949A96B697B393A294B081490000000A63000600EA0000000009834C838C834183570000000A64000600ED000000000B836E838A837D834F838D0000000A65000600EF0000000011834A834E8354839383668381834C83930003000002390006000600000E8CC2906C208B9091E58B9B94740001617E43303581798BA38B5A93E09765817A0A7E433030834E83478358836782C592DE82C182BD8B9B82CC83548343835982F08BA382A40A7E433034817991CE8FDB8B9B817A0A7E433030834C838C8341835781410A836E838A837D834F838D8141834A834E8354839383668381834C83930A7E433037817993FC8FDC8FDC9569817A0A7E4330308B9B947482CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C5000000023A0011000700001297C292632082668B89E8E891CA935694740000ED7E43303581798BA38B5A93E09765817A0A7E43303081E182DD82F182C882C5949A96B697B393A294B0814981E282F00A93AF82B697C2926382C98F8A91AE82B782E934906C82DC82C582CC0A97C2926388F582C582A282A982C9918182AD834E838A834182B782E982A90A82F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303091E631343789F18EEB906C8DD582CC8DB02831816032303088CA290A0A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C50A000000023B001000070000128CC2906C2082668B89E8E891CA935694740001497E43303581798BA38B5A93E09765817A0A7E43303081E1949A96B697B393A294B0814981E282F00A82A282A982C9918182AD834E838A834182B782E982A982F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303089A48ED282CC8381835F838B283188CA290A2F8CF68EAE82CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C500")
- bf.WriteBytes(d)
+ bf.WriteUint8(3)
+ ps.Uint8(bf, "", false)
+ bf.WriteUint16(0) // numEvents
+ bf.WriteUint8(0) // numCups
+
+ /*
+ struct event
+ uint32 eventID
+ uint16 unk
+ uint16 unk
+ uint32 unk
+ psUint8 name
+
+ struct cup
+ uint32 cupID
+ uint16 unk
+ uint16 unk
+ uint16 unk
+ psUint8 name
+ psUint16 desc
+ */
+
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
+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_prizes_accepted")
+ s.server.db.Exec("UPDATE guild_characters SET souls=0")
+}
+
+func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
+ timestamps := make([]uint32, 5)
+ midnight := Time_Current_Midnight()
+ if debug && start <= 3 {
+ midnight := uint32(midnight.Unix())
+ switch start {
+ case 1:
+ timestamps[0] = midnight
+ timestamps[1] = timestamps[0] + 604800
+ timestamps[2] = timestamps[1] + 604800
+ timestamps[3] = timestamps[2] + 9000
+ timestamps[4] = timestamps[3] + 1240200
+ case 2:
+ timestamps[0] = midnight - 604800
+ timestamps[1] = midnight
+ timestamps[2] = timestamps[1] + 604800
+ timestamps[3] = timestamps[2] + 9000
+ timestamps[4] = timestamps[3] + 1240200
+ case 3:
+ timestamps[0] = midnight - 1209600
+ timestamps[1] = midnight - 604800
+ timestamps[2] = midnight
+ timestamps[3] = timestamps[2] + 9000
+ timestamps[4] = timestamps[3] + 1240200
+ }
+ return timestamps
+ }
+ if start == 0 || Time_Current_Adjusted().Unix() > int64(start)+2977200 {
+ cleanupFesta(s)
+ // Generate a new festa, starting midnight tomorrow
+ start = uint32(midnight.Add(24 * time.Hour).Unix())
+ s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('festa', to_timestamp($1)::timestamp without time zone)", start)
+ }
+ timestamps[0] = start
+ timestamps[1] = timestamps[0] + 604800
+ timestamps[2] = timestamps[1] + 604800
+ timestamps[3] = timestamps[2] + 9000
+ timestamps[4] = timestamps[3] + 1240200
+ return timestamps
+}
+
+type Trial struct {
+ ID uint32 `db:"id"`
+ Objective uint8 `db:"objective"`
+ GoalID uint32 `db:"goal_id"`
+ TimesReq uint16 `db:"times_req"`
+ Locale uint16 `db:"locale_req"`
+ Reward uint16 `db:"reward"`
+}
+
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
bf := byteframe.NewByteFrame()
- state := s.server.erupeConfig.DevModeOptions.FestaEvent
- bf.WriteUint32(0xdeadbeef) // festaID
- // Registration Week Start
- // Introductory Week Start
- // Totalling Time
- // Reward Festival Start (2.5hrs after totalling)
- // 2 weeks after RewardFes (next fes?)
- midnight := Time_Current_Midnight()
- switch state {
- case 1:
- bf.WriteUint32(uint32(midnight.Unix()))
- bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 150*time.Minute).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24*28*time.Hour + 11*time.Hour).Unix()))
- case 2:
- bf.WriteUint32(uint32(midnight.Add(-24 * 7 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Unix()))
- bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24*7*time.Hour + 150*time.Minute).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Add(11 * time.Hour).Unix()))
- case 3:
- bf.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Add(-24*7*time.Hour + 11*time.Hour).Unix()))
- bf.WriteUint32(uint32(midnight.Unix()))
- bf.WriteUint32(uint32(midnight.Add(150 * time.Minute).Unix()))
- bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 11*time.Hour).Unix()))
- default:
+
+ id, start := uint32(0xDEADBEEF), uint32(0)
+ rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
+ for rows.Next() {
+ rows.Scan(&id, &start)
+ }
+
+ var timestamps []uint32
+ if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
+ if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
+ timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
+ } else {
+ timestamps = generateFestaTimestamps(s, start, false)
+ }
+
+ if timestamps[0] > uint32(Time_Current_Adjusted().Unix()) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
- bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
+
+ 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)
+
+ bf.WriteUint32(id)
+ for _, timestamp := range timestamps {
+ bf.WriteUint32(timestamp)
+ }
+ bf.WriteUint32(uint32(Time_Current_Adjusted().Unix()))
bf.WriteUint8(4)
ps.Uint8(bf, "", false)
bf.WriteUint32(0)
- bf.WriteUint32(0) // Blue souls
- bf.WriteUint32(0) // Red souls
+ bf.WriteUint32(blueSouls)
+ bf.WriteUint32(redSouls)
- trials := 0
- bf.WriteUint16(uint16(trials))
- for i := 0; i < trials; i++ {
- bf.WriteUint32(uint32(i + 1)) // trialID
- bf.WriteUint8(0xFF) // unk
- bf.WriteUint8(uint8(i)) // objective
- bf.WriteUint32(0x1B) // monID, itemID if deliver
- bf.WriteUint16(1) // huntsRemain?
- bf.WriteUint16(0) // location
- bf.WriteUint16(1) // numSoulsReward
- bf.WriteUint8(0xFF) // unk
- bf.WriteUint8(0xFF) // monopolised
- bf.WriteUint16(0) // unk
+ rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
+ trialData := byteframe.NewByteFrame()
+ var count uint16
+ for rows.Next() {
+ trial := &Trial{}
+ err := rows.StructScan(&trial)
+ if err != nil {
+ continue
+ }
+ count++
+ trialData.WriteUint32(trial.ID)
+ trialData.WriteUint8(0) // Unk
+ trialData.WriteUint8(trial.Objective)
+ trialData.WriteUint32(trial.GoalID)
+ trialData.WriteUint16(trial.TimesReq)
+ trialData.WriteUint16(trial.Locale)
+ trialData.WriteUint16(trial.Reward)
+ trialData.WriteUint8(0xFF) // Unk
+ trialData.WriteUint8(0xFF) // MonopolyState
+ trialData.WriteUint16(0) // Unk
+ }
+ bf.WriteUint16(count)
+ bf.WriteBytes(trialData.Data())
+
+ // Static bonus rewards
+ rewards, _ := hex.DecodeString("001901000007015E05F000000000000100000703E81B6300000000010100000C03E8000000000000000100000D0000000000000000000100000100000000000000000002000007015E05F000000000000200000703E81B6300000000010200000C03E8000000000000000200000D0000000000000000000200000400000000000000000003000007015E05F000000000000300000703E81B6300000000010300000C03E8000000000000000300000D0000000000000000000300000100000000000000000004000007015E05F000000000000400000703E81B6300000000010400000C03E8000000000000000400000D0000000000000000000400000400000000000000000005000007015E05F000000000000500000703E81B6300000000010500000C03E8000000000000000500000D00000000000000000005000001000000000000000000")
+ bf.WriteBytes(rewards)
+
+ bf.WriteUint16(0x0001)
+ bf.WriteUint32(0xD4C001F4)
+
+ 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
}
- unk := 0 // static rewards?
- bf.WriteUint16(uint16(unk))
- for i := 0; i < unk; i++ {
- bf.WriteUint32(0)
- bf.WriteUint16(0)
- bf.WriteUint16(0)
- bf.WriteUint32(0)
- bf.WriteBool(false)
+ 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
}
- d, _ := hex.DecodeString("0001D4C001F4000411B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10003000109E54BE54E89B38F970029FDCE04000400001381818D84836C8352819993A294B091D1818100000811B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100030001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100040001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100050001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100060001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10007000109E54BE54E89B38F9700000000000008000001000000000100001388000007D0000003E800000064012C00C8009600640032")
+ d, _ := hex.DecodeString("000000000000000100001388000007D0000003E800000064012C00C8009600640032")
bf.WriteBytes(d)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
@@ -144,8 +244,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
// state festa (U)ser
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
+ guild, err := GetGuildInfoByCharacterId(s, s.charID)
+ applicant := false
+ if guild != nil {
+ applicant, _ = guild.HasApplicationForCharID(s, s.charID)
+ }
+ if err != nil || guild == nil || applicant {
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
+ var souls uint32
+ s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls)
bf := byteframe.NewByteFrame()
- bf.WriteUint32(0) // souls
+ bf.WriteUint32(souls)
bf.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -153,80 +264,156 @@ func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
// state festa (G)uild
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
+ guild, err := GetGuildInfoByCharacterId(s, s.charID)
+ applicant := false
+ if guild != nil {
+ applicant, _ = guild.HasApplicationForCharID(s, s.charID)
+ }
resp := byteframe.NewByteFrame()
- resp.WriteUint32(0) // souls
- resp.WriteUint32(1) // unk
- resp.WriteUint32(1) // unk
- resp.WriteUint32(1) // unk, rank?
- resp.WriteUint32(1) // unk
+ if err != nil || guild == nil || applicant {
+ resp.WriteUint32(0)
+ resp.WriteUint32(0)
+ resp.WriteUint32(0xFFFFFFFF)
+ resp.WriteUint32(0)
+ resp.WriteUint32(0)
+ doAckBufSucceed(s, pkt.AckHandle, resp.Data())
+ return
+ }
+ resp.WriteUint32(guild.Souls)
+ resp.WriteUint32(0) // unk
+ resp.WriteUint32(0) // unk, rank?
+ resp.WriteUint32(0) // unk
+ resp.WriteUint32(0) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
+ guild, err := GetGuildInfoByCharacterId(s, s.charID)
+ if err != nil || guild == nil {
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
+ members, err := GetGuildMembers(s, guild.ID, false)
+ if err != nil {
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
bf := byteframe.NewByteFrame()
- bf.WriteUint16(0) // numMembers
- // uint16 unk
- // uint32 charID
- // uint32 souls
+ bf.WriteUint16(uint16(len(members)))
+ bf.WriteUint16(0) // Unk
+ for _, member := range members {
+ bf.WriteUint32(member.CharID)
+ bf.WriteUint32(member.Souls)
+ }
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
- pkt := p.(*mhfpacket.MsgMhfEntryFesta)
+ pkt := p.(*mhfpacket.MsgMhfVoteFesta)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
- bf := byteframe.NewByteFrame()
+ guild, err := GetGuildInfoByCharacterId(s, s.charID)
+ if err != nil || guild == nil {
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
rand.Seed(time.Now().UnixNano())
- bf.WriteUint32(uint32(rand.Intn(2)))
- // Update guild table
+ team := uint32(rand.Intn(2))
+ switch team {
+ case 0:
+ s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)
+ case 1:
+ s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'red')", guild.ID)
+ }
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(team)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
- // Update festa state table
+ s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
- // Mark festa as claimed
- // Update guild table?
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
- // Set prize as claimed
+ s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
- // Set prize as claimed
+ s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
-// uint32 numPrizes
-// struct festaPrize
-// uint32 prizeID
-// uint32 prizeTier (1/2/3, 3 = GR)
-// uint32 soulsReq
-// uint32 unk (00 00 00 07)
-// uint32 itemID
-// uint32 numItem
-// bool claimed
+type Prize struct {
+ ID uint32 `db:"id"`
+ Tier uint32 `db:"tier"`
+ SoulsReq uint32 `db:"souls_req"`
+ ItemID uint32 `db:"item_id"`
+ NumItem uint32 `db:"num_item"`
+ Claimed int `db:"claimed"`
+}
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ rows, _ := s.server.db.Queryx("SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = 4) AS claimed FROM festa_prizes fp WHERE type='personal'")
+ var count uint32
+ prizeData := byteframe.NewByteFrame()
+ for rows.Next() {
+ prize := &Prize{}
+ err := rows.StructScan(&prize)
+ if err != nil {
+ continue
+ }
+ count++
+ prizeData.WriteUint32(prize.ID)
+ prizeData.WriteUint32(prize.Tier)
+ prizeData.WriteUint32(prize.SoulsReq)
+ prizeData.WriteUint32(7) // Unk
+ prizeData.WriteUint32(prize.ItemID)
+ prizeData.WriteUint32(prize.NumItem)
+ prizeData.WriteBool(prize.Claimed > 0)
+ }
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(count)
+ bf.WriteBytes(prizeData.Data())
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ rows, _ := s.server.db.Queryx("SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = 4) AS claimed FROM festa_prizes fp WHERE type='guild'")
+ var count uint32
+ prizeData := byteframe.NewByteFrame()
+ for rows.Next() {
+ prize := &Prize{}
+ err := rows.StructScan(&prize)
+ if err != nil {
+ continue
+ }
+ count++
+ prizeData.WriteUint32(prize.ID)
+ prizeData.WriteUint32(prize.Tier)
+ prizeData.WriteUint32(prize.SoulsReq)
+ prizeData.WriteUint32(7) // Unk
+ prizeData.WriteUint32(prize.ItemID)
+ prizeData.WriteUint32(prize.NumItem)
+ prizeData.WriteBool(prize.Claimed > 0)
+ }
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(count)
+ bf.WriteBytes(prizeData.Data())
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go
index fcaddbaf9..6bbee7eba 100644
--- a/server/channelserver/handlers_guild.go
+++ b/server/channelserver/handlers_guild.go
@@ -56,6 +56,7 @@ type Guild struct {
PugiName3 string `db:"pugi_name_3"`
Recruiting bool `db:"recruiting"`
FestivalColour FestivalColour `db:"festival_colour"`
+ Souls uint32 `db:"souls"`
Rank uint16 `db:"rank"`
AllianceID uint32 `db:"alliance_id"`
Icon *GuildIcon `db:"icon"`
@@ -121,11 +122,12 @@ SELECT
leader_id,
lc.name as leader_name,
comment,
- pugi_name_1,
- pugi_name_2,
- pugi_name_3,
+ COALESCE(pugi_name_1, '') AS pugi_name_1,
+ COALESCE(pugi_name_2, '') AS pugi_name_2,
+ COALESCE(pugi_name_3, '') AS pugi_name_3,
recruiting,
- festival_colour,
+ 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,
CASE
WHEN rank_rp <= 48 THEN rank_rp/24
WHEN rank_rp <= 288 THEN rank_rp/48+1
@@ -134,23 +136,14 @@ SELECT
WHEN rank_rp < 1200 THEN 16
ELSE 17
END rank,
- CASE WHEN (
+ COALESCE((
SELECT id FROM guild_alliances ga WHERE
ga.parent_id = g.id OR
ga.sub1_id = g.id OR
ga.sub2_id = g.id
- ) IS NULL THEN 0
- ELSE (
- SELECT id FROM guild_alliances ga WHERE
- ga.parent_id = g.id OR
- ga.sub1_id = g.id OR
- ga.sub2_id = g.id
- )
- END alliance_id,
+ ), 0) AS alliance_id,
icon,
- (
- SELECT count(1) FROM guild_characters gc WHERE gc.guild_id = g.id
- ) AS member_count
+ (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
@@ -158,8 +151,8 @@ SELECT
func (guild *Guild) Save(s *Session) error {
_, err := s.server.db.Exec(`
- UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7, festival_colour=$8, icon=$9 WHERE id=$1
- `, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3, guild.FestivalColour, guild.Icon)
+ UPDATE guilds SET main_motto=$2, sub_motto=$3, comment=$4, pugi_name_1=$5, pugi_name_2=$6, pugi_name_3=$7, icon=$8, leader_id=$9 WHERE id=$1
+ `, guild.ID, guild.MainMotto, guild.SubMotto, guild.Comment, guild.PugiName1, guild.PugiName2, guild.PugiName3, guild.Icon, guild.GuildLeader.LeaderCharID)
if err != nil {
s.logger.Error("failed to update guild data", zap.Error(err), zap.Uint32("guildID", guild.ID))
@@ -643,6 +636,33 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(uint32(response))
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
return
+ case mhfpacket.OPERATE_GUILD_RESIGN:
+ guildMembers, err := GetGuildMembers(s, guild.ID, false)
+ success := false
+ if err == nil {
+ sort.Slice(guildMembers[:], func(i, j int) bool {
+ return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
+ })
+ for i := 1; i < len(guildMembers); i++ {
+ if !guildMembers[i].AvoidLeadership {
+ guild.LeaderCharID = guildMembers[i].CharID
+ guildMembers[0].OrderIndex = guildMembers[i].OrderIndex
+ guildMembers[i].OrderIndex = 1
+ guildMembers[0].Save(s)
+ guildMembers[i].Save(s)
+ bf.WriteUint32(guildMembers[i].CharID)
+ success = true
+ break
+ }
+ }
+ guild.Save(s)
+ doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
+ }
+ if !success {
+ bf.WriteUint32(0)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ }
+ return
case mhfpacket.OPERATE_GUILD_APPLY:
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
@@ -667,6 +687,14 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
// All successful acks return 0x01, assuming 0x00 is failure
response = 0x00
+ } else {
+ mail := Mail{
+ RecipientID: s.charID,
+ Subject: "Withdrawal",
+ Body: fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
+ IsSystemMessage: true,
+ }
+ mail.Send(s, nil)
}
bf.WriteUint32(uint32(response))
@@ -790,14 +818,14 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
if err != nil || guild == nil {
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
actorCharacter, err := GetCharacterGuildData(s, s.charID)
if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) {
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -806,41 +834,45 @@ func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
err = guild.AcceptApplication(s, pkt.CharID)
mail = Mail{
- SenderID: s.charID,
- RecipientID: pkt.CharID,
- Subject: "Accepted!",
- Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
- IsGuildInvite: false,
+ RecipientID: pkt.CharID,
+ Subject: "Accepted!",
+ Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
+ IsSystemMessage: true,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
err = guild.RejectApplication(s, pkt.CharID)
mail = Mail{
- SenderID: s.charID,
- RecipientID: pkt.CharID,
- Subject: "Rejected",
- Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
- IsGuildInvite: false,
+ RecipientID: pkt.CharID,
+ Subject: "Rejected",
+ Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
+ IsSystemMessage: true,
}
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
err = guild.RemoveCharacter(s, pkt.CharID)
mail = Mail{
- SenderID: s.charID,
- RecipientID: pkt.CharID,
- Subject: "Kicked",
- Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
- IsGuildInvite: false,
+ RecipientID: pkt.CharID,
+ Subject: "Kicked",
+ Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
+ IsSystemMessage: true,
}
default:
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
s.logger.Warn(fmt.Sprintf("unhandled operateGuildMember action '%d'", pkt.Action))
}
if err != nil {
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
mail.Send(s, nil)
+ for _, channel := range s.server.Channels {
+ for _, session := range channel.sessions {
+ if session.charID == pkt.CharID {
+ SendMailNotification(s, &mail, session)
+ }
+ }
+ }
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
- doAckSimpleSucceed(s, pkt.AckHandle, nil)
}
func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
@@ -914,24 +946,12 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteBytes(guildLeaderName)
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
bf.WriteBool(false) // isReturnGuild
- bf.WriteBytes([]byte{0x01, 0x02, 0x02}) // Unk
+ bf.WriteBool(false) // earnedSpecialHall
+ bf.WriteBytes([]byte{0x02, 0x02}) // Unk
bf.WriteUint32(guild.EventRP)
-
- if guild.PugiName1 == "" {
- bf.WriteUint16(0x0100)
- } else {
- ps.Uint8(bf, guild.PugiName1, true)
- }
- if guild.PugiName2 == "" {
- bf.WriteUint16(0x0100)
- } else {
- ps.Uint8(bf, guild.PugiName2, true)
- }
- if guild.PugiName3 == "" {
- bf.WriteUint16(0x0100)
- } else {
- ps.Uint8(bf, guild.PugiName3, true)
- }
+ ps.Uint8(bf, guild.PugiName1, true)
+ ps.Uint8(bf, guild.PugiName2, true)
+ ps.Uint8(bf, guild.PugiName3, true)
// probably guild pugi properties, should be status, stamina and luck outfits
bf.WriteBytes([]byte{
@@ -952,7 +972,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint32(alliance.ID)
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
- bf.WriteUint16(uint16(alliance.TotalMembers))
+ bf.WriteUint16(alliance.TotalMembers)
bf.WriteUint16(0) // Unk0
ps.Uint16(bf, alliance.Name, true)
if alliance.SubGuild1ID > 0 {
@@ -1015,7 +1035,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
- if err != nil {
+ if err != nil || characterGuildData.IsApplicant {
bf.WriteUint16(0)
} else {
bf.WriteUint16(uint16(len(applicants)))
@@ -1063,15 +1083,11 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint8(0x00)
}
+ bf.WriteUint8(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
- //// REALLY large/complex format... stubbing it out here for simplicity.
- //resp := byteframe.NewByteFrame()
- //resp.WriteUint32(0) // Count
- //resp.WriteUint8(0) // Unk, read if count == 0.
-
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 8))
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5))
}
}
@@ -1260,6 +1276,14 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByCharacterId(s, s.charID)
}
+ if guild != nil {
+ isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
+ if isApplicant {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ return
+ }
+ }
+
if guild == nil && s.prevGuildID > 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
}
@@ -1305,7 +1329,8 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0x0600)
}
bf.WriteUint8(member.OrderIndex)
- ps.Uint16(bf, member.Name, true)
+ bf.WriteBool(member.AvoidLeadership)
+ ps.Uint8(bf, member.Name, true)
}
for _, member := range guildMembers {
@@ -1414,12 +1439,8 @@ func handleMsgMhfGetGuildTargetMemberNum(s *Session, p mhfpacket.MHFPacket) {
guild, err = GetGuildInfoByID(s, pkt.GuildID)
}
- if err != nil {
- s.logger.Warn("failed to find guild", zap.Error(err), zap.Uint32("guildID", pkt.GuildID))
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
- return
- } else if guild == nil {
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ if err != nil || guild == nil {
+ doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
return
}
@@ -1865,7 +1886,10 @@ func handleMsgMhfUpdateGuildMessageBoard(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
-func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfEntryRookieGuild(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfEntryRookieGuild)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+}
func handleMsgMhfUpdateForceGuildRank(s *Session, p mhfpacket.MHFPacket) {}
@@ -1883,8 +1907,18 @@ func handleMsgMhfUpdateGuild(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetGuildManageRight)
s.server.db.Exec("UPDATE guild_characters SET recruiter=$1 WHERE character_id=$2", pkt.Allowed, pkt.CharID)
- // TODO: What is this supposed to return? This works for now
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x01})
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+}
+
+func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfCheckMonthlyItem)
+ doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
+ // TODO: Implement month-by-month tracker, 0 = Not claimed, 1 = Claimed
+}
+
+func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfAcquireMonthlyItem)
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
@@ -1892,6 +1926,9 @@ func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
stubEnumerateNoResults(s, pkt.AckHandle)
}
-func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfOperationInvGuild)
+ doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
+}
func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/handlers_guild_member.go b/server/channelserver/handlers_guild_member.go
index 5fa83fb6b..2c3f0cd1a 100644
--- a/server/channelserver/handlers_guild_member.go
+++ b/server/channelserver/handlers_guild_member.go
@@ -12,6 +12,7 @@ type GuildMember struct {
GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"`
JoinedAt *time.Time `db:"joined_at"`
+ Souls uint32 `db:"souls"`
Name string `db:"name"`
IsApplicant bool `db:"is_applicant"`
OrderIndex uint8 `db:"order_index"`
@@ -25,12 +26,25 @@ type GuildMember struct {
WeaponType uint16 `db:"weapon_type"`
}
+func (gm *GuildMember) CanRecruit() bool {
+ if gm.Recruiter {
+ return true
+ }
+ if gm.OrderIndex <= 3 {
+ return true
+ }
+ if gm.IsLeader {
+ return true
+ }
+ return false
+}
+
func (gm *GuildMember) IsSubLeader() bool {
- return gm.OrderIndex <= 3 && !gm.AvoidLeadership
+ return gm.OrderIndex <= 3
}
func (gm *GuildMember) Save(s *Session) error {
- _, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1 WHERE character_id=$2", gm.AvoidLeadership, gm.CharID)
+ _, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3", gm.AvoidLeadership, gm.OrderIndex, gm.CharID)
if err != nil {
s.logger.Error(
@@ -48,6 +62,7 @@ const guildMembersSelectSQL = `
SELECT
g.id as guild_id,
joined_at,
+ coalesce(souls, 0) as souls,
c.name,
character.character_id,
coalesce(gc.order_index, 0) as order_index,
diff --git a/server/channelserver/handlers_guild_scout.go b/server/channelserver/handlers_guild_scout.go
index 4bcbb6b91..f47c28bc6 100644
--- a/server/channelserver/handlers_guild_scout.go
+++ b/server/channelserver/handlers_guild_scout.go
@@ -21,7 +21,7 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
- if actorCharGuildData == nil || !actorCharGuildData.Recruiter {
+ if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -59,19 +59,13 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
- senderName, err := getCharacterName(s, s.charID)
-
- if err != nil {
- panic(err)
- }
-
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
- Subject: "Guild! ヽ(・∀・)ノ",
+ Subject: "Invitation!",
Body: fmt.Sprintf(
- "%s has invited you to join the wonderful guild %s, do you accept this challenge?",
- senderName,
+ "%s has invited you to join 「%s」\nDo you want to accept?",
+ getCharacterName(s, s.charID),
guildInfo.Name,
),
IsGuildInvite: true,
@@ -104,7 +98,7 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
panic(err)
}
- if guildCharData == nil || !guildCharData.Recruiter {
+ if guildCharData == nil || !guildCharData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
@@ -128,61 +122,72 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
-
+ bf := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil {
panic(err)
}
- _, err = guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
+ app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
- if err != nil {
+ if app == nil || err != nil {
s.logger.Warn(
- "could not retrieve guild invitation",
+ "Guild invite missing, deleted?",
zap.Error(err),
zap.Uint32("guildID", guild.ID),
zap.Uint32("charID", s.charID),
)
- doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
+ bf.WriteUint32(7)
+ bf.WriteUint32(guild.ID)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
+ var mail []Mail
if pkt.Answer {
err = guild.AcceptApplication(s, s.charID)
+ mail = append(mail, Mail{
+ RecipientID: s.charID,
+ Subject: "Success!",
+ Body: fmt.Sprintf("You successfully joined 「%s」.", guild.Name),
+ IsSystemMessage: true,
+ })
+ mail = append(mail, Mail{
+ SenderID: s.charID,
+ RecipientID: pkt.LeaderID,
+ Subject: "Accepted",
+ Body: fmt.Sprintf("%s accepted your invitation to join 「%s」.", s.Name, guild.Name),
+ IsSystemMessage: true,
+ })
} else {
err = guild.RejectApplication(s, s.charID)
+ mail = append(mail, Mail{
+ RecipientID: s.charID,
+ Subject: "Declined",
+ Body: fmt.Sprintf("You declined the invitation to join 「%s」.", guild.Name),
+ IsSystemMessage: true,
+ })
+ mail = append(mail, Mail{
+ SenderID: s.charID,
+ RecipientID: pkt.LeaderID,
+ Subject: "Declined",
+ Body: fmt.Sprintf("%s declined your invitation to join 「%s」.", s.Name, guild.Name),
+ IsSystemMessage: true,
+ })
}
-
if err != nil {
- doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
- return
+ bf.WriteUint32(7)
+ bf.WriteUint32(guild.ID)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ } else {
+ bf.WriteUint32(0)
+ bf.WriteUint32(guild.ID)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ for _, m := range mail {
+ m.Send(s, nil)
+ }
}
-
- senderName, err := getCharacterName(s, pkt.LeaderID)
-
- if err != nil {
- doAckSimpleFail(s, pkt.AckHandle, nil)
- panic(err)
- }
-
- successMail := Mail{
- SenderID: pkt.LeaderID,
- RecipientID: s.charID,
- Subject: "Happy days!",
- Body: fmt.Sprintf("You successfully joined %s and should be proud of all you have accomplished.", guild.Name),
- IsGuildInvite: false,
- SenderName: senderName,
- }
-
- err = successMail.Send(s, nil)
-
- if err != nil {
- doAckSimpleFail(s, pkt.AckHandle, nil)
- panic(err)
- }
-
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x81, 0x7e})
}
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
@@ -191,12 +196,12 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if guildInfo == nil && s.prevGuildID == 0 {
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil {
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
@@ -210,7 +215,7 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
- doAckSimpleFail(s, pkt.AckHandle, nil)
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go
index 92d7c853e..280f27238 100644
--- a/server/channelserver/handlers_house.go
+++ b/server/channelserver/handlers_house.go
@@ -5,9 +5,37 @@ import (
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
+ "fmt"
"go.uber.org/zap"
+ "io"
+ "time"
)
+const warehouseNamesQuery = `
+SELECT
+COALESCE(item0name, ''),
+COALESCE(item1name, ''),
+COALESCE(item2name, ''),
+COALESCE(item3name, ''),
+COALESCE(item4name, ''),
+COALESCE(item5name, ''),
+COALESCE(item6name, ''),
+COALESCE(item7name, ''),
+COALESCE(item8name, ''),
+COALESCE(item9name, ''),
+COALESCE(equip0name, ''),
+COALESCE(equip1name, ''),
+COALESCE(equip2name, ''),
+COALESCE(equip3name, ''),
+COALESCE(equip4name, ''),
+COALESCE(equip5name, ''),
+COALESCE(equip6name, ''),
+COALESCE(equip7name, ''),
+COALESCE(equip8name, ''),
+COALESCE(equip9name, '')
+FROM warehouse
+`
+
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
@@ -64,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
}
case 3:
house := HouseData{}
- row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name=$1", pkt.Name)
+ row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name ILIKE $1", fmt.Sprintf(`%%%s%%`, pkt.Name))
err := row.StructScan(&house)
if err != nil {
panic(err)
@@ -291,27 +319,238 @@ func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
+type Title struct {
+ ID uint16 `db:"id"`
+ Acquired time.Time `db:"unlocked_at"`
+ Updated time.Time `db:"updated_at"`
+}
+
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
+ var count uint16
bf := byteframe.NewByteFrame()
- if pkt.CharID == 0 {
- titleCount := 114 // all titles unlocked
- bf.WriteUint16(uint16(titleCount)) // title count
- bf.WriteUint16(0) // unk
- for i := 0; i < titleCount; i++ {
- bf.WriteUint16(uint16(i))
- bf.WriteUint16(0) // unk
- bf.WriteUint32(0) // timestamp acquired
- bf.WriteUint32(0) // timestamp updated
- }
- } else {
- bf.WriteUint16(0)
+ bf.WriteUint16(0)
+ bf.WriteUint16(0) // Unk
+ rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID)
+ if err != nil {
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+ return
}
+ for rows.Next() {
+ title := &Title{}
+ err = rows.StructScan(&title)
+ if err != nil {
+ continue
+ }
+ count++
+ bf.WriteUint16(title.ID)
+ bf.WriteUint16(0) // Unk
+ bf.WriteUint32(uint32(title.Acquired.Unix()))
+ bf.WriteUint32(uint32(title.Updated.Unix()))
+ }
+ bf.Seek(0, io.SeekStart)
+ bf.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
-func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfAcquireTitle)
+ var exists int
+ err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists)
+ if err != nil || exists == 0 {
+ s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID)
+ } else {
+ s.server.db.Exec("UPDATE titles SET updated_at=now()")
+ }
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
+}
-func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
-func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
+ var t int
+ err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
+ if err != nil {
+ s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID)
+ }
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint8(pkt.Operation)
+ switch pkt.Operation {
+ case 0:
+ var count uint8
+ itemNames := make([]string, 10)
+ equipNames := make([]string, 10)
+ s.server.db.QueryRow(fmt.Sprintf("%s WHERE character_id=$1", warehouseNamesQuery), s.charID).Scan(&itemNames[0],
+ &itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4], &itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9], &equipNames[0],
+ &equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4], &equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9])
+ bf.WriteUint32(0)
+ bf.WriteUint16(10000) // Usages
+ temp := byteframe.NewByteFrame()
+ for i, name := range itemNames {
+ if len(name) > 0 {
+ count++
+ temp.WriteUint8(0)
+ temp.WriteUint8(uint8(i))
+ ps.Uint8(temp, name, true)
+ }
+ }
+ for i, name := range equipNames {
+ if len(name) > 0 {
+ count++
+ temp.WriteUint8(1)
+ temp.WriteUint8(uint8(i))
+ ps.Uint8(temp, name, true)
+ }
+ }
+ bf.WriteUint8(count)
+ bf.WriteBytes(temp.Data())
+ case 1:
+ bf.WriteUint8(0)
+ case 2:
+ s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%dname=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), pkt.Name, s.charID)
+ case 3:
+ bf.WriteUint32(0) // Usage renewal time, >1 = disabled
+ bf.WriteUint16(10000) // Usages
+ case 4:
+ bf.WriteUint32(0)
+ bf.WriteUint16(10000) // Usages
+ bf.WriteUint8(0)
+ }
+ // Opcodes
+ // 0 = Get box names
+ // 1 = Commit usage
+ // 2 = Rename
+ // 3 = Get usage limit
+ // 4 = Get gift box names (doesn't do anything?)
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
+
+func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) {
+ giftBox := getWarehouseBox(s, boxType, 10)
+ if boxType == "item" {
+ exists := false
+ for i, stack := range giftBox {
+ if stack.ItemID == giftStack.ItemID {
+ exists = true
+ giftBox[i].Quantity += giftStack.Quantity
+ break
+ }
+ }
+ if exists == false {
+ giftBox = append(giftBox, giftStack)
+ }
+ } else {
+ giftBox = append(giftBox, giftStack)
+ }
+ s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s10=$1 WHERE character_id=$2", boxType), boxToBytes(giftBox, boxType), s.charID)
+}
+
+func getWarehouseBox(s *Session, boxType string, boxIndex uint8) []mhfpacket.WarehouseStack {
+ var data []byte
+ s.server.db.QueryRow(fmt.Sprintf("SELECT %s%d FROM warehouse WHERE character_id=$1", boxType, boxIndex), s.charID).Scan(&data)
+ if len(data) > 0 {
+ box := byteframe.NewByteFrameFromBytes(data)
+ numStacks := box.ReadUint16()
+ stacks := make([]mhfpacket.WarehouseStack, numStacks)
+ for i := 0; i < int(numStacks); i++ {
+ if boxType == "item" {
+ stacks[i].ID = box.ReadUint32()
+ stacks[i].Index = box.ReadUint16()
+ stacks[i].ItemID = box.ReadUint16()
+ stacks[i].Quantity = box.ReadUint16()
+ box.ReadUint16()
+ } else {
+ stacks[i].ID = box.ReadUint32()
+ stacks[i].Index = box.ReadUint16()
+ stacks[i].EquipType = box.ReadUint16()
+ stacks[i].ItemID = box.ReadUint16()
+ stacks[i].Data = box.ReadBytes(56)
+ }
+ }
+ return stacks
+ } else {
+ return make([]mhfpacket.WarehouseStack, 0)
+ }
+}
+
+func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte {
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint16(uint16(len(stacks)))
+ for i, stack := range stacks {
+ if boxType == "item" {
+ bf.WriteUint32(stack.ID)
+ bf.WriteUint16(uint16(i + 1))
+ bf.WriteUint16(stack.ItemID)
+ bf.WriteUint16(stack.Quantity)
+ bf.WriteUint16(0)
+ } else {
+ bf.WriteUint32(stack.ID)
+ bf.WriteUint16(uint16(i + 1))
+ bf.WriteUint16(stack.EquipType)
+ bf.WriteUint16(stack.ItemID)
+ bf.WriteBytes(stack.Data)
+ }
+ }
+ bf.WriteUint16(0)
+ return bf.Data()
+}
+
+func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
+ box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
+ if len(box) > 0 {
+ doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType))
+ } else {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
+ }
+}
+
+func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
+ box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
+ // Update existing stacks
+ var newStacks []mhfpacket.WarehouseStack
+ for _, update := range pkt.Updates {
+ exists := false
+ if pkt.BoxType == "item" {
+ for i, stack := range box {
+ if stack.Index == update.Index {
+ exists = true
+ box[i].Quantity = update.Quantity
+ break
+ }
+ }
+ } else {
+ for i, stack := range box {
+ if stack.Index == update.Index {
+ exists = true
+ box[i].ItemID = update.ItemID
+ break
+ }
+ }
+ }
+ if exists == false {
+ newStacks = append(newStacks, update)
+ }
+ }
+ // Append new stacks
+ for _, stack := range newStacks {
+ box = append(box, stack)
+ }
+ // Slice empty stacks
+ var cleanedBox []mhfpacket.WarehouseStack
+ for _, stack := range box {
+ if pkt.BoxType == "item" {
+ if stack.Quantity > 0 {
+ cleanedBox = append(cleanedBox, stack)
+ }
+ } else {
+ if stack.ItemID != 0 {
+ cleanedBox = append(cleanedBox, stack)
+ }
+ }
+ }
+ s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%d=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), boxToBytes(cleanedBox, pkt.BoxType), s.charID)
+ doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
+}
diff --git a/server/channelserver/handlers_mail.go b/server/channelserver/handlers_mail.go
index 1e060b245..e31e90eed 100644
--- a/server/channelserver/handlers_mail.go
+++ b/server/channelserver/handlers_mail.go
@@ -25,21 +25,22 @@ type Mail struct {
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
+ IsSystemMessage bool `db:"is_sys_message"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
query := `
- INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
- VALUES ($1, $2, $3, $4, $5, $6, $7)
+ INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`
var err error
if transaction == nil {
- _, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
+ _, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
} else {
- _, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
+ _, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
}
if err != nil {
@@ -53,6 +54,7 @@ func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
+ zap.Bool("isSystemMessage", m.IsSystemMessage),
)
return err
}
@@ -141,6 +143,7 @@ func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
+ m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
@@ -189,6 +192,7 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
+ m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
@@ -215,16 +219,10 @@ func GetMailByID(s *Session, ID int) (*Mail, error) {
}
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
- senderName, err := getCharacterName(s, m.SenderID)
-
- if err != nil {
- panic(err)
- }
-
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
- SenderName: senderName,
+ SenderName: getCharacterName(s, m.SenderID),
}
notification.Build(bf)
@@ -241,7 +239,7 @@ func SendMailNotification(s *Session, m *Mail, recipient *Session) {
recipient.QueueSendMHF(castedBinary)
}
-func getCharacterName(s *Session, charID uint32) (string, error) {
+func getCharacterName(s *Session, charID uint32) string {
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
@@ -249,10 +247,9 @@ func getCharacterName(s *Session, charID uint32) (string, error) {
err := row.Scan(&charName)
if err != nil {
- return "", err
+ return ""
}
-
- return charName, nil
+ return charName
}
func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
@@ -325,8 +322,9 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
flags |= 0x02
}
- // System message, hides ID
- // flags |= 0x04
+ if m.IsSystemMessage {
+ flags |= 0x04
+ }
// Workaround until EN mail items are patched
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DisableMailItems {
diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go
index 606579fe5..31bc26249 100644
--- a/server/channelserver/handlers_mercenary.go
+++ b/server/channelserver/handlers_mercenary.go
@@ -9,7 +9,6 @@ import (
"go.uber.org/zap"
"io"
"io/ioutil"
- "math/rand"
"os"
"path/filepath"
)
@@ -40,7 +39,7 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePartner)
- dumpSaveData(s, pkt.RawDataPayload, "_partner")
+ dumpSaveData(s, pkt.RawDataPayload, "partner")
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
@@ -76,7 +75,7 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
- dumpSaveData(s, pkt.RawDataPayload, "_hunternavi")
+ dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
if pkt.IsDataDiff {
var data []byte
@@ -122,75 +121,87 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
- doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A))
+ if pkt.Unk0 == 1 {
+ // Format:
+ // uint8 Hunts
+ // struct Hunt
+ // uint32 HuntID
+ // uint32 MonID
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
+ } else {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
+ }
}
-func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {}
+func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
+ pkt := p.(*mhfpacket.MsgMhfEnumerateMercenaryLog)
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(0)
+ // Format:
+ // struct Log
+ // uint32 Timestamp
+ // []byte Name (len 18)
+ // uint8 Unk
+ // uint8 Unk
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
+}
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
bf := byteframe.NewByteFrame()
- bf.WriteUint32(0x00) // Unk
- bf.WriteUint32(rand.Uint32()) // Partner ID?
+ var nextID uint32
+ s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
+
+ bf.WriteUint32(nextID) // New MercID
+ bf.WriteUint32(0xDEADBEEF) // Unk
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
- bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
- GCPValue := bf.ReadUint32()
- _ = bf.ReadUint32() // unk
- MercDataSize := bf.ReadUint32()
- MercData := bf.ReadBytes(uint(MercDataSize))
- _ = bf.ReadUint32() // unk
-
- if MercDataSize > 0 {
- // the save packet has an extra null byte after its size
- _, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID)
- if err != nil {
- s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
- }
- }
- // gcp value is always present regardless
- _, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID)
- if err != nil {
- s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
+ if len(pkt.MercData) > 0 {
+ s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID)
}
+ s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
+ if pkt.Unk0 {
+ doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
+ return
+ }
var data []byte
var gcp uint32
- // still has issues
- err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
- if err != nil {
- s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err))
- }
-
- err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
- if err != nil {
- panic(err)
- }
- if len(data) == 0 {
- data = []byte{0x00}
- }
+ s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
+ s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
resp := byteframe.NewByteFrame()
- resp.WriteBytes(data)
- resp.WriteUint16(0)
+ if len(data) == 0 {
+ resp.WriteBytes(make([]byte, 3))
+ } else {
+ resp.WriteBytes(data[1:])
+ resp.WriteUint32(0) // Unk
+ }
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
- // accessing actual rasta data of someone else still unsure of the formatting of this
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
+ var data []byte
+ s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
+ resp := byteframe.NewByteFrame()
+ if len(data) == 0 {
+ resp.WriteBool(false)
+ } else {
+ resp.WriteBytes(data[4:])
+ }
+ doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
@@ -217,6 +228,7 @@ func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
+ dumpSaveData(s, pkt.RawDataPayload, "otomoairou")
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
if err != nil {
s.logger.Error("Failed to decompress airou", zap.Error(err))
diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go
index 3dd65cf6a..2c44cc6db 100644
--- a/server/channelserver/handlers_register.go
+++ b/server/channelserver/handlers_register.go
@@ -11,7 +11,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
s.server.raviente.Lock()
switch pkt.SemaphoreID {
- case 3:
+ case 4:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -49,7 +49,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
- case 4:
+ case 5:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -74,7 +74,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
- case 5:
+ case 6:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
@@ -242,15 +242,15 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
func (s *Session) notifyRavi() {
var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame()
- temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 3}
- raviNotif.WriteUint16(uint16(temp.Opcode()))
- temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
+ temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6}
+ raviNotif.WriteUint16(uint16(temp.Opcode()))
+ temp.Build(raviNotif, s.clientContext)
raviNotif.WriteUint16(0x0010) // End it.
sema := getRaviSemaphore(s)
if sema != "" {
@@ -262,7 +262,7 @@ func (s *Session) notifyRavi() {
func getRaviSemaphore(s *Session) string {
for _, semaphore := range s.server.semaphore {
- if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
+ if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "4") {
return semaphore.id_semaphore
}
}
diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go
index d7b60686b..6fdce7c22 100644
--- a/server/channelserver/handlers_rengoku.go
+++ b/server/channelserver/handlers_rengoku.go
@@ -1,6 +1,8 @@
package channelserver
import (
+ ps "erupe-ce/common/pascalstring"
+ "fmt"
"io/ioutil"
"path/filepath"
@@ -17,7 +19,19 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
if err != nil {
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
}
-
+ bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
+ bf.Seek(71, 0)
+ maxStageMp := bf.ReadUint32()
+ maxScoreMp := bf.ReadUint32()
+ bf.Seek(4, 1)
+ maxStageSp := bf.ReadUint32()
+ maxScoreSp := bf.ReadUint32()
+ var t int
+ err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t)
+ if err != nil {
+ s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID)
+ }
+ s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
@@ -81,16 +95,191 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, data)
}
+const rengokuScoreQuery = `
+SELECT max_stages_mp, max_points_mp, max_stages_sp, max_points_sp, c.name, gc.guild_id
+FROM rengoku_score rs
+LEFT JOIN characters c ON c.id = rs.character_id
+LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id
+`
+
+type RengokuScore struct {
+ Name string `db:"name"`
+ GuildID int `db:"guild_id"`
+ MaxStagesMP uint32 `db:"max_stages_mp"`
+ MaxPointsMP uint32 `db:"max_points_mp"`
+ MaxStagesSP uint32 `db:"max_stages_sp"`
+ MaxPointsSP uint32 `db:"max_points_sp"`
+}
+
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
- doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
+
+ guild, _ := GetGuildInfoByCharacterId(s, s.charID)
+ isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
+ if isApplicant {
+ guild = nil
+ }
+
+ var score RengokuScore
+ i := uint32(1)
+ bf := byteframe.NewByteFrame()
+ scoreData := byteframe.NewByteFrame()
+ switch pkt.Leaderboard {
+ case 0: // Max stage overall MP
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_mp DESC", rengokuScoreQuery))
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxStagesMP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxStagesMP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ case 1: // Max RdP overall MP
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_mp DESC", rengokuScoreQuery))
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxPointsMP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxPointsMP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ case 2: // Max stage guild MP
+ if guild != nil {
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID)
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxStagesMP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxStagesMP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ } else {
+ bf.WriteBytes(make([]byte, 11))
+ }
+ case 3: // Max RdP guild MP
+ if guild != nil {
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID)
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxPointsMP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxPointsMP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ } else {
+ bf.WriteBytes(make([]byte, 11))
+ }
+ case 4: // Max stage overall SP
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_sp DESC", rengokuScoreQuery))
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxStagesSP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxStagesSP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ case 5: // Max RdP overall SP
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_sp DESC", rengokuScoreQuery))
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxPointsSP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxPointsSP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ case 6: // Max stage guild SP
+ if guild != nil {
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID)
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxStagesSP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxStagesSP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ } else {
+ bf.WriteBytes(make([]byte, 11))
+ }
+ case 7: // Max RdP guild SP
+ if guild != nil {
+ rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID)
+ for rows.Next() {
+ rows.StructScan(&score)
+ if score.Name == s.Name {
+ bf.WriteUint32(i)
+ bf.WriteUint32(score.MaxPointsSP)
+ ps.Uint8(bf, s.Name, true)
+ ps.Uint8(bf, "", false)
+ }
+ scoreData.WriteUint32(i)
+ scoreData.WriteUint32(score.MaxPointsSP)
+ ps.Uint8(scoreData, score.Name, true)
+ ps.Uint8(scoreData, "", false)
+ i++
+ }
+ } else {
+ bf.WriteBytes(make([]byte, 11))
+ }
+ }
+ bf.WriteUint8(uint8(i) - 1)
+ bf.WriteBytes(scoreData.Data())
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
-
- resp := byteframe.NewByteFrame()
- resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
-
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
+ // What is this for?
+ bf := byteframe.NewByteFrame()
+ bf.WriteUint32(0) // Max stage overall MP rank
+ bf.WriteUint32(0) // Max RdP overall MP rank
+ doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go
index 6659bd2da..cbf1a0a7f 100644
--- a/server/channelserver/handlers_semaphore.go
+++ b/server/channelserver/handlers_semaphore.go
@@ -45,12 +45,10 @@ func destructEmptySemaphores(s *Session) {
}
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
- if !strings.HasSuffix(sema.id_semaphore, "5") {
- delete(sema.reservedClientSlots, s.charID)
- delete(sema.clients, s)
- }
- if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
- s.logger.Debug("Raviente semaphore is empty, resetting")
+ delete(sema.reservedClientSlots, s.charID)
+ delete(sema.clients, s)
+ if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 {
+ s.logger.Debug("Main raviente semaphore is empty, resetting")
resetRavi(s)
}
}
@@ -91,7 +89,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32)
s.server.semaphore[SemaphoreID] = &Semaphore{
id_semaphore: pkt.SemaphoreID,
- id: uint32(suffix),
+ id: uint32(suffix + 1),
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: 32,
@@ -119,7 +117,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
- doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
+ doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
}
diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go
index 4bf19d114..906480be7 100644
--- a/server/channelserver/handlers_stage.go
+++ b/server/channelserver/handlers_stage.go
@@ -135,7 +135,6 @@ func removeSessionFromStage(s *Session) {
// Remove client from old stage.
s.stage.Lock()
delete(s.stage.clients, s)
- delete(s.stage.reservedClientSlots, s.charID)
// Delete old stage objects owned by the client.
s.logger.Info("Sending notification to old stage clients")
@@ -157,6 +156,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
if s.stageID == "" {
s.stageMoveStack.Set(pkt.StageID)
} else {
+ s.stage.reservedClientSlots[s.charID] = false
s.stageMoveStack.Push(s.stageID)
s.stageMoveStack.Lock()
}
@@ -175,11 +175,18 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
// Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop()
-
if err != nil {
panic(err)
}
+ if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
+ delete(s.stage.reservedClientSlots, s.charID)
+ }
+
+ if _, exists := s.server.stages[backStage].reservedClientSlots[s.charID]; exists {
+ delete(s.server.stages[backStage].reservedClientSlots, s.charID)
+ }
+
doStageTransfer(s, pkt.AckHandle, backStage)
}
diff --git a/server/channelserver/handlers_tactics.go b/server/channelserver/handlers_tactics.go
index 585c0ef79..4991ce846 100644
--- a/server/channelserver/handlers_tactics.go
+++ b/server/channelserver/handlers_tactics.go
@@ -2,7 +2,6 @@ package channelserver
import (
"encoding/hex"
-
"erupe-ce/network/mhfpacket"
)
@@ -58,11 +57,3 @@ func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdTacticsLog(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {}
-
-func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/handlers_users.go b/server/channelserver/handlers_users.go
index 6db6613c0..3bebcc97a 100644
--- a/server/channelserver/handlers_users.go
+++ b/server/channelserver/handlers_users.go
@@ -3,7 +3,6 @@ package channelserver
import (
"fmt"
- "erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
@@ -17,7 +16,8 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
s.server.userBinaryPartsLock.Unlock()
- err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID)
+ var exists []byte
+ err := s.server.db.QueryRow("SELECT type2 FROM user_binaries WHERE id=$1", s.charID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO user_binaries (id) VALUES ($1)", s.charID)
}
@@ -39,25 +39,18 @@ func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
s.server.userBinaryPartsLock.RLock()
defer s.server.userBinaryPartsLock.RUnlock()
data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}]
- resp := byteframe.NewByteFrame()
// If we can't get the real data, try to get it from the database.
if !ok {
- var data []byte
- rows, _ := s.server.db.Queryx(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID)
- for rows.Next() {
- rows.Scan(&data)
- resp.WriteBytes(data)
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
- return
+ err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binaries WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
+ if err != nil {
+ doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
+ } else {
+ doAckBufSucceed(s, pkt.AckHandle, data)
}
- doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
- return
} else {
- resp.WriteBytes(data)
+ doAckBufSucceed(s, pkt.AckHandle, data)
}
-
- doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go
index a23ec2834..82f29d03a 100644
--- a/server/channelserver/sys_channel_server.go
+++ b/server/channelserver/sys_channel_server.go
@@ -11,27 +11,11 @@ import (
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/discordbot"
+
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
-type StageIdType = string
-
-const (
- // GlobalStage is the stage that is used for all users.
- MezeportaStageId StageIdType = "sl1Ns200p0a0u0"
- GuildHallLv1StageId StageIdType = "sl1Ns202p0a0u0"
- GuildHallLv2StageId StageIdType = "sl1Ns203p0a0u0"
- GuildHallLv3StageId StageIdType = "sl1Ns204p0a0u0"
- PugiFarmStageId StageIdType = "sl1Ns205p0a0u0"
- RastaBarStageId StageIdType = "sl1Ns211p0a0u0"
- PalloneCaravanStageId StageIdType = "sl1Ns260p0a0u0"
- GookFarmStageId StageIdType = "sl1Ns265p0a0u0"
- DivaFountainStageId StageIdType = "sl2Ns379p0a0u0"
- DivaHallStageId StageIdType = "sl1Ns445p0a0u0"
- MezFesStageId StageIdType = "sl1Ns462p0a0u0"
-)
-
// Config struct allows configuring the server.
type Config struct {
ID uint16
@@ -80,8 +64,7 @@ type Server struct {
// Discord chat integration
discordBot *discordbot.DiscordBot
- name string
- enable bool
+ name string
raviente *Raviente
}
@@ -154,43 +137,30 @@ func NewServer(config *Config) *Server {
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
- semaphoreIndex: 5,
+ semaphoreIndex: 7,
discordBot: config.DiscordBot,
name: config.Name,
- enable: config.Enable,
raviente: NewRaviente(),
}
// Mezeporta
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
- // Guild Hall LV1
- s.stages["sl1Ns202p0a0u0"] = NewStage("sl1Ns202p0a0u0")
-
- // Guild Hall LV2
- s.stages["sl1Ns203p0a0u0"] = NewStage("sl1Ns203p0a0u0")
-
- // Guild Hall LV3
- s.stages["sl1Ns204p0a0u0"] = NewStage("sl1Ns204p0a0u0")
-
- // Pugi Farm
- s.stages["sl1Ns205p0a0u0"] = NewStage("sl1Ns205p0a0u0")
-
// Rasta bar stage
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
// Pallone Carvan
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
- // Gook Farm
- s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0")
+ // Pallone Guest House 1st Floor
+ s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0")
+
+ // Pallone Guest House 2nd Floor
+ s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0")
// Diva fountain / prayer fountain.
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
- // Diva Hall
- s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0")
-
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
@@ -367,26 +337,18 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
- message := fmt.Sprintf("**%s** : %s", charName, content)
+ message := fmt.Sprintf("**%s**: %s", charName, content)
s.discordBot.RealtimeChannelSend(message)
}
}
func (s *Server) FindSessionByCharID(charID uint32) *Session {
for _, c := range s.Channels {
- c.stagesLock.RLock()
- for _, stage := range c.stages {
- stage.RLock()
- for client := range stage.clients {
- if client.charID == charID {
- stage.RUnlock()
- c.stagesLock.RUnlock()
- return client
- }
+ for _, session := range c.sessions {
+ if session.charID == charID {
+ return session
}
- stage.RUnlock()
}
- c.stagesLock.RUnlock()
}
return nil
}
@@ -413,6 +375,9 @@ func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
+ if s.semaphoreIndex == 0 {
+ s.semaphoreIndex = 7 // Skip reserved indexes
+ }
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
diff --git a/server/channelserver/sys_stage.go b/server/channelserver/sys_stage.go
index 9a968bf3f..827b2d6be 100644
--- a/server/channelserver/sys_stage.go
+++ b/server/channelserver/sys_stage.go
@@ -98,35 +98,6 @@ func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0
}
-func (s *Stage) GetName() string {
- switch s.id {
- case MezeportaStageId:
- return "Mezeporta"
- case GuildHallLv1StageId:
- return "Guild Hall Lv1"
- case GuildHallLv2StageId:
- return "Guild Hall Lv2"
- case GuildHallLv3StageId:
- return "Guild Hall Lv3"
- case PugiFarmStageId:
- return "Pugi Farm"
- case RastaBarStageId:
- return "Rasta Bar"
- case PalloneCaravanStageId:
- return "Pallone Caravan"
- case GookFarmStageId:
- return "Gook Farm"
- case DivaFountainStageId:
- return "Diva Fountain"
- case DivaHallStageId:
- return "Diva Hall"
- case MezFesStageId:
- return "Mez Fes"
- default:
- return ""
- }
-}
-
func (s *Stage) NextObjectID() uint32 {
s.objectIndex = s.objectIndex + 1
// Objects beyond 127 do not duplicate correctly
diff --git a/server/channelserver/sys_timefix.go b/server/channelserver/sys_timefix.go
index 839382053..4cde6a319 100644
--- a/server/channelserver/sys_timefix.go
+++ b/server/channelserver/sys_timefix.go
@@ -31,6 +31,16 @@ func Time_Current_Midnight() time.Time {
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
}
+func TimeWeekStart() time.Time {
+ midnight := Time_Current_Midnight()
+ offset := (int(midnight.Weekday()) - 1) * -24
+ return midnight.Add(time.Hour * time.Duration(offset))
+}
+
+func TimeWeekNext() time.Time {
+ return TimeWeekStart().Add(time.Hour * 24 * 7)
+}
+
func Time_Current_Week_uint8() uint8 {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
diff --git a/server/discordbot/discord_bot.go b/server/discordbot/discord_bot.go
index df817d50a..fc5c41ce8 100644
--- a/server/discordbot/discord_bot.go
+++ b/server/discordbot/discord_bot.go
@@ -1,11 +1,10 @@
package discordbot
import (
- "regexp"
-
"erupe-ce/config"
"github.com/bwmarrin/discordgo"
"go.uber.org/zap"
+ "regexp"
)
type DiscordBot struct {
@@ -16,12 +15,12 @@ type DiscordBot struct {
RealtimeChannel *discordgo.Channel
}
-type DiscordBotOptions struct {
+type Options struct {
Config *config.Config
Logger *zap.Logger
}
-func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error) {
+func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) {
session, err := discordgo.New("Bot " + options.Config.Discord.BotToken)
if err != nil {
@@ -29,13 +28,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
return nil, err
}
- mainGuild, err := session.Guild(options.Config.Discord.ServerID)
-
- if err != nil {
- options.Logger.Fatal("Discord failed to get main guild", zap.Error(err))
- return nil, err
- }
-
realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
if err != nil {
@@ -47,7 +39,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
config: options.Config,
logger: options.Logger,
Session: session,
- MainGuild: mainGuild,
RealtimeChannel: realtimeChannel,
}
@@ -60,21 +51,10 @@ func (bot *DiscordBot) Start() (err error) {
return
}
-func (bot *DiscordBot) FindRoleByID(id string) *discordgo.Role {
- for _, role := range bot.MainGuild.Roles {
- if role.ID == id {
- return role
- }
- }
-
- return nil
-}
-
// Replace all mentions to real name from the message.
func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
emojiRegex := regexp.MustCompile(`(?:)?`)
- roleRegex := regexp.MustCompile(`<@&(\d{17,19})>`)
result := ReplaceTextAll(message, userRegex, func(userId string) string {
user, err := bot.Session.User(userId)
@@ -90,17 +70,7 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
return ":" + emojiName + ":"
})
- result = ReplaceTextAll(result, roleRegex, func(roleId string) string {
- role := bot.FindRoleByID(roleId)
-
- if role != nil {
- return "@!" + role.Name
- }
-
- return "@!unknown"
- })
-
- return string(result)
+ return result
}
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {
diff --git a/server/signserver/dbutils.go b/server/signserver/dbutils.go
index 8f1b89a01..d39d96c92 100644
--- a/server/signserver/dbutils.go
+++ b/server/signserver/dbutils.go
@@ -29,7 +29,7 @@ func (s *Server) newUserChara(username string) error {
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login)
- VALUES($1, False, True, '', '', 1, 0, 0, $2)`,
+ VALUES($1, False, True, '', '', 0, 0, 0, $2)`,
id,
uint32(time.Now().Unix()),
)
@@ -47,7 +47,7 @@ func (s *Server) registerDBAccount(username string, password string) error {
return err
}
- _, err = s.db.Exec("INSERT INTO users (username, password) VALUES ($1, $2)", username, string(passwordHash))
+ _, err = s.db.Exec("INSERT INTO users (username, password, return_expires) VALUES ($1, $2, $3)", username, string(passwordHash), time.Now().Add(time.Hour*24*30))
if err != nil {
return err
}
@@ -88,13 +88,30 @@ type character struct {
func (s *Server) getCharactersForUser(uid int) ([]character, error) {
characters := make([]character, 0)
- err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false", uid)
+ err := s.db.Select(&characters, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE user_id = $1 AND deleted = false ORDER BY id ASC", uid)
if err != nil {
return nil, err
}
return characters, nil
}
+func (s *Server) getReturnExpiry(uid int) time.Time {
+ var returnExpiry, lastLogin time.Time
+ s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
+ if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
+ returnExpiry = time.Now().Add(time.Hour * 24 * 30)
+ s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
+ } else {
+ err := s.db.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid)
+ if err != nil {
+ returnExpiry = time.Now()
+ s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
+ }
+ }
+ s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid)
+ return returnExpiry
+}
+
func (s *Server) getLastCID(uid int) uint32 {
var lastPlayed uint32
_ = s.db.QueryRow("SELECT last_character FROM users WHERE id=$1", uid).Scan(&lastPlayed)
diff --git a/server/signserver/dsgn_resp.go b/server/signserver/dsgn_resp.go
index 650188000..a169d93a1 100644
--- a/server/signserver/dsgn_resp.go
+++ b/server/signserver/dsgn_resp.go
@@ -28,6 +28,8 @@ func randSeq(n int) string {
}
func (s *Session) makeSignInResp(uid int) []byte {
+ returnExpiry := s.server.getReturnExpiry(uid)
+
// Get the characters from the DB.
chars, err := s.server.getCharactersForUser(uid)
if err != nil {
@@ -115,15 +117,7 @@ func (s *Session) makeSignInResp(uid int) []byte {
bf.WriteUint8(0x00)
bf.WriteUint32(0xCA110001)
bf.WriteUint32(0x4E200000)
-
- returning := false
- // return course end time
- if returning {
- bf.WriteUint32(uint32(channelserver.Time_Current_Adjusted().Add(30 * 24 * time.Hour).Unix()))
- } else {
- bf.WriteUint32(0)
- }
-
+ bf.WriteUint32(uint32(returnExpiry.Unix()))
bf.WriteUint32(0x00000000)
bf.WriteUint32(0x0A5197DF)
diff --git a/www/erupe/index.html b/www/erupe/index.html
index 57aa1c568..0b130c684 100644
--- a/www/erupe/index.html
+++ b/www/erupe/index.html
@@ -118,11 +118,11 @@