Merge branch 'main' into feature/festa

This commit is contained in:
wish
2022-08-19 21:12:39 +10:00
39 changed files with 1293 additions and 673 deletions

View File

@@ -120,7 +120,11 @@ func CSVAdd(csv string, v int) string {
if len(csv) == 0 { if len(csv) == 0 {
return strconv.Itoa(v) 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 { func CSVRemove(csv string, v int) string {

View File

@@ -28,10 +28,7 @@
"discord": { "discord": {
"enabled": false, "enabled": false,
"bottoken": "", "bottoken": "",
"realtimeChannelID": "", "realtimeChannelID": ""
"serverId": "",
"devRoles": [],
"devMode": false
}, },
"database": { "database": {
"host": "localhost", "host": "localhost",

View File

@@ -65,7 +65,7 @@ func main() {
var discordBot *discordbot.DiscordBot = nil var discordBot *discordbot.DiscordBot = nil
if erupeConfig.Discord.Enabled { if erupeConfig.Discord.Enabled {
bot, err := discordbot.NewDiscordBot(discordbot.DiscordBotOptions{ bot, err := discordbot.NewDiscordBot(discordbot.Options{
Logger: logger, Logger: logger,
Config: erupeConfig, Config: erupeConfig,
}) })
@@ -82,6 +82,7 @@ func main() {
} }
discordBot = bot discordBot = bot
logger.Info("Discord bot is enabled")
} else { } else {
logger.Info("Discord bot is disabled") logger.Info("Discord bot is disabled")
} }
@@ -108,9 +109,11 @@ func main() {
} }
logger.Info("Connected to database") logger.Info("Connected to database")
// Clear existing tokens // Clear stale data
_ = db.MustExec("DELETE FROM sign_sessions") _ = db.MustExec("DELETE FROM sign_sessions")
_ = db.MustExec("DELETE FROM servers") _ = 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. // Clean the DB if the option is on.
if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB { if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB {

View File

@@ -1,15 +1,21 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfAcquireMonthlyItem represents the MSG_MHF_ACQUIRE_MONTHLY_ITEM // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID { func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID {
@@ -18,7 +24,12 @@ func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfAcquireMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfCheckMonthlyItem represents the MSG_MHF_CHECK_MONTHLY_ITEM // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID { func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfCheckMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,16 @@
package mhfpacket package mhfpacket
import ( import (
"errors"
"erupe-ce/common/byteframe"
"erupe-ce/network" "erupe-ce/network"
"erupe-ce/network/clientctx" "erupe-ce/network/clientctx"
"erupe-ce/common/byteframe"
) )
// MsgMhfCheckWeeklyStamp represents the MSG_MHF_CHECK_WEEKLY_STAMP // MsgMhfCheckWeeklyStamp represents the MSG_MHF_CHECK_WEEKLY_STAMP
type MsgMhfCheckWeeklyStamp struct { type MsgMhfCheckWeeklyStamp struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 StampType string
Unk1 bool Unk1 bool
Unk2 uint16 // Hardcoded 0 in the binary Unk2 uint16 // Hardcoded 0 in the binary
} }
@@ -22,7 +23,13 @@ func (m *MsgMhfCheckWeeklyStamp) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() 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.Unk1 = bf.ReadBool()
m.Unk2 = bf.ReadUint16() m.Unk2 = bf.ReadUint16()
return nil 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. // Build builds a binary packet from the current data.
func (m *MsgMhfCheckWeeklyStamp) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfCheckWeeklyStamp) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.AckHandle) return errors.New("NOT IMPLEMENTED")
bf.WriteUint8(m.Unk0)
bf.WriteBool(m.Unk1)
bf.WriteUint16(m.Unk2)
return nil
} }

View File

@@ -1,19 +1,19 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfEnumerateRengokuRanking represents the MSG_MHF_ENUMERATE_RENGOKU_RANKING // MsgMhfEnumerateRengokuRanking represents the MSG_MHF_ENUMERATE_RENGOKU_RANKING
type MsgMhfEnumerateRengokuRanking struct { type MsgMhfEnumerateRengokuRanking struct {
AckHandle uint32 AckHandle uint32
Unk0 uint32 Leaderboard uint32
Unk1 uint16 // Hardcoded 0 in the binary Unk1 uint16 // Hardcoded 0 in the binary
Unk2 uint16 // Hardcoded 00 01 in the binary Unk2 uint16 // Hardcoded 00 01 in the binary
} }
// Opcode returns the ID associated with this packet type. // 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 // Parse parses the packet from binary
func (m *MsgMhfEnumerateRengokuRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgMhfEnumerateRengokuRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint32() m.Leaderboard = bf.ReadUint32()
m.Unk1 = bf.ReadUint16() m.Unk1 = bf.ReadUint16()
m.Unk2 = bf.ReadUint16() m.Unk2 = bf.ReadUint16()
return nil return nil

View File

@@ -1,15 +1,19 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID { func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
@@ -18,7 +22,17 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,20 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfExchangeWeeklyStamp represents the MSG_MHF_EXCHANGE_WEEKLY_STAMP // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID { func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID {
@@ -18,7 +23,17 @@ func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfExchangeWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,22 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/common/stringsupport"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfOperateWarehouse represents the MSG_MHF_OPERATE_WAREHOUSE // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID { func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
@@ -18,7 +25,20 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,22 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfOperationInvGuild represents the MSG_MHF_OPERATION_INV_GUILD // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID { func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID {
@@ -18,7 +25,13 @@ func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfOperationInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfPostCafeDurationBonusReceived represents the MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID { func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID {
@@ -18,7 +21,12 @@ func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfPostCafeDurationBonusReceived) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,18 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/network"
"erupe-ce/network/clientctx"
) )
// MsgMhfStartBoostTime represents the MSG_MHF_START_BOOST_TIME // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfStartBoostTime) Opcode() network.PacketID { func (m *MsgMhfStartBoostTime) Opcode() network.PacketID {
@@ -18,7 +21,9 @@ func (m *MsgMhfStartBoostTime) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfStartBoostTime) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -1,15 +1,28 @@
package mhfpacket package mhfpacket
import ( import (
"errors" "errors"
"erupe-ce/network/clientctx"
"erupe-ce/network"
"erupe-ce/common/byteframe" "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 // 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. // Opcode returns the ID associated with this packet type.
func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID { func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
@@ -18,7 +31,37 @@ func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { 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. // Build builds a binary packet from the current data.

View File

@@ -37,7 +37,7 @@ type ClientRight struct {
// MsgSysUpdateRight represents the MSG_SYS_UPDATE_RIGHT // MsgSysUpdateRight represents the MSG_SYS_UPDATE_RIGHT
type MsgSysUpdateRight struct { type MsgSysUpdateRight struct {
ClientRespAckHandle uint32 // If non-0, requests the client to send back a MSG_SYS_ACK packet with this value. 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 Rights []ClientRight
UnkSize uint16 // Count of some buf up to 0x800 bytes following it. 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. // Build builds a binary packet from the current data.
func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
bf.WriteUint32(m.ClientRespAckHandle) bf.WriteUint32(m.ClientRespAckHandle)
bf.WriteUint32(m.Unk1) bf.WriteUint32(m.Bitfield)
bf.WriteUint16(uint16(len(m.Rights))) bf.WriteUint16(uint16(len(m.Rights)))
bf.WriteUint16(0) bf.WriteUint16(0)
for _, v := range m.Rights { for _, v := range m.Rights {

View File

@@ -2,4 +2,6 @@ BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq; CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
UPDATE characters SET savemercenary=NULL;
END; END;

40
patch-schema/netcafe.sql Normal file
View 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;

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

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

View File

@@ -9,10 +9,6 @@ import (
"io" "io"
"net" "net"
"strings" "strings"
"io/ioutil"
"math/bits"
"math/rand"
"time" "time"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
@@ -20,6 +16,9 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/text/encoding/japanese" "golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform" "golang.org/x/text/transform"
"io/ioutil"
"math/bits"
"math/rand"
) )
// Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet // Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet
@@ -80,7 +79,7 @@ func doAckSimpleFail(s *Session, ackHandle uint32, data []byte) {
func updateRights(s *Session) { func updateRights(s *Session) {
update := &mhfpacket.MsgSysUpdateRight{ update := &mhfpacket.MsgSysUpdateRight{
ClientRespAckHandle: 0, ClientRespAckHandle: 0,
Unk1: s.rights, Bitfield: s.rights,
Rights: []mhfpacket.ClientRight{ Rights: []mhfpacket.ClientRight{
{ {
ID: 1, ID: 1,
@@ -192,8 +191,12 @@ func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {
} }
func logoutPlayer(s *Session) { func logoutPlayer(s *Session) {
delete(s.server.sessions, s.rawConn) if _, exists := s.server.sessions[s.rawConn]; exists {
s.rawConn.Close() 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) _, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL, char_id=NULL WHERE token=$1", s.token)
if err != nil { if err != nil {
@@ -206,13 +209,13 @@ func logoutPlayer(s *Session) {
} }
var timePlayed int var timePlayed int
var sessionTime int
_ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed) _ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed)
sessionTime = int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)
timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed timePlayed += sessionTime
var rpGained int var rpGained int
if s.rights >= 0x40000000 { // N Course
if s.rights > 0x40000000 { // N Course
rpGained = timePlayed / 900 rpGained = timePlayed / 900
timePlayed = timePlayed % 900 timePlayed = timePlayed % 900
} else { } else {
@@ -220,10 +223,10 @@ func logoutPlayer(s *Session) {
timePlayed = timePlayed % 1800 timePlayed = timePlayed % 1800
} }
_, err = s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID) s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
if err != nil { s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID)
panic(err)
} treasureHuntUnregister(s)
if s.stage == nil { if s.stage == nil {
return return
@@ -243,7 +246,6 @@ func logoutPlayer(s *Session) {
removeSessionFromSemaphore(s) removeSessionFromSemaphore(s)
removeSessionFromStage(s) removeSessionFromStage(s)
treasureHuntUnregister(s)
saveData, err := GetCharacterSaveData(s, s.charID) saveData, err := GetCharacterSaveData(s, s.charID)
if err != nil { if err != nil {
@@ -421,6 +423,9 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
} }
for session := range stage.clients { for session := range stage.clients {
count++ 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) sessionStage := stringsupport.UTF8ToSJIS(session.stageID)
sessionName := stringsupport.UTF8ToSJIS(session.Name) sessionName := stringsupport.UTF8ToSJIS(session.Name)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
@@ -433,8 +438,8 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
resp.WriteBytes(make([]byte, 48)) resp.WriteBytes(make([]byte, 48))
resp.WriteNullTerminatedBytes(sessionStage) resp.WriteNullTerminatedBytes(sessionStage)
resp.WriteNullTerminatedBytes(sessionName) resp.WriteNullTerminatedBytes(sessionName)
resp.WriteUint16(999) // HR resp.WriteUint16(hrp)
resp.WriteUint16(999) // GR resp.WriteUint16(gr)
resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk
} }
} }
@@ -443,12 +448,23 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
} }
case 4: // Find Party case 4: // Find Party
bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) bf := byteframe.NewByteFrameFromBytes(pkt.MessageData)
bf.ReadUint8() setting := bf.ReadUint8()
maxResults := bf.ReadUint16() maxResults := bf.ReadUint16()
bf.ReadUint8() bf.Seek(2, 1)
bf.ReadUint8()
partyType := bf.ReadUint16() 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 var stagePrefix string
switch partyType { switch partyType {
case 0: // Public Bar case 0: // Public Bar
@@ -468,31 +484,41 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
break break
} }
if strings.HasPrefix(stage.id, stagePrefix) { 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++ count++
sessionStage := stringsupport.UTF8ToSJIS(stage.id) sessionStage := stringsupport.UTF8ToSJIS(stage.id)
resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4()))
resp.WriteUint16(c.Port) resp.WriteUint16(c.Port)
resp.WriteUint16(0) // Static?
// TODO: This is half right, could be trimmed resp.WriteUint16(0) // Unk
resp.WriteUint16(0)
resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(uint16(len(stage.clients))) resp.WriteUint16(uint16(len(stage.clients)))
resp.WriteUint16(stage.maxPlayers) resp.WriteUint16(stage.maxPlayers)
resp.WriteUint16(0) resp.WriteUint16(0) // Num clients entered from stage
resp.WriteUint16(uint16(len(stage.clients))) resp.WriteUint16(stage.maxPlayers)
// resp.WriteUint8(1) // Static?
resp.WriteUint8(uint8(len(sessionStage) + 1))
resp.WriteUint16(uint16(len(sessionStage) + 1)) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}])))
resp.WriteUint8(1)
resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) 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.WriteNullTerminatedBytes(sessionStage)
resp.WriteBytes([]byte{0x00}) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}])
resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}]) 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.Seek(0, io.SeekStart)
resp.WriteUint16(count) resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
@@ -641,86 +667,57 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) 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 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) { func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp) pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp)
weekCurrentStart := TimeWeekStart()
resp := byteframe.NewByteFrame() weekNextStart := TimeWeekNext()
resp.WriteUint16(0x000E) var total, redeemed, updated uint16
resp.WriteUint16(0x0001) var nextClaim time.Time
resp.WriteUint16(0x0000) err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim)
resp.WriteUint16(0x0000) // 0x0000 stops the vaguely annoying log in pop up if err != nil {
resp.WriteUint32(0) s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart)
resp.WriteUint32(0x5dddcbb3) // Timestamp nextClaim = weekNextStart
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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) { func getGookData(s *Session, cid uint32) (uint16, []byte) {
var data []byte var data []byte

View 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) {}

View File

@@ -165,6 +165,66 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
fmt.Printf("Got chat message: %+v\n", chatMessage) 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 // Set account rights
if strings.HasPrefix(chatMessage.Message, "!rights") { if strings.HasPrefix(chatMessage.Message, "!rights") {
var v uint32 var v uint32
@@ -180,7 +240,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
} }
// Discord integration // 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) s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
} }

View File

@@ -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.Fatal("Failed to update savedata in db", zap.Error(err))
} }
s.logger.Info("Wrote recompressed savedata back to DB.") 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) _, err = s.server.db.Exec("UPDATE characters SET weapon_type=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID)
if err != nil { if err != nil {
@@ -275,8 +275,8 @@ func dumpSaveData(s *Session, data []byte, suffix string) {
if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled { if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled {
return return
} else { } else {
dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name)) 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("%s_", s.Name), fmt.Sprintf("%d_%s_%s%s.bin", s.charID, s.Name, Time_Current().Format("2006-01-02_15.04.05"), suffix)) 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) { if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, os.ModeDir) os.Mkdir(dir, os.ModeDir)
@@ -297,10 +297,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
} }
var data []byte var data []byte
err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data) err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil { if err != nil || len(data) == 0 {
s.logger.Fatal("Failed to get savedata from db", zap.Error(err)) 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) doAckBufSucceed(s, pkt.AckHandle, data)
@@ -310,11 +311,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) {
} }
bf := byteframe.NewByteFrameFromBytes(decompSaveData) bf := byteframe.NewByteFrameFromBytes(decompSaveData)
bf.Seek(88, io.SeekStart) bf.Seek(88, io.SeekStart)
binary1 := bf.ReadNullTerminatedBytes() name := bf.ReadNullTerminatedBytes()
s.server.userBinaryPartsLock.Lock() 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.server.userBinaryPartsLock.Unlock()
s.Name = stringsupport.SJISToUTF8(binary1) s.Name = stringsupport.SJISToUTF8(name)
} }
func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -2,87 +2,45 @@ package channelserver
import ( import (
"fmt" "fmt"
"github.com/bwmarrin/discordgo"
"sort" "sort"
"strings" "strings"
"unicode"
"github.com/bwmarrin/discordgo"
) )
type Character struct { type Player struct {
ID uint32 `db:"id"` CharName string
IsFemale bool `db:"is_female"` QuestID int
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"`
} }
var weapons = []string{ func getPlayerSlice(s *Server) []Player {
"<:gs:970861408227049477>", var p []Player
"<:hbg:970861408281563206>", var questIndex int
"<: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 (s *Server) getCharacterForUser(uid int) (*Character, error) { for _, channel := range s.Channels {
character := Character{} for _, stage := range channel.stages {
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 len(stage.clients) == 0 {
if err != nil { continue
return nil, err }
questID := 0
if stage.isQuest() {
questIndex++
questID = questIndex
}
for client := range stage.clients {
p = append(p, Player{
CharName: client.Name,
QuestID: questID,
})
}
}
} }
return p
return &character, nil
} }
func CountChars(s *Server) string { func getCharacterList(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{}
questEmojis := []string{ questEmojis := []string{
":person_in_lotus_position:",
":white_circle:", ":white_circle:",
":red_circle:", ":red_circle:",
":blue_circle:", ":blue_circle:",
@@ -91,255 +49,41 @@ func getPlayerList(s *Server) ([]ListPlayer, int) {
":purple_circle:", ":purple_circle:",
":yellow_circle:", ":yellow_circle:",
":orange_circle:", ":orange_circle:",
":black_circle:",
} }
bigNameLen := 0 playerSlice := getPlayerSlice(s)
for _, stage := range s.stages { sort.SliceStable(playerSlice, func(i, j int) bool {
if len(stage.clients) == 0 { return playerSlice[i].QuestID < playerSlice[j].QuestID
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
}) })
for _, lp := range listPlayers { message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice))
list += lp.toString(bigNameLen) + "\n" for _, player := range playerSlice {
count++ 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 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. // onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel. // 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 return
} }
// Ignore other channels in devMode paddedName := strings.TrimSpace(strings.Map(func(r rune) rune {
if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID { if r > unicode.MaxASCII {
return return -1
}
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
} }
return r
}, m.Author.Username))
charName := strings.Join(args[1:], " ") for i := 0; i < 8-len(m.Author.Username); i++ {
ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName)) paddedName += " "
return
} }
if commandName == "!debug" && s.isDiscordAdmin(ds, m) { message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
ds.ChannelMessageSend(m.ChannelID, debug(s)) s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
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))
}
} }

View File

@@ -303,34 +303,6 @@ func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) 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 handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -1892,11 +1892,25 @@ func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) 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) { func handleMsgMhfEnumerateInvGuild(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild) pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild)
stubEnumerateNoResults(s, pkt.AckHandle) 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) {} func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -5,11 +5,37 @@ import (
ps "erupe-ce/common/pascalstring" ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport" "erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap" "go.uber.org/zap"
"io" "io"
"time" "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) { func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateInterior) pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID) _, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
@@ -66,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
} }
case 3: case 3:
house := HouseData{} 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) err := row.StructScan(&house)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -341,8 +367,190 @@ func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(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 handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {} 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 handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {} 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))
}

View File

@@ -39,7 +39,7 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePartner) 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) _, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil { if err != nil {
@@ -75,7 +75,7 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi) pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
dumpSaveData(s, pkt.RawDataPayload, "_hunternavi") dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
if pkt.IsDataDiff { if pkt.IsDataDiff {
var data []byte var data []byte
@@ -121,10 +121,30 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata) 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) { func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary) pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
@@ -208,6 +228,7 @@ func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou) pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
dumpSaveData(s, pkt.RawDataPayload, "otomoairou")
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:]) decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
if err != nil { if err != nil {
s.logger.Error("Failed to decompress airou", zap.Error(err)) s.logger.Error("Failed to decompress airou", zap.Error(err))

View File

@@ -11,7 +11,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
s.server.raviente.Lock() s.server.raviente.Lock()
switch pkt.SemaphoreID { switch pkt.SemaphoreID {
case 3: case 4:
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
size := 6 size := 6
for i := 0; i < len(bf.Data())-1; i += size { for i := 0; i < len(bf.Data())-1; i += size {
@@ -49,7 +49,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
} }
resp.WriteUint8(0) resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 4: case 5:
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
size := 6 size := 6
for i := 0; i < len(bf.Data())-1; i += size { for i := 0; i < len(bf.Data())-1; i += size {
@@ -74,7 +74,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
} }
resp.WriteUint8(0) resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 5: case 6:
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
size := 6 size := 6
for i := 0; i < len(bf.Data())-1; i += size { 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() { func (s *Session) notifyRavi() {
var temp mhfpacket.MHFPacket var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame() raviNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 3}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4} temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
raviNotif.WriteUint16(uint16(temp.Opcode())) raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext) temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5} temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
raviNotif.WriteUint16(uint16(temp.Opcode())) raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext) 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. raviNotif.WriteUint16(0x0010) // End it.
sema := getRaviSemaphore(s) sema := getRaviSemaphore(s)
if sema != "" { if sema != "" {
@@ -262,7 +262,7 @@ func (s *Session) notifyRavi() {
func getRaviSemaphore(s *Session) string { func getRaviSemaphore(s *Session) string {
for _, semaphore := range s.server.semaphore { 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 return semaphore.id_semaphore
} }
} }

View File

@@ -1,6 +1,8 @@
package channelserver package channelserver
import ( import (
ps "erupe-ce/common/pascalstring"
"fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
@@ -17,7 +19,19 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
if err != nil { if err != nil {
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err)) 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}) 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) 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) { func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking) 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) { func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank) pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
// What is this for?
resp := byteframe.NewByteFrame() bf := byteframe.NewByteFrame()
resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) bf.WriteUint32(0) // Max stage overall MP rank
bf.WriteUint32(0) // Max RdP overall MP rank
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} }

View File

@@ -45,12 +45,10 @@ func destructEmptySemaphores(s *Session) {
} }
func releaseRaviSemaphore(s *Session, sema *Semaphore) { func releaseRaviSemaphore(s *Session, sema *Semaphore) {
if !strings.HasSuffix(sema.id_semaphore, "5") { delete(sema.reservedClientSlots, s.charID)
delete(sema.reservedClientSlots, s.charID) delete(sema.clients, s)
delete(sema.clients, s) if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 {
} s.logger.Debug("Main raviente semaphore is empty, resetting")
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
s.logger.Debug("Raviente semaphore is empty, resetting")
resetRavi(s) resetRavi(s)
} }
} }
@@ -91,7 +89,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32) suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32)
s.server.semaphore[SemaphoreID] = &Semaphore{ s.server.semaphore[SemaphoreID] = &Semaphore{
id_semaphore: pkt.SemaphoreID, id_semaphore: pkt.SemaphoreID,
id: uint32(suffix), id: uint32(suffix + 1),
clients: make(map[*Session]uint32), clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}), reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: 32, maxPlayers: 32,

View File

@@ -135,7 +135,6 @@ func removeSessionFromStage(s *Session) {
// Remove client from old stage. // Remove client from old stage.
s.stage.Lock() s.stage.Lock()
delete(s.stage.clients, s) delete(s.stage.clients, s)
delete(s.stage.reservedClientSlots, s.charID)
// Delete old stage objects owned by the client. // Delete old stage objects owned by the client.
s.logger.Info("Sending notification to old stage clients") s.logger.Info("Sending notification to old stage clients")
@@ -157,6 +156,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
if s.stageID == "" { if s.stageID == "" {
s.stageMoveStack.Set(pkt.StageID) s.stageMoveStack.Set(pkt.StageID)
} else { } else {
s.stage.reservedClientSlots[s.charID] = false
s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Push(s.stageID)
s.stageMoveStack.Lock() 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. // Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock() s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop() backStage, err := s.stageMoveStack.Pop()
if err != nil { if err != nil {
panic(err) 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) doStageTransfer(s, pkt.AckHandle, backStage)
} }

View File

@@ -2,7 +2,6 @@ package channelserver
import ( import (
"encoding/hex" "encoding/hex"
"erupe-ce/network/mhfpacket" "erupe-ce/network/mhfpacket"
) )
@@ -58,11 +57,3 @@ func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdTacticsLog(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) {}

View File

@@ -15,23 +15,6 @@ import (
"go.uber.org/zap" "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. // Config struct allows configuring the server.
type Config struct { type Config struct {
ID uint16 ID uint16
@@ -80,8 +63,7 @@ type Server struct {
// Discord chat integration // Discord chat integration
discordBot *discordbot.DiscordBot discordBot *discordbot.DiscordBot
name string name string
enable bool
raviente *Raviente raviente *Raviente
} }
@@ -154,43 +136,30 @@ func NewServer(config *Config) *Server {
stages: make(map[string]*Stage), stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte), userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore), semaphore: make(map[string]*Semaphore),
semaphoreIndex: 5, semaphoreIndex: 7,
discordBot: config.DiscordBot, discordBot: config.DiscordBot,
name: config.Name, name: config.Name,
enable: config.Enable,
raviente: NewRaviente(), raviente: NewRaviente(),
} }
// Mezeporta // Mezeporta
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0") 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 // Rasta bar stage
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0") s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
// Pallone Carvan // Pallone Carvan
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0") s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
// Gook Farm // Pallone Guest House 1st Floor
s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0") s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0")
// Pallone Guest House 2nd Floor
s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0")
// Diva fountain / prayer fountain. // Diva fountain / prayer fountain.
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0") s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
// Diva Hall
s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0")
// MezFes // MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0") s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
@@ -367,7 +336,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
func (s *Server) DiscordChannelSend(charName string, content string) { func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil { 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) s.discordBot.RealtimeChannelSend(message)
} }
} }
@@ -413,6 +382,9 @@ func (s *Server) NextSemaphoreID() uint32 {
for { for {
exists := false exists := false
s.semaphoreIndex = s.semaphoreIndex + 1 s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex == 0 {
s.semaphoreIndex = 7 // Skip reserved indexes
}
for _, semaphore := range s.semaphore { for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex { if semaphore.id == s.semaphoreIndex {
exists = true exists = true

View File

@@ -98,35 +98,6 @@ func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0 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 { func (s *Stage) NextObjectID() uint32 {
s.objectIndex = s.objectIndex + 1 s.objectIndex = s.objectIndex + 1
// Objects beyond 127 do not duplicate correctly // Objects beyond 127 do not duplicate correctly

View File

@@ -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()) 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 { func Time_Current_Week_uint8() uint8 {
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust) baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)

View File

@@ -1,11 +1,10 @@
package discordbot package discordbot
import ( import (
"regexp"
"erupe-ce/config" "erupe-ce/config"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"go.uber.org/zap" "go.uber.org/zap"
"regexp"
) )
type DiscordBot struct { type DiscordBot struct {
@@ -16,12 +15,12 @@ type DiscordBot struct {
RealtimeChannel *discordgo.Channel RealtimeChannel *discordgo.Channel
} }
type DiscordBotOptions struct { type Options struct {
Config *config.Config Config *config.Config
Logger *zap.Logger 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) session, err := discordgo.New("Bot " + options.Config.Discord.BotToken)
if err != nil { if err != nil {
@@ -29,13 +28,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
return nil, err 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) realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID)
if err != nil { if err != nil {
@@ -47,7 +39,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error
config: options.Config, config: options.Config,
logger: options.Logger, logger: options.Logger,
Session: session, Session: session,
MainGuild: mainGuild,
RealtimeChannel: realtimeChannel, RealtimeChannel: realtimeChannel,
} }
@@ -60,21 +51,10 @@ func (bot *DiscordBot) Start() (err error) {
return 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. // Replace all mentions to real name from the message.
func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`) userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`) emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
roleRegex := regexp.MustCompile(`<@&(\d{17,19})>`)
result := ReplaceTextAll(message, userRegex, func(userId string) string { result := ReplaceTextAll(message, userRegex, func(userId string) string {
user, err := bot.Session.User(userId) user, err := bot.Session.User(userId)
@@ -90,17 +70,7 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string {
return ":" + emojiName + ":" return ":" + emojiName + ":"
}) })
result = ReplaceTextAll(result, roleRegex, func(roleId string) string { return result
role := bot.FindRoleByID(roleId)
if role != nil {
return "@!" + role.Name
}
return "@!unknown"
})
return string(result)
} }
func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) { func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) {

View File

@@ -118,11 +118,11 @@
</div> </div>
</div> </div>
<div id="launcher_menu" class="unselectable"> <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 class="pastebin btn" onmouseenter="soundSel()" onclick="soundOk(); toggleModal('openLink', 'https://pastebin.com/QqAwZSTC')"></div>
</div> </div>
<div id="footer"> <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 class="link unselectable" onclick="toggleModal('openLink', 'https://discord.gg/CFnzbhQ')"><img src="img/icons/discord.png"></div>
</div> </div>
<div class="grabbable" id="launcher_modal"> <div class="grabbable" id="launcher_modal">