diff --git a/Source/engine/demomode.cpp b/Source/engine/demomode.cpp index 6869f02f7..da025765a 100644 --- a/Source/engine/demomode.cpp +++ b/Source/engine/demomode.cpp @@ -3,6 +3,10 @@ #include #include #include +#include +#include + +#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -36,27 +40,21 @@ enum class LoadingStatus : uint8_t { UnsupportedVersion, }; -enum class DemoMsgType : uint8_t { - GameTick = 0, - Rendering = 1, - Message = 2, -}; - struct MouseMotionEventData { uint16_t x; uint16_t y; }; struct MouseButtonEventData { - uint8_t button; uint16_t x; uint16_t y; uint16_t mod; + uint8_t button; }; struct MouseWheelEventData { - int32_t x; - int32_t y; + int16_t x; + int16_t y; uint16_t mod; }; @@ -65,16 +63,39 @@ struct KeyEventData { uint16_t mod; }; +union DemoMsgEventData { + MouseMotionEventData motion; + MouseButtonEventData button; + MouseWheelEventData wheel; + KeyEventData key; +}; + struct DemoMsg { - DemoMsgType type; - uint8_t progressToNextGameTick; - uint32_t eventType; - union { - MouseMotionEventData motion; - MouseButtonEventData button; - MouseWheelEventData wheel; - KeyEventData key; + enum EventType : uint8_t { + GameTick = 0, + Rendering = 1, + + // Inputs: + MinEvent = 8, + + QuitEvent = 8, + MouseMotionEvent = 9, + MouseButtonDownEvent = 10, + MouseButtonUpEvent = 11, + MouseWheelEvent = 12, + KeyDownEvent = 13, + KeyUpEvent = 14, + + MinCustomEvent = 64, }; + + EventType type; + uint8_t progressToNextGameTick; + + [[nodiscard]] bool isEvent() const + { + return type >= MinEvent; + } }; int DemoNumber = -1; @@ -111,16 +132,55 @@ struct { } DemoSettings; FILE *DemoRecording; -std::deque Demo_Message_Queue; uint32_t DemoModeLastTick = 0; int LogicTick = 0; -int StartTime = 0; +uint32_t StartTime = 0; uint16_t DemoGraphicsWidth = 640; uint16_t DemoGraphicsHeight = 480; -void ReadSettings(FILE *in, uint8_t version) +std::deque DemoMessageQueue; +std::deque MouseMotionEventDataQueue; +std::deque MouseButtonEventDataQueue; +std::deque MouseWheelEventDataQueue; +std::deque KeyEventDataQueue; + +struct DemoMessageAndData { + DemoMsg message; + DemoMsgEventData data; +}; +DemoMessageAndData PopDemoMessage() +{ + DemoMessageAndData result; + result.message = DemoMessageQueue.front(); + DemoMessageQueue.pop_front(); + switch (result.message.type) { + case DemoMsg::MouseMotionEvent: + result.data.motion = MouseMotionEventDataQueue.front(); + MouseMotionEventDataQueue.pop_front(); + break; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + result.data.button = MouseButtonEventDataQueue.front(); + MouseButtonEventDataQueue.pop_front(); + break; + case DemoMsg::MouseWheelEvent: + result.data.wheel = MouseWheelEventDataQueue.front(); + MouseWheelEventDataQueue.pop_front(); + break; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + result.data.key = KeyEventDataQueue.front(); + KeyEventDataQueue.pop_front(); + break; + default: + break; + } + return result; +} + +void ReadSettings(FILE *in, uint8_t version) // NOLINT(readability-identifier-length) { DemoGraphicsWidth = ReadLE16(in); DemoGraphicsHeight = ReadLE16(in); @@ -157,23 +217,23 @@ void WriteSettings(FILE *out) { WriteLE16(out, gnScreenWidth); WriteLE16(out, gnScreenHeight); - WriteByte(out, *sgOptions.Gameplay.runInTown); - WriteByte(out, *sgOptions.Gameplay.theoQuest); - WriteByte(out, *sgOptions.Gameplay.cowQuest); - WriteByte(out, *sgOptions.Gameplay.autoGoldPickup); - WriteByte(out, *sgOptions.Gameplay.autoElixirPickup); - WriteByte(out, *sgOptions.Gameplay.autoOilPickup); - WriteByte(out, *sgOptions.Gameplay.autoPickupInTown); - WriteByte(out, *sgOptions.Gameplay.adriaRefillsMana); - WriteByte(out, *sgOptions.Gameplay.autoEquipWeapons); - WriteByte(out, *sgOptions.Gameplay.autoEquipArmor); - WriteByte(out, *sgOptions.Gameplay.autoEquipHelms); - WriteByte(out, *sgOptions.Gameplay.autoEquipShields); - WriteByte(out, *sgOptions.Gameplay.autoEquipJewelry); - WriteByte(out, *sgOptions.Gameplay.randomizeQuests); - WriteByte(out, *sgOptions.Gameplay.showItemLabels); - WriteByte(out, *sgOptions.Gameplay.autoRefillBelt); - WriteByte(out, *sgOptions.Gameplay.disableCripplingShrines); + WriteByte(out, static_cast(*sgOptions.Gameplay.runInTown)); + WriteByte(out, static_cast(*sgOptions.Gameplay.theoQuest)); + WriteByte(out, static_cast(*sgOptions.Gameplay.cowQuest)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoGoldPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoElixirPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoOilPickup)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoPickupInTown)); + WriteByte(out, static_cast(*sgOptions.Gameplay.adriaRefillsMana)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipWeapons)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipArmor)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipHelms)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipShields)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoEquipJewelry)); + WriteByte(out, static_cast(*sgOptions.Gameplay.randomizeQuests)); + WriteByte(out, static_cast(*sgOptions.Gameplay.showItemLabels)); + WriteByte(out, static_cast(*sgOptions.Gameplay.autoRefillBelt)); + WriteByte(out, static_cast(*sgOptions.Gameplay.disableCripplingShrines)); WriteByte(out, *sgOptions.Gameplay.numHealPotionPickup); WriteByte(out, *sgOptions.Gameplay.numFullHealPotionPickup); WriteByte(out, *sgOptions.Gameplay.numManaPotionPickup); @@ -183,43 +243,47 @@ void WriteSettings(FILE *out) } #if SDL_VERSION_ATLEAST(2, 0, 0) -bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) +bool CreateSdlEvent(const DemoMsg &dmsg, const DemoMsgEventData &data, SDL_Event &event, uint16_t &modState) { - event.type = dmsg.eventType; - switch (static_cast(dmsg.eventType)) { - case SDL_MOUSEMOTION: + const uint8_t type = dmsg.type; + switch (type) { + case DemoMsg::MouseMotionEvent: + event.type = SDL_MOUSEMOTION; event.motion.which = 0; - event.motion.x = dmsg.motion.x; - event.motion.y = dmsg.motion.y; + event.motion.x = data.motion.x; + event.motion.y = data.motion.y; return true; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; event.button.which = 0; - event.button.button = dmsg.button.button; - event.button.state = dmsg.eventType == SDL_MOUSEBUTTONDOWN ? SDL_PRESSED : SDL_RELEASED; - event.button.x = dmsg.button.x; - event.button.y = dmsg.button.y; - modState = dmsg.button.mod; + event.button.button = data.button.button; + event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED; + event.button.x = data.button.x; + event.button.y = data.button.y; + modState = data.button.mod; return true; - case SDL_MOUSEWHEEL: + case DemoMsg::MouseWheelEvent: + event.type = SDL_MOUSEWHEEL; event.wheel.which = 0; - event.wheel.x = dmsg.wheel.x; - event.wheel.y = dmsg.wheel.y; - modState = dmsg.wheel.mod; + event.wheel.x = data.wheel.x; + event.wheel.y = data.wheel.y; + modState = data.wheel.mod; return true; - case SDL_KEYDOWN: - case SDL_KEYUP: - event.key.state = dmsg.eventType == SDL_KEYDOWN ? SDL_PRESSED : SDL_RELEASED; - event.key.keysym.sym = dmsg.key.sym; - event.key.keysym.mod = dmsg.key.mod; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP; + event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED; + event.key.keysym.sym = data.key.sym; + event.key.keysym.mod = data.key.mod; return true; default: - if (dmsg.eventType >= SDL_USEREVENT) { - event.type = CustomEventToSdlEvent(static_cast(dmsg.eventType - SDL_USEREVENT)); + if (type >= DemoMsg::MinCustomEvent) { + event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); - LogWarn("Unsupported demo event (type={:x})", dmsg.eventType); + LogWarn("Unsupported demo event (type={})", type); return false; } } @@ -272,120 +336,139 @@ uint8_t Sdl2ToSdl1MouseButton(uint8_t button) } } -bool CreateSdlEvent(const DemoMsg &dmsg, SDL_Event &event, uint16_t &modState) +bool CreateSdlEvent(const DemoMsg &dmsg, const DemoMsgEventData &data, SDL_Event &event, uint16_t &modState) { - switch (dmsg.eventType) { - case 0x400: + const uint8_t type = dmsg.type; + switch (type) { + case DemoMsg::MouseMotionEvent: event.type = SDL_MOUSEMOTION; event.motion.which = 0; - event.motion.x = dmsg.motion.x; - event.motion.y = dmsg.motion.y; + event.motion.x = data.motion.x; + event.motion.y = data.motion.y; return true; - case 0x401: - case 0x402: - event.type = dmsg.eventType == 0x401 ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + event.type = type == DemoMsg::MouseButtonDownEvent ? SDL_MOUSEBUTTONDOWN : SDL_MOUSEBUTTONUP; event.button.which = 0; - event.button.button = Sdl2ToSdl1MouseButton(dmsg.button.button); - event.button.state = dmsg.eventType == 0x401 ? SDL_PRESSED : SDL_RELEASED; - event.button.x = dmsg.button.x; - event.button.y = dmsg.button.y; - modState = dmsg.button.mod; + event.button.button = Sdl2ToSdl1MouseButton(data.button.button); + event.button.state = type == DemoMsg::MouseButtonDownEvent ? SDL_PRESSED : SDL_RELEASED; + event.button.x = data.button.x; + event.button.y = data.button.y; + modState = data.button.mod; return true; - case 0x403: // SDL_MOUSEWHEEL - if (dmsg.wheel.y == 0) { + case DemoMsg::MouseWheelEvent: + if (data.wheel.y == 0) { LogWarn("Demo: unsupported event (mouse wheel y == 0)"); return false; } event.type = SDL_MOUSEBUTTONDOWN; event.button.which = 0; - event.button.button = dmsg.wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; - modState = dmsg.wheel.mod; + event.button.button = data.wheel.y > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN; + modState = data.wheel.mod; return true; - case 0x300: - case 0x301: - event.type = dmsg.eventType == 0x300 ? SDL_KEYDOWN : SDL_KEYUP; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + event.type = type == DemoMsg::KeyDownEvent ? SDL_KEYDOWN : SDL_KEYUP; event.key.which = 0; - event.key.state = dmsg.eventType == 0x300 ? SDL_PRESSED : SDL_RELEASED; - event.key.keysym.sym = Sdl2ToSdl1Key(dmsg.key.sym); - event.key.keysym.mod = static_cast(dmsg.key.mod); + event.key.state = type == DemoMsg::KeyDownEvent ? SDL_PRESSED : SDL_RELEASED; + event.key.keysym.sym = Sdl2ToSdl1Key(data.key.sym); + event.key.keysym.mod = static_cast(data.key.mod); return true; default: - if (dmsg.eventType >= 0x8000) { - event.type = CustomEventToSdlEvent(static_cast(dmsg.eventType - 0x8000)); + if (type >= DemoMsg::MinCustomEvent) { + event.type = CustomEventToSdlEvent(static_cast(type - DemoMsg::MinCustomEvent)); return true; } event.type = static_cast(0); - LogWarn("Demo: unsupported event (type={:x})", dmsg.eventType); + LogWarn("Demo: unsupported event (type={:x})", type); return false; } } #endif -void LogDemoMessage(const DemoMsg &msg) +uint8_t MapPreV2DemoMsgEventType(uint16_t type) +{ + switch (type) { + case 0x100: + return DemoMsg::QuitEvent; + case 0x300: + return DemoMsg::KeyDownEvent; + case 0x301: + return DemoMsg::KeyUpEvent; + case 0x400: + return DemoMsg::MouseMotionEvent; + case 0x401: + return DemoMsg::MouseButtonDownEvent; + case 0x402: + return DemoMsg::MouseButtonUpEvent; + case 0x403: + return DemoMsg::MouseWheelEvent; + + default: + if (type < 0x8000) { // SDL_USEREVENT + app_fatal(StrCat("Unknown event ", type)); + } + return DemoMsg::MinCustomEvent + (type - 0x8000); + } +} + +void LogDemoMessage(const DemoMsg &msg, const DemoMsgEventData &data = DemoMsgEventData {}) { #ifdef LOG_DEMOMODE_MESSAGES const uint8_t progressToNextGameTick = msg.progressToNextGameTick; switch (msg.type) { - case DemoMsgType::Message: { - const uint32_t eventType = msg.eventType; - switch (eventType) { - case 0x400: // SDL_MOUSEMOTION -#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION - Log("🖱️ Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick, - msg.motion.x, msg.motion.y); -#endif - break; - case 0x401: // SDL_MOUSEBUTTONDOWN - case 0x402: // SDL_MOUSEBUTTONUP - Log("🖱️ Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick, - eventType == 0x401 ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP", - msg.button.button, msg.button.x, msg.button.y, msg.button.mod); - break; - case 0x403: // SDL_MOUSEWHEEL - Log("🖱️ Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick, - msg.wheel.x, msg.wheel.y, msg.wheel.mod); - break; - case 0x300: // SDL_KEYDOWN - case 0x301: // SDL_KEYUP - Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick, - eventType == 0x300 ? "KEYDOWN" : "KEYUP", - msg.key.sym, msg.key.mod); - break; - case 0x100: // SDL_QUIT - Log("❎ Message {:>3} QUIT", progressToNextGameTick); - break; - default: - Log("📨 Message {:>3} USEREVENT 0x{:x}", progressToNextGameTick, eventType); - break; - } - } break; - case DemoMsgType::GameTick: + case DemoMsg::GameTick: #ifdef LOG_DEMOMODE_MESSAGES_GAMETICK Log("⏲️ GameTick {:>3}", progressToNextGameTick); #endif break; - case DemoMsgType::Rendering: + case DemoMsg::Rendering: #ifdef LOG_DEMOMODE_MESSAGES_RENDERING Log("🖼️ Rendering {:>3}", progressToNextGameTick); #endif break; + case DemoMsg::MouseMotionEvent: +#ifdef LOG_DEMOMODE_MESSAGES_MOUSEMOTION + Log("🖱️ Message {:>3} MOUSEMOTION {} {}", progressToNextGameTick, + data.motion.x, data.motion.y); +#endif + break; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: + Log("🖱️ Message {:>3} {} {} {} {} 0x{:x}", progressToNextGameTick, + msg.type == DemoMsg::MouseButtonDownEvent ? "MOUSEBUTTONDOWN" : "MOUSEBUTTONUP", + data.button.button, data.button.x, data.button.y, data.button.mod); + break; + case DemoMsg::MouseWheelEvent: + Log("🖱️ Message {:>3} MOUSEWHEEL {} {} 0x{:x}", progressToNextGameTick, + data.wheel.x, data.wheel.y, data.wheel.mod); + break; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: + Log("🔤 Message {:>3} {} 0x{:x} 0x{:x}", progressToNextGameTick, + msg.type == DemoMsg::KeyDownEvent ? "KEYDOWN" : "KEYUP", + data.key.sym, data.key.mod); + break; + case DemoMsg::QuitEvent: + Log("❎ Message {:>3} QUIT", progressToNextGameTick); + break; default: - LogError("INVALID DEMO MODE MESSAGE {} {:>3}", static_cast(msg.type), progressToNextGameTick); + Log("📨 Message {:>3} USEREVENT {}", progressToNextGameTick, static_cast(msg.type)); break; } #endif // LOG_DEMOMODE_MESSAGES } -LoadingStatus LoadDemoMessages(int i) +LoadingStatus LoadDemoMessages(int demoNumber) { - const std::string path = StrCat(paths::PrefPath(), "demo_", i, ".dmo"); + const std::string path = StrCat(paths::PrefPath(), "demo_", demoNumber, ".dmo"); FILE *demofile = OpenFile(path.c_str(), "rb"); if (demofile == nullptr) { return LoadingStatus::FileNotFound; } const uint8_t version = ReadByte(demofile); - if (version != 0 && version != 1) { + if (version > 2) { return LoadingStatus::UnsupportedVersion; } @@ -393,53 +476,59 @@ LoadingStatus LoadDemoMessages(int i) ReadSettings(demofile, version); while (true) { - const uint32_t typeNum = ReadLE32(demofile); - if (std::feof(demofile)) + const uint8_t typeNum = version >= 2 ? ReadByte(demofile) : ReadLE32(demofile); + if (std::feof(demofile) != 0) break; - const auto type = static_cast(typeNum); const uint8_t progressToNextGameTick = ReadByte(demofile); - switch (type) { - case DemoMsgType::Message: { - const uint32_t eventType = ReadLE32(demofile); - DemoMsg msg { type, progressToNextGameTick, eventType, {} }; + switch (typeNum) { + case DemoMsg::GameTick: + case DemoMsg::Rendering: + DemoMessageQueue.push_back(DemoMsg { static_cast(typeNum), progressToNextGameTick }); + break; + default: { + const uint8_t eventType = version >= 2 ? typeNum : MapPreV2DemoMsgEventType(static_cast(ReadLE32(demofile))); + DemoMessageQueue.push_back(DemoMsg { static_cast(eventType), progressToNextGameTick }); switch (eventType) { - case 0x400: // SDL_MOUSEMOTION - msg.motion.x = ReadLE16(demofile); - msg.motion.y = ReadLE16(demofile); - break; - case 0x401: // SDL_MOUSEBUTTONDOWN - case 0x402: // SDL_MOUSEBUTTONUP - msg.button.button = ReadByte(demofile); - msg.button.x = ReadLE16(demofile); - msg.button.y = ReadLE16(demofile); - msg.button.mod = ReadLE16(demofile); - break; - case 0x403: // SDL_MOUSEWHEEL - msg.wheel.x = ReadLE32(demofile); - msg.wheel.y = ReadLE32(demofile); - msg.wheel.mod = ReadLE16(demofile); - break; - case 0x300: // SDL_KEYDOWN - case 0x301: // SDL_KEYUP - msg.key.sym = static_cast(ReadLE32(demofile)); - msg.key.mod = static_cast(ReadLE16(demofile)); - break; - case 0x100: // SDL_QUIT + case DemoMsg::MouseMotionEvent: { + MouseMotionEventData motion; + motion.x = ReadLE16(demofile); + motion.y = ReadLE16(demofile); + MouseMotionEventDataQueue.push_back(motion); + } break; + case DemoMsg::MouseButtonDownEvent: + case DemoMsg::MouseButtonUpEvent: { + MouseButtonEventData button; + button.button = ReadByte(demofile); + button.x = ReadLE16(demofile); + button.y = ReadLE16(demofile); + button.mod = ReadLE16(demofile); + MouseButtonEventDataQueue.push_back(button); + } break; + case DemoMsg::MouseWheelEvent: { + MouseWheelEventData wheel; + wheel.x = version >= 2 ? ReadLE16(demofile) : static_cast(ReadLE32(demofile)); + wheel.y = version >= 2 ? ReadLE16(demofile) : static_cast(ReadLE32(demofile)); + wheel.mod = ReadLE16(demofile); + MouseWheelEventDataQueue.push_back(wheel); + } break; + case DemoMsg::KeyDownEvent: + case DemoMsg::KeyUpEvent: { + KeyEventData key; + key.sym = static_cast(ReadLE32(demofile)); + key.mod = static_cast(ReadLE16(demofile)); + KeyEventDataQueue.push_back(key); + } break; + case DemoMsg::QuitEvent: // SDL_QUIT break; default: - if (eventType < 0x8000) { // SDL_USEREVENT + if (eventType < DemoMsg::MinCustomEvent) { app_fatal(StrCat("Unknown event ", eventType)); } break; } - Demo_Message_Queue.push_back(msg); - break; - } - default: - Demo_Message_Queue.push_back(DemoMsg { type, progressToNextGameTick, 0, {} }); - break; + } break; } } @@ -450,11 +539,10 @@ LoadingStatus LoadDemoMessages(int i) return LoadingStatus::Success; } -void RecordEventHeader(const SDL_Event &event) +void WriteDemoMsgHeader(DemoMsg::EventType type) { - WriteLE32(DemoRecording, static_cast(DemoMsgType::Message)); + WriteByte(DemoRecording, type); WriteByte(DemoRecording, ProgressToNextGameTick); - WriteLE32(DemoRecording, event.type); } } // namespace @@ -467,7 +555,7 @@ void InitPlayBack(int demoNumber, bool timedemo) Timedemo = timedemo; ControlMode = ControlTypes::KeyboardAndMouse; - LoadingStatus status = LoadDemoMessages(demoNumber); + const LoadingStatus status = LoadDemoMessages(demoNumber); switch (status) { case LoadingStatus::Success: return; @@ -540,29 +628,29 @@ bool IsRecording() bool GetRunGameLoop(bool &drawGame, bool &processInput) { - if (Demo_Message_Queue.empty()) + if (DemoMessageQueue.empty()) app_fatal("Demo queue empty"); - const DemoMsg dmsg = Demo_Message_Queue.front(); + const DemoMsg &dmsg = DemoMessageQueue.front(); + if (dmsg.isEvent()) + app_fatal("Unexpected event demo message in GetRunGameLoop"); LogDemoMessage(dmsg); - if (dmsg.type == DemoMsgType::Message) - app_fatal("Unexpected Message"); if (Timedemo) { // disable additonal rendering to speedup replay - drawGame = dmsg.type == DemoMsgType::GameTick && !HeadlessMode; + drawGame = dmsg.type == DemoMsg::GameTick && !HeadlessMode; } else { int currentTickCount = SDL_GetTicks(); int ticksElapsed = currentTickCount - DemoModeLastTick; bool tickDue = ticksElapsed >= gnTickDelay; drawGame = false; if (tickDue) { - if (dmsg.type == DemoMsgType::GameTick) { + if (dmsg.type == DemoMsg::GameTick) { DemoModeLastTick = currentTickCount; } } else { int32_t fraction = ticksElapsed * AnimationInfo::baseValueFraction / gnTickDelay; fraction = std::clamp(fraction, 0, AnimationInfo::baseValueFraction); uint8_t progressToNextGameTick = static_cast(fraction); - if (dmsg.type == DemoMsgType::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) { + if (dmsg.type == DemoMsg::GameTick || dmsg.progressToNextGameTick > progressToNextGameTick) { // we are ahead of the replay => add a additional rendering for smoothness if (gbRunGame && PauseMode == 0 && (gbIsMultiplayer || !gmenu_is_active()) && gbProcessPlayers) // if game is not running or paused there is no next gametick in the near future ProgressToNextGameTick = progressToNextGameTick; @@ -573,10 +661,11 @@ bool GetRunGameLoop(bool &drawGame, bool &processInput) } } ProgressToNextGameTick = dmsg.progressToNextGameTick; - Demo_Message_Queue.pop_front(); - if (dmsg.type == DemoMsgType::GameTick) + const bool isGameTick = dmsg.type == DemoMsg::GameTick; + DemoMessageQueue.pop_front(); + if (isGameTick) LogicTick++; - return dmsg.type == DemoMsgType::GameTick; + return isGameTick; } bool FetchMessage(SDL_Event *event, uint16_t *modState) @@ -591,7 +680,7 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) return true; } if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) { - Demo_Message_Queue.clear(); + DemoMessageQueue.clear(); DemoNumber = -1; Timedemo = false; last_tick = SDL_GetTicks(); @@ -608,14 +697,15 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) } } - if (!Demo_Message_Queue.empty()) { - const DemoMsg dmsg = Demo_Message_Queue.front(); - LogDemoMessage(dmsg); - if (dmsg.type == DemoMsgType::Message) { - const bool hasEvent = CreateSdlEvent(dmsg, *event, *modState); - ProgressToNextGameTick = dmsg.progressToNextGameTick; - Demo_Message_Queue.pop_front(); + if (!DemoMessageQueue.empty()) { + if (DemoMessageQueue.front().isEvent()) { + const DemoMessageAndData dmsg = PopDemoMessage(); + LogDemoMessage(dmsg.message, dmsg.data); + const bool hasEvent = CreateSdlEvent(dmsg.message, dmsg.data, *event, *modState); + ProgressToNextGameTick = dmsg.message.progressToNextGameTick; return hasEvent; + } else { + LogDemoMessage(DemoMessageQueue.front()); } } @@ -624,8 +714,7 @@ bool FetchMessage(SDL_Event *event, uint16_t *modState) void RecordGameLoopResult(bool runGameLoop) { - WriteLE32(DemoRecording, static_cast(runGameLoop ? DemoMsgType::GameTick : DemoMsgType::Rendering)); - WriteByte(DemoRecording, ProgressToNextGameTick); + WriteDemoMsgHeader(runGameLoop ? DemoMsg::GameTick : DemoMsg::Rendering); } void RecordMessage(const SDL_Event &event, uint16_t modState) @@ -636,49 +725,64 @@ void RecordMessage(const SDL_Event &event, uint16_t modState) return; switch (event.type) { case SDL_MOUSEMOTION: - RecordEventHeader(event); + WriteDemoMsgHeader(DemoMsg::MouseMotionEvent); WriteLE16(DemoRecording, event.motion.x); WriteLE16(DemoRecording, event.motion.y); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: - RecordEventHeader(event); - WriteByte(DemoRecording, event.button.button); - WriteLE16(DemoRecording, event.button.x); - WriteLE16(DemoRecording, event.button.y); - WriteLE16(DemoRecording, modState); +#ifdef USE_SDL1 + if (event.button.button == SDL_BUTTON_WHEELUP || event.button.button == SDL_BUTTON_WHEELDOWN) { + WriteDemoMsgHeader(DemoMsg::MouseWheelEvent); + WriteLE16(DemoRecording, 0); + WriteLE16(DemoRecording, event.button.button == SDL_BUTTON_WHEELUP ? 1 : -1); + WriteLE16(DemoRecording, modState); + } else { +#endif + WriteDemoMsgHeader(event.type == SDL_MOUSEBUTTONDOWN ? DemoMsg::MouseButtonDownEvent : DemoMsg::MouseButtonUpEvent); + WriteByte(DemoRecording, event.button.button); + WriteLE16(DemoRecording, event.button.x); + WriteLE16(DemoRecording, event.button.y); + WriteLE16(DemoRecording, modState); +#ifdef USE_SDL1 + } +#endif break; #ifndef USE_SDL1 case SDL_MOUSEWHEEL: - RecordEventHeader(event); - WriteLE32(DemoRecording, event.wheel.x); - WriteLE32(DemoRecording, event.wheel.y); + WriteDemoMsgHeader(DemoMsg::MouseWheelEvent); + if (event.wheel.x < std::numeric_limits::min() + || event.wheel.x > std::numeric_limits::max() + || event.wheel.y < std::numeric_limits::min() + || event.wheel.y > std::numeric_limits::max()) { + app_fatal(fmt::format("Mouse wheel event x/y out of int16_t range. x={} y={}", + event.wheel.x, event.wheel.y)); + } + WriteLE16(DemoRecording, event.wheel.x); + WriteLE16(DemoRecording, event.wheel.y); WriteLE16(DemoRecording, modState); break; #endif case SDL_KEYDOWN: case SDL_KEYUP: - RecordEventHeader(event); + WriteDemoMsgHeader(event.type == SDL_KEYDOWN ? DemoMsg::KeyDownEvent : DemoMsg::KeyUpEvent); WriteLE32(DemoRecording, static_cast(event.key.keysym.sym)); WriteLE16(DemoRecording, static_cast(event.key.keysym.mod)); break; #ifndef USE_SDL1 case SDL_WINDOWEVENT: if (event.window.type == SDL_WINDOWEVENT_CLOSE) { - SDL_Event quitEvent; - quitEvent.type = SDL_QUIT; - RecordEventHeader(quitEvent); + WriteDemoMsgHeader(DemoMsg::QuitEvent); } break; #endif case SDL_QUIT: - RecordEventHeader(event); + WriteDemoMsgHeader(DemoMsg::QuitEvent); break; default: if (IsCustomEvent(event.type)) { - SDL_Event stableCustomEvent; - stableCustomEvent.type = SDL_USEREVENT + static_cast(GetCustomEvent(event.type)); - RecordEventHeader(stableCustomEvent); + WriteDemoMsgHeader(static_cast( + DemoMsg::MinCustomEvent + static_cast(GetCustomEvent(event.type)))); } break; } @@ -694,7 +798,7 @@ void NotifyGameLoopStart() LogError("Failed to open {} for writing", path); return; } - constexpr uint8_t Version = 1; + constexpr uint8_t Version = 2; WriteByte(DemoRecording, Version); WriteLE32(DemoRecording, gSaveNumber); WriteSettings(DemoRecording); @@ -719,7 +823,7 @@ void NotifyGameLoopEnd() } if (IsRunning() && !HeadlessMode) { - float seconds = (SDL_GetTicks() - StartTime) / 1000.0f; + const float seconds = (SDL_GetTicks() - StartTime) / 1000.0F; SDL_Log("%d frames, %.2f seconds: %.1f fps", LogicTick, seconds, LogicTick / seconds); gbRunGameResult = false; gbRunGame = false; diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 791071253..21469bd20 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -46,8 +46,8 @@ const int BarPos[3][2] = { { 53, 37 }, { 53, 421 }, { 53, 37 } }; OptionalOwnedClxSpriteList ArtCutsceneWidescreen; -uint32_t CustomEventsBegin = SDL_USEREVENT; -constexpr uint32_t NumCustomEvents = WM_LAST - WM_FIRST + 1; +uint16_t CustomEventsBegin = SDL_USEREVENT; +constexpr uint16_t NumCustomEvents = WM_LAST - WM_FIRST + 1; Cutscenes GetCutSceneFromLevelType(dungeon_type type) { @@ -234,17 +234,17 @@ void RegisterCustomEvents() #endif } -bool IsCustomEvent(uint32_t eventType) +bool IsCustomEvent(uint16_t eventType) { return eventType >= CustomEventsBegin && eventType < CustomEventsBegin + NumCustomEvents; } -interface_mode GetCustomEvent(uint32_t eventType) +interface_mode GetCustomEvent(uint16_t eventType) { return static_cast(eventType - CustomEventsBegin); } -uint32_t CustomEventToSdlEvent(interface_mode eventType) +uint16_t CustomEventToSdlEvent(interface_mode eventType) { return CustomEventsBegin + eventType; } diff --git a/Source/interfac.h b/Source/interfac.h index ab33c521d..29ec122b6 100644 --- a/Source/interfac.h +++ b/Source/interfac.h @@ -14,7 +14,7 @@ namespace devilution { /** * @brief Custom events. */ -enum interface_mode : uint16_t { +enum interface_mode : uint8_t { WM_DIABNEXTLVL = 0, WM_DIABPREVLVL, WM_DIABRTNLVL, @@ -32,11 +32,11 @@ enum interface_mode : uint16_t { void RegisterCustomEvents(); -bool IsCustomEvent(uint32_t eventType); +bool IsCustomEvent(uint16_t eventType); -interface_mode GetCustomEvent(uint32_t eventType); +interface_mode GetCustomEvent(uint16_t eventType); -uint32_t CustomEventToSdlEvent(interface_mode eventType); +uint16_t CustomEventToSdlEvent(interface_mode eventType); enum Cutscenes : uint8_t { CutStart, diff --git a/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo b/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo index a7c10bc76..be134143f 100644 Binary files a/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo and b/test/fixtures/timedemo/WarriorLevel1to2/demo_0.dmo differ