2020-06-27 09:48:55 +00:00
/*
* * screenjob . cpp
* *
* * Generic asynchronous screen display
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2020 Christoph Oelckers
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
2020-06-20 07:46:41 +00:00
# include "types.h"
# include "build.h"
# include "screenjob.h"
2020-06-27 09:48:55 +00:00
# include "i_time.h"
# include "v_2ddrawer.h"
2020-06-27 22:32:28 +00:00
# include "animlib.h"
# include "v_draw.h"
# include "s_soundinternal.h"
# include "animtexture.h"
2020-07-19 10:48:31 +00:00
# include "gamestate.h"
2020-10-04 16:31:48 +00:00
# include "razemenu.h"
2020-07-26 16:02:24 +00:00
# include "raze_sound.h"
2020-07-29 21:18:08 +00:00
# include "SmackerDecoder.h"
2020-07-23 20:26:07 +00:00
# include "movie/playmve.h"
2020-07-28 19:05:14 +00:00
# include "gamecontrol.h"
2020-09-10 15:54:27 +00:00
# include <vpx/vpx_decoder.h>
# include <vpx/vp8dx.h>
2020-09-05 09:58:19 +00:00
# include "raze_music.h"
2021-04-22 16:52:39 +00:00
# include "vm.h"
2020-06-20 07:46:41 +00:00
2020-06-27 09:48:55 +00:00
IMPLEMENT_CLASS ( DScreenJob , true , false )
2021-04-22 16:52:39 +00:00
IMPLEMENT_CLASS ( DSkippableScreenJob , true , false )
IMPLEMENT_CLASS ( DBlackScreen , true , false )
2020-06-28 20:17:27 +00:00
IMPLEMENT_CLASS ( DImageScreen , true , false )
2021-04-22 16:52:39 +00:00
DEFINE_FIELD ( DScreenJob , flags )
DEFINE_FIELD ( DScreenJob , fadetime )
DEFINE_FIELD_NAMED ( DScreenJob , state , jobstate )
DEFINE_FIELD ( DScreenJob , fadestate )
DEFINE_FIELD ( DScreenJob , ticks )
DEFINE_FIELD ( DScreenJob , pausable )
DEFINE_FIELD ( DBlackScreen , wait )
DEFINE_FIELD ( DBlackScreen , cleared )
DEFINE_FIELD ( DImageScreen , tilenum )
DEFINE_FIELD ( DImageScreen , trans )
DEFINE_FIELD ( DImageScreen , waittime )
DEFINE_FIELD ( DImageScreen , cleared )
DEFINE_FIELD ( DImageScreen , texid )
DEFINE_ACTION_FUNCTION ( DScreenJob , Init )
{
// todo
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DScreenJob , ProcessInput )
{
PARAM_SELF_PROLOGUE ( DScreenJob ) ;
ACTION_RETURN_BOOL ( self - > ProcessInput ( ) ) ;
}
DEFINE_ACTION_FUNCTION ( DScreenJob , Start )
{
PARAM_SELF_PROLOGUE ( DScreenJob ) ;
self - > Start ( ) ;
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DScreenJob , OnEvent )
{
PARAM_SELF_PROLOGUE ( DScreenJob ) ;
PARAM_POINTER ( evt , FInputEvent ) ;
if ( evt - > Type ! = EV_KeyDown )
{
// not needed in the transition phase
ACTION_RETURN_BOOL ( false ) ;
}
event_t ev = { } ;
ev . type = EV_KeyDown ;
ev . data1 = evt - > KeyScan ;
ACTION_RETURN_BOOL ( self - > OnEvent ( & ev ) ) ;
}
DEFINE_ACTION_FUNCTION ( DScreenJob , OnTick )
{
PARAM_SELF_PROLOGUE ( DScreenJob ) ;
self - > OnTick ( ) ;
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DScreenJob , Draw )
{
PARAM_SELF_PROLOGUE ( DScreenJob ) ;
PARAM_FLOAT ( smooth ) ;
self - > Draw ( smooth ) ;
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DSkippableScreenJob , Init )
{
// todo
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DSkippableScreenJob , Skipped )
{
PARAM_SELF_PROLOGUE ( DSkippableScreenJob ) ;
self - > Skipped ( ) ;
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DBlackScreen , Init )
{
// todo
return 0 ;
}
DEFINE_ACTION_FUNCTION ( DImageScreen , Init )
{
// todo
return 0 ;
}
2021-04-21 22:25:55 +00:00
void DScreenJob : : OnDestroy ( )
{
if ( flags & stopmusic ) Mus_Stop ( ) ;
if ( flags & stopsound ) FX_StopAllSounds ( ) ;
}
2020-07-29 21:18:08 +00:00
2021-04-15 22:02:09 +00:00
bool DSkippableScreenJob : : OnEvent ( event_t * evt )
{
2021-04-20 13:01:26 +00:00
if ( evt - > type = = EV_KeyDown & & ! specialKeyEvent ( evt ) )
2021-04-16 16:43:59 +00:00
{
2021-04-20 13:01:26 +00:00
state = skipped ;
Skipped ( ) ;
2021-04-16 16:43:59 +00:00
}
2021-04-15 22:02:09 +00:00
return true ;
}
2021-04-15 22:11:02 +00:00
void DBlackScreen : : OnTick ( )
{
if ( cleared )
{
int span = ticks * 1000 / GameTicRate ;
if ( span > wait ) state = finished ;
}
}
2021-04-15 22:02:09 +00:00
2021-04-15 22:11:02 +00:00
void DBlackScreen : : Draw ( double )
2020-07-29 21:18:08 +00:00
{
2021-04-15 22:11:02 +00:00
cleared = true ;
2020-07-29 21:18:08 +00:00
twod - > ClearScreen ( ) ;
}
2020-06-28 20:17:27 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-04-15 22:11:02 +00:00
void DImageScreen : : OnTick ( )
2020-06-28 20:17:27 +00:00
{
2021-04-15 22:11:02 +00:00
if ( cleared )
2020-09-04 19:17:24 +00:00
{
2021-04-15 22:11:02 +00:00
int span = ticks * 1000 / GameTicRate ;
if ( span > waittime ) state = finished ;
2020-09-04 19:17:24 +00:00
}
2021-04-15 22:11:02 +00:00
}
void DImageScreen : : Draw ( double smoothratio )
{
if ( tilenum > 0 ) tex = tileGetTexture ( tilenum , true ) ;
2020-06-28 20:17:27 +00:00
twod - > ClearScreen ( ) ;
2021-04-15 22:11:02 +00:00
if ( tex ) DrawTexture ( twod , tex , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , DTA_LegacyRenderStyle , STYLE_Normal , DTA_TranslationIndex , trans , TAG_DONE ) ;
cleared = true ;
2020-06-28 20:17:27 +00:00
}
2020-07-19 09:57:00 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2021-04-21 22:51:14 +00:00
ScreenJobRunner : : ScreenJobRunner ( TArray < DScreenJob * > & jobs_ , CompletionFunc completion_ , bool clearbefore_ , bool skipall_ )
2021-04-21 22:35:48 +00:00
: completion ( std : : move ( completion_ ) ) , clearbefore ( clearbefore_ ) , skipall ( skipall_ )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
jobs = std : : move ( jobs_ ) ;
2021-04-21 22:35:48 +00:00
// Release all jobs from the garbage collector - the code as it is cannot deal with them getting collected. This should be removed later once the GC is working.
2021-04-21 22:51:14 +00:00
for ( unsigned i = 0 ; i < jobs . Size ( ) ; i + + )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
jobs [ i ] - > Release ( ) ;
2020-07-19 09:57:00 +00:00
}
2021-04-21 22:35:48 +00:00
AdvanceJob ( false ) ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-07-19 09:57:00 +00:00
2021-04-21 22:35:48 +00:00
ScreenJobRunner : : ~ ScreenJobRunner ( )
{
DeleteJobs ( ) ;
}
void ScreenJobRunner : : DeleteJobs ( )
{
for ( auto & job : jobs )
2020-07-19 10:48:31 +00:00
{
2021-04-21 22:51:14 +00:00
job - > ObjectFlags | = OF_YesReallyDelete ;
delete job ;
2020-07-19 10:48:31 +00:00
}
2021-04-21 22:35:48 +00:00
jobs . Clear ( ) ;
}
2020-07-19 10:48:31 +00:00
2021-04-21 22:35:48 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ScreenJobRunner : : AdvanceJob ( bool skip )
{
if ( index > = 0 )
2020-07-19 10:48:31 +00:00
{
2021-04-21 22:35:48 +00:00
//if (jobs[index].postAction) jobs[index].postAction();
2021-04-21 22:51:14 +00:00
jobs [ index ] - > Destroy ( ) ;
2020-07-19 10:48:31 +00:00
}
2021-04-21 22:35:48 +00:00
index + + ;
2021-04-21 22:51:14 +00:00
while ( index < jobs . Size ( ) & & ( jobs [ index ] = = nullptr | | ( skip & & skipall ) ) )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
if ( jobs [ index ] ! = nullptr ) jobs [ index ] - > Destroy ( ) ;
2020-07-19 09:57:00 +00:00
index + + ;
}
2021-04-21 22:35:48 +00:00
actionState = clearbefore ? State_Clear : State_Run ;
if ( index < jobs . Size ( ) )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
jobs [ index ] - > fadestate = ! paused & & jobs [ index ] - > flags & DScreenJob : : fadein ? DScreenJob : : fadein : DScreenJob : : visible ;
jobs [ index ] - > Start ( ) ;
2020-07-19 09:57:00 +00:00
}
2021-04-21 22:35:48 +00:00
inputState . ClearAllInput ( ) ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-07-19 09:57:00 +00:00
2021-04-21 22:35:48 +00:00
int ScreenJobRunner : : DisplayFrame ( double smoothratio )
{
auto & job = jobs [ index ] ;
auto now = I_GetTimeNS ( ) ;
2021-04-21 22:51:14 +00:00
bool processed = job - > ProcessInput ( ) ;
2021-04-21 22:35:48 +00:00
2021-04-21 22:51:14 +00:00
if ( job - > fadestate = = DScreenJob : : fadein )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
double ms = ( job - > ticks + smoothratio ) * 1000 / GameTicRate / job - > fadetime ;
2021-04-21 22:35:48 +00:00
float screenfade = ( float ) clamp ( ms , 0. , 1. ) ;
2021-04-16 19:27:54 +00:00
twod - > SetScreenFade ( screenfade ) ;
2021-04-21 22:51:14 +00:00
if ( screenfade = = 1.f ) job - > fadestate = DScreenJob : : visible ;
2020-07-19 09:57:00 +00:00
}
2021-04-21 22:51:14 +00:00
int state = job - > DrawFrame ( smoothratio ) ;
2021-04-21 22:35:48 +00:00
twod - > SetScreenFade ( 1.f ) ;
return state ;
}
2020-07-19 09:57:00 +00:00
2021-04-21 22:35:48 +00:00
int ScreenJobRunner : : FadeoutFrame ( double smoothratio )
{
auto & job = jobs [ index ] ;
2021-04-21 22:51:14 +00:00
double ms = ( fadeticks + smoothratio ) * 1000 / GameTicRate / job - > fadetime ;
2021-04-21 22:35:48 +00:00
float screenfade = 1.f - ( float ) clamp ( ms , 0. , 1. ) ;
twod - > SetScreenFade ( screenfade ) ;
2021-04-21 22:51:14 +00:00
job - > DrawFrame ( 1. ) ;
2021-04-21 22:35:48 +00:00
return ( screenfade > 0.f ) ;
}
2021-04-16 20:39:48 +00:00
2021-04-21 22:35:48 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ScreenJobRunner : : OnEvent ( event_t * ev )
{
if ( paused | | index > = jobs . Size ( ) ) return false ;
if ( ev - > type = = EV_KeyDown )
{
// We never reach the key binding checks in G_Responder, so for the console we have to check for ourselves here.
auto binding = Bindings . GetBinding ( ev - > data1 ) ;
if ( binding . CompareNoCase ( " toggleconsole " ) = = 0 )
2021-04-16 20:39:48 +00:00
{
2021-04-21 22:35:48 +00:00
C_ToggleConsole ( ) ;
return true ;
2021-04-16 20:39:48 +00:00
}
2021-04-21 22:35:48 +00:00
}
2021-04-16 20:39:48 +00:00
2021-04-21 22:51:14 +00:00
if ( jobs [ index ] - > state ! = DScreenJob : : running ) return false ;
2021-04-20 13:01:26 +00:00
2021-04-21 22:51:14 +00:00
return jobs [ index ] - > OnEvent ( ev ) ;
2021-04-21 22:35:48 +00:00
}
void ScreenJobRunner : : OnFinished ( )
{
if ( completion ) completion ( false ) ;
completion = nullptr ; // only finish once.
}
2021-04-15 22:50:13 +00:00
2021-04-21 22:35:48 +00:00
void ScreenJobRunner : : OnTick ( )
{
if ( paused ) return ;
if ( index > = jobs . Size ( ) )
2021-04-15 22:50:13 +00:00
{
2021-04-21 22:35:48 +00:00
//DeleteJobs();
//twod->SetScreenFade(1);
//twod->ClearScreen(); // This must not leave the 2d buffer empty.
//if (gamestate == GS_INTRO) OnFinished();
//else Net_WriteByte(DEM_ENDSCREENJOB); // intermissions must be terminated synchronously.
2021-04-15 22:50:13 +00:00
}
2021-04-21 22:35:48 +00:00
else
2021-04-15 22:50:13 +00:00
{
2021-04-21 22:51:14 +00:00
if ( jobs [ index ] - > state = = DScreenJob : : running )
2021-04-15 22:50:13 +00:00
{
2021-04-21 22:51:14 +00:00
jobs [ index ] - > ticks + + ;
jobs [ index ] - > OnTick ( ) ;
2021-04-15 22:50:13 +00:00
}
2021-04-21 22:51:14 +00:00
else if ( jobs [ index ] - > state = = DScreenJob : : stopping )
2021-04-15 22:50:13 +00:00
{
2021-04-21 22:35:48 +00:00
fadeticks + + ;
2021-04-15 22:50:13 +00:00
}
}
2021-04-21 22:35:48 +00:00
}
2021-04-15 22:50:13 +00:00
2021-04-21 22:35:48 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ScreenJobRunner : : RunFrame ( )
{
if ( index > = jobs . Size ( ) )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:35:48 +00:00
DeleteJobs ( ) ;
twod - > SetScreenFade ( 1 ) ;
twod - > ClearScreen ( ) ; // This must not leave the 2d buffer empty.
if ( completion ) completion ( false ) ;
return false ;
}
2021-04-16 19:27:54 +00:00
2021-04-21 22:35:48 +00:00
// ensure that we won't go back in time if the menu is dismissed without advancing our ticker
bool menuon = paused ;
2021-04-21 22:51:14 +00:00
if ( menuon ) last_paused_tic = jobs [ index ] - > ticks ;
else if ( last_paused_tic = = jobs [ index ] - > ticks ) menuon = true ;
2021-04-21 22:35:48 +00:00
double smoothratio = menuon ? 1. : I_GetTimeFrac ( ) ;
2021-04-16 19:27:54 +00:00
2021-04-21 22:35:48 +00:00
if ( actionState = = State_Clear )
{
actionState = State_Run ;
twod - > ClearScreen ( ) ;
}
else if ( actionState = = State_Run )
{
terminateState = DisplayFrame ( smoothratio ) ;
if ( terminateState < 1 )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:35:48 +00:00
// Must lock before displaying.
2021-04-21 22:51:14 +00:00
if ( jobs [ index ] - > flags & DScreenJob : : fadeout )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
jobs [ index ] - > fadestate = DScreenJob : : fadeout ;
jobs [ index ] - > state = DScreenJob : : stopping ;
2021-04-21 22:35:48 +00:00
actionState = State_Fadeout ;
fadeticks = 0 ;
2020-07-19 09:57:00 +00:00
}
2021-04-21 22:35:48 +00:00
else
2020-07-19 09:57:00 +00:00
{
AdvanceJob ( terminateState < 0 ) ;
}
}
}
2021-04-21 22:35:48 +00:00
else if ( actionState = = State_Fadeout )
{
int ended = FadeoutFrame ( smoothratio ) ;
if ( ended < 1 )
{
2021-04-21 22:51:14 +00:00
jobs [ index ] - > state = DScreenJob : : stopped ;
2021-04-21 22:35:48 +00:00
AdvanceJob ( terminateState < 0 ) ;
}
}
return true ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-07-19 09:57:00 +00:00
2020-07-19 10:48:31 +00:00
ScreenJobRunner * runner ;
2021-04-21 22:51:14 +00:00
void RunScreenJob ( TArray < DScreenJob * > & jobs , CompletionFunc completion , int flags )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
assert ( completion ! = nullptr ) ;
2020-08-07 20:00:43 +00:00
videoclearFade ( ) ;
2021-04-21 22:51:14 +00:00
if ( jobs . Size ( ) )
2020-07-19 09:57:00 +00:00
{
2021-04-21 22:51:14 +00:00
runner = new ScreenJobRunner ( jobs , completion , ! ( flags & SJ_DONTCLEAR ) , ! ! ( flags & SJ_SKIPALL ) ) ;
2021-04-21 22:18:53 +00:00
gameaction = ( flags & SJ_BLOCKUI ) ? ga_intro : ga_intermission ;
2020-09-05 13:43:34 +00:00
}
else
{
completion ( false ) ;
2020-07-19 09:57:00 +00:00
}
}
2020-07-19 10:48:31 +00:00
void DeleteScreenJob ( )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
if ( runner )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
delete runner ;
runner = nullptr ;
2020-07-19 09:57:00 +00:00
}
2021-04-07 17:39:48 +00:00
twod - > SetScreenFade ( 1 ) ;
2020-07-19 10:48:31 +00:00
}
2020-07-19 09:57:00 +00:00
2021-04-15 22:50:13 +00:00
void EndScreenJob ( )
{
if ( runner ) runner - > OnFinished ( ) ;
DeleteScreenJob ( ) ;
}
bool ScreenJobResponder ( event_t * ev )
{
if ( runner ) return runner - > OnEvent ( ev ) ;
return false ;
}
void ScreenJobTick ( )
{
if ( runner ) runner - > OnTick ( ) ;
}
2021-04-16 20:03:01 +00:00
bool ScreenJobDraw ( )
2020-07-19 10:48:31 +00:00
{
// we cannot recover from this because we have no completion callback to call.
2020-09-04 18:46:44 +00:00
if ( ! runner )
{
// We can get here before a gameaction has been processed. In that case just draw a black screen and wait.
if ( gameaction = = ga_nothing ) I_Error ( " Trying to run a non-existent screen job " ) ;
twod - > ClearScreen ( ) ;
2021-04-16 20:03:01 +00:00
return false ;
2020-09-04 18:46:44 +00:00
}
2020-07-19 10:48:31 +00:00
auto res = runner - > RunFrame ( ) ;
2020-07-26 10:43:32 +00:00
if ( ! res )
{
2020-09-04 18:46:44 +00:00
assert ( ( gamestate ! = GS_INTERMISSION & & gamestate ! = GS_INTRO ) | | gameaction ! = ga_nothing ) ;
2020-07-26 10:43:32 +00:00
DeleteScreenJob ( ) ;
}
2021-04-16 20:03:01 +00:00
return res ;
2020-07-19 09:57:00 +00:00
}