From 916241dcdbb3f83ce842cebda51e3def1a571eca Mon Sep 17 00:00:00 2001 From: Mitchell Richters Date: Mon, 23 Aug 2021 09:00:30 +1000 Subject: [PATCH] - Blood: Implement `defineqav` DEF parser with hookup to game-side code. --- source/core/defparser.cpp | 162 +++++++++++++++++++++++++++++++++ source/core/gamestruct.h | 3 + source/games/blood/src/blood.h | 3 + source/games/blood/src/qav.cpp | 29 ++++-- source/games/blood/src/qav.h | 8 ++ 5 files changed, 196 insertions(+), 9 deletions(-) diff --git a/source/core/defparser.cpp b/source/core/defparser.cpp index 11c92a3ae..4d8c68a8d 100644 --- a/source/core/defparser.cpp +++ b/source/core/defparser.cpp @@ -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>& ignoredata, const int& numframes) +{ + FScanner::SavedPos blockend; + FScriptPosition pos = sc; + + FString scframes, sctiles; + TArray 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& 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> 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 }, }; diff --git a/source/core/gamestruct.h b/source/core/gamestruct.h index 5b864412b..c1212b088 100644 --- a/source/core/gamestruct.h +++ b/source/core/gamestruct.h @@ -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>& ignoredata) { } + virtual void RemoveQAVInterpProps(const int& res_id) { } virtual FString statFPS() { diff --git a/source/games/blood/src/blood.h b/source/games/blood/src/blood.h index 69b92a1e2..33d012bb6 100644 --- a/source/games/blood/src/blood.h +++ b/source/games/blood/src/blood.h @@ -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>& ignoredata) override; + void RemoveQAVInterpProps(const int& res_id) override; GameStats getStats() override; }; diff --git a/source/games/blood/src/qav.cpp b/source/games/blood/src/qav.cpp index 314b0d104..874726c55 100644 --- a/source/games/blood/src/qav.cpp +++ b/source/games/blood/src/qav.cpp @@ -46,23 +46,22 @@ enum }; static TMap qavPrevTileFinders; -static TMap>> qavSkippedFrameTiles; static TMap 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>& 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; diff --git a/source/games/blood/src/qav.h b/source/games/blood/src/qav.h index 372a6f64f..57a9bed2f 100644 --- a/source/games/blood/src/qav.h +++ b/source/games/blood/src/qav.h @@ -244,6 +244,14 @@ struct QAVInterpProps { int flags; QAVPrevTileFinder PrevTileFinder; + TMap> 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);