diff --git a/config.json b/config.json index 31629b894..a62b45325 100644 --- a/config.json +++ b/config.json @@ -12,6 +12,7 @@ "ScreenshotAPIURL": "", "DeleteOnSaveCorruption": false, "ClientMode": "ZZ", + "QuestCacheExpiry": 300, "DevMode": true, "DevModeOptions": { "AutoCreateAccount": true, diff --git a/config/config.go b/config/config.go index 65c663511..4c156d35e 100644 --- a/config/config.go +++ b/config/config.go @@ -79,6 +79,7 @@ type Config struct { DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion ClientMode string RealClientMode Mode + QuestCacheExpiry int // Number of seconds to keep quest data cached DevMode bool DevModeOptions DevModeOptions diff --git a/patch-schema/11-event-quest-flags.sql b/patch-schema/11-event-quest-flags.sql new file mode 100644 index 000000000..5f88d732d --- /dev/null +++ b/patch-schema/11-event-quest-flags.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE IF EXISTS public.event_quests ADD COLUMN IF NOT EXISTS flags integer; + +END; \ No newline at end of file diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index da3c8166a..ef77ef59b 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -47,6 +47,10 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { ) } + if s.server.erupeConfig.GameplayOptions.SeasonOverride { + pkt.Filename = seasonConversion(s, pkt.Filename) + } + data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename))) if err != nil { s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename)) @@ -58,6 +62,33 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) { } } +func seasonConversion(s *Session, questFile string) string { + filename := fmt.Sprintf("%s%d", questFile[:6], s.server.Season()) + + // Return the seasonal file + if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil { + return filename + } else { + // Attempt to return the requested quest file if the seasonal file doesn't exist + if _, err = os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil { + return questFile + } + + // If the code reaches this point, it's most likely a custom quest with no seasonal variations in the files. + // Since event quests when seasonal pick day or night and the client requests either one, we need to differentiate between the two to prevent issues. + var _time string + + if TimeGameAbsolute() > 2880 { + _time = "d" + } else { + _time = "n" + } + + // Request a d0 or n0 file depending on the time of day. The time of day matters and issues will occur if it's different to the one it requests. + return fmt.Sprintf("%s%s%d", questFile[:5], _time, 0) + } +} + func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest) var data []byte @@ -77,6 +108,11 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { } func loadQuestFile(s *Session, questId int) []byte { + data, exists := s.server.questCacheData[questId] + if exists && s.server.questCacheTime[questId].Add(time.Duration(s.server.erupeConfig.QuestCacheExpiry)*time.Second).After(time.Now()) { + return data + } + file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId))) if err != nil { return nil @@ -113,14 +149,16 @@ func loadQuestFile(s *Session, questId int) []byte { } questBody.WriteBytes(newStrings.Data()) + s.server.questCacheData[questId] = questBody.Data() + s.server.questCacheTime[questId] = time.Now() return questBody.Data() } func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { var id, mark uint32 - var questId int + var questId, flags int var maxPlayers, questType uint8 - rows.Scan(&id, &maxPlayers, &questType, &questId, &mark) + rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags) data := loadQuestFile(s, questId) if data == nil { @@ -168,7 +206,12 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { if s.server.erupeConfig.GameplayOptions.SeasonOverride { bf.WriteUint8(flagByte & 0b11100000) } else { - bf.WriteUint8(flagByte) + // Allow for seasons to be specified in database, otherwise use the one in the file. + if flags < 0 { + bf.WriteUint8(flagByte) + } else { + bf.WriteUint8(uint8(flags)) + } } // Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden @@ -192,7 +235,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { bf := byteframe.NewByteFrame() bf.WriteUint16(0) - rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id") + rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1) FROM event_quests ORDER BY quest_id") for rows.Next() { data, err := makeEventQuest(s, rows) if err != nil { @@ -202,18 +245,10 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { continue } else { totalCount++ - if _config.ErupeConfig.RealClientMode == _config.F5 { - if totalCount > pkt.Offset && len(bf.Data()) < 21550 { - returnedCount++ - bf.WriteBytes(data) - continue - } - } else { - if totalCount > pkt.Offset && len(bf.Data()) < 60000 { - returnedCount++ - bf.WriteBytes(data) - continue - } + if totalCount > pkt.Offset && len(bf.Data()) < 60000 { + returnedCount++ + bf.WriteBytes(data) + continue } } } diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 1dfef82d0..d12d713f5 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -5,6 +5,7 @@ import ( "net" "strings" "sync" + "time" "erupe-ce/common/byteframe" ps "erupe-ce/common/pascalstring" @@ -73,6 +74,9 @@ type Server struct { name string raviente *Raviente + + questCacheData map[int][]byte + questCacheTime map[int]time.Time } type Raviente struct { @@ -163,6 +167,8 @@ func NewServer(config *Config) *Server { state: make([]uint32, 30), support: make([]uint32, 30), }, + questCacheData: make(map[int][]byte), + questCacheTime: make(map[int]time.Time), } // Mezeporta @@ -316,7 +322,6 @@ func (s *Server) BroadcastChatMessage(message string) { msgBinChat.Build(bf) s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{ - CharID: 0xFFFFFFFF, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(), }, nil) @@ -348,7 +353,6 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u bf.WriteUint16(0) // Unk bf.WriteBytes(stage) s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{ - CharID: 0x00000000, BroadcastType: BroadcastTypeServer, MessageType: BinaryMessageTypeChat, RawDataPayload: bf.Data(),