2019-09-19 22:42:45 +00:00
//-------------------------------------------------------------------------
/*
Copyright ( C ) 2010 - 2019 EDuke32 developers and contributors
Copyright ( C ) 2019 Nuke . YKT
This file is part of NBlood .
NBlood is free software ; you can redistribute it and / or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
See the GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
//-------------------------------------------------------------------------
2019-09-21 18:59:54 +00:00
# include "ns.h" // Must come before everything else!
2019-09-19 22:42:45 +00:00
# include "build.h"
2020-07-27 21:29:10 +00:00
# include "v_2ddrawer.h"
2019-09-19 22:42:45 +00:00
# include "compat.h"
# include "common_game.h"
2020-07-31 18:39:02 +00:00
# include "v_draw.h"
2020-12-09 14:56:32 +00:00
# include "blood.h"
2019-09-19 22:42:45 +00:00
2019-09-22 06:39:22 +00:00
BEGIN_BLD_NS
2020-11-17 08:03:10 +00:00
extern void ( * qavClientCallback [ ] ) ( int , void * ) ;
2019-09-19 22:42:45 +00:00
2021-08-21 11:25:26 +00:00
//==========================================================================
//
// QAV interpolation functions
//
//==========================================================================
enum
{
2021-08-23 02:05:13 +00:00
kQAVIsLoopable = 1 < < 0 ,
2021-08-21 11:25:26 +00:00
} ;
static TMap < FString , QAVPrevTileFinder > qavPrevTileFinders ;
static TMap < int , QAVInterpProps > qavInterpProps ;
static void qavInitTileFinderMap ( )
{
// Interpolate between frames if the picnums match. This is safest but could miss interpolations between suitable picnums.
2021-08-22 23:00:30 +00:00
qavPrevTileFinders . Insert ( " picnum " , [ ] ( FRAMEINFO * const thisFrame , FRAMEINFO * const prevFrame , const int & i ) - > TILE_FRAME * {
2021-08-21 11:25:26 +00:00
return prevFrame - > tiles [ i ] . picnum = = thisFrame - > tiles [ i ] . picnum ? & prevFrame - > tiles [ i ] : nullptr ;
} ) ;
// Interpolate between frames if the picnum is valid. This can be problematic if tile indices change between frames.
2021-08-22 23:00:30 +00:00
qavPrevTileFinders . Insert ( " index " , [ ] ( FRAMEINFO * const thisFrame , FRAMEINFO * const prevFrame , const int & i ) - > TILE_FRAME * {
2021-08-21 11:25:26 +00:00
return prevFrame - > tiles [ i ] . picnum > 0 ? & prevFrame - > tiles [ i ] : nullptr ;
} ) ;
// Find previous frame by iterating all previous frame's tiles and return on first matched x coordinate.
2021-08-22 23:00:30 +00:00
qavPrevTileFinders . Insert ( " x " , [ ] ( FRAMEINFO * const thisFrame , FRAMEINFO * const prevFrame , const int & i ) - > TILE_FRAME * {
2021-08-21 11:25:26 +00:00
for ( int j = 0 ; j < 8 ; j + + ) if ( thisFrame - > tiles [ i ] . x = = prevFrame - > tiles [ j ] . x )
{
return & prevFrame - > tiles [ j ] ;
}
return nullptr ;
} ) ;
// Find previous frame by iterating all previous frame's tiles and return on first matched y coordinate.
2021-08-22 23:00:30 +00:00
qavPrevTileFinders . Insert ( " y " , [ ] ( FRAMEINFO * const thisFrame , FRAMEINFO * const prevFrame , const int & i ) - > TILE_FRAME * {
2021-08-21 11:25:26 +00:00
for ( int j = 0 ; j < 8 ; j + + ) if ( thisFrame - > tiles [ i ] . y = = prevFrame - > tiles [ j ] . y )
{
return & prevFrame - > tiles [ j ] ;
}
return nullptr ;
} ) ;
}
QAVPrevTileFinder qavGetInterpType ( const FString & type )
{
if ( ! qavPrevTileFinders . CountUsed ( ) ) qavInitTileFinderMap ( ) ;
return * qavPrevTileFinders . CheckKey ( type ) ;
}
2021-08-22 23:00:30 +00:00
bool GameInterface : : IsQAVInterpTypeValid ( const FString & type )
{
return qavGetInterpType ( type ) ! = nullptr ;
}
void GameInterface : : AddQAVInterpProps ( const int & res_id , const FString & interptype , const bool & loopable , const TMap < int , TArray < int > > & ignoredata )
{
2021-08-23 02:05:13 +00:00
qavInterpProps . Insert ( res_id , { loopable ? kQAVIsLoopable : 0 , qavGetInterpType ( interptype ) , ignoredata } ) ;
2021-08-22 23:00:30 +00:00
}
void GameInterface : : RemoveQAVInterpProps ( const int & res_id )
{
qavInterpProps . Remove ( res_id ) ;
}
2021-08-21 11:25:26 +00:00
2021-07-29 05:14:21 +00:00
void DrawFrame ( double x , double y , double z , double a , TILE_FRAME * pTile , int stat , int shade , int palnum , bool to3dview )
2019-09-19 22:42:45 +00:00
{
stat | = pTile - > stat ;
2020-09-25 21:55:20 +00:00
if ( palnum < = 0 ) palnum = pTile - > palnum ;
2020-07-27 21:29:10 +00:00
if ( ! to3dview )
{
2020-08-14 19:01:27 +00:00
auto tex = tileGetTexture ( pTile - > picnum ) ;
2021-07-29 05:14:21 +00:00
double scale = z * ( 1. / 65536. ) ;
double angle = a * BAngToDegree ;
2020-08-14 19:01:27 +00:00
int renderstyle = ( stat & RS_NOMASK ) ? STYLE_Normal : STYLE_Translucent ;
double alpha = ( stat & RS_TRANS1 ) ? glblend [ 0 ] . def [ ! ! ( stat & RS_TRANS2 ) ] . alpha : 1. ;
int pin = ( stat & kQavOrientationLeft ) ? - 1 : ( stat & RS_ALIGN_R ) ? 1 : 0 ;
2020-08-14 19:18:14 +00:00
auto translation = TRANSLATION ( Translation_Remap , palnum ) ;
2020-08-14 19:01:27 +00:00
bool topleft = ! ! ( stat & RS_TOPLEFT ) ;
bool xflip = ! ! ( stat & 0x100 ) ; // repurposed flag
bool yflip = ! ! ( stat & RS_YFLIP ) ;
auto color = shadeToLight ( pTile - > shade + shade ) ;
2020-07-31 18:39:02 +00:00
DrawTexture ( twod , tex , x , y , DTA_ScaleX , scale , DTA_ScaleY , scale , DTA_Rotate , angle , DTA_LegacyRenderStyle , renderstyle , DTA_Alpha , alpha , DTA_Pin , pin , DTA_TranslationIndex , translation ,
2020-08-24 18:25:53 +00:00
DTA_TopLeft , topleft , DTA_CenterOffsetRel , ! topleft , DTA_FullscreenScale , FSMode_Fit320x200 , DTA_FlipOffsets , true , DTA_Color , color ,
2020-07-31 18:39:02 +00:00
DTA_FlipX , xflip , DTA_FlipY , yflip , TAG_DONE ) ;
2020-07-27 21:29:10 +00:00
}
else
{
2020-08-19 14:56:36 +00:00
// there's some disagreements about flag values between QAV and the drawer. Shuffle these around.
2020-08-14 19:01:27 +00:00
if ( stat & RS_YFLIP ) stat | = RS_YFLIPHUD ;
stat & = ~ RS_YFLIP ;
if ( stat & 0x100 ) stat | = RS_XFLIPHUD ;
2020-08-19 14:56:36 +00:00
stat & = ~ 0x100 ;
2020-08-14 19:01:27 +00:00
if ( ( stat & kQavOrientationLeft ) ) stat | = RS_ALIGN_L ;
2020-08-19 14:56:36 +00:00
stat & = ~ kQavOrientationLeft ;
2020-08-14 19:01:27 +00:00
2021-07-29 05:14:21 +00:00
hud_drawsprite ( x , y , z , a , pTile - > picnum , pTile - > shade + shade , palnum , stat ) ;
2020-07-27 21:29:10 +00:00
}
2019-09-19 22:42:45 +00:00
}
2021-08-22 23:32:00 +00:00
void QAV : : Draw ( double x , double y , int ticks , int stat , int shade , int palnum , bool to3dview , double const smoothratio )
2019-09-19 22:42:45 +00:00
{
2020-10-11 10:38:17 +00:00
assert ( ticksPerFrame > 0 ) ;
2021-07-29 05:14:21 +00:00
2021-08-21 11:25:26 +00:00
auto const interpdata = qavInterpProps . CheckKey ( res_id ) ;
2021-07-29 05:14:21 +00:00
2021-08-21 11:25:26 +00:00
auto const nFrame = clamp ( ticks / ticksPerFrame , 0 , nFrames - 1 ) ;
FRAMEINFO * const thisFrame = & frames [ nFrame ] ;
2021-07-29 05:14:21 +00:00
2021-08-22 23:32:00 +00:00
auto const oFrame = clamp ( ( nFrame = = 0 & & ( interpdata & & ( interpdata - > flags & kQAVIsLoopable ) ) ? nFrames : nFrame ) - 1 , 0 , nFrames - 1 ) ;
2021-08-21 11:25:26 +00:00
FRAMEINFO * const prevFrame = & frames [ oFrame ] ;
2021-07-29 09:22:52 +00:00
2021-08-21 11:25:26 +00:00
bool const interpolate = interpdata & & cl_hudinterpolation & & cl_bloodqavinterp & & ( nFrames > 1 ) & & ( nFrame ! = oFrame ) & & ( smoothratio ! = MaxSmoothRatio ) ;
2021-07-29 09:22:52 +00:00
2021-07-31 00:05:18 +00:00
for ( int i = 0 ; i < 8 ; i + + )
{
2021-08-21 11:25:26 +00:00
if ( thisFrame - > tiles [ i ] . picnum > 0 )
2021-07-31 00:05:18 +00:00
{
2021-08-21 11:25:26 +00:00
TILE_FRAME * const thisTile = & thisFrame - > tiles [ i ] ;
2021-08-22 23:00:30 +00:00
TILE_FRAME * const prevTile = interpolate & & interpdata - > CanInterpFrameTile ( nFrame , i ) ? interpdata - > PrevTileFinder ( thisFrame , prevFrame , i ) : nullptr ;
2021-07-31 00:05:18 +00:00
2021-08-21 11:25:26 +00:00
double tileX = x ;
double tileY = y ;
double tileZ ;
double tileA ;
if ( prevTile )
{
tileX + = interpolatedvaluef ( prevTile - > x , thisTile - > x , smoothratio ) ;
tileY + = interpolatedvaluef ( prevTile - > y , thisTile - > y , smoothratio ) ;
tileZ = interpolatedvaluef ( prevTile - > z , thisTile - > z , smoothratio ) ;
tileA = interpolatedangle ( buildang ( prevTile - > angle ) , buildang ( thisTile - > angle ) , smoothratio ) . asbuildf ( ) ;
2021-07-29 05:14:21 +00:00
}
else
{
2021-08-21 11:25:26 +00:00
tileX + = thisTile - > x ;
tileY + = thisTile - > y ;
tileZ = thisTile - > z ;
tileA = thisTile - > angle ;
2021-07-29 05:14:21 +00:00
}
2021-08-21 11:25:26 +00:00
DrawFrame ( tileX , tileY , tileZ , tileA , thisTile , stat , shade , palnum , to3dview ) ;
2021-07-29 05:14:21 +00:00
}
}
2020-08-02 22:25:40 +00:00
}
2019-09-19 22:42:45 +00:00
void QAV : : Play ( int start , int end , int nCallback , void * pData )
{
2020-10-11 10:38:17 +00:00
assert ( ticksPerFrame > 0 ) ;
2019-09-19 22:42:45 +00:00
int frame ;
int ticks ;
if ( start < 0 )
frame = ( start + 1 ) / ticksPerFrame ;
else
frame = start / ticksPerFrame + 1 ;
for ( ticks = ticksPerFrame * frame ; ticks < = end ; frame + + , ticks + = ticksPerFrame )
{
if ( frame > = 0 & & frame < nFrames )
{
FRAMEINFO * pFrame = & frames [ frame ] ;
SOUNDINFO * pSound = & pFrame - > sound ;
// by NoOne: handle Sound kill flags
2019-07-29 16:18:41 +00:00
if ( ! VanillaMode ( ) & & pSound - > sndFlags > 0 & & pSound - > sndFlags < = kFlagSoundKillAll ) {
2019-09-19 22:42:45 +00:00
for ( int i = 0 ; i < nFrames ; i + + ) {
FRAMEINFO * pFrame2 = & frames [ i ] ;
SOUNDINFO * pSound2 = & pFrame2 - > sound ;
if ( pSound2 - > sound ! = 0 ) {
if ( pSound - > sndFlags ! = kFlagSoundKillAll & & pSound2 - > priority ! = pSound - > priority ) continue ;
else if ( nSprite > = 0 ) {
// We need stop all sounds in a range
for ( int a = 0 ; a < = pSound2 - > sndRange ; a + + )
sfxKill3DSound ( & sprite [ nSprite ] , - 1 , pSound2 - > sound + a ) ;
} else {
sndKillAllSounds ( ) ;
}
}
}
}
if ( pSound - > sound > 0 ) {
2019-07-29 16:18:41 +00:00
int sound = pSound - > sound ;
// by NoOne: add random rage sound feature
if ( pSound - > sndRange > 0 & & ! VanillaMode ( ) )
sound + = Random ( ( pSound - > sndRange = = 1 ) ? 2 : pSound - > sndRange ) ;
2020-11-17 08:03:10 +00:00
if ( nSprite = = - 1 ) sndStartSample ( sound , - 1 , - 1 , 0 ) ;
else sfxPlay3DSound ( & sprite [ nSprite ] , sound , 16 + pSound - > priority , 6 ) ;
2019-09-19 22:42:45 +00:00
}
if ( pFrame - > nCallbackId > 0 & & nCallback ! = - 1 ) {
2020-10-11 08:54:05 +00:00
qavClientCallback [ nCallback ] ( pFrame - > nCallbackId , pData ) ;
2019-09-19 22:42:45 +00:00
}
}
}
}
2021-04-11 10:42:59 +00:00
void QAV : : Precache ( int palette )
2020-01-02 11:01:18 +00:00
{
for ( int i = 0 ; i < nFrames ; i + + )
{
for ( int j = 0 ; j < 8 ; j + + )
{
if ( frames [ i ] . tiles [ j ] . picnum > = 0 )
2021-04-11 10:42:59 +00:00
tilePrecacheTile ( frames [ i ] . tiles [ j ] . picnum , 0 , palette ) ;
2020-01-02 11:01:18 +00:00
}
}
2020-01-02 18:11:09 +00:00
}
2020-07-25 21:35:39 +00:00
2021-08-05 04:48:32 +00:00
void qavProcessTicker ( QAV * const pQAV , int * duration , int * lastTick )
{
if ( * duration > 0 )
{
auto thisTick = I_GetTime ( pQAV - > ticrate ) ;
auto numTicks = thisTick - ( * lastTick ) ;
if ( numTicks )
{
* lastTick = thisTick ;
* duration - = pQAV - > ticksPerFrame * numTicks ;
}
}
* duration = ClipLow ( * duration , 0 ) ;
}
2021-08-05 02:38:26 +00:00
void qavProcessTimer ( PLAYER * const pPlayer , QAV * const pQAV , int * duration , double * smoothratio , bool const fixedduration )
{
2021-08-23 01:05:08 +00:00
// Process if not paused.
if ( ! paused )
2021-08-05 02:38:26 +00:00
{
2021-08-23 01:05:08 +00:00
// Process clock based on QAV's ticrate and last tick value.
qavProcessTicker ( pQAV , & pPlayer - > qavTimer , & pPlayer - > qavLastTick ) ;
if ( pPlayer - > weaponTimer = = 0 )
{
// Check if we're playing an idle QAV as per the ticker's weapon timer.
* duration = fixedduration ? pQAV - > duration - 1 : I_GetBuildTime ( ) % pQAV - > duration ;
* smoothratio = MaxSmoothRatio ;
}
else if ( pPlayer - > qavTimer = = 0 )
{
// If qavTimer is 0, play the last frame uninterpolated. Sometimes the timer can be just ahead of weaponTimer.
* duration = pQAV - > duration - 1 ;
* smoothratio = MaxSmoothRatio ;
}
else
{
// Apply normal values.
* duration = pQAV - > duration - pPlayer - > qavTimer ;
* smoothratio = I_GetTimeFrac ( pQAV - > ticrate ) * MaxSmoothRatio ;
}
2021-08-05 02:38:26 +00:00
}
else
{
2021-08-23 01:05:08 +00:00
* smoothratio = MaxSmoothRatio ;
2021-08-05 02:38:26 +00:00
}
}
2020-07-25 21:35:39 +00:00
// This is to eliminate a huge design issue in NBlood that was apparently copied verbatim from the DOS-Version.
// Sequences were cached in the resource and directly returned from there in writable form, with byte swapping directly performed in the cache on Big Endian systems.
// To avoid such unsafe operations this caches the read data separately.
extern FMemArena seqcache ; // Use the same storage as the SEQs.
2020-10-11 08:54:05 +00:00
static TMap < int , QAV * > qavcache ;
2020-07-25 21:35:39 +00:00
QAV * getQAV ( int res_id )
{
2020-10-11 08:54:05 +00:00
auto p = qavcache . CheckKey ( res_id ) ;
2020-07-25 21:35:39 +00:00
if ( p ! = nullptr ) return * p ;
int index = fileSystem . FindResource ( res_id , " QAV " ) ;
if ( index < 0 )
{
return nullptr ;
}
auto fr = fileSystem . OpenFileReader ( index ) ;
2021-08-18 10:00:15 +00:00
// Start reading QAV for nFrames, skipping padded data.
for ( int i = 0 ; i < 8 ; i + + ) fr . ReadUInt8 ( ) ;
int nFrames = fr . ReadInt32 ( ) ;
auto qavdata = ( QAV * ) seqcache . Alloc ( sizeof ( QAV ) + ( ( nFrames - 1 ) * sizeof ( FRAMEINFO ) ) ) ;
// Write out QAV data.
qavdata - > nFrames = nFrames ;
qavdata - > ticksPerFrame = fr . ReadInt32 ( ) ;
qavdata - > duration = fr . ReadInt32 ( ) ;
qavdata - > x = fr . ReadInt32 ( ) ;
qavdata - > y = fr . ReadInt32 ( ) ;
qavdata - > nSprite = fr . ReadInt32 ( ) ;
for ( int i = 0 ; i < 4 ; i + + ) fr . ReadUInt8 ( ) ;
// Read FRAMEINFO data.
for ( int i = 0 ; i < qavdata - > nFrames ; i + + )
{
qavdata - > frames [ i ] . nCallbackId = fr . ReadInt32 ( ) ;
// Read SOUNDINFO data.
qavdata - > frames [ i ] . sound . sound = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . sound . priority = fr . ReadUInt8 ( ) ;
qavdata - > frames [ i ] . sound . sndFlags = fr . ReadUInt8 ( ) ;
qavdata - > frames [ i ] . sound . sndRange = fr . ReadUInt8 ( ) ;
for ( int i = 0 ; i < 1 ; i + + ) fr . ReadUInt8 ( ) ;
// Read TILE_FRAME data.
for ( int j = 0 ; j < 8 ; j + + )
{
qavdata - > frames [ i ] . tiles [ j ] . picnum = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . x = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . y = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . z = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . stat = fr . ReadInt32 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . shade = fr . ReadInt8 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . palnum = fr . ReadUInt8 ( ) ;
qavdata - > frames [ i ] . tiles [ j ] . angle = fr . ReadUInt16 ( ) ;
}
}
2021-08-05 04:48:32 +00:00
// Write out additions.
2021-08-04 01:38:00 +00:00
qavdata - > res_id = res_id ;
2021-08-05 04:48:32 +00:00
qavdata - > ticrate = 120. / qavdata - > ticksPerFrame ;
2020-10-11 08:54:05 +00:00
qavcache . Insert ( res_id , qavdata ) ;
2020-07-25 21:35:39 +00:00
return qavdata ;
}
2020-01-02 18:11:09 +00:00
END_BLD_NS