- Blood: Implement `defineqav` DEF parser with hookup to game-side code.

This commit is contained in:
Mitchell Richters 2021-08-23 09:00:30 +10:00
parent da78160cd1
commit 916241dcdb
5 changed files with 196 additions and 9 deletions

View File

@ -40,6 +40,7 @@
#include "buildtiles.h"
#include "bitmap.h"
#include "m_argv.h"
#include "gamestruct.h"
#include "gamecontrol.h"
#include "palettecontainer.h"
#include "mapinfo.h"
@ -2008,6 +2009,166 @@ void parseModel(FScanner& sc, FScriptPosition& pos)
}
}
//===========================================================================
//
//
//
//===========================================================================
static bool parseDefineQAVInterpolateIgnoreBlock(FScanner& sc, const int& res_id, TMap<int, TArray<int>>& ignoredata, const int& numframes)
{
FScanner::SavedPos blockend;
FScriptPosition pos = sc;
FString scframes, sctiles;
TArray<int> framearray, tilearray;
if (sc.StartBraces(&blockend))
{
pos.Message(MSG_ERROR, "defineqav (%d): interpolate: malformed syntax, unable to continue", res_id);
return false;
}
while (!sc.FoundEndBrace(blockend))
{
sc.GetString();
if (sc.Compare("frames")) sc.GetString(scframes);
else if (sc.Compare("tiles")) sc.GetString(sctiles);
}
// Confirm we received something for 'frames' and 'tiles'.
if (scframes.IsEmpty() || sctiles.IsEmpty())
{
pos.Message(MSG_ERROR, "defineqav (%d): interpolate: unable to get any values for 'frames' or 'tiles', unable to continue", res_id);
return false;
}
auto arraybuilder = [&](const FString& input, TArray<int>& output, const int& maxvalue) -> bool
{
// Split input if it is an array, otherwise push the singular value twice.
if (input.IndexOf("-") != -1)
{
auto temparray = input.Split("-");
for (auto& value : temparray) output.Push(atoi(value));
}
else
{
auto tempvalue = atoi(input);
for (auto i = 0; i < 2; i++) output.Push(tempvalue);
}
if (output.Size() != 2 || output[0] > output[1] || output[1] > maxvalue)
{
pos.Message(MSG_ERROR, "defineqav (%d): interpolate: ignore: value of '%s' is malformed, unable to continue", res_id, input.GetChars());
return false;
}
return true;
};
if (!arraybuilder(scframes, framearray, numframes - 1)) return false;
if (!arraybuilder(sctiles, tilearray, 7)) return false;
// Process arrays and add ignored frames as required.
for (auto i = framearray[0]; i <= framearray[1]; i++)
{
auto& frametiles = ignoredata[i];
for (auto j = tilearray[0]; j <= tilearray[1]; j++)
{
if (!frametiles.Contains(j)) frametiles.Push(j);
}
}
return true;
}
static bool parseDefineQAVInterpolateBlock(FScanner& sc, const int& res_id, const int& numframes)
{
FScanner::SavedPos blockend;
FScriptPosition pos = sc;
FString interptype;
bool loopable = false;
TMap<int, TArray<int>> ignoredata;
if (sc.StartBraces(&blockend))
{
pos.Message(MSG_ERROR, "defineqav (%d): interpolate (%s): malformed syntax, unable to continue", res_id, interptype.GetChars());
return false;
}
while (!sc.FoundEndBrace(blockend))
{
sc.GetString();
if (sc.Compare("type"))
{
sc.GetString(interptype);
if (!gi->IsQAVInterpTypeValid(interptype))
{
pos.Message(MSG_ERROR, "defineqav (%d): interpolate (%s): interpolation type not found", res_id, interptype.GetChars());
return false;
}
}
else if (sc.Compare("loopable")) loopable = true;
else if (sc.Compare("ignore")) if (!parseDefineQAVInterpolateIgnoreBlock(sc, res_id, ignoredata, numframes)) return false;
}
// Add interpolation properties to game for processing while drawing.
gi->AddQAVInterpProps(res_id, interptype, loopable, ignoredata);
return true;
}
void parseDefineQAV(FScanner& sc, FScriptPosition& pos)
{
FScanner::SavedPos blockend;
FString fn;
int res_id = -1;
int numframes = -1;
bool interpolate = false;
if (!sc.GetNumber(res_id, true))
{
pos.Message(MSG_ERROR, "defineqav: invalid or non-defined resource ID");
return;
}
if (sc.StartBraces(&blockend))
{
pos.Message(MSG_ERROR, "defineqav (%d): malformed syntax, unable to continue", res_id);
return;
}
while (!sc.FoundEndBrace(blockend))
{
sc.MustGetString();
if (sc.Compare("file"))
{
sc.GetString(fn);
// Test file's validity.
FixPathSeperator(fn);
auto lump = fileSystem.FindFile(fn);
if (lump < 0)
{
pos.Message(MSG_ERROR, "defineqav (%d): file '%s' could not be found", res_id, fn.GetChars());
return;
}
// Read file to get number of frames from QAV, skipping first 8 bytes.
auto fr = fileSystem.OpenFileReader(lump);
fr.ReadUInt64();
numframes = fr.ReadInt32();
}
else if (sc.Compare("interpolate"))
{
interpolate = true;
if (!parseDefineQAVInterpolateBlock(sc, res_id, numframes)) return;
}
}
// If we're not interpolating, remove any reference to interpolation data for this res_id.
if (!interpolate) gi->RemoveQAVInterpProps(res_id);
// Add new file to filesystem.
fileSystem.CreatePathlessCopy(fn, res_id, 0);
}
//===========================================================================
//
//
@ -2098,6 +2259,7 @@ static const dispatch basetokens[] =
{ "shadefactor", parseSkip<1> },
{ "newgamechoices", parseEmptyBlock },
{ "rffdefineid", parseRffDefineId },
{ "defineqav", parseDefineQAV },
{ nullptr, nullptr },
};

View File

@ -122,6 +122,9 @@ struct GameInterface
virtual int Voxelize(int sprnum) { return -1; }
virtual void AddExcludedEpisode(FString episode) {}
virtual int GetCurrentSkill() { return -1; }
virtual bool IsQAVInterpTypeValid(const FString& type) { return false; }
virtual void AddQAVInterpProps(const int& res_id, const FString& interptype, const bool& loopable, const TMap<int, TArray<int>>& ignoredata) { }
virtual void RemoveQAVInterpProps(const int& res_id) { }
virtual FString statFPS()
{

View File

@ -150,6 +150,9 @@ struct GameInterface : public ::GameInterface
void LeavePortal(spritetype* viewer, int type) override;
void LoadGameTextures() override;
int GetCurrentSkill() override;
bool IsQAVInterpTypeValid(const FString& type) override;
void AddQAVInterpProps(const int& res_id, const FString& interptype, const bool& loopable, const TMap<int, TArray<int>>& ignoredata) override;
void RemoveQAVInterpProps(const int& res_id) override;
GameStats getStats() override;
};

View File

@ -46,23 +46,22 @@ enum
};
static TMap<FString, QAVPrevTileFinder> qavPrevTileFinders;
static TMap<int, TMap<int, TArray<int>>> qavSkippedFrameTiles;
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.
qavPrevTileFinders.Insert("interpolate-picnum", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
qavPrevTileFinders.Insert("picnum", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
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.
qavPrevTileFinders.Insert("interpolate-index", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
qavPrevTileFinders.Insert("index", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
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.
qavPrevTileFinders.Insert("interpolate-x", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
qavPrevTileFinders.Insert("x", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
for (int j = 0; j < 8; j++) if (thisFrame->tiles[i].x == prevFrame->tiles[j].x)
{
return &prevFrame->tiles[j];
@ -71,16 +70,13 @@ static void qavInitTileFinderMap()
});
// Find previous frame by iterating all previous frame's tiles and return on first matched y coordinate.
qavPrevTileFinders.Insert("interpolate-y", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
qavPrevTileFinders.Insert("y", [](FRAMEINFO* const thisFrame, FRAMEINFO* const prevFrame, const int& i) -> TILE_FRAME* {
for (int j = 0; j < 8; j++) if (thisFrame->tiles[i].y == prevFrame->tiles[j].y)
{
return &prevFrame->tiles[j];
}
return nullptr;
});
// When type is unspecified, default to using the safest interpolation option.
qavPrevTileFinders.Insert("interpolate", *qavPrevTileFinders.CheckKey("interpolate-picnum"));
}
QAVPrevTileFinder qavGetInterpType(const FString& type)
@ -89,6 +85,21 @@ QAVPrevTileFinder qavGetInterpType(const FString& type)
return *qavPrevTileFinders.CheckKey(type);
}
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)
{
qavInterpProps.Insert(res_id, { loopable << kQAVIsLoopable, qavGetInterpType(interptype), ignoredata });
}
void GameInterface::RemoveQAVInterpProps(const int& res_id)
{
qavInterpProps.Remove(res_id);
}
void DrawFrame(double x, double y, double z, double a, TILE_FRAME *pTile, int stat, int shade, int palnum, bool to3dview)
{
@ -147,7 +158,7 @@ void QAV::Draw(double x, double y, int ticks, int stat, int shade, int palnum, b
if (thisFrame->tiles[i].picnum > 0)
{
TILE_FRAME* const thisTile = &thisFrame->tiles[i];
TILE_FRAME* const prevTile = interpolate ? interpdata->PrevTileFinder(thisFrame, prevFrame, i) : nullptr;
TILE_FRAME* const prevTile = interpolate && interpdata->CanInterpFrameTile(nFrame, i) ? interpdata->PrevTileFinder(thisFrame, prevFrame, i) : nullptr;
double tileX = x;
double tileY = y;

View File

@ -244,6 +244,14 @@ struct QAVInterpProps
{
int flags;
QAVPrevTileFinder PrevTileFinder;
TMap<int, TArray<int>> IgnoreData;
bool CanInterpFrameTile(const int& nFrame, const int& i)
{
// Check whether the current frame's tile is skippable.
auto thisFrame = IgnoreData.CheckKey(nFrame);
return thisFrame ? !thisFrame->Contains(i) : true;
}
};
QAV* getQAV(int res_id);