mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-04 09:15:08 +01:00
Merge branch 'main' into feature/quest-enum
This commit is contained in:
21
.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
@@ -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.
|
||||
17
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
11
config.json
11
config.json
@@ -5,16 +5,16 @@
|
||||
"devmode": true,
|
||||
"devmodeoptions": {
|
||||
"serverName" : "",
|
||||
"EnableLauncherServer": false,
|
||||
"hideLoginNotice": false,
|
||||
"loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9 (Patch 1)!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>this wouldn't exist without you.",
|
||||
"loginNotice": "<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe SU9.1 Beta!<BR><BODY><LEFT><SIZE_2><C_5>Erupe is experimental software<C_7>, we are not liable for any<BR><BODY>issues caused by installing the software!<BR><BODY><BR><BODY><C_4>■Report bugs on Discord!<C_7><BR><BODY><BR><BODY><C_4>■Test everything!<C_7><BR><BODY><BR><BODY><C_4>■Don't talk to softlocking NPCs!<C_7><BR><BODY><BR><BODY><C_4>■Fork the code on GitHub!<C_7><BR><BODY><BR><BODY>Thank you to all of the contributors,<BR><BODY><BR><BODY>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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
4
go.mod
4
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
|
||||
)
|
||||
|
||||
42
main.go
42
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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
41
patch-schema/achievements.sql
Normal file
41
patch-schema/achievements.sql
Normal file
@@ -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;
|
||||
311
patch-schema/festa.sql
Normal file
311
patch-schema/festa.sql
Normal file
@@ -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;
|
||||
13
patch-schema/mail-system-messages.sql
Normal file
13
patch-schema/mail-system-messages.sql
Normal file
@@ -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;
|
||||
7
patch-schema/mercenary.sql
Normal file
7
patch-schema/mercenary.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
|
||||
|
||||
UPDATE characters SET savemercenary=NULL;
|
||||
|
||||
END;
|
||||
40
patch-schema/netcafe.sql
Normal file
40
patch-schema/netcafe.sql
Normal file
@@ -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;
|
||||
9
patch-schema/return.sql
Normal file
9
patch-schema/return.sql
Normal file
@@ -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;
|
||||
11
patch-schema/road-leaderboard.sql
Normal file
11
patch-schema/road-leaderboard.sql
Normal file
@@ -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;
|
||||
13
patch-schema/stamps.sql
Normal file
13
patch-schema/stamps.sql
Normal file
@@ -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;
|
||||
11
patch-schema/titles.sql
Normal file
11
patch-schema/titles.sql
Normal file
@@ -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;
|
||||
49
patch-schema/warehouse.sql
Normal file
49
patch-schema/warehouse.sql
Normal file
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
239
server/channelserver/handlers_cafe.go
Normal file
239
server/channelserver/handlers_cafe.go
Normal file
@@ -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) {}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <char name>")
|
||||
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 <stage id>")
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
|
||||
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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -118,11 +118,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="launcher_menu" class="unselectable">
|
||||
<div class="manual btn" onmouseenter="soundSel()" onclick="soundOk(); toggleModal('openLink', 'https://mhfz.capcom.com.tw/manuals/')"></div>
|
||||
<div class="manual btn" onmouseenter="soundSel()" onclick="soundOk(); toggleModal('openLink', 'https://zerulight.github.io/MHFZManual/')"></div>
|
||||
<div class="pastebin btn" onmouseenter="soundSel()" onclick="soundOk(); toggleModal('openLink', 'https://pastebin.com/QqAwZSTC')"></div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div class="link unselectable" onclick="toggleModal('openLink', 'https://github.com/xl3lackout/Erupe')"><img src="img/icons/github.png"></div>
|
||||
<div class="link unselectable" onclick="toggleModal('openLink', 'https://github.com/ZeruLight/Erupe')"><img src="img/icons/github.png"></div>
|
||||
<div class="link unselectable" onclick="toggleModal('openLink', 'https://discord.gg/CFnzbhQ')"><img src="img/icons/discord.png"></div>
|
||||
</div>
|
||||
<div class="grabbable" id="launcher_modal">
|
||||
|
||||
Reference in New Issue
Block a user