diff --git a/common/stringsupport/string_convert.go b/common/stringsupport/string_convert.go index 84574375b..ab91311ac 100644 --- a/common/stringsupport/string_convert.go +++ b/common/stringsupport/string_convert.go @@ -120,7 +120,11 @@ func CSVAdd(csv string, v int) string { if len(csv) == 0 { return strconv.Itoa(v) } - return csv + "," + strconv.Itoa(v) + if CSVContains(csv, v) { + return csv + } else { + return csv + "," + strconv.Itoa(v) + } } func CSVRemove(csv string, v int) string { diff --git a/config.json b/config.json index ae9f2a8de..6b9d85944 100644 --- a/config.json +++ b/config.json @@ -28,10 +28,7 @@ "discord": { "enabled": false, "bottoken": "", - "realtimeChannelID": "", - "serverId": "", - "devRoles": [], - "devMode": false + "realtimeChannelID": "" }, "database": { "host": "localhost", diff --git a/main.go b/main.go index d0b13d649..2bc598a06 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ func main() { var discordBot *discordbot.DiscordBot = nil if erupeConfig.Discord.Enabled { - bot, err := discordbot.NewDiscordBot(discordbot.DiscordBotOptions{ + bot, err := discordbot.NewDiscordBot(discordbot.Options{ Logger: logger, Config: erupeConfig, }) @@ -82,6 +82,7 @@ func main() { } discordBot = bot + logger.Info("Discord bot is enabled") } else { logger.Info("Discord bot is disabled") } @@ -108,9 +109,11 @@ func main() { } logger.Info("Connected to database") - // Clear existing tokens + // Clear stale data _ = db.MustExec("DELETE FROM sign_sessions") _ = db.MustExec("DELETE FROM servers") + _ = db.MustExec("DELETE FROM cafe_accepted") + _ = db.MustExec("UPDATE characters SET cafe_time=0") // Clean the DB if the option is on. if erupeConfig.DevMode && erupeConfig.DevModeOptions.CleanDB { diff --git a/network/mhfpacket/msg_mhf_acquire_monthly_item.go b/network/mhfpacket/msg_mhf_acquire_monthly_item.go index a8d8e2f04..acc10b42a 100644 --- a/network/mhfpacket/msg_mhf_acquire_monthly_item.go +++ b/network/mhfpacket/msg_mhf_acquire_monthly_item.go @@ -1,15 +1,21 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfAcquireMonthlyItem represents the MSG_MHF_ACQUIRE_MONTHLY_ITEM -type MsgMhfAcquireMonthlyItem struct{} +type MsgMhfAcquireMonthlyItem struct { + AckHandle uint32 + Unk0 uint16 + Unk1 uint16 + Unk2 uint32 + Unk3 uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID { @@ -18,7 +24,12 @@ func (m *MsgMhfAcquireMonthlyItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfAcquireMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint16() + m.Unk1 = bf.ReadUint16() + m.Unk2 = bf.ReadUint32() + m.Unk3 = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_check_monthly_item.go b/network/mhfpacket/msg_mhf_check_monthly_item.go index 2e04c3226..d79c65240 100644 --- a/network/mhfpacket/msg_mhf_check_monthly_item.go +++ b/network/mhfpacket/msg_mhf_check_monthly_item.go @@ -1,15 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfCheckMonthlyItem represents the MSG_MHF_CHECK_MONTHLY_ITEM -type MsgMhfCheckMonthlyItem struct{} +type MsgMhfCheckMonthlyItem struct { + AckHandle uint32 + Unk uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID { @@ -18,7 +21,9 @@ func (m *MsgMhfCheckMonthlyItem) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfCheckMonthlyItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_check_weekly_stamp.go b/network/mhfpacket/msg_mhf_check_weekly_stamp.go index 83f6e5ce8..f03b1d1e7 100644 --- a/network/mhfpacket/msg_mhf_check_weekly_stamp.go +++ b/network/mhfpacket/msg_mhf_check_weekly_stamp.go @@ -1,15 +1,16 @@ package mhfpacket import ( + "errors" + "erupe-ce/common/byteframe" "erupe-ce/network" "erupe-ce/network/clientctx" - "erupe-ce/common/byteframe" ) // MsgMhfCheckWeeklyStamp represents the MSG_MHF_CHECK_WEEKLY_STAMP type MsgMhfCheckWeeklyStamp struct { AckHandle uint32 - Unk0 uint8 + StampType string Unk1 bool Unk2 uint16 // Hardcoded 0 in the binary } @@ -22,7 +23,13 @@ func (m *MsgMhfCheckWeeklyStamp) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint8() + stampType := bf.ReadUint8() + switch stampType { + case 1: + m.StampType = "hl" + case 2: + m.StampType = "ex" + } m.Unk1 = bf.ReadBool() m.Unk2 = bf.ReadUint16() return nil @@ -30,9 +37,5 @@ func (m *MsgMhfCheckWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.C // Build builds a binary packet from the current data. func (m *MsgMhfCheckWeeklyStamp) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - bf.WriteUint32(m.AckHandle) - bf.WriteUint8(m.Unk0) - bf.WriteBool(m.Unk1) - bf.WriteUint16(m.Unk2) - return nil + return errors.New("NOT IMPLEMENTED") } diff --git a/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go b/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go index d35bc7227..21817237d 100644 --- a/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go +++ b/network/mhfpacket/msg_mhf_enumerate_rengoku_ranking.go @@ -1,19 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateRengokuRanking represents the MSG_MHF_ENUMERATE_RENGOKU_RANKING type MsgMhfEnumerateRengokuRanking struct { - AckHandle uint32 - Unk0 uint32 - Unk1 uint16 // Hardcoded 0 in the binary - Unk2 uint16 // Hardcoded 00 01 in the binary + AckHandle uint32 + Leaderboard uint32 + Unk1 uint16 // Hardcoded 0 in the binary + Unk2 uint16 // Hardcoded 00 01 in the binary } // Opcode returns the ID associated with this packet type. @@ -24,7 +24,7 @@ func (m *MsgMhfEnumerateRengokuRanking) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateRengokuRanking) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { m.AckHandle = bf.ReadUint32() - m.Unk0 = bf.ReadUint32() + m.Leaderboard = bf.ReadUint32() m.Unk1 = bf.ReadUint16() m.Unk2 = bf.ReadUint16() return nil diff --git a/network/mhfpacket/msg_mhf_enumerate_warehouse.go b/network/mhfpacket/msg_mhf_enumerate_warehouse.go index f567e8bcb..3f1358045 100644 --- a/network/mhfpacket/msg_mhf_enumerate_warehouse.go +++ b/network/mhfpacket/msg_mhf_enumerate_warehouse.go @@ -1,15 +1,19 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfEnumerateWarehouse represents the MSG_MHF_ENUMERATE_WAREHOUSE -type MsgMhfEnumerateWarehouse struct{} +type MsgMhfEnumerateWarehouse struct { + AckHandle uint32 + BoxType string + BoxIndex uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID { @@ -18,7 +22,17 @@ func (m *MsgMhfEnumerateWarehouse) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfEnumerateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + boxType := bf.ReadUint8() + switch boxType { + case 0: + m.BoxType = "item" + case 1: + m.BoxType = "equip" + } + m.BoxIndex = bf.ReadUint8() + _ = bf.ReadUint16() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go index 6cce19147..918a870ac 100644 --- a/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go +++ b/network/mhfpacket/msg_mhf_exchange_weekly_stamp.go @@ -1,15 +1,20 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfExchangeWeeklyStamp represents the MSG_MHF_EXCHANGE_WEEKLY_STAMP -type MsgMhfExchangeWeeklyStamp struct{} +type MsgMhfExchangeWeeklyStamp struct { + AckHandle uint32 + StampType string + Unk1 uint8 + Unk2 uint16 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID { @@ -18,7 +23,17 @@ func (m *MsgMhfExchangeWeeklyStamp) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfExchangeWeeklyStamp) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + stampType := bf.ReadUint8() + switch stampType { + case 1: + m.StampType = "hl" + case 2: + m.StampType = "ex" + } + m.Unk1 = bf.ReadUint8() + m.Unk2 = bf.ReadUint16() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_operate_warehouse.go b/network/mhfpacket/msg_mhf_operate_warehouse.go index 9df4e3770..ba0ee7b78 100644 --- a/network/mhfpacket/msg_mhf_operate_warehouse.go +++ b/network/mhfpacket/msg_mhf_operate_warehouse.go @@ -1,15 +1,22 @@ package mhfpacket -import ( - "errors" +import ( + "errors" + "erupe-ce/common/stringsupport" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfOperateWarehouse represents the MSG_MHF_OPERATE_WAREHOUSE -type MsgMhfOperateWarehouse struct{} +type MsgMhfOperateWarehouse struct { + AckHandle uint32 + Operation uint8 + BoxType string + BoxIndex uint8 + Name string +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID { @@ -18,7 +25,20 @@ func (m *MsgMhfOperateWarehouse) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfOperateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Operation = bf.ReadUint8() + boxType := bf.ReadUint8() + switch boxType { + case 0: + m.BoxType = "item" + case 1: + m.BoxType = "equip" + } + m.BoxIndex = bf.ReadUint8() + _ = bf.ReadUint8() // lenName + _ = bf.ReadUint16() // Unk + m.Name = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_operation_inv_guild.go b/network/mhfpacket/msg_mhf_operation_inv_guild.go index b07311c9d..bcd0b45b8 100644 --- a/network/mhfpacket/msg_mhf_operation_inv_guild.go +++ b/network/mhfpacket/msg_mhf_operation_inv_guild.go @@ -1,15 +1,22 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfOperationInvGuild represents the MSG_MHF_OPERATION_INV_GUILD -type MsgMhfOperationInvGuild struct{} +type MsgMhfOperationInvGuild struct { + AckHandle uint32 + Operation uint8 + ActiveHours uint8 + DaysActive uint8 + PlayStyle uint8 + GuildRequest uint8 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID { @@ -18,7 +25,13 @@ func (m *MsgMhfOperationInvGuild) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfOperationInvGuild) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Operation = bf.ReadUint8() + m.ActiveHours = bf.ReadUint8() + m.DaysActive = bf.ReadUint8() + m.PlayStyle = bf.ReadUint8() + m.GuildRequest = bf.ReadUint8() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go b/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go index c89159c0c..baa102f61 100644 --- a/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go +++ b/network/mhfpacket/msg_mhf_post_cafe_duration_bonus_received.go @@ -1,15 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfPostCafeDurationBonusReceived represents the MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED -type MsgMhfPostCafeDurationBonusReceived struct{} +type MsgMhfPostCafeDurationBonusReceived struct { + AckHandle uint32 + CafeBonusID []uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID { @@ -18,7 +21,12 @@ func (m *MsgMhfPostCafeDurationBonusReceived) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfPostCafeDurationBonusReceived) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + ids := int(bf.ReadUint32()) + for i := 0; i < ids; i++ { + m.CafeBonusID = append(m.CafeBonusID, bf.ReadUint32()) + } + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_start_boost_time.go b/network/mhfpacket/msg_mhf_start_boost_time.go index ff424aca9..583ab3ab0 100644 --- a/network/mhfpacket/msg_mhf_start_boost_time.go +++ b/network/mhfpacket/msg_mhf_start_boost_time.go @@ -1,15 +1,18 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfStartBoostTime represents the MSG_MHF_START_BOOST_TIME -type MsgMhfStartBoostTime struct{} +type MsgMhfStartBoostTime struct { + AckHandle uint32 + Unk0 uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfStartBoostTime) Opcode() network.PacketID { @@ -18,7 +21,9 @@ func (m *MsgMhfStartBoostTime) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfStartBoostTime) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_mhf_update_warehouse.go b/network/mhfpacket/msg_mhf_update_warehouse.go index ba0321910..962906988 100644 --- a/network/mhfpacket/msg_mhf_update_warehouse.go +++ b/network/mhfpacket/msg_mhf_update_warehouse.go @@ -1,15 +1,28 @@ package mhfpacket -import ( - "errors" - - "erupe-ce/network/clientctx" - "erupe-ce/network" +import ( + "errors" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) +type WarehouseStack struct { + ID uint32 + Index uint16 + EquipType uint16 + ItemID uint16 + Quantity uint16 + Data []byte +} + // MsgMhfUpdateWarehouse represents the MSG_MHF_UPDATE_WAREHOUSE -type MsgMhfUpdateWarehouse struct{} +type MsgMhfUpdateWarehouse struct { + AckHandle uint32 + BoxType string + BoxIndex uint8 + Updates []WarehouseStack +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID { @@ -18,7 +31,37 @@ func (m *MsgMhfUpdateWarehouse) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUpdateWarehouse) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + boxType := bf.ReadUint8() + switch boxType { + case 0: + m.BoxType = "item" + case 1: + m.BoxType = "equip" + } + m.BoxIndex = bf.ReadUint8() + changes := int(bf.ReadUint16()) + var stackUpdate WarehouseStack + for i := 0; i < changes; i++ { + switch boxType { + case 0: + stackUpdate.ID = bf.ReadUint32() + stackUpdate.Index = bf.ReadUint16() + stackUpdate.ItemID = bf.ReadUint16() + stackUpdate.Quantity = bf.ReadUint16() + _ = bf.ReadUint16() // Unk + m.Updates = append(m.Updates, stackUpdate) + case 1: + stackUpdate.ID = bf.ReadUint32() + stackUpdate.Index = bf.ReadUint16() + stackUpdate.EquipType = bf.ReadUint16() + stackUpdate.ItemID = bf.ReadUint16() + stackUpdate.Data = bf.ReadBytes(56) + m.Updates = append(m.Updates, stackUpdate) + } + } + _ = bf.ReadUint16() + return nil } // Build builds a binary packet from the current data. diff --git a/network/mhfpacket/msg_sys_update_right.go b/network/mhfpacket/msg_sys_update_right.go index 3a7c95f4e..b343dd0c4 100644 --- a/network/mhfpacket/msg_sys_update_right.go +++ b/network/mhfpacket/msg_sys_update_right.go @@ -37,7 +37,7 @@ type ClientRight struct { // MsgSysUpdateRight represents the MSG_SYS_UPDATE_RIGHT type MsgSysUpdateRight struct { ClientRespAckHandle uint32 // If non-0, requests the client to send back a MSG_SYS_ACK packet with this value. - Unk1 uint32 + Bitfield uint32 Rights []ClientRight UnkSize uint16 // Count of some buf up to 0x800 bytes following it. } @@ -55,7 +55,7 @@ func (m *MsgSysUpdateRight) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client // Build builds a binary packet from the current data. func (m *MsgSysUpdateRight) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { bf.WriteUint32(m.ClientRespAckHandle) - bf.WriteUint32(m.Unk1) + bf.WriteUint32(m.Bitfield) bf.WriteUint16(uint16(len(m.Rights))) bf.WriteUint16(0) for _, v := range m.Rights { diff --git a/patch-schema/mercenary.sql b/patch-schema/mercenary.sql index f67e4fe4e..9000db9f3 100644 --- a/patch-schema/mercenary.sql +++ b/patch-schema/mercenary.sql @@ -2,4 +2,6 @@ BEGIN; CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq; +UPDATE characters SET savemercenary=NULL; + END; \ No newline at end of file diff --git a/patch-schema/netcafe.sql b/patch-schema/netcafe.sql new file mode 100644 index 000000000..e284742ce --- /dev/null +++ b/patch-schema/netcafe.sql @@ -0,0 +1,40 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.characters + ADD COLUMN IF NOT EXISTS cafe_time integer DEFAULT 0; + +ALTER TABLE IF EXISTS public.characters + DROP COLUMN IF EXISTS netcafe_points; + +ALTER TABLE IF EXISTS public.characters + ADD COLUMN IF NOT EXISTS netcafe_points int DEFAULT 0; + +ALTER TABLE IF EXISTS public.characters + ADD COLUMN IF NOT EXISTS boost_time timestamp without time zone; + +CREATE TABLE IF NOT EXISTS public.cafebonus +( + id serial NOT NULL PRIMARY KEY, + time_req integer NOT NULL, + item_type integer NOT NULL, + item_id integer NOT NULL, + quantity integer NOT NULL +); + +CREATE TABLE IF NOT EXISTS public.cafe_accepted +( + cafe_id integer NOT NULL, + character_id integer NOT NULL +); + +INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity) +VALUES + (1800, 17, 0, 250), + (3600, 17, 0, 500), + (7200, 17, 0, 1000), + (10800, 17, 0, 1500), + (18000, 17, 0, 1750), + (28800, 17, 0, 3000), + (43200, 17, 0, 4000); + +END; \ No newline at end of file diff --git a/patch-schema/road-leaderboard.sql b/patch-schema/road-leaderboard.sql new file mode 100644 index 000000000..8dfea2875 --- /dev/null +++ b/patch-schema/road-leaderboard.sql @@ -0,0 +1,11 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS rengoku_score ( + character_id integer PRIMARY KEY, + max_stages_mp integer, + max_points_mp integer, + max_stages_sp integer, + max_points_sp integer +); + +END; \ No newline at end of file diff --git a/patch-schema/stamps.sql b/patch-schema/stamps.sql new file mode 100644 index 000000000..2b940fa8c --- /dev/null +++ b/patch-schema/stamps.sql @@ -0,0 +1,13 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS public.stamps ( + character_id integer PRIMARY KEY, + hl_total integer DEFAULT 0, + hl_redeemed integer DEFAULT 0, + hl_next timestamp without time zone, + ex_total integer DEFAULT 0, + ex_redeemed integer DEFAULT 0, + ex_next timestamp without time zone +); + +END; \ No newline at end of file diff --git a/patch-schema/warehouse.sql b/patch-schema/warehouse.sql new file mode 100644 index 000000000..2f2a5adde --- /dev/null +++ b/patch-schema/warehouse.sql @@ -0,0 +1,49 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS public.warehouse ( + character_id integer PRIMARY KEY, + item0 bytea, + item1 bytea, + item2 bytea, + item3 bytea, + item4 bytea, + item5 bytea, + item6 bytea, + item7 bytea, + item8 bytea, + item9 bytea, + item10 bytea, + item0name text, + item1name text, + item2name text, + item3name text, + item4name text, + item5name text, + item6name text, + item7name text, + item8name text, + item9name text, + equip0 bytea, + equip1 bytea, + equip2 bytea, + equip3 bytea, + equip4 bytea, + equip5 bytea, + equip6 bytea, + equip7 bytea, + equip8 bytea, + equip9 bytea, + equip10 bytea, + equip0name text, + equip1name text, + equip2name text, + equip3name text, + equip4name text, + equip5name text, + equip6name text, + equip7name text, + equip8name text, + equip9name text +); + +END; \ No newline at end of file diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 58ea6b401..53e59d25f 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -9,10 +9,6 @@ import ( "io" "net" "strings" - - "io/ioutil" - "math/bits" - "math/rand" "time" "erupe-ce/common/byteframe" @@ -20,6 +16,9 @@ import ( "go.uber.org/zap" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" + "io/ioutil" + "math/bits" + "math/rand" ) // 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) { update := &mhfpacket.MsgSysUpdateRight{ ClientRespAckHandle: 0, - Unk1: s.rights, + Bitfield: s.rights, Rights: []mhfpacket.ClientRight{ { ID: 1, @@ -192,8 +191,12 @@ func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) { } func logoutPlayer(s *Session) { - delete(s.server.sessions, s.rawConn) - s.rawConn.Close() + if _, exists := s.server.sessions[s.rawConn]; exists { + delete(s.server.sessions, s.rawConn) + s.rawConn.Close() + } else { + return // Prevent re-running logout logic on real logouts + } _, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL, char_id=NULL WHERE token=$1", s.token) if err != nil { @@ -206,13 +209,13 @@ func logoutPlayer(s *Session) { } var timePlayed int + var sessionTime int _ = s.server.db.QueryRow("SELECT time_played FROM characters WHERE id = $1", s.charID).Scan(&timePlayed) - - timePlayed = (int(Time_Current_Adjusted().Unix()) - int(s.sessionStart)) + timePlayed + sessionTime = int(Time_Current_Adjusted().Unix()) - int(s.sessionStart) + timePlayed += sessionTime var rpGained int - - if s.rights > 0x40000000 { // N Course + if s.rights >= 0x40000000 { // N Course rpGained = timePlayed / 900 timePlayed = timePlayed % 900 } else { @@ -220,10 +223,10 @@ func logoutPlayer(s *Session) { timePlayed = timePlayed % 1800 } - _, err = s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID) - if err != nil { - panic(err) - } + s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID) + s.server.db.Exec("UPDATE characters SET cafe_time=cafe_time+$1 WHERE id=$2", sessionTime, s.charID) + + treasureHuntUnregister(s) if s.stage == nil { return @@ -243,7 +246,6 @@ func logoutPlayer(s *Session) { removeSessionFromSemaphore(s) removeSessionFromStage(s) - treasureHuntUnregister(s) saveData, err := GetCharacterSaveData(s, s.charID) if err != nil { @@ -421,6 +423,9 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } for session := range stage.clients { count++ + hrp := uint16(1) + gr := uint16(0) + s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr) sessionStage := stringsupport.UTF8ToSJIS(session.stageID) sessionName := stringsupport.UTF8ToSJIS(session.Name) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) @@ -433,8 +438,8 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { resp.WriteBytes(make([]byte, 48)) resp.WriteNullTerminatedBytes(sessionStage) resp.WriteNullTerminatedBytes(sessionName) - resp.WriteUint16(999) // HR - resp.WriteUint16(999) // GR + resp.WriteUint16(hrp) + resp.WriteUint16(gr) resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk } } @@ -443,12 +448,23 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } case 4: // Find Party bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - bf.ReadUint8() + setting := bf.ReadUint8() maxResults := bf.ReadUint16() - bf.ReadUint8() - bf.ReadUint8() + bf.Seek(2, 1) partyType := bf.ReadUint16() - _ = bf.DataFromCurrent() // Restrictions + rankRestriction := uint16(0) + if setting >= 2 { + bf.Seek(2, 1) + rankRestriction = bf.ReadUint16() + } + targets := make([]uint16, 4) + if setting >= 3 { + bf.Seek(1, 1) + lenTargets := int(bf.ReadUint8()) + for i := 0; i < lenTargets; i++ { + targets[i] = bf.ReadUint16() + } + } var stagePrefix string switch partyType { case 0: // Public Bar @@ -468,31 +484,41 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { break } if strings.HasPrefix(stage.id, stagePrefix) { + sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}]) + sb3.Seek(4, 0) + stageRankRestriction := sb3.ReadUint16() + stageTarget := sb3.ReadUint16() + if rankRestriction != 0xFFFF && stageRankRestriction < rankRestriction { + continue + } count++ sessionStage := stringsupport.UTF8ToSJIS(stage.id) resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) resp.WriteUint16(c.Port) - - // TODO: This is half right, could be trimmed - resp.WriteUint16(0) - resp.WriteUint16(uint16(len(stage.clients))) + resp.WriteUint16(0) // Static? + resp.WriteUint16(0) // Unk resp.WriteUint16(uint16(len(stage.clients))) resp.WriteUint16(stage.maxPlayers) - resp.WriteUint16(0) - resp.WriteUint16(uint16(len(stage.clients))) - // - - resp.WriteUint16(uint16(len(sessionStage) + 1)) - resp.WriteUint8(1) + resp.WriteUint16(0) // Num clients entered from stage + resp.WriteUint16(stage.maxPlayers) + resp.WriteUint8(1) // Static? + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}]))) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) - resp.WriteBytes(make([]byte, 16)) + resp.WriteUint16(stageRankRestriction) + resp.WriteUint16(stageTarget) + resp.WriteBytes(make([]byte, 12)) resp.WriteNullTerminatedBytes(sessionStage) - resp.WriteBytes([]byte{0x00}) + resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}]) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}]) } } } } + if (pkt.SearchType == 1 || pkt.SearchType == 3) && count == 0 { + doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) + return + } resp.Seek(0, io.SeekStart) resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) @@ -641,86 +667,57 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } -func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem) - var netcafe_points int - err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafe_points) - if err != nil { - s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err)) - } - resp := byteframe.NewByteFrame() - resp.WriteUint32(uint32(netcafe_points)) - doAckSimpleSucceed(s, pkt.AckHandle, resp.Data()) -} - -func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint) - var netcafe_points int - err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafe_points) - if err != nil { - s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err)) - } - resp := byteframe.NewByteFrame() - resp.WriteUint32(0) - resp.WriteUint32(uint32(netcafe_points)) - doAckSimpleSucceed(s, pkt.AckHandle, resp.Data()) -} - -func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint) - - // I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes - // 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client - // available once after midday every day - - // get next midday - var t = Time_static() - year, month, day := t.Date() - midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location()) - if t.After(midday) { - midday = midday.Add(24 * time.Hour) - } - - // get time after which daily claiming would be valid from db - var dailyTime time.Time - err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime) - if err != nil { - s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err)) - } - - if t.After(dailyTime) { - // +5 netcafe points and setting next valid window - _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points::int + 5 WHERE id=$2", midday, s.charID) - if err != nil { - s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err)) - } - doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01}) - } else { - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - } -} - func handleMsgMhfGetCogInfo(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfCheckMonthlyItem(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfAcquireMonthlyItem(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfCheckWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCheckWeeklyStamp) - - resp := byteframe.NewByteFrame() - resp.WriteUint16(0x000E) - resp.WriteUint16(0x0001) - resp.WriteUint16(0x0000) - resp.WriteUint16(0x0000) // 0x0000 stops the vaguely annoying log in pop up - resp.WriteUint32(0) - resp.WriteUint32(0x5dddcbb3) // Timestamp - - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + weekCurrentStart := TimeWeekStart() + weekNextStart := TimeWeekNext() + var total, redeemed, updated uint16 + var nextClaim time.Time + err := s.server.db.QueryRow(fmt.Sprintf("SELECT %s_next FROM stamps WHERE character_id=$1", pkt.StampType), s.charID).Scan(&nextClaim) + if err != nil { + s.server.db.Exec("INSERT INTO stamps (character_id, hl_next, ex_next) VALUES ($1, $2, $2)", s.charID, weekNextStart) + nextClaim = weekNextStart + } + if nextClaim.Before(weekCurrentStart) { + s.server.db.Exec(fmt.Sprintf("UPDATE stamps SET %s_total=%s_total+1, %s_next=$1 WHERE character_id=$2", pkt.StampType, pkt.StampType, pkt.StampType), weekNextStart, s.charID) + updated = 1 + } + s.server.db.QueryRow(fmt.Sprintf("SELECT %s_total, %s_redeemed FROM stamps WHERE character_id=$1", pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed) + bf := byteframe.NewByteFrame() + bf.WriteUint16(total) + bf.WriteUint16(redeemed) + bf.WriteUint16(updated) + bf.WriteUint32(0) // Unk + bf.WriteUint32(uint32(weekCurrentStart.Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } -func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfExchangeWeeklyStamp(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfExchangeWeeklyStamp) + var total, redeemed uint16 + var tktStack mhfpacket.WarehouseStack + if pkt.Unk1 == 0xA { // Yearly Sub Ex + s.server.db.QueryRow("UPDATE stamps SET hl_total=hl_total-48, hl_redeemed=hl_redeemed-48 WHERE character_id=$1 RETURNING hl_total, hl_redeemed", s.charID).Scan(&total, &redeemed) + tktStack = mhfpacket.WarehouseStack{ItemID: 0x08A2, Quantity: 1} + } else { + s.server.db.QueryRow(fmt.Sprintf("UPDATE stamps SET %s_redeemed=%s_redeemed+8 WHERE character_id=$1 RETURNING %s_total, %s_redeemed", pkt.StampType, pkt.StampType, pkt.StampType, pkt.StampType), s.charID).Scan(&total, &redeemed) + if pkt.StampType == "hl" { + tktStack = mhfpacket.WarehouseStack{ItemID: 0x065E, Quantity: 5} + } else { + tktStack = mhfpacket.WarehouseStack{ItemID: 0x065F, Quantity: 5} + } + } + addWarehouseGift(s, "item", tktStack) + bf := byteframe.NewByteFrame() + bf.WriteUint16(total) + bf.WriteUint16(redeemed) + bf.WriteUint16(0) + bf.WriteUint32(0) // Unk, but has possible values + bf.WriteUint32(uint32(TimeWeekStart().Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} func getGookData(s *Session, cid uint32) (uint16, []byte) { var data []byte diff --git a/server/channelserver/handlers_cafe.go b/server/channelserver/handlers_cafe.go new file mode 100644 index 000000000..c7d826429 --- /dev/null +++ b/server/channelserver/handlers_cafe.go @@ -0,0 +1,239 @@ +package channelserver + +import ( + "erupe-ce/common/byteframe" + ps "erupe-ce/common/pascalstring" + "erupe-ce/network/mhfpacket" + "go.uber.org/zap" + "io" + "time" +) + +func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem) + var netcafePoints uint32 + err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints) + if err != nil { + s.logger.Fatal("Failed to get netcafe points from db", zap.Error(err)) + } + resp := byteframe.NewByteFrame() + resp.WriteUint32(netcafePoints) + doAckSimpleSucceed(s, pkt.AckHandle, resp.Data()) +} + +func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint) + var netcafePoints uint32 + err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints) + if err != nil { + s.logger.Fatal("Failed to get netcate points from db", zap.Error(err)) + } + resp := byteframe.NewByteFrame() + resp.WriteUint32(netcafePoints) + doAckSimpleSucceed(s, pkt.AckHandle, resp.Data()) +} + +func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint) + + // I am not sure exactly what this does, but all responses I have seen include this exact sequence of bytes + // 1 daily, 5 daily halk pots, 3 point boosted quests, also adds 5 netcafe points but not sent to client + // available once after midday every day + + // get next midday + var t = Time_static() + year, month, day := t.Date() + midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location()) + if t.After(midday) { + midday = midday.Add(24 * time.Hour) + } + + // get time after which daily claiming would be valid from db + var dailyTime time.Time + err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime) + if err != nil { + s.logger.Fatal("Failed to get daily_time savedata from db", zap.Error(err)) + } + + if t.After(dailyTime) { + // +5 netcafe points and setting next valid window + _, err := s.server.db.Exec("UPDATE characters SET daily_time=$1, netcafe_points=netcafe_points+5 WHERE id=$2", midday, s.charID) + if err != nil { + s.logger.Fatal("Failed to update daily_time and netcafe_points savedata in db", zap.Error(err)) + } + doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01}) + } else { + doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + } +} + +func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetCafeDuration) + bf := byteframe.NewByteFrame() + + var cafeTime uint32 + err := s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime) + if err != nil { + panic(err) + } + cafeTime = uint32(Time_Current_Adjusted().Unix()) - uint32(s.sessionStart) + cafeTime + bf.WriteUint32(cafeTime) // Total cafe time + bf.WriteUint16(0) + ps.Uint16(bf, "Resets at next maintenance", true) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +type CafeBonus struct { + ID uint32 `db:"id"` + TimeReq uint32 `db:"time_req"` + ItemType uint32 `db:"item_type"` + ItemID uint32 `db:"item_id"` + Quantity uint32 `db:"quantity"` + Claimed bool `db:"claimed"` +} + +func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo) + bf := byteframe.NewByteFrame() + + var count uint32 + rows, err := s.server.db.Queryx(` + SELECT cb.id, time_req, item_type, item_id, quantity, + ( + SELECT count(*) + FROM cafe_accepted ca + WHERE cb.id = ca.cafe_id AND ca.character_id = $1 + )::int::bool AS claimed + FROM cafebonus cb ORDER BY id ASC;`, s.charID) + if err != nil { + s.logger.Error("Error getting cafebonus", zap.Error(err)) + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4)) + } else { + for rows.Next() { + count++ + cafeBonus := &CafeBonus{} + err = rows.StructScan(&cafeBonus) + if err != nil { + s.logger.Error("Error scanning cafebonus", zap.Error(err)) + } + bf.WriteUint32(cafeBonus.TimeReq) + bf.WriteUint32(cafeBonus.ItemType) + bf.WriteUint32(cafeBonus.ItemID) + bf.WriteUint32(cafeBonus.Quantity) + bf.WriteBool(cafeBonus.Claimed) + } + resp := byteframe.NewByteFrame() + resp.WriteUint32(0) + resp.WriteUint32(uint32(time.Now().Unix())) + resp.WriteUint32(count) + resp.WriteBytes(bf.Data()) + doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + } +} + +func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus) + bf := byteframe.NewByteFrame() + var count uint32 + bf.WriteUint32(0) + rows, err := s.server.db.Queryx(` + SELECT c.id, time_req, item_type, item_id, quantity + FROM cafebonus c + WHERE ( + SELECT count(*) + FROM cafe_accepted ca + WHERE c.id = ca.cafe_id AND ca.character_id = $1 + ) < 1 AND ( + SELECT ch.cafe_time + $2 + FROM characters ch + WHERE ch.id = $1 + ) >= time_req`, s.charID, Time_Current_Adjusted().Unix()-s.sessionStart) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + } else { + for rows.Next() { + cafeBonus := &CafeBonus{} + err = rows.StructScan(cafeBonus) + if err != nil { + continue + } + count++ + bf.WriteUint32(cafeBonus.ID) + bf.WriteUint32(cafeBonus.ItemType) + bf.WriteUint32(cafeBonus.ItemID) + bf.WriteUint32(cafeBonus.Quantity) + } + bf.Seek(0, io.SeekStart) + bf.WriteUint32(count) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + } +} + +func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived) + var cafeBonus CafeBonus + for _, cbID := range pkt.CafeBonusID { + err := s.server.db.QueryRow(` + SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1 + `, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity) + if err == nil { + if cafeBonus.ItemType == 17 { + s.server.db.Exec("UPDATE characters SET netcafe_points=netcafe_points+$1 WHERE id=$2", cafeBonus.Quantity, s.charID) + } + } + s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID) + } + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + +func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfStartBoostTime) + bf := byteframe.NewByteFrame() + boostLimit := Time_Current_Adjusted().Add(100 * time.Minute) + s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID) + bf.WriteUint32(uint32(boostLimit.Unix())) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} + +func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetBoostTime) + doAckBufSucceed(s, pkt.AckHandle, []byte{}) +} + +func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit) + bf := byteframe.NewByteFrame() + var boostLimit time.Time + err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit) + if err != nil { + bf.WriteUint32(0) + } else { + bf.WriteUint32(uint32(boostLimit.Unix())) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} + +func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfGetBoostRight) + var boostLimit time.Time + err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit) + if err != nil { + doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) + return + } + if boostLimit.Unix() > Time_Current_Adjusted().Unix() { + doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01}) + } else { + doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02}) + } +} + +func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn) + doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) +} + +func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {} + +func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index bf0f2f105..35538e0fb 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -165,6 +165,66 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { fmt.Printf("Got chat message: %+v\n", chatMessage) + // Flush all objects and users and reload + if strings.HasPrefix(chatMessage.Message, "!reload") { + sendServerChatMessage(s, "Reloading players...") + var temp mhfpacket.MHFPacket + deleteNotif := byteframe.NewByteFrame() + for _, object := range s.stage.objects { + if object.ownerCharID == s.charID { + continue + } + temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id} + deleteNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(deleteNotif, s.clientContext) + } + for _, session := range s.server.sessions { + if s == session { + continue + } + temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID} + deleteNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(deleteNotif, s.clientContext) + } + deleteNotif.WriteUint16(0x0010) + s.QueueSend(deleteNotif.Data()) + time.Sleep(500 * time.Millisecond) + reloadNotif := byteframe.NewByteFrame() + for _, session := range s.server.sessions { + if s == session { + continue + } + temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID} + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + for i := 0; i < 3; i++ { + temp = &mhfpacket.MsgSysNotifyUserBinary{ + CharID: session.charID, + BinaryType: uint8(i + 1), + } + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + } + } + for _, obj := range s.stage.objects { + if obj.ownerCharID == s.charID { + continue + } + temp = &mhfpacket.MsgSysDuplicateObject{ + ObjID: obj.id, + X: obj.x, + Y: obj.y, + Z: obj.z, + Unk0: 0, + OwnerCharID: obj.ownerCharID, + } + reloadNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(reloadNotif, s.clientContext) + } + reloadNotif.WriteUint16(0x0010) + s.QueueSend(reloadNotif.Data()) + } + // Set account rights if strings.HasPrefix(chatMessage.Message, "!rights") { var v uint32 @@ -180,7 +240,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { } // Discord integration - if chatMessage.Type == binpacket.ChatTypeLocal || chatMessage.Type == binpacket.ChatTypeParty { + if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld { s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message) } diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index e98578da7..8ed55c54a 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -56,7 +56,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { s.logger.Fatal("Failed to update savedata in db", zap.Error(err)) } s.logger.Info("Wrote recompressed savedata back to DB.") - dumpSaveData(s, pkt.RawDataPayload, "") + dumpSaveData(s, pkt.RawDataPayload, "savedata") _, err = s.server.db.Exec("UPDATE characters SET weapon_type=$1 WHERE id=$2", uint16(decompressedData[128789]), s.charID) if err != nil { @@ -275,8 +275,8 @@ func dumpSaveData(s *Session, data []byte, suffix string) { if !s.server.erupeConfig.DevModeOptions.SaveDumps.Enabled { return } else { - dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name)) - path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%s_", s.Name), fmt.Sprintf("%d_%s_%s%s.bin", s.charID, s.Name, Time_Current().Format("2006-01-02_15.04.05"), suffix)) + dir := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name)) + path := filepath.Join(s.server.erupeConfig.DevModeOptions.SaveDumps.OutputDir, fmt.Sprintf("%d_%s", s.charID, s.Name), fmt.Sprintf("%d_%s_%s.bin", s.charID, s.Name, suffix)) if _, err := os.Stat(dir); os.IsNotExist(err) { os.Mkdir(dir, os.ModeDir) @@ -297,10 +297,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { } var data []byte - err := s.server.db.QueryRow("SELECT savedata FROM characters WHERE id = $1", s.charID).Scan(&data) - if err != nil { - s.logger.Fatal("Failed to get savedata from db", zap.Error(err)) + if err != nil || len(data) == 0 { + s.logger.Warn(fmt.Sprintf("Failed to load savedata (CID: %d)", s.charID), zap.Error(err)) + s.rawConn.Close() // Terminate the connection + return } doAckBufSucceed(s, pkt.AckHandle, data) @@ -310,11 +311,11 @@ func handleMsgMhfLoaddata(s *Session, p mhfpacket.MHFPacket) { } bf := byteframe.NewByteFrameFromBytes(decompSaveData) bf.Seek(88, io.SeekStart) - binary1 := bf.ReadNullTerminatedBytes() + name := bf.ReadNullTerminatedBytes() s.server.userBinaryPartsLock.Lock() - s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(binary1, []byte{0x00}...) + s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 1}] = append(name, []byte{0x00}...) s.server.userBinaryPartsLock.Unlock() - s.Name = stringsupport.SJISToUTF8(binary1) + s.Name = stringsupport.SJISToUTF8(name) } func handleMsgMhfSaveScenarioData(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_discord.go b/server/channelserver/handlers_discord.go index 38619e079..1f3398d18 100644 --- a/server/channelserver/handlers_discord.go +++ b/server/channelserver/handlers_discord.go @@ -2,87 +2,45 @@ package channelserver import ( "fmt" + "github.com/bwmarrin/discordgo" "sort" "strings" - - "github.com/bwmarrin/discordgo" + "unicode" ) -type Character struct { - ID uint32 `db:"id"` - IsFemale bool `db:"is_female"` - IsNewCharacter bool `db:"is_new_character"` - Name string `db:"name"` - UnkDescString string `db:"unk_desc_string"` - HRP uint16 `db:"hrp"` - GR uint16 `db:"gr"` - WeaponType uint16 `db:"weapon_type"` - LastLogin uint32 `db:"last_login"` +type Player struct { + CharName string + QuestID int } -var weapons = []string{ - "<:gs:970861408227049477>", - "<:hbg:970861408281563206>", - "<:hm:970861408239628308>", - "<:lc:970861408298352660>", - "<:sns:970861408319315988>", - "<:lbg:970861408327725137>", - "<:ds:970861408277368883>", - "<:ls:970861408319311872>", - "<:hh:970861408222863400>", - "<:gl:970861408327720980>", - "<:bw:970861408294174780>", - "<:tf:970861408424177744>", - "<:sw:970861408340283472>", - "<:ms:970861408411594842>", -} +func getPlayerSlice(s *Server) []Player { + var p []Player + var questIndex int -func (s *Server) getCharacterForUser(uid int) (*Character, error) { - character := Character{} - err := s.db.Get(&character, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE id = $1", uid) - if err != nil { - return nil, err + for _, channel := range s.Channels { + for _, stage := range channel.stages { + if len(stage.clients) == 0 { + continue + } + questID := 0 + if stage.isQuest() { + questIndex++ + questID = questIndex + } + for client := range stage.clients { + p = append(p, Player{ + CharName: client.Name, + QuestID: questID, + }) + } + } } - - return &character, nil + return p } -func CountChars(s *Server) string { - count := 0 - for _, stage := range s.stages { - count += len(stage.clients) - } - - message := fmt.Sprintf("Server [%s]: %d players;", s.name, count) - - return message -} - -type ListPlayer struct { - CharName string - InQuest bool - WeaponEmoji string - QuestEmoji string - StageName string -} - -func (p *ListPlayer) toString(length int) string { - status := "" - if p.InQuest { - status = "(in quest " + p.QuestEmoji + ")" - } else { - status = p.StageName - } - - missingSpace := length - len(p.CharName) - whitespaces := strings.Repeat(" ", missingSpace+5) - - return fmt.Sprintf("%s %s %s %s", p.WeaponEmoji, p.CharName, whitespaces, status) -} - -func getPlayerList(s *Server) ([]ListPlayer, int) { - list := []ListPlayer{} +func getCharacterList(s *Server) string { questEmojis := []string{ + ":person_in_lotus_position:", ":white_circle:", ":red_circle:", ":blue_circle:", @@ -91,255 +49,41 @@ func getPlayerList(s *Server) ([]ListPlayer, int) { ":purple_circle:", ":yellow_circle:", ":orange_circle:", + ":black_circle:", } - bigNameLen := 0 + playerSlice := getPlayerSlice(s) - for _, stage := range s.stages { - if len(stage.clients) == 0 { - continue - } - - questEmoji := ":black_circle:" - - if len(questEmojis) > 0 { - questEmoji = questEmojis[len(questEmojis)-1] - questEmojis = questEmojis[:len(questEmojis)-1] - } - - isQuest := stage.isQuest() - for client := range stage.clients { - char, err := s.getCharacterForUser(int(client.charID)) - if err == nil { - if len(char.Name) > bigNameLen { - bigNameLen = len(char.Name) - } - - list = append(list, ListPlayer{ - CharName: char.Name, - InQuest: isQuest, - QuestEmoji: questEmoji, - WeaponEmoji: weapons[char.WeaponType], - StageName: stage.GetName(), - }) - - } - } - } - - return list, bigNameLen -} - -func PlayerList(s *Server) string { - list := "" - count := 0 - listPlayers, bigNameLen := getPlayerList(s) - - sort.SliceStable(listPlayers, func(i, j int) bool { - return listPlayers[i].CharName < listPlayers[j].CharName + sort.SliceStable(playerSlice, func(i, j int) bool { + return playerSlice[i].QuestID < playerSlice[j].QuestID }) - for _, lp := range listPlayers { - list += lp.toString(bigNameLen) + "\n" - count++ + message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice)) + for _, player := range playerSlice { + message += fmt.Sprintf("%s %s", questEmojis[player.QuestID], player.CharName) } - message := fmt.Sprintf("<:SnS:822963937360347148> Frontier Hunters Online: [%s ] <:switcha:822963906401533992> \n============== Total %d =============\n", s.name, count) - message += list - return message } -func debug(s *Server) string { - list := "" - - for _, stage := range s.stages { - if !stage.isQuest() && len(stage.objects) == 0 { - continue - } - - list += fmt.Sprintf(" -> Stage: %s StageId: %s\n", stage.GetName(), stage.id) - isQuest := "false" - hasDeparted := "false" - - if stage.isQuest() { - isQuest = "true" - } - - list += fmt.Sprintf(" '-> isQuest: %s\n", isQuest) - - if stage.isQuest() { - if len(stage.clients) > 0 { - hasDeparted = "true" - } - - list += fmt.Sprintf(" '-> isDeparted: %s\n", hasDeparted) - list += fmt.Sprintf(" '-> reserveSlots (%d/%d)\n", len(stage.reservedClientSlots), stage.maxPlayers) - - for charid, _ := range stage.reservedClientSlots { - char, err := s.getCharacterForUser(int(charid)) - if err == nil { - list += fmt.Sprintf(" '-> %s\n", char.Name) - } - } - } - - list += " '-> objects: \n" - for _, obj := range stage.objects { - objInfo := fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z) - list += fmt.Sprintf(" '-> ObjectId: %d - %s\n", obj.id, objInfo) - } - } - - message := fmt.Sprintf("Objects in Server: [%s ]\n", s.name) - message += list - - return message -} - -func questlist(s *Server) string { - list := "" - - for _, stage := range s.stages { - if !stage.isQuest() { - continue - } - - hasDeparted := "" - if len(stage.clients) > 0 { - hasDeparted = " - departed" - } - list += fmt.Sprintf(" '-> StageId: %s (%d/%d) %s - %s\n", stage.id, len(stage.reservedClientSlots), stage.maxPlayers, hasDeparted, stage.createdAt) - - for charid, _ := range stage.reservedClientSlots { - char, err := s.getCharacterForUser(int(charid)) - if err == nil { - list += fmt.Sprintf(" '-> %s\n", char.Name) - } - } - } - - message := fmt.Sprintf("Quests in Server: [%s ]\n", s.name) - message += list - - return message -} - -func removeStageById(s *Server, stageId string) string { - if s.stages[stageId] != nil { - delete(s.stages, stageId) - return "Stage deleted!" - } - - return "Stage not found!" -} - -func cleanStr(str string) string { - return strings.ToLower(strings.Trim(str, " ")) -} - -func getCharInfo(server *Server, charName string) string { - var s *Stage - var c *Session - - for _, stage := range server.stages { - for client := range stage.clients { - - if client.Name == "" { - continue - } - - if cleanStr(client.Name) == cleanStr(charName) { - s = stage - c = client - } - - } - } - - if s == nil { - return "Character not found" - } - - objInfo := "" - - obj := server.FindObjectByChar(c.charID) - // server.logger.Info("Found object: %+v", zap.Object("obj", obj)) - - if obj != nil { - objInfo = fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z) - } - - return fmt.Sprintf("Character: %s\nStage: %s\nStageId: %s\n%s", c.Name, s.GetName(), s.id, objInfo) -} - -func (s *Server) isDiscordAdmin(ds *discordgo.Session, m *discordgo.MessageCreate) bool { - for _, role := range m.Member.Roles { - for _, id := range s.erupeConfig.Discord.DevRoles { - if id == role { - return true - } - } - } - - return false -} - // onDiscordMessage handles receiving messages from discord and forwarding them ingame. func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) { // Ignore messages from our bot, or ones that are not in the correct channel. - if m.Author.ID == ds.State.User.ID || !s.enable { + if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID { return } - // Ignore other channels in devMode - if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID { - return - } - - args := strings.Split(m.Content, " ") - commandName := args[0] - - // Move to slash commadns - if commandName == "!players" { - ds.ChannelMessageSend(m.ChannelID, PlayerList(s)) - return - } - - if commandName == "-char" { - if len(args) < 2 { - ds.ChannelMessageSend(m.ChannelID, "Usage: !char ") - return + paddedName := strings.TrimSpace(strings.Map(func(r rune) rune { + if r > unicode.MaxASCII { + return -1 } + return r + }, m.Author.Username)) - charName := strings.Join(args[1:], " ") - ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName)) - return + for i := 0; i < 8-len(m.Author.Username); i++ { + paddedName += " " } - if commandName == "!debug" && s.isDiscordAdmin(ds, m) { - ds.ChannelMessageSend(m.ChannelID, debug(s)) - return - } - - if commandName == "!questlist" && s.isDiscordAdmin(ds, m) { - ds.ChannelMessageSend(m.ChannelID, questlist(s)) - return - } - - if commandName == "!remove-stage" && s.isDiscordAdmin(ds, m) { - if len(args) < 2 { - ds.ChannelMessageSend(m.ChannelID, "Usage: !remove-stage ") - return - } - - stageId := strings.Join(args[1:], " ") - ds.ChannelMessageSend(m.ChannelID, removeStageById(s, stageId)) - return - } - - if m.ChannelID == s.erupeConfig.Discord.RealtimeChannelID { - message := fmt.Sprintf("[DISCORD] %s: %s", m.Author.Username, m.Content) - s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message)) - } + message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content) + s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message)) } diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index af16799c7..7ec7c30e7 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -303,34 +303,6 @@ func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, resp.Data()) } -func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetBoostTime) - - doAckBufSucceed(s, pkt.AckHandle, []byte{}) - updateRights(s) -} - -func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit) - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) -} - -func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfGetBoostRight) - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) -} - -func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) { - pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn) - doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) -} - -func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {} - func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index e87872ca6..de71aa79c 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -1892,11 +1892,25 @@ func handleMsgMhfSetGuildManageRight(s *Session, p mhfpacket.MHFPacket) { 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) { pkt := p.(*mhfpacket.MsgMhfEnumerateInvGuild) stubEnumerateNoResults(s, pkt.AckHandle) } -func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfOperationInvGuild(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfOperationInvGuild) + doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4)) +} func handleMsgMhfUpdateGuildcard(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_house.go b/server/channelserver/handlers_house.go index 401347b76..280f27238 100644 --- a/server/channelserver/handlers_house.go +++ b/server/channelserver/handlers_house.go @@ -5,11 +5,37 @@ import ( ps "erupe-ce/common/pascalstring" "erupe-ce/common/stringsupport" "erupe-ce/network/mhfpacket" + "fmt" "go.uber.org/zap" "io" "time" ) +const warehouseNamesQuery = ` +SELECT +COALESCE(item0name, ''), +COALESCE(item1name, ''), +COALESCE(item2name, ''), +COALESCE(item3name, ''), +COALESCE(item4name, ''), +COALESCE(item5name, ''), +COALESCE(item6name, ''), +COALESCE(item7name, ''), +COALESCE(item8name, ''), +COALESCE(item9name, ''), +COALESCE(equip0name, ''), +COALESCE(equip1name, ''), +COALESCE(equip2name, ''), +COALESCE(equip3name, ''), +COALESCE(equip4name, ''), +COALESCE(equip5name, ''), +COALESCE(equip6name, ''), +COALESCE(equip7name, ''), +COALESCE(equip8name, ''), +COALESCE(equip9name, '') +FROM warehouse +` + func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfUpdateInterior) _, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID) @@ -66,7 +92,7 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) { } case 3: house := HouseData{} - row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name=$1", pkt.Name) + row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name ILIKE $1", fmt.Sprintf(`%%%s%%`, pkt.Name)) err := row.StructScan(&house) if err != nil { panic(err) @@ -341,8 +367,190 @@ func handleMsgMhfAcquireTitle(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)) +} diff --git a/server/channelserver/handlers_mercenary.go b/server/channelserver/handlers_mercenary.go index 37ce9e628..31bc26249 100644 --- a/server/channelserver/handlers_mercenary.go +++ b/server/channelserver/handlers_mercenary.go @@ -39,7 +39,7 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSavePartner) - dumpSaveData(s, pkt.RawDataPayload, "_partner") + dumpSaveData(s, pkt.RawDataPayload, "partner") _, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID) if err != nil { @@ -75,7 +75,7 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi) - dumpSaveData(s, pkt.RawDataPayload, "_hunternavi") + dumpSaveData(s, pkt.RawDataPayload, "hunternavi") if pkt.IsDataDiff { var data []byte @@ -121,10 +121,30 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata) - doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A)) + if pkt.Unk0 == 1 { + // Format: + // uint8 Hunts + // struct Hunt + // uint32 HuntID + // uint32 MonID + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1)) + } else { + doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0)) + } } -func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfEnumerateMercenaryLog) + bf := byteframe.NewByteFrame() + bf.WriteUint32(0) + // Format: + // struct Log + // uint32 Timestamp + // []byte Name (len 18) + // uint8 Unk + // uint8 Unk + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) +} func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfCreateMercenary) @@ -208,6 +228,7 @@ func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou) + dumpSaveData(s, pkt.RawDataPayload, "otomoairou") decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:]) if err != nil { s.logger.Error("Failed to decompress airou", zap.Error(err)) diff --git a/server/channelserver/handlers_register.go b/server/channelserver/handlers_register.go index 3dd65cf6a..2c44cc6db 100644 --- a/server/channelserver/handlers_register.go +++ b/server/channelserver/handlers_register.go @@ -11,7 +11,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) s.server.raviente.Lock() switch pkt.SemaphoreID { - case 3: + case 4: resp := byteframe.NewByteFrame() size := 6 for i := 0; i < len(bf.Data())-1; i += size { @@ -49,7 +49,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { } resp.WriteUint8(0) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 4: + case 5: resp := byteframe.NewByteFrame() size := 6 for i := 0; i < len(bf.Data())-1; i += size { @@ -74,7 +74,7 @@ func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) { } resp.WriteUint8(0) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) - case 5: + case 6: resp := byteframe.NewByteFrame() size := 6 for i := 0; i < len(bf.Data())-1; i += size { @@ -242,15 +242,15 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) { func (s *Session) notifyRavi() { var temp mhfpacket.MHFPacket raviNotif := byteframe.NewByteFrame() - temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 3} - raviNotif.WriteUint16(uint16(temp.Opcode())) - temp.Build(raviNotif, s.clientContext) temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4} raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5} raviNotif.WriteUint16(uint16(temp.Opcode())) temp.Build(raviNotif, s.clientContext) + temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6} + raviNotif.WriteUint16(uint16(temp.Opcode())) + temp.Build(raviNotif, s.clientContext) raviNotif.WriteUint16(0x0010) // End it. sema := getRaviSemaphore(s) if sema != "" { @@ -262,7 +262,7 @@ func (s *Session) notifyRavi() { func getRaviSemaphore(s *Session) string { for _, semaphore := range s.server.semaphore { - if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") { + if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "4") { return semaphore.id_semaphore } } diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index d7b60686b..6fdce7c22 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -1,6 +1,8 @@ package channelserver import ( + ps "erupe-ce/common/pascalstring" + "fmt" "io/ioutil" "path/filepath" @@ -17,7 +19,19 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { if err != nil { s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err)) } - + bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload) + bf.Seek(71, 0) + maxStageMp := bf.ReadUint32() + maxScoreMp := bf.ReadUint32() + bf.Seek(4, 1) + maxStageSp := bf.ReadUint32() + maxScoreSp := bf.ReadUint32() + var t int + err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t) + if err != nil { + s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID) + } + s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID) doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) } @@ -81,16 +95,191 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) { doAckBufSucceed(s, pkt.AckHandle, data) } +const rengokuScoreQuery = ` +SELECT max_stages_mp, max_points_mp, max_stages_sp, max_points_sp, c.name, gc.guild_id +FROM rengoku_score rs +LEFT JOIN characters c ON c.id = rs.character_id +LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id +` + +type RengokuScore struct { + Name string `db:"name"` + GuildID int `db:"guild_id"` + MaxStagesMP uint32 `db:"max_stages_mp"` + MaxPointsMP uint32 `db:"max_points_mp"` + MaxStagesSP uint32 `db:"max_stages_sp"` + MaxPointsSP uint32 `db:"max_points_sp"` +} + func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking) - doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + + guild, _ := GetGuildInfoByCharacterId(s, s.charID) + isApplicant, _ := guild.HasApplicationForCharID(s, s.charID) + if isApplicant { + guild = nil + } + + var score RengokuScore + i := uint32(1) + bf := byteframe.NewByteFrame() + scoreData := byteframe.NewByteFrame() + switch pkt.Leaderboard { + case 0: // Max stage overall MP + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_mp DESC", rengokuScoreQuery)) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxStagesMP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxStagesMP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + case 1: // Max RdP overall MP + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_mp DESC", rengokuScoreQuery)) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxPointsMP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxPointsMP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + case 2: // Max stage guild MP + if guild != nil { + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxStagesMP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxStagesMP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + } else { + bf.WriteBytes(make([]byte, 11)) + } + case 3: // Max RdP guild MP + if guild != nil { + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxPointsMP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxPointsMP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + } else { + bf.WriteBytes(make([]byte, 11)) + } + case 4: // Max stage overall SP + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_stages_sp DESC", rengokuScoreQuery)) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxStagesSP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxStagesSP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + case 5: // Max RdP overall SP + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s ORDER BY max_points_sp DESC", rengokuScoreQuery)) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxPointsSP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxPointsSP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + case 6: // Max stage guild SP + if guild != nil { + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxStagesSP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxStagesSP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + } else { + bf.WriteBytes(make([]byte, 11)) + } + case 7: // Max RdP guild SP + if guild != nil { + rows, _ := s.server.db.Queryx(fmt.Sprintf("%s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID) + for rows.Next() { + rows.StructScan(&score) + if score.Name == s.Name { + bf.WriteUint32(i) + bf.WriteUint32(score.MaxPointsSP) + ps.Uint8(bf, s.Name, true) + ps.Uint8(bf, "", false) + } + scoreData.WriteUint32(i) + scoreData.WriteUint32(score.MaxPointsSP) + ps.Uint8(scoreData, score.Name, true) + ps.Uint8(scoreData, "", false) + i++ + } + } else { + bf.WriteBytes(make([]byte, 11)) + } + } + bf.WriteUint8(uint8(i) - 1) + bf.WriteBytes(scoreData.Data()) + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank) - - resp := byteframe.NewByteFrame() - resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) - - doAckBufSucceed(s, pkt.AckHandle, resp.Data()) + // What is this for? + bf := byteframe.NewByteFrame() + bf.WriteUint32(0) // Max stage overall MP rank + bf.WriteUint32(0) // Max RdP overall MP rank + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } diff --git a/server/channelserver/handlers_semaphore.go b/server/channelserver/handlers_semaphore.go index 95af79314..cbf1a0a7f 100644 --- a/server/channelserver/handlers_semaphore.go +++ b/server/channelserver/handlers_semaphore.go @@ -45,12 +45,10 @@ func destructEmptySemaphores(s *Session) { } func releaseRaviSemaphore(s *Session, sema *Semaphore) { - if !strings.HasSuffix(sema.id_semaphore, "5") { - delete(sema.reservedClientSlots, s.charID) - delete(sema.clients, s) - } - if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 { - s.logger.Debug("Raviente semaphore is empty, resetting") + delete(sema.reservedClientSlots, s.charID) + delete(sema.clients, s) + if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 { + s.logger.Debug("Main raviente semaphore is empty, resetting") resetRavi(s) } } @@ -91,7 +89,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) { suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32) s.server.semaphore[SemaphoreID] = &Semaphore{ id_semaphore: pkt.SemaphoreID, - id: uint32(suffix), + id: uint32(suffix + 1), clients: make(map[*Session]uint32), reservedClientSlots: make(map[uint32]interface{}), maxPlayers: 32, diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index 4bf19d114..906480be7 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -135,7 +135,6 @@ func removeSessionFromStage(s *Session) { // Remove client from old stage. s.stage.Lock() delete(s.stage.clients, s) - delete(s.stage.reservedClientSlots, s.charID) // Delete old stage objects owned by the client. s.logger.Info("Sending notification to old stage clients") @@ -157,6 +156,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { if s.stageID == "" { s.stageMoveStack.Set(pkt.StageID) } else { + s.stage.reservedClientSlots[s.charID] = false s.stageMoveStack.Push(s.stageID) s.stageMoveStack.Lock() } @@ -175,11 +175,18 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { // Transfer back to the saved stage ID before the previous move or enter. s.stageMoveStack.Unlock() backStage, err := s.stageMoveStack.Pop() - if err != nil { panic(err) } + if _, exists := s.stage.reservedClientSlots[s.charID]; exists { + delete(s.stage.reservedClientSlots, s.charID) + } + + if _, exists := s.server.stages[backStage].reservedClientSlots[s.charID]; exists { + delete(s.server.stages[backStage].reservedClientSlots, s.charID) + } + doStageTransfer(s, pkt.AckHandle, backStage) } diff --git a/server/channelserver/handlers_tactics.go b/server/channelserver/handlers_tactics.go index 585c0ef79..4991ce846 100644 --- a/server/channelserver/handlers_tactics.go +++ b/server/channelserver/handlers_tactics.go @@ -2,7 +2,6 @@ package channelserver import ( "encoding/hex" - "erupe-ce/network/mhfpacket" ) @@ -58,11 +57,3 @@ func handleMsgMhfGetUdTacticsRanking(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfSetUdTacticsFollower(s *Session, p mhfpacket.MHFPacket) {} func handleMsgMhfGetUdTacticsLog(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {} - -func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index a23ec2834..6b2d46f3c 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -15,23 +15,6 @@ import ( "go.uber.org/zap" ) -type StageIdType = string - -const ( - // GlobalStage is the stage that is used for all users. - MezeportaStageId StageIdType = "sl1Ns200p0a0u0" - GuildHallLv1StageId StageIdType = "sl1Ns202p0a0u0" - GuildHallLv2StageId StageIdType = "sl1Ns203p0a0u0" - GuildHallLv3StageId StageIdType = "sl1Ns204p0a0u0" - PugiFarmStageId StageIdType = "sl1Ns205p0a0u0" - RastaBarStageId StageIdType = "sl1Ns211p0a0u0" - PalloneCaravanStageId StageIdType = "sl1Ns260p0a0u0" - GookFarmStageId StageIdType = "sl1Ns265p0a0u0" - DivaFountainStageId StageIdType = "sl2Ns379p0a0u0" - DivaHallStageId StageIdType = "sl1Ns445p0a0u0" - MezFesStageId StageIdType = "sl1Ns462p0a0u0" -) - // Config struct allows configuring the server. type Config struct { ID uint16 @@ -80,8 +63,7 @@ type Server struct { // Discord chat integration discordBot *discordbot.DiscordBot - name string - enable bool + name string raviente *Raviente } @@ -154,43 +136,30 @@ func NewServer(config *Config) *Server { stages: make(map[string]*Stage), userBinaryParts: make(map[userBinaryPartID][]byte), semaphore: make(map[string]*Semaphore), - semaphoreIndex: 5, + semaphoreIndex: 7, discordBot: config.DiscordBot, name: config.Name, - enable: config.Enable, raviente: NewRaviente(), } // Mezeporta s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0") - // Guild Hall LV1 - s.stages["sl1Ns202p0a0u0"] = NewStage("sl1Ns202p0a0u0") - - // Guild Hall LV2 - s.stages["sl1Ns203p0a0u0"] = NewStage("sl1Ns203p0a0u0") - - // Guild Hall LV3 - s.stages["sl1Ns204p0a0u0"] = NewStage("sl1Ns204p0a0u0") - - // Pugi Farm - s.stages["sl1Ns205p0a0u0"] = NewStage("sl1Ns205p0a0u0") - // Rasta bar stage s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0") // Pallone Carvan s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0") - // Gook Farm - s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0") + // Pallone Guest House 1st Floor + s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0") + + // Pallone Guest House 2nd Floor + s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0") // Diva fountain / prayer fountain. s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0") - // Diva Hall - s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0") - // MezFes s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0") @@ -367,7 +336,7 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u func (s *Server) DiscordChannelSend(charName string, content string) { if s.erupeConfig.Discord.Enabled && s.discordBot != nil { - message := fmt.Sprintf("**%s** : %s", charName, content) + message := fmt.Sprintf("**%s**: %s", charName, content) s.discordBot.RealtimeChannelSend(message) } } @@ -413,6 +382,9 @@ func (s *Server) NextSemaphoreID() uint32 { for { exists := false s.semaphoreIndex = s.semaphoreIndex + 1 + if s.semaphoreIndex == 0 { + s.semaphoreIndex = 7 // Skip reserved indexes + } for _, semaphore := range s.semaphore { if semaphore.id == s.semaphoreIndex { exists = true diff --git a/server/channelserver/sys_stage.go b/server/channelserver/sys_stage.go index 9a968bf3f..827b2d6be 100644 --- a/server/channelserver/sys_stage.go +++ b/server/channelserver/sys_stage.go @@ -98,35 +98,6 @@ func (s *Stage) isQuest() bool { return len(s.reservedClientSlots) > 0 } -func (s *Stage) GetName() string { - switch s.id { - case MezeportaStageId: - return "Mezeporta" - case GuildHallLv1StageId: - return "Guild Hall Lv1" - case GuildHallLv2StageId: - return "Guild Hall Lv2" - case GuildHallLv3StageId: - return "Guild Hall Lv3" - case PugiFarmStageId: - return "Pugi Farm" - case RastaBarStageId: - return "Rasta Bar" - case PalloneCaravanStageId: - return "Pallone Caravan" - case GookFarmStageId: - return "Gook Farm" - case DivaFountainStageId: - return "Diva Fountain" - case DivaHallStageId: - return "Diva Hall" - case MezFesStageId: - return "Mez Fes" - default: - return "" - } -} - func (s *Stage) NextObjectID() uint32 { s.objectIndex = s.objectIndex + 1 // Objects beyond 127 do not duplicate correctly diff --git a/server/channelserver/sys_timefix.go b/server/channelserver/sys_timefix.go index 839382053..4cde6a319 100644 --- a/server/channelserver/sys_timefix.go +++ b/server/channelserver/sys_timefix.go @@ -31,6 +31,16 @@ func Time_Current_Midnight() time.Time { return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location()) } +func TimeWeekStart() time.Time { + midnight := Time_Current_Midnight() + offset := (int(midnight.Weekday()) - 1) * -24 + return midnight.Add(time.Hour * time.Duration(offset)) +} + +func TimeWeekNext() time.Time { + return TimeWeekStart().Add(time.Hour * 24 * 7) +} + func Time_Current_Week_uint8() uint8 { baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust) diff --git a/server/discordbot/discord_bot.go b/server/discordbot/discord_bot.go index df817d50a..fc5c41ce8 100644 --- a/server/discordbot/discord_bot.go +++ b/server/discordbot/discord_bot.go @@ -1,11 +1,10 @@ package discordbot import ( - "regexp" - "erupe-ce/config" "github.com/bwmarrin/discordgo" "go.uber.org/zap" + "regexp" ) type DiscordBot struct { @@ -16,12 +15,12 @@ type DiscordBot struct { RealtimeChannel *discordgo.Channel } -type DiscordBotOptions struct { +type Options struct { Config *config.Config Logger *zap.Logger } -func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error) { +func NewDiscordBot(options Options) (discordBot *DiscordBot, err error) { session, err := discordgo.New("Bot " + options.Config.Discord.BotToken) if err != nil { @@ -29,13 +28,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error return nil, err } - mainGuild, err := session.Guild(options.Config.Discord.ServerID) - - if err != nil { - options.Logger.Fatal("Discord failed to get main guild", zap.Error(err)) - return nil, err - } - realtimeChannel, err := session.Channel(options.Config.Discord.RealtimeChannelID) if err != nil { @@ -47,7 +39,6 @@ func NewDiscordBot(options DiscordBotOptions) (discordBot *DiscordBot, err error config: options.Config, logger: options.Logger, Session: session, - MainGuild: mainGuild, RealtimeChannel: realtimeChannel, } @@ -60,21 +51,10 @@ func (bot *DiscordBot) Start() (err error) { return } -func (bot *DiscordBot) FindRoleByID(id string) *discordgo.Role { - for _, role := range bot.MainGuild.Roles { - if role.ID == id { - return role - } - } - - return nil -} - // Replace all mentions to real name from the message. func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`) emojiRegex := regexp.MustCompile(`(?:)?`) - roleRegex := regexp.MustCompile(`<@&(\d{17,19})>`) result := ReplaceTextAll(message, userRegex, func(userId string) string { user, err := bot.Session.User(userId) @@ -90,17 +70,7 @@ func (bot *DiscordBot) NormalizeDiscordMessage(message string) string { return ":" + emojiName + ":" }) - result = ReplaceTextAll(result, roleRegex, func(roleId string) string { - role := bot.FindRoleByID(roleId) - - if role != nil { - return "@!" + role.Name - } - - return "@!unknown" - }) - - return string(result) + return result } func (bot *DiscordBot) RealtimeChannelSend(message string) (err error) { diff --git a/www/erupe/index.html b/www/erupe/index.html index 57aa1c568..0b130c684 100644 --- a/www/erupe/index.html +++ b/www/erupe/index.html @@ -118,11 +118,11 @@
-
+