diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 32a2445c0..8bc6d00f6 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -824,17 +824,29 @@ void RunGameLoop(interface_mode uMsg) } if (!gbRunGame) break; - if (!nthread_has_500ms_passed()) { - ProcessInput(); + + bool drawGame = true; + bool processInput = true; + bool runGameLoop = demoMode ? GetDemoRunGameLoop(drawGame, processInput) : nthread_has_500ms_passed(); + if (!runGameLoop) { + if (recordDemo != -1) + demoRecording << static_cast(DemoMsgType::Rendering) << "," << gfProgressToNextGameTick << "\n"; + if (processInput) + ProcessInput(); + if (!drawGame) + continue; force_redraw |= 1; DrawAndBlit(); continue; } + if (recordDemo != -1) + demoRecording << static_cast(DemoMsgType::GameTick) << "," << gfProgressToNextGameTick << "\n"; diablo_color_cyc_logic(); multi_process_network_packets(); game_loop(gbGameLoopStartup); gbGameLoopStartup = false; - DrawAndBlit(); + if (drawGame) + DrawAndBlit(); logicTick++; #ifdef GPERF_HEAP_FIRST_GAME_ITERATION if (run_game_iteration++ == 0) @@ -2184,7 +2196,7 @@ void game_loop(bool bStartup) TimeoutCursor(false); GameLogic(); - if (!gbRunGame || !gbIsMultiplayer || !nthread_has_500ms_passed()) + if (!gbRunGame || !gbIsMultiplayer || demoMode || recordDemo != -1 || !nthread_has_500ms_passed()) break; } } diff --git a/Source/miniwin/miniwin.h b/Source/miniwin/miniwin.h index 0b262ad05..4190adc04 100644 --- a/Source/miniwin/miniwin.h +++ b/Source/miniwin/miniwin.h @@ -34,6 +34,7 @@ struct tagMSG { }; enum class DemoMsgType { + GameTick = 0, Rendering = 1, Message = 2, }; @@ -43,9 +44,11 @@ struct demoMsg { uint32_t message; int32_t wParam; int32_t lParam; + float progressToNextGameTick; }; extern std::ofstream demoRecording; +bool GetDemoRunGameLoop(bool &drawGame, bool &processInput); // // Everything else diff --git a/Source/miniwin/misc_msg.cpp b/Source/miniwin/misc_msg.cpp index 1e8f6d8cc..54699f38e 100644 --- a/Source/miniwin/misc_msg.cpp +++ b/Source/miniwin/misc_msg.cpp @@ -593,6 +593,7 @@ bool FetchMessage_Real(tagMSG *lpMsg) std::ofstream demoRecording; static std::deque demo_message_queue; +uint32_t demoModeLastTick = 0; void CreateDemoFile(int i) { @@ -605,16 +606,17 @@ void CreateDemoFile(int i) void SaveDemoMessage(tagMSG *lpMsg) { - demoRecording << tick << ",0," << lpMsg->message << "," << lpMsg->wParam << "," << lpMsg->lParam << "\n"; + demoRecording << static_cast(DemoMsgType::Message) << "," << gfProgressToNextGameTick << "," << lpMsg->message << "," << lpMsg->wParam << "," << lpMsg->lParam << "\n"; } -void PumpDemoMessage(int tick, uint32_t message, int32_t wParam, int32_t lParam) +void PumpDemoMessage(DemoMsgType demoMsgType, uint32_t message, int32_t wParam, int32_t lParam, float progressToNextGameTick) { demoMsg msg; msg.type = demoMsgType; msg.message = message; msg.wParam = wParam; msg.lParam = lParam; + msg.progressToNextGameTick = progressToNextGameTick; demo_message_queue.push_back(msg); } @@ -654,26 +656,33 @@ bool LoadDemoMessages(int i) std::stringstream command(line); std::getline(command, number, ','); - int tick = std::stoi(number); + int typeNum = std::stoi(number); + DemoMsgType type = static_cast(typeNum); std::getline(command, number, ','); - int type = std::stoi(number); + float progressToNextGameTick = std::stof(number); switch (type) { - case 0: + case DemoMsgType::Message: { std::getline(command, number, ','); uint32_t message = std::stoi(number); std::getline(command, number, ','); int32_t wParam = std::stoi(number); std::getline(command, number, ','); int32_t lParam = std::stoi(number); - PumpDemoMessage(tick, message, wParam, lParam); + PumpDemoMessage(type, message, wParam, lParam, progressToNextGameTick); + break; + } + default: + PumpDemoMessage(type, 0, 0, 0, progressToNextGameTick); break; } } demofile.close(); + demoModeLastTick = SDL_GetTicks(); + return true; } @@ -702,6 +711,7 @@ bool DemoMessage(tagMSG *lpMsg) lpMsg->message = dmsg.message; lpMsg->lParam = dmsg.lParam; lpMsg->wParam = dmsg.wParam; + gfProgressToNextGameTick = dmsg.progressToNextGameTick; demo_message_queue.pop_front(); return true; } @@ -714,17 +724,49 @@ bool DemoMessage(tagMSG *lpMsg) return false; } -bool FetchMessage(tagMSG *lpMsg, int tick) +bool GetDemoRunGameLoop(bool &drawGame, bool &processInput) +{ + if (demo_message_queue.empty()) + app_fatal("Demo queue empty"); + demoMsg dmsg = demo_message_queue.front(); + if (dmsg.type == DemoMsgType::Message) + app_fatal("Unexpected Message"); + // disable additonal rendering to speedup replay + drawGame = dmsg.type == DemoMsgType::GameTick; + if (!timedemo) { + int currentTickCount = SDL_GetTicks(); + int ticksElapsed = currentTickCount - demoModeLastTick; + bool tickDue = ticksElapsed >= gnTickDelay; + if (tickDue) { + if (dmsg.type == DemoMsgType::GameTick) { + demoModeLastTick = currentTickCount; + } + } else { + float progressToNextGameTick = clamp((float)ticksElapsed / (float)gnTickDelay, 0.F, 1.F); + if (dmsg.progressToNextGameTick > progressToNextGameTick) { + // we are ahead of the replay => add a additional rendering for smoothness + gfProgressToNextGameTick = progressToNextGameTick; + processInput = false; + drawGame = true; + return false; + } + } + } + gfProgressToNextGameTick = dmsg.progressToNextGameTick; + demo_message_queue.pop_front(); + return dmsg.type == DemoMsgType::GameTick; +} + +bool FetchMessage(tagMSG *lpMsg) { - bool available; + if (demoMode) { + return DemoMessage(lpMsg); + } - if (!demoMode) - available = FetchMessage_Real(lpMsg); - else - available = DemoMessage(lpMsg, tick); + bool available = FetchMessage_Real(lpMsg); - if (recordDemo != -1 && available && tick > -1) - SaveDemoMessage(lpMsg, tick); + if (recordDemo != -1 && available && gbRunGame) + SaveDemoMessage(lpMsg); return available; } diff --git a/Source/nthread.cpp b/Source/nthread.cpp index 7a803ddf1..5f04bee9f 100644 --- a/Source/nthread.cpp +++ b/Source/nthread.cpp @@ -227,7 +227,7 @@ bool nthread_has_500ms_passed() void nthread_UpdateProgressToNextGameTick() { - 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 + if (!gbRunGame || PauseMode != 0 || (!gbIsMultiplayer && gmenu_is_active()) || !gbProcessPlayers || demoMode) // if game is not running or paused there is no next gametick in the near future return; int currentTickCount = SDL_GetTicks(); int ticksElapsed = last_tick - currentTickCount;