/* $Id: replayer.cpp,v 1.33.4.1 2006/01/20 11:33:53 chfreund Exp $ */ #include "replayer.hpp" #include #include #include "fontfactory.h" #include "layout.h" #include "button.h" using namespace SDLwidgets; #include "constants.hpp" #include "loader.hpp" #include "message.hpp" #include "rope.hpp" #include "progresslog.hpp" #include "wopsettings.hpp" #include "wopsprites.cpp" #include "graphics.hpp" #include "random.hpp" #include "gamereplay.hpp" Replayer::Replayer( const char* filename ) : m_focusedObject( 0 ), m_frameDuration( 40 ), m_frameCounter( 0 ), m_in( filename ), m_quitFlag( false ), m_screenshotNumber( 0 ), m_worldBuffer( 0 ) { ASSERT( !SDL_InitSubSystem( SDL_INIT_TIMER ), "Replayer::Replayer: Could not initialize timer\n" ); // init loader video object Loader::setVideo( &m_video ); m_video.showMouseCursor( true ); std::string fontPath( WopSettings::getInstance()->getData() ); fontPath += "/images/gui"; SDLwidgets::FontFactory::getInstance()->setFontPath( fontPath ); ProgressLog progressLog; progressLog.reset(); progressLog.create( 30, 1 ); m_video.loadSprites( &progressLog ); m_audio = Audio::getInstance(); m_frameTimer = 0; Uint8* sizeBuffer = new Uint8[Serialize::sizeOf()]; m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); Uint8* bufferPointer = sizeBuffer; Uint32 worldSize; Serialize::deserialize( bufferPointer, worldSize ); Uint8* worldBuffer = new Uint8[worldSize]; m_in.read( reinterpret_cast( worldBuffer ), worldSize ); bufferPointer = worldBuffer; m_world.deserialize( bufferPointer ); delete[] worldBuffer; // scan file for bookmarks ifstream::pos_type pos = m_in.tellg(); std::cout << "Scanning for bookmarks..." << std::endl; scanForBookmarks(); m_in.clear(); // rewind to the previous position m_in.seekg( pos, ios_base::beg ); m_world.setSpriteInterface( m_video.getSpriteInterface() ); // create a surface from the pixel values in the map m_world.optimizeForClient( m_video.getScreen()->format, SDL_SWSURFACE ); ClientMap* clientMap = dynamic_cast( m_world.getMap() ); /* Map* clientMap = m_world.getMap();*/ ASSERT( clientMap, "Replayer::Replayer: did not get ClientMap\n" ); m_mapSurface = clientMap->getPixels(); /* m_mapSurface = clientMap->getSurface();*/ ASSERT( m_mapSurface, "Replayer::Replayer: Could not create map surface (%s)\n", SDL_GetError() ); SDL_SetColorKey( m_mapSurface, SDL_SRCCOLORKEY, 0 ); WopSettings* settings = WopSettings::getInstance(); String themePath( settings->getData() ); themePath += '/'; themePath += THEME_CONFIG_PATH; Theme theme( settings->getTheme() ); ASSERT( theme.load((char*)themePath, THEME_CONFIG), "Replayer::Replayer: could not load theme \"%s/"THEME_CONFIG ":%s\"\n", (char*)themePath, (char*)theme.getName() ); INFO( "Theme background is '%s'\n", theme.getBackgroundPath().getString() ); if ( theme.hasBackgroundPath() ) { String backgroundPath( "images/" ); backgroundPath += theme.getBackgroundPath(); m_background = NEW WopBackground( m_world.getMap()->getSizeX(), m_world.getMap()->getSizeY(), backgroundPath.getString(), m_video.getScreen()->format ); } else { m_background = NEW WopBackground( m_world.getMap()->getSizeX(), m_world.getMap()->getSizeY(), "images/mapstuff/background/dark_earth.jpg", m_video.getScreen()->format ); } m_mapRect.x = 0; m_mapRect.y = 0; m_mapRect.w = m_video.getScreen()->w; m_mapRect.h = m_video.getScreen()->h; createGUI(); m_displayLoopMutex = SDL_CreateMutex(); m_displayLoopCond = SDL_CreateCond(); m_displayThread = SDL_CreateThread( replayDisplayLoopWrapper, this ); m_worldMutex = SDL_CreateMutex(); // SDL_EnableKeyRepeat( 10, 10 ); } Replayer::~Replayer() { m_in.close(); } void Replayer::nextFrame() { Uint8 recordTypeBuffer[Serialize::sizeOf()]; GameReplay::RecordType recordType; Uint8 sizeBuffer[Serialize::sizeOf()]; do { m_in.read( reinterpret_cast( recordTypeBuffer ), Serialize::sizeOf() ); Uint8* bufferPointer = recordTypeBuffer; Serialize::deserialize( bufferPointer, recordType ); switch ( recordType ) { // FIXME: move variable declarations out of cases case GameReplay::ECHO: { DBG( 5 ) INFO( "Replayer::nextFrame: Applying echo\n" ); m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 echoSize; Serialize::deserialize( bufferPointer, echoSize ); Uint8* echoBuffer = new Uint8[echoSize]; m_in.read( reinterpret_cast( echoBuffer ), echoSize ); bufferPointer = echoBuffer; EchoMessage echo; echo.deserialize( bufferPointer ); delete[] echoBuffer; // Check if the random counter from the server matches our counter ASSERT ( echo.randomCounter == m_world.getRandom().getCounter(), "\nReplay is no longer synchronous in frame %d (server: %d, replayer: %d)\n", m_frameCounter, echo.randomCounter, m_world.getRandom().getCounter() ); DBG( 5 ) INFO( "Replayer::nextFrame: random counter = %d\n", echo.randomCounter ); SDL_mutexP( m_worldMutex ); m_world.applyEvents( &echo ); m_world.doTimestep(); SDL_mutexV( m_worldMutex ); break; } case GameReplay::PLAYER_ENTER: { DBG( 4 ) INFO( "Replayer::nextFrame: New player enters\n" ); m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 playerSize; Serialize::deserialize( bufferPointer, playerSize ); Uint8* playerBuffer = new Uint8[playerSize]; m_in.read( reinterpret_cast( playerBuffer ), playerSize ); bufferPointer = playerBuffer; Player* player = new Player(); player->deserialize( bufferPointer ); delete[] playerBuffer; // player->setLocalPlayer( true ); SDL_mutexP( m_worldMutex ); m_world.addPlayer( player ); SDL_mutexV( m_worldMutex ); break; } case GameReplay::PLAYER_QUIT: { DBG( 4 ) INFO( "Replayer::nextFrame: Player quits\n" ); Uint8* indexBuffer = new Uint8[Serialize::sizeOf()]; m_in.read( reinterpret_cast( indexBuffer ), Serialize::sizeOf() ); bufferPointer = indexBuffer; Uint8 playerIndex; Serialize::deserialize( bufferPointer, playerIndex ); delete[] indexBuffer; SDL_mutexP( m_worldMutex ); Player* player = m_world.getPlayerByID( playerIndex ); m_world.removePlayer( playerIndex ); SDL_mutexV( m_worldMutex ); delete player; break; } case GameReplay::CHAT_MESSAGE: { DBG( 4 ) INFO( "Replayer::nextFrame: Chat message\n" ); m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 messageSize; Serialize::deserialize( bufferPointer, messageSize ); Uint8* messageBuffer = new Uint8[messageSize]; m_in.read( reinterpret_cast( messageBuffer ), messageSize ); bufferPointer = messageBuffer; String message; message.deserialize( bufferPointer ); delete[] messageBuffer; // do something useful with the message... INFO( "Message: %s\n", message.getString() ); break; } case GameReplay::BOOKMARK: DBG( 4 ) INFO( "Replayer::nextFrame: hit bookmark\n" ); break; default: DBG( 4 ) INFO( "Replayer::nextFrame: Unknown record type\n" ); } } while ( recordType != GameReplay::ECHO ); m_frameCounter++; } void Replayer::drawWorld( SDL_Surface* surface ) { // draw the background image SDL_BlitSurface( m_background->getGraphic(), &m_mapRect, surface, 0 ); // draw the map SDL_BlitSurface( m_mapSurface, &m_mapRect, surface, NULL ); // draw the world m_world.draw( surface, m_video.getSprites(), m_mapRect ); } void Replayer::updateGraphics() { // check for focused objects if ( m_focusedObject ) { m_mapRect.x = m_focusedObject->getIntPosX(); m_mapRect.y = m_focusedObject->getIntPosY(); } // set viewport position for stereo audio m_audio->setListeningPosition( m_mapRect.x + m_mapRect.w / 2, m_mapRect.y + m_mapRect.h / 2 ); SDL_mutexP( m_worldMutex ); drawWorld( m_video.getScreen() ); SDL_mutexV( m_worldMutex ); std::stringstream counterStream; counterStream << m_frameCounter; m_frameCounterDisplay->setText( counterStream.str() ); std::stringstream rateStream; rateStream << 40.0 / static_cast( m_frameDuration ); m_playbackRateDisplay->setText( rateStream.str() ); m_rootContainer->draw(); SDL_Flip( m_video.getScreen() ); } void Replayer::run() { while ( !m_quitFlag ) { SDL_Event event; SDL_WaitEvent( &event ); handleEvent( &event ); } stopPlayback(); int returnValue; SDL_WaitThread( m_displayThread, &returnValue ); } void Replayer::handleEvent( SDL_Event* event ) { if ( m_rootContainer->handleEvent( event ) ) return; if ( event->type == SDL_KEYDOWN ) { switch( event->key.keysym.sym ) { case SDLK_ESCAPE: m_quitFlag = true; break; case SDLK_UP: if ( m_mapRect.y > 0 ) { m_mapRect.y--; } break; case SDLK_DOWN: if ( m_mapRect.y + m_mapRect.h < m_mapSurface->h ) { m_mapRect.y++; } break; case SDLK_LEFT: if ( m_mapRect.x > 0 ) { m_mapRect.x--; } break; case SDLK_RIGHT: if ( m_mapRect.x + m_mapRect.w < m_mapSurface->w ) { m_mapRect.x++; } break; case SDLK_SPACE: if ( isPlaybackRunning() ) { stopPlayback(); } else { startPlayback(); } break; case SDLK_b: if ( m_frameTimer ) { SDL_RemoveTimer( m_frameTimer ); m_frameTimer = 0; } nextFrame(); signalDisplayThread(); break; case SDLK_m: if ( m_frameDuration < 320 ) { m_frameDuration *= 2; } break; case SDLK_n: if ( m_frameDuration > 5 ) { m_frameDuration /= 2; } break; case SDLK_k: stopPlayback(); skipFrames( m_N_SKIP_FRAMES ); signalDisplayThread(); break; case SDLK_s: stopPlayback(); saveScreenshot(); break; case SDLK_q: stopPlayback(); createMovie(); break; case SDLK_p: stopPlayback(); gotoNextBookmark(); break; case SDLK_o: stopPlayback(); rewind(); break; case SDLK_t: m_video.toggleFullscreen(); break; case SDLK_F10: // m_world.focusNextAvatar(); // m_focusedObject = m_world.getFocusedObject(); break; case SDLK_d: stopPlayback(); m_world.dump( std::cout ); break; default: break; } } else if ( event->type == SDL_MOUSEBUTTONDOWN ) { if ( event->button.button == SDL_BUTTON_LEFT ) { m_grabX = event->button.x; m_grabY = event->button.y; } } else if ( event->type == SDL_MOUSEMOTION ) { if ( event->motion.state & SDL_BUTTON( SDL_BUTTON_LEFT ) ) { m_focusedObject = 0; Sint16 newX = m_mapRect.x - event->motion.xrel; Sint16 newY = m_mapRect.y - event->motion.yrel; if ( newX < 0 ) newX = 0; else if ( newX > m_mapSurface->w - m_mapRect.w ) newX = m_mapSurface->w - m_mapRect.w; if ( newY < 0 ) newY = 0; else if ( newY > m_mapSurface->h - m_mapRect.h ) newY = m_mapSurface->h - m_mapRect.h; m_mapRect.x = newX; m_mapRect.y = newY; } } } void Replayer::displayLoop() { while( !m_quitFlag ) { SDL_mutexP( m_displayLoopMutex ); if ( SDL_CondWaitTimeout( m_displayLoopCond, m_displayLoopMutex, 40 ) != 0 ) { DBG( 5 ) CHECK( false, "Replayer::displayLoop: timeout when waiting for display condition\n" ); } updateGraphics(); SDL_mutexV( m_displayLoopMutex ); } } void Replayer::createGUI() { m_rootContainer = new RootContainer( m_video.getScreen() ); WidgetComposite* buttonContainer = new WidgetComposite(); buttonContainer->setLayout( new HorizontalLayout( buttonContainer ) ); const SDL_Color hlColor = {100, 100, 255, 0}; m_playButton = new TextButton( " Play " ); m_playButton->addActionListener( this ); m_playButton->setHighlightColor( hlColor ); m_playButton->setHighlightAlpha( 150 ); m_playButton->setHighlightable( true ); buttonContainer->add( m_playButton ); m_stopButton = new TextButton( " Stop " ); m_stopButton->addActionListener( this ); m_stopButton->setHighlightColor( hlColor ); m_stopButton->setHighlightAlpha( 150 ); m_stopButton->setHighlightable( true ); buttonContainer->add( m_stopButton ); m_skipButton = new TextButton( " Skip " ); m_skipButton->addActionListener( this ); m_skipButton->setHighlightColor( hlColor ); m_skipButton->setHighlightAlpha( 150 ); m_skipButton->setHighlightable( true ); buttonContainer->add( m_skipButton ); m_screenshotButton = new TextButton( " Screenshot " ); m_screenshotButton->addActionListener( this ); m_screenshotButton->setHighlightColor( hlColor ); m_screenshotButton->setHighlightAlpha( 150 ); m_screenshotButton->setHighlightable( true ); buttonContainer->add( m_screenshotButton ); buttonContainer->setPosition( ( m_rootContainer->getWidth() - buttonContainer->getWidth() ) / 2, m_rootContainer->getHeight() - buttonContainer->getHeight() ); m_rootContainer->add( buttonContainer ); std::stringstream counterStream; counterStream << m_frameCounter; m_frameCounterDisplay = new TextLabel( counterStream.str() ); m_rootContainer->add( m_frameCounterDisplay ); m_frameCounterDisplay->setPosition( 0, 0 ); std::stringstream rateStream; rateStream << 40.0 / static_cast( m_frameDuration ); m_playbackRateDisplay = new TextLabel( rateStream.str() ); m_rootContainer->add( m_playbackRateDisplay ); m_playbackRateDisplay->setPosition( 600, 0 ); } void Replayer::actionPerformed( Uint32 cmd, void* source ) { if ( cmd == Button::BUTTON_PRESSED ) { if ( source == m_playButton ) { startPlayback(); } else if ( source == m_stopButton ) { stopPlayback(); } else if ( source == m_skipButton ) { stopPlayback(); skipFrames( m_N_SKIP_FRAMES ); signalDisplayThread(); } else if ( source == m_screenshotButton ) { stopPlayback(); saveScreenshot(); } } } void Replayer::startPlayback() { if ( !m_frameTimer ) { m_frameTimer = SDL_AddTimer( 40, replayFrameTimerCallback, this ); } } void Replayer::stopPlayback() { if ( m_frameTimer ) { SDL_RemoveTimer( m_frameTimer ); m_frameTimer = 0; } } void Replayer::skipFrames( Uint32 numberFrames ) { for ( Uint32 i = 0; i < numberFrames; i++ ) { nextFrame(); } } void Replayer::saveScreenshot() { String filename; filename.format( "wop_%02i.bmp", m_screenshotNumber++ ); SDL_Surface* screen = m_video.getScreen(); SDL_Surface* surface = SDL_CreateRGBSurface( SDL_SWSURFACE, screen->w, screen->h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask ); drawWorld( surface ); SDL_SaveBMP( surface, filename ); SDL_FreeSurface( surface ); } void Replayer::createMovie() { static int frameCounter = 0; String filename; SDL_Surface* screen = m_video.getScreen(); SDL_Surface* surface = SDL_CreateRGBSurface( SDL_SWSURFACE, screen->w, screen->h, screen->format->BitsPerPixel, screen->format->Rmask, screen->format->Gmask, screen->format->Bmask, screen->format->Amask ); Audio::getInstance()->startRecording(); for ( int startFrameCounter = frameCounter; frameCounter-startFrameCounter < m_N_MOVIE_FRAMES; frameCounter++ ) { drawWorld( surface ); filename.format( "movie_%04i.bmp", frameCounter ); SDL_SaveBMP( surface, filename ); nextFrame(); Audio::getInstance()->nextRecordFrame(); } Audio::getInstance()->stopRecording(); String wavFilename; wavFilename.format( "movie_sound_%02i.raw", (frameCounter+1)/m_N_MOVIE_FRAMES-1 ); Audio::getInstance()->writeRecordToFile( wavFilename ); SDL_FreeSurface( surface ); } void Replayer::scanForBookmarks() { Uint8 recordTypeBuffer[Serialize::sizeOf()]; GameReplay::RecordType recordType; Uint8 sizeBuffer[Serialize::sizeOf()]; Uint32 frameCounter = 0; do { m_in.read( reinterpret_cast( recordTypeBuffer ), Serialize::sizeOf() ); Uint8* bufferPointer = recordTypeBuffer; Serialize::deserialize( bufferPointer, recordType ); switch ( recordType ) { // FIXME: move variable declarations out of cases case GameReplay::ECHO: { m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 echoSize; Serialize::deserialize( bufferPointer, echoSize ); m_in.seekg( echoSize, ios_base::cur ); frameCounter++; break; } case GameReplay::PLAYER_ENTER: { m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 playerSize; Serialize::deserialize( bufferPointer, playerSize ); m_in.seekg( playerSize, ios_base::cur ); break; } case GameReplay::PLAYER_QUIT: { m_in.seekg( Serialize::sizeOf(), ios_base::cur ); break; } case GameReplay::CHAT_MESSAGE: { m_in.read( reinterpret_cast( sizeBuffer ), Serialize::sizeOf() ); bufferPointer = sizeBuffer; Uint32 messageSize; Serialize::deserialize( bufferPointer, messageSize ); m_in.seekg( messageSize, ios_base::cur ); break; } case GameReplay::BOOKMARK: { Uint32 frame = frameCounter - m_BOOKMARK_REWIND; if ( frame < 0 ) frame = 0; m_bookmarks.push_back( frame ); std::cout << "Found bookmark at frame " << frameCounter << ", storing for frame " << frame << std::endl; break; } default: std::cerr << "Unknown record type" << std::endl; } } while ( !m_in.eof() ); } void Replayer::gotoNextBookmark() { std::vector::const_iterator it; for ( it = m_bookmarks.begin(); it != m_bookmarks.end() && *it <= m_frameCounter; it++ ) ; if ( it == m_bookmarks.end() ) std::cout << "No bookmark found after current frame" << std::endl; else { skipFrames( *it - m_frameCounter ); // save world in buffer delete[] m_worldBuffer; m_worldBuffer = new Uint8[m_world.getSerializeBufferSize()]; Uint8* bufferPointer = m_worldBuffer; m_world.serialize( bufferPointer ); m_posBuffer = m_in.tellg(); m_frameCounterBuffer = m_frameCounter; } } void Replayer::rewind() { if ( m_worldBuffer /* && m_randomBuffer */ ) { m_world.reset(); Uint8* bufferPointer = m_worldBuffer; m_world.deserialize( bufferPointer ); m_in.seekg( m_posBuffer, ios_base::beg ); m_frameCounter = m_frameCounterBuffer; } } Uint32 replayFrameTimerCallback( Uint32 interval, void* data ) { // INFO( "Start callback (interval: %d)\n", interval ); Uint32 ticksBefore = SDL_GetTicks(); Replayer* player = static_cast( data ); player->nextFrame(); player->signalDisplayThread(); Uint32 ticksAfter = SDL_GetTicks(); Uint32 duration = ticksAfter - ticksBefore; Uint32 newInterval = ( duration >= player->getFrameDuration() ) ? 1 : player->getFrameDuration() - duration; // INFO( "End callback (duration: %d)\n", duration ); return newInterval; } int replayDisplayLoopWrapper( void* data ) { Replayer* playerPtr = static_cast( data ); playerPtr->displayLoop(); return 0; }