@ -3,6 +3,10 @@
# include <cstdint>
# include <cstdio>
# include <deque>
# include <limits>
# include <optional>
# include <fmt/format.h>
# 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 < DemoMsg > Demo_Message_Queue ;
uint32_t DemoModeLastTick = 0 ;
int LogicTick = 0 ;
int StartTime = 0 ;
u int32_ t StartTime = 0 ;
uint16_t DemoGraphicsWidth = 640 ;
uint16_t DemoGraphicsHeight = 480 ;
void ReadSettings ( FILE * in , uint8_t version )
std : : deque < DemoMsg > DemoMessageQueue ;
std : : deque < MouseMotionEventData > MouseMotionEventDataQueue ;
std : : deque < MouseButtonEventData > MouseButtonEventDataQueue ;
std : : deque < MouseWheelEventData > MouseWheelEventDataQueue ;
std : : deque < KeyEventData > 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 < uint8_t > ( * sgOptions . Gameplay . runInTown ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . theoQuest ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . cowQuest ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoGoldPickup ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoElixirPickup ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoOilPickup ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoPickupInTown ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . adriaRefillsMana ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoEquipWeapons ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoEquipArmor ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoEquipHelms ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoEquipShields ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoEquipJewelry ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . randomizeQuests ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . showItemLabels ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * sgOptions . Gameplay . autoRefillBelt ) ) ;
WriteByte ( out , static_cast < uint8_t > ( * 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 < SDL_EventType > ( 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 < interface_mode > ( dmsg . eventType - SDL_USEREVENT ) ) ;
if ( type > = DemoMsg : : MinCustomEvent ) {
event . type = CustomEventToSdlEvent ( static_cast < interface_mode > ( type - DemoMsg : : MinCustomEvent ) ) ;
return true ;
}
event . type = static_cast < SDL_EventType > ( 0 ) ;
LogWarn ( " Unsupported demo event (type={:x }) " , dmsg . even tT ype) ;
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 < SDL_Keymod > ( 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 < SDL_Keymod > ( data . key . mod ) ;
return true ;
default :
if ( dmsg . eventType > = 0x8000 ) {
event . type = CustomEventToSdlEvent ( static_cast < interface_mode > ( dmsg . eventType - 0x8000 ) ) ;
if ( type > = DemoMsg : : MinCustomEvent ) {
event . type = CustomEventToSdlEvent ( static_cast < interface_mode > ( type - DemoMsg : : MinCustomEvent ) ) ;
return true ;
}
event . type = static_cast < SDL_EventType > ( 0 ) ;
LogWarn ( " Demo: unsupported event (type={:x}) " , dmsg . even tT ype) ;
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 < uint32_t > ( msg . type ) , progressToNextGameTick ) ;
Log ( " 📨 Message {:>3} USEREVENT {} " , progressToNextGameTick , static_cast < uint8 _t> ( 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 < DemoMsgType > ( 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 < DemoMsg : : EventType > ( typeNum ) , progressToNextGameTick } ) ;
break ;
default : {
const uint8_t eventType = version > = 2 ? typeNum : MapPreV2DemoMsgEventType ( static_cast < uint16_t > ( ReadLE32 ( demofile ) ) ) ;
DemoMessageQueue . push_back ( DemoMsg { static_cast < DemoMsg : : EventType > ( 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 < int32_t > ( demofile ) ;
msg . wheel . y = ReadLE32 < int32_t > ( demofile ) ;
msg . wheel . mod = ReadLE16 ( demofile ) ;
break ;
case 0x300 : // SDL_KEYDOWN
case 0x301 : // SDL_KEYUP
msg . key . sym = static_cast < SDL_Keycode > ( ReadLE32 ( demofile ) ) ;
msg . key . mod = static_cast < SDL_Keymod > ( 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 < int16_t > ( demofile ) : static_cast < int16_t > ( ReadLE32 < int32_t > ( demofile ) ) ;
wheel . y = version > = 2 ? ReadLE16 < int16_t > ( demofile ) : static_cast < int16_t > ( ReadLE32 < int32_t > ( demofile ) ) ;
wheel . mod = ReadLE16 ( demofile ) ;
MouseWheelEventDataQueue . push_back ( wheel ) ;
} break ;
case DemoMsg : : KeyDownEvent :
case DemoMsg : : KeyUpEvent : {
KeyEventData key ;
key . sym = static_cast < SDL_Keycode > ( ReadLE32 ( demofile ) ) ;
key . mod = static_cast < SDL_Keymod > ( 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 < uint32_t > ( DemoMsgT ype: : Message ) ) ;
WriteByte ( DemoRecording , t ype) ;
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 < int32_t > ( fraction , 0 , AnimationInfo : : baseValueFraction ) ;
uint8_t progressToNextGameTick = static_cast < uint8_t > ( 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 is GameTick;
}
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 < uint32_t > ( 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 :
RecordEvent Header( event ) ;
WriteDemoMsg Header( D emoMsg : : MouseMotionE vent) ;
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 < int16_t > : : min ( )
| | event . wheel . x > std : : numeric_limits < int16_t > : : max ( )
| | event . wheel . y < std : : numeric_limits < int16_t > : : min ( )
| | event . wheel . y > std : : numeric_limits < int16_t > : : 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 : : KeyUpE vent) ;
WriteLE32 ( DemoRecording , static_cast < uint32_t > ( event . key . keysym . sym ) ) ;
WriteLE16 ( DemoRecording , static_cast < uint16_t > ( 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 :
RecordEvent Header( event ) ;
WriteDemoMsg Header( D emoMsg : : QuitE vent) ;
break ;
default :
if ( IsCustomEvent ( event . type ) ) {
SDL_Event stableCustomEvent ;
stableCustomEvent . type = SDL_USEREVENT + static_cast < uint32_t > ( GetCustomEvent ( event . type ) ) ;
RecordEventHeader ( stableCustomEvent ) ;
WriteDemoMsgHeader ( static_cast < DemoMsg : : EventType > (
DemoMsg : : MinCustomEvent + static_cast < uint8_t > ( 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 ;