2017-06-28 19:00:08 -04:00
|
|
|
#pragma once
|
|
|
|
#include "stdafx.h"
|
2018-03-14 23:25:06 -04:00
|
|
|
#include <unordered_set>
|
2017-06-28 19:00:08 -04:00
|
|
|
#include "PPU.h"
|
|
|
|
#include "../Utilities/HexUtilities.h"
|
|
|
|
|
|
|
|
struct HdTileKey
|
|
|
|
{
|
2018-07-01 15:21:05 -04:00
|
|
|
static constexpr int32_t NoTile = -1;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
|
|
|
uint32_t PaletteColors;
|
|
|
|
uint8_t TileData[16];
|
2018-03-24 11:22:43 -04:00
|
|
|
int32_t TileIndex;
|
2017-06-28 19:00:08 -04:00
|
|
|
bool IsChrRamTile = false;
|
|
|
|
|
|
|
|
HdTileKey GetKey(bool defaultKey)
|
|
|
|
{
|
|
|
|
if(defaultKey) {
|
|
|
|
HdTileKey copy = *this;
|
|
|
|
copy.PaletteColors = 0xFFFFFFFF;
|
|
|
|
return copy;
|
|
|
|
} else {
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t GetHashCode() const
|
|
|
|
{
|
|
|
|
if(IsChrRamTile) {
|
|
|
|
return CalculateHash((uint8_t*)&PaletteColors, 20);
|
|
|
|
} else {
|
|
|
|
uint64_t key = TileIndex | ((uint64_t)PaletteColors << 32);
|
|
|
|
return CalculateHash((uint8_t*)&key, sizeof(key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t operator() (const HdTileKey &tile) const {
|
|
|
|
return tile.GetHashCode();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const HdTileKey &other) const
|
|
|
|
{
|
|
|
|
if(IsChrRamTile) {
|
|
|
|
return memcmp((uint8_t*)&PaletteColors, (uint8_t*)&other.PaletteColors, 20) == 0;
|
|
|
|
} else {
|
2017-07-25 19:46:25 -04:00
|
|
|
return TileIndex == other.TileIndex && PaletteColors == other.PaletteColors;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t CalculateHash(const uint8_t* key, size_t len) const
|
|
|
|
{
|
|
|
|
uint32_t result = 0;
|
|
|
|
for(size_t i = 0; i < len; i += 4) {
|
2018-06-19 20:43:16 -04:00
|
|
|
uint32_t chunk;
|
|
|
|
memcpy(&chunk, key, sizeof(uint32_t));
|
|
|
|
|
|
|
|
result += chunk;
|
2017-06-28 19:00:08 -04:00
|
|
|
result = (result << 2) | (result >> 30);
|
|
|
|
key += 4;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsSpriteTile()
|
|
|
|
{
|
|
|
|
return (PaletteColors & 0xFF000000) == 0xFF000000;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
namespace std {
|
|
|
|
template <> struct hash<HdTileKey>
|
|
|
|
{
|
|
|
|
size_t operator()(const HdTileKey& x) const
|
|
|
|
{
|
|
|
|
return x.GetHashCode();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
struct HdPpuTileInfo : public HdTileKey
|
|
|
|
{
|
|
|
|
uint8_t OffsetX;
|
|
|
|
uint8_t OffsetY;
|
|
|
|
bool HorizontalMirroring;
|
|
|
|
bool VerticalMirroring;
|
|
|
|
bool BackgroundPriority;
|
2017-07-25 19:46:25 -04:00
|
|
|
|
2017-06-28 19:00:08 -04:00
|
|
|
uint8_t BgColorIndex;
|
|
|
|
uint8_t SpriteColorIndex;
|
|
|
|
uint8_t BgColor;
|
|
|
|
uint8_t SpriteColor;
|
2017-07-25 19:46:25 -04:00
|
|
|
uint8_t PpuBackgroundColor;
|
2021-08-11 09:52:55 +08:00
|
|
|
uint32_t PaletteOffset;
|
Add sprite frame range condition
A new condition named "spriteFrameRange" is added. It works like frameRange except it follows frame counters assigned to each sprite. Frame counter picks up the nearest frame counter within 6x6 pixels from the last frame and resets if graphics, palette, bg priority or orientation has changed. The condition can by used by sprite tiles only. For example:
<condition>myCondition,spriteFrameRange,301,60
[myCondition]<tile>0,1001,FF0F3600,8,0,1,N
2021-04-15 19:54:01 +08:00
|
|
|
|
|
|
|
uint8_t OAMIndex;
|
2017-06-28 19:00:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
struct HdPpuPixelInfo
|
|
|
|
{
|
|
|
|
HdPpuTileInfo Tile;
|
2017-07-25 19:46:25 -04:00
|
|
|
vector<HdPpuTileInfo> Sprite;
|
|
|
|
int SpriteCount;
|
2018-02-17 23:44:25 -05:00
|
|
|
|
|
|
|
uint16_t TmpVideoRamAddr;
|
|
|
|
uint8_t XScroll;
|
2018-03-11 22:42:32 -04:00
|
|
|
uint8_t EmphasisBits;
|
|
|
|
bool Grayscale;
|
2017-07-25 19:46:25 -04:00
|
|
|
|
|
|
|
HdPpuPixelInfo()
|
|
|
|
{
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
|
|
Sprite.push_back(HdPpuTileInfo());
|
|
|
|
}
|
|
|
|
}
|
2017-06-28 19:00:08 -04:00
|
|
|
};
|
|
|
|
|
Add sprite frame range condition
A new condition named "spriteFrameRange" is added. It works like frameRange except it follows frame counters assigned to each sprite. Frame counter picks up the nearest frame counter within 6x6 pixels from the last frame and resets if graphics, palette, bg priority or orientation has changed. The condition can by used by sprite tiles only. For example:
<condition>myCondition,spriteFrameRange,301,60
[myCondition]<tile>0,1001,FF0F3600,8,0,1,N
2021-04-15 19:54:01 +08:00
|
|
|
struct HdScreenTileInfo : public HdTileKey
|
|
|
|
{
|
|
|
|
int16_t ScreenX;
|
|
|
|
int16_t ScreenY;
|
|
|
|
bool HorizontalMirroring;
|
|
|
|
bool VerticalMirroring;
|
|
|
|
bool BackgroundPriority;
|
|
|
|
bool IsNew;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HdSpriteFrameRangeInfo
|
|
|
|
{
|
|
|
|
bool lastUpdated;
|
|
|
|
bool updated;
|
|
|
|
HdScreenTileInfo last;
|
|
|
|
HdScreenTileInfo current;
|
|
|
|
uint32_t lastStartFrameNumber;
|
|
|
|
uint32_t startFrameNumber;
|
|
|
|
|
|
|
|
HdSpriteFrameRangeInfo() {
|
|
|
|
updated = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
struct HdScreenInfo
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
2018-02-17 23:44:25 -05:00
|
|
|
HdPpuPixelInfo* ScreenTiles;
|
|
|
|
std::unordered_map<uint32_t, uint8_t> WatchedAddressValues;
|
|
|
|
uint32_t FrameNumber;
|
Add sprite frame range condition
A new condition named "spriteFrameRange" is added. It works like frameRange except it follows frame counters assigned to each sprite. Frame counter picks up the nearest frame counter within 6x6 pixels from the last frame and resets if graphics, palette, bg priority or orientation has changed. The condition can by used by sprite tiles only. For example:
<condition>myCondition,spriteFrameRange,301,60
[myCondition]<tile>0,1001,FF0F3600,8,0,1,N
2021-04-15 19:54:01 +08:00
|
|
|
HdSpriteFrameRangeInfo* spriteFrameRanges;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
HdScreenInfo(const HdScreenInfo& that) = delete;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-07-03 22:48:28 -04:00
|
|
|
HdScreenInfo(bool isChrRamGame)
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
2018-02-17 23:44:25 -05:00
|
|
|
ScreenTiles = new HdPpuPixelInfo[PPU::PixelCount];
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
for(int i = 0; i < PPU::PixelCount; i++) {
|
|
|
|
ScreenTiles[i].Tile.BackgroundPriority = false;
|
2018-07-03 22:48:28 -04:00
|
|
|
ScreenTiles[i].Tile.IsChrRamTile = isChrRamGame;
|
2018-02-17 23:44:25 -05:00
|
|
|
ScreenTiles[i].Tile.HorizontalMirroring = false;
|
|
|
|
ScreenTiles[i].Tile.VerticalMirroring = false;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
for(int j = 0; j < 4; j++) {
|
2018-07-03 22:48:28 -04:00
|
|
|
ScreenTiles[i].Sprite[j].IsChrRamTile = isChrRamGame;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
}
|
2018-02-17 23:44:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
~HdScreenInfo()
|
|
|
|
{
|
|
|
|
delete[] ScreenTiles;
|
|
|
|
}
|
|
|
|
};
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
struct HdPackCondition
|
|
|
|
{
|
|
|
|
string Name;
|
|
|
|
|
|
|
|
virtual string GetConditionName() = 0;
|
2018-12-12 20:47:23 -05:00
|
|
|
virtual bool IsExcludedFromFile() { return Name.size() > 0 && Name[0] == '!'; }
|
2018-02-17 23:44:25 -05:00
|
|
|
virtual string ToString() = 0;
|
|
|
|
|
2018-06-11 19:07:05 -04:00
|
|
|
virtual ~HdPackCondition() { }
|
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
void ClearCache()
|
|
|
|
{
|
|
|
|
_resultCache = -1;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
bool CheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile)
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
2018-02-17 23:44:25 -05:00
|
|
|
if(_resultCache == -1) {
|
|
|
|
bool result = InternalCheckCondition(screenInfo, x, y, tile);
|
2018-02-25 17:03:06 -05:00
|
|
|
if(Name[0] == '!') {
|
|
|
|
result = !result;
|
|
|
|
}
|
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
if(_useCache) {
|
|
|
|
_resultCache = result ? 1 : 0;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
2018-02-17 23:44:25 -05:00
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
return (bool)_resultCache;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
}
|
2018-02-17 23:44:25 -05:00
|
|
|
|
|
|
|
protected:
|
|
|
|
int8_t _resultCache = -1;
|
|
|
|
bool _useCache = false;
|
|
|
|
|
|
|
|
virtual bool InternalCheckCondition(HdScreenInfo *screenInfo, int x, int y, HdPpuTileInfo* tile) = 0;
|
2017-06-28 19:00:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
struct HdPackTileInfo : public HdTileKey
|
|
|
|
{
|
|
|
|
uint32_t X;
|
|
|
|
uint32_t Y;
|
|
|
|
uint32_t BitmapIndex;
|
2019-12-20 13:53:13 -05:00
|
|
|
int Brightness;
|
2017-06-28 19:00:08 -04:00
|
|
|
bool DefaultTile;
|
|
|
|
bool Blank;
|
2017-07-25 19:46:25 -04:00
|
|
|
bool HasTransparentPixels;
|
2017-08-15 19:18:00 -04:00
|
|
|
bool TransparencyRequired;
|
2017-07-25 19:46:25 -04:00
|
|
|
bool IsFullyTransparent;
|
2017-06-28 19:00:08 -04:00
|
|
|
vector<uint32_t> HdTileData;
|
|
|
|
uint32_t ChrBankId;
|
|
|
|
|
|
|
|
vector<HdPackCondition*> Conditions;
|
2018-02-17 23:44:25 -05:00
|
|
|
bool ForceDisableCache;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
2018-02-17 23:44:25 -05:00
|
|
|
bool MatchesCondition(HdScreenInfo *hdScreenInfo, int x, int y, HdPpuTileInfo* tile)
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
|
|
|
for(HdPackCondition* condition : Conditions) {
|
2018-02-17 23:44:25 -05:00
|
|
|
if(!condition->CheckCondition(hdScreenInfo, x, y, tile)) {
|
2017-06-28 19:00:08 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-13 22:19:26 -04:00
|
|
|
vector<uint32_t> ToRgb(uint32_t* palette)
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
|
|
|
vector<uint32_t> rgbBuffer;
|
|
|
|
for(uint8_t i = 0; i < 8; i++) {
|
|
|
|
uint8_t lowByte = TileData[i];
|
|
|
|
uint8_t highByte = TileData[i + 8];
|
|
|
|
for(uint8_t j = 0; j < 8; j++) {
|
|
|
|
uint8_t color = ((lowByte >> (7 - j)) & 0x01) | (((highByte >> (7 - j)) & 0x01) << 1);
|
|
|
|
uint32_t rgbColor;
|
2017-08-15 19:18:00 -04:00
|
|
|
if(IsSpriteTile() || TransparencyRequired) {
|
2017-06-28 19:00:08 -04:00
|
|
|
rgbColor = color == 0 ? 0x00FFFFFF : palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F];
|
|
|
|
} else {
|
|
|
|
rgbColor = palette[(PaletteColors >> ((3 - color) * 8)) & 0x3F];
|
|
|
|
}
|
|
|
|
rgbBuffer.push_back(rgbColor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rgbBuffer;
|
|
|
|
}
|
|
|
|
|
2017-07-25 19:46:25 -04:00
|
|
|
void UpdateFlags()
|
2017-06-28 19:00:08 -04:00
|
|
|
{
|
2017-07-25 19:46:25 -04:00
|
|
|
Blank = true;
|
|
|
|
HasTransparentPixels = false;
|
|
|
|
IsFullyTransparent = true;
|
2017-06-28 19:00:08 -04:00
|
|
|
for(size_t i = 0; i < HdTileData.size(); i++) {
|
|
|
|
if(HdTileData[i] != HdTileData[0]) {
|
|
|
|
Blank = false;
|
2017-07-25 19:46:25 -04:00
|
|
|
}
|
|
|
|
if((HdTileData[i] & 0xFF000000) != 0xFF000000) {
|
|
|
|
HasTransparentPixels = true;
|
|
|
|
}
|
|
|
|
if(HdTileData[i] & 0xFF000000) {
|
|
|
|
IsFullyTransparent = false;
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string ToString(int pngIndex)
|
|
|
|
{
|
|
|
|
stringstream out;
|
|
|
|
|
|
|
|
if(Conditions.size() > 0) {
|
|
|
|
out << "[";
|
2017-07-25 19:46:25 -04:00
|
|
|
for(size_t i = 0; i < Conditions.size(); i++) {
|
2017-06-28 19:00:08 -04:00
|
|
|
if(i > 0) {
|
|
|
|
out << "&";
|
|
|
|
}
|
|
|
|
out << Conditions[i]->Name;
|
|
|
|
}
|
|
|
|
out << "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
if(IsChrRamTile) {
|
|
|
|
out << "<tile>" << pngIndex << ",";
|
|
|
|
|
|
|
|
for(int i = 0; i < 16; i++) {
|
|
|
|
out << HexUtilities::ToHex(TileData[i]);
|
|
|
|
}
|
|
|
|
out << "," <<
|
|
|
|
HexUtilities::ToHex(PaletteColors, true) << "," <<
|
|
|
|
X << "," <<
|
|
|
|
Y << "," <<
|
|
|
|
(double)Brightness / 255 << "," <<
|
|
|
|
(DefaultTile ? "Y" : "N") << "," <<
|
|
|
|
ChrBankId << "," <<
|
|
|
|
TileIndex;
|
|
|
|
} else {
|
|
|
|
out << "<tile>" <<
|
|
|
|
pngIndex << "," <<
|
2018-09-15 09:59:17 -04:00
|
|
|
HexUtilities::ToHex(TileIndex) << "," <<
|
2017-06-28 19:00:08 -04:00
|
|
|
HexUtilities::ToHex(PaletteColors, true) << "," <<
|
|
|
|
X << "," <<
|
|
|
|
Y << "," <<
|
|
|
|
(double)Brightness / 255 << "," <<
|
|
|
|
(DefaultTile ? "Y" : "N");
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.str();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HdPackBitmapInfo
|
|
|
|
{
|
2018-06-19 20:43:16 -04:00
|
|
|
vector<uint32_t> PixelData;
|
2017-06-28 19:00:08 -04:00
|
|
|
uint32_t Width;
|
|
|
|
uint32_t Height;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HdBackgroundFileData
|
|
|
|
{
|
|
|
|
string PngName;
|
|
|
|
uint32_t Width;
|
|
|
|
uint32_t Height;
|
|
|
|
|
2018-06-19 20:43:16 -04:00
|
|
|
vector<uint32_t> PixelData;
|
2017-06-28 19:00:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
struct HdBackgroundInfo
|
|
|
|
{
|
|
|
|
HdBackgroundFileData* Data;
|
2019-12-20 13:53:13 -05:00
|
|
|
int Brightness;
|
2017-06-28 19:00:08 -04:00
|
|
|
vector<HdPackCondition*> Conditions;
|
2018-02-17 23:44:25 -05:00
|
|
|
float HorizontalScrollRatio;
|
|
|
|
float VerticalScrollRatio;
|
2020-05-08 20:08:41 -04:00
|
|
|
uint8_t Priority;
|
|
|
|
|
2019-12-20 13:53:13 -05:00
|
|
|
uint32_t Left;
|
|
|
|
uint32_t Top;
|
2017-06-28 19:00:08 -04:00
|
|
|
|
|
|
|
uint32_t* data()
|
|
|
|
{
|
2018-06-19 20:43:16 -04:00
|
|
|
return Data->PixelData.data();
|
2017-06-28 19:00:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
string ToString()
|
|
|
|
{
|
|
|
|
stringstream out;
|
|
|
|
|
|
|
|
if(Conditions.size() > 0) {
|
|
|
|
out << "[";
|
2017-07-25 19:46:25 -04:00
|
|
|
for(size_t i = 0; i < Conditions.size(); i++) {
|
2017-06-28 19:00:08 -04:00
|
|
|
if(i > 0) {
|
|
|
|
out << "&";
|
|
|
|
}
|
|
|
|
out << Conditions[i]->Name;
|
|
|
|
}
|
|
|
|
out << "]";
|
|
|
|
}
|
2018-12-12 20:47:23 -05:00
|
|
|
|
|
|
|
out << "<background>";
|
2017-06-28 19:00:08 -04:00
|
|
|
out << Data->PngName << ",";
|
|
|
|
out << (Brightness / 255.0);
|
|
|
|
|
|
|
|
return out.str();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-12-20 22:26:12 +08:00
|
|
|
|
|
|
|
struct HdBGMInfo {
|
|
|
|
string File;
|
|
|
|
uint32_t LoopPoint;
|
|
|
|
int16_t Volume;
|
|
|
|
int16_t PlaybackOptions;
|
|
|
|
};
|
|
|
|
|
2017-06-28 19:00:08 -04:00
|
|
|
struct HdPackData
|
|
|
|
{
|
|
|
|
vector<HdBackgroundInfo> Backgrounds;
|
|
|
|
vector<unique_ptr<HdBackgroundFileData>> BackgroundFileData;
|
|
|
|
vector<unique_ptr<HdPackTileInfo>> Tiles;
|
|
|
|
vector<unique_ptr<HdPackCondition>> Conditions;
|
2018-03-14 23:25:06 -04:00
|
|
|
std::unordered_set<uint32_t> WatchedMemoryAddresses;
|
2017-06-28 19:00:08 -04:00
|
|
|
std::unordered_map<HdTileKey, vector<HdPackTileInfo*>> TileByKey;
|
|
|
|
std::unordered_map<string, string> PatchesByHash;
|
2020-12-20 22:26:12 +08:00
|
|
|
std::unordered_map<int, HdBGMInfo> BgmFilesById;
|
2017-08-19 16:46:57 -04:00
|
|
|
std::unordered_map<int, string> SfxFilesById;
|
2017-06-28 19:00:08 -04:00
|
|
|
vector<uint32_t> Palette;
|
2017-08-12 21:21:55 -04:00
|
|
|
|
|
|
|
bool HasOverscanConfig = false;
|
|
|
|
OverscanDimensions Overscan;
|
|
|
|
|
2017-06-28 19:00:08 -04:00
|
|
|
uint32_t Scale = 1;
|
|
|
|
uint32_t Version = 0;
|
|
|
|
uint32_t OptionFlags = 0;
|
|
|
|
|
2017-08-12 21:21:55 -04:00
|
|
|
HdPackData() { }
|
|
|
|
~HdPackData() { }
|
2017-06-28 19:00:08 -04:00
|
|
|
|
|
|
|
HdPackData(const HdPackData&) = delete;
|
|
|
|
HdPackData& operator=(const HdPackData&) = delete;
|
|
|
|
};
|
|
|
|
|
|
|
|
enum class HdPackOptions
|
|
|
|
{
|
|
|
|
None = 0,
|
|
|
|
NoSpriteLimit = 1,
|
2017-08-19 16:46:57 -04:00
|
|
|
AlternateRegisterRange = 2,
|
2018-02-17 23:44:25 -05:00
|
|
|
DisableCache = 8,
|
2019-12-20 13:53:13 -05:00
|
|
|
DontRenderOriginalTiles = 16
|
2020-12-20 22:26:12 +08:00
|
|
|
};
|
|
|
|
|
Add sprite frame range condition
A new condition named "spriteFrameRange" is added. It works like frameRange except it follows frame counters assigned to each sprite. Frame counter picks up the nearest frame counter within 6x6 pixels from the last frame and resets if graphics, palette, bg priority or orientation has changed. The condition can by used by sprite tiles only. For example:
<condition>myCondition,spriteFrameRange,301,60
[myCondition]<tile>0,1001,FF0F3600,8,0,1,N
2021-04-15 19:54:01 +08:00
|
|
|
|