/* * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved. */ #pragma once #include #include #include #include #include #include "color.hpp" #include "context.hpp" #include "ptidxconv.hpp" namespace nostalgia::core { extern ox::Array charMap; class Drawer { public: virtual ~Drawer() = default; virtual void draw(Context*) noexcept = 0; }; enum class TileSheetSpace { Background, Sprite }; struct NostalgiaPalette { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette"; static constexpr auto TypeVersion = 1; ox::Vector colors = {}; }; struct Palette { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette"; static constexpr auto TypeVersion = 1; ox::Vector colors = {}; [[nodiscard]] constexpr Color16 color(auto idx) const noexcept { if (idx < colors.size()) [[likely]] { return colors[idx]; } return 0; } }; // Predecessor to TileSheet, kept for backward compatibility struct NostalgiaGraphic { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic"; static constexpr auto TypeVersion = 1; int8_t bpp = 0; // rows and columns are really only used by TileSheetEditor int rows = 1; int columns = 1; ox::FileAddress defaultPalette; Palette pal; ox::Vector pixels = {}; }; struct TileSheet { using SubSheetIdx = ox::Vector; struct SubSheet { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet"; static constexpr auto TypeVersion = 1; ox::String name; int columns = 0; int rows = 0; ox::Vector subsheets; ox::Vector pixels; constexpr SubSheet() noexcept = default; constexpr SubSheet(const SubSheet &other) noexcept { name = other.name; columns = other.columns; rows = other.rows; subsheets = other.subsheets; pixels = other.pixels; } constexpr SubSheet(SubSheet &&other) noexcept { name = std::move(other.name); columns = other.columns; rows = other.rows; subsheets = std::move(other.subsheets); pixels = std::move(other.pixels); other.name = ""; other.columns = 0; other.rows = 0; } constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept: name(pName), columns(pColumns), rows(pRows), pixels(static_cast(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) { } constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, ox::Vector pPixels) noexcept: name(pName), columns(pColumns), rows(pRows), pixels(std::move(pPixels)) { } constexpr SubSheet &operator=(const SubSheet &other) noexcept { name = other.name; columns = other.columns; rows = other.rows; subsheets = other.subsheets; pixels = other.pixels; return *this; } constexpr SubSheet &operator=(SubSheet &&other) noexcept { name = std::move(other.name); columns = other.columns; rows = other.rows; subsheets = std::move(other.subsheets); pixels = std::move(other.pixels); return *this; } [[nodiscard]] constexpr auto idx(const geo::Point &pt) const noexcept { return ptToIdx(pt, columns); } /** * Reads all pixels of this sheet or its children into the given pixel list * @param pixels */ constexpr void readPixelsTo(ox::Vector *pPixels, int8_t bpp) const noexcept { if (subsheets.size()) { for (auto &s: subsheets) { s.readPixelsTo(pPixels); } } else { if (bpp == 4) { for (auto p: this->pixels) { pPixels->emplace_back(p & 0b1111); pPixels->emplace_back(p >> 4); } } else { for (auto p: this->pixels) { pPixels->emplace_back(p); } } } } /** * Reads all pixels of this sheet or its children into the given pixel list * @param pixels */ constexpr void readPixelsTo(ox::Vector *pPixels) const noexcept { if (subsheets.size()) { for (auto &s: subsheets) { s.readPixelsTo(pPixels); } } else { for (auto p : this->pixels) { pPixels->emplace_back(p); } } } [[nodiscard]] constexpr std::size_t size() const noexcept { return static_cast(columns) * static_cast(rows); } [[nodiscard]] constexpr std::size_t unusedPixels() const noexcept { std::size_t childrenSize = 0; for (auto &c : subsheets) { childrenSize += c.size(); } return size() - childrenSize; } [[nodiscard]] constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept { if (idx & 1) { return this->pixels[idx / 2] >> 4; } else { return this->pixels[idx / 2] & 0b0000'1111; } } [[nodiscard]] constexpr uint8_t getPixel8Bpp(std::size_t idx) const noexcept { return this->pixels[idx]; } [[nodiscard]] constexpr auto getPixel(int8_t pBpp, std::size_t idx) const noexcept { if (pBpp == 4) { return getPixel4Bpp(idx); } else { return getPixel8Bpp(idx); } } [[nodiscard]] constexpr auto getPixel4Bpp(const geo::Point &pt) const noexcept { const auto idx = ptToIdx(pt, columns); return getPixel4Bpp(idx); } [[nodiscard]] constexpr auto getPixel8Bpp(const geo::Point &pt) const noexcept { const auto idx = ptToIdx(pt, columns); return getPixel8Bpp(idx); } [[nodiscard]] constexpr auto getPixel(int8_t pBpp, const geo::Point &pt) const noexcept { const auto idx = ptToIdx(pt, columns); return getPixel(pBpp, idx); } constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept { if (pBpp == 4) { const auto pixelCnt = ox::min(static_cast(columns * rows * PixelsPerTile) / 2, pixels.size()); //oxAssert(pixels.size() == pixelCnt, "Pixel count does not match rows and columns"); for (std::size_t i = 0; i < pixelCnt; ++i) { const auto colorIdx1 = pixels[i] & 0xF; const auto colorIdx2 = pixels[i] >> 4; callback(i * 2 + 0, colorIdx1); callback(i * 2 + 1, colorIdx2); } } else { const auto pixelCnt = ox::min(static_cast(columns * rows * PixelsPerTile), pixels.size()); for (std::size_t i = 0; i < pixelCnt; ++i) { const auto p = pixels[i]; callback(i, p); } } } constexpr void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept { auto &pixel = this->pixels[idx / 2]; if (pBpp == 4) { if (idx & 1) { pixel = (pixel & 0b0000'1111) | (palIdx << 4); } else { pixel = (pixel & 0b1111'0000) | (palIdx); } } else { pixel = palIdx; } } constexpr void setPixel(int8_t pBpp, const geo::Point &pt, uint8_t palIdx) noexcept { const auto idx = ptToIdx(pt, columns); setPixel(pBpp, idx, palIdx); } constexpr auto setPixelCount(int8_t pBpp, std::size_t cnt) noexcept { switch (pBpp) { case 4: pixels.resize(cnt / 2); return OxError(0); case 8: pixels.resize(cnt); return OxError(0); default: return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount"); } } /** * Gets a count of the pixels in this sheet, and not that of its children. * @param pBpp bits per pixel, need for knowing how to count the pixels * @return a count of the pixels in this sheet */ [[nodiscard]] constexpr auto pixelCnt(int8_t pBpp) const noexcept { return pBpp == 4 ? pixels.size() * 2 : pixels.size(); } }; static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet"; static constexpr auto TypeVersion = 2; int8_t bpp = 4; ox::FileAddress defaultPalette; SubSheet subsheet{"Root", 1, 1, bpp}; constexpr TileSheet() noexcept = default; TileSheet(const TileSheet &other) noexcept = default; inline TileSheet(TileSheet &&other) noexcept: bpp(other.bpp), defaultPalette(std::move(other.defaultPalette)), subsheet(std::move(other.subsheet)) { } inline auto &operator=(const TileSheet &other) noexcept { if (this != &other) { bpp = other.bpp; defaultPalette = other.defaultPalette; subsheet = other.subsheet; } return *this; } inline auto &operator=(TileSheet &&other) noexcept { bpp = other.bpp; defaultPalette = std::move(other.defaultPalette); subsheet = std::move(other.subsheet); return *this; } [[nodiscard]] constexpr auto validateSubSheetIdx(const SubSheetIdx &pIdx, std::size_t pIdxIt, const SubSheet *pSubsheet) noexcept { if (pIdxIt == pIdx.size()) { return pIdx; } const auto currentIdx = pIdx[pIdxIt]; if (pSubsheet->subsheets.size() <= currentIdx) { auto out = pIdx; if (pSubsheet->subsheets.size()) { out.back().value = pSubsheet->subsheets.size() - 1; } else { out.pop_back(); } return out; } return validateSubSheetIdx(pIdx, pIdxIt + 1, &pSubsheet->subsheets[pIdx[pIdxIt]]); } /** * validateSubSheetIdx takes a SubSheetIdx and moves the index to the * preceding or parent sheet if the current corresponding sheet does * not exist. * @param idx SubSheetIdx to validate and correct * @return a valid version of idx */ [[nodiscard]] constexpr auto validateSubSheetIdx(const SubSheetIdx &idx) noexcept { return validateSubSheetIdx(idx, 0, &subsheet); } [[nodiscard]] constexpr static const auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, const SubSheet *pSubsheet) noexcept { if (idxIt == idx.size()) { return *pSubsheet; } const auto currentIdx = idx[idxIt]; if (pSubsheet->subsheets.size() < currentIdx) { return *pSubsheet; } return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[currentIdx]); } [[nodiscard]] constexpr static auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, SubSheet *pSubsheet) noexcept { if (idxIt == idx.size()) { return *pSubsheet; } return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]); } [[nodiscard]] constexpr const auto &getSubSheet(const SubSheetIdx &idx) const noexcept { return getSubSheet(idx, 0, &subsheet); } [[nodiscard]] constexpr auto &getSubSheet(const SubSheetIdx &idx) noexcept { return getSubSheet(idx, 0, &subsheet); } constexpr ox::Error addSubSheet(const SubSheetIdx &idx) noexcept { auto &parent = getSubSheet(idx); if (parent.subsheets.size() < 2) { parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", parent.subsheets.size()), 1, 1, bpp); } else { parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows, bpp); parent.subsheets.emplace_back("Subsheet 1", 1, 1, bpp); } return OxError(0); } [[nodiscard]] constexpr static auto rmSubSheet(const SubSheetIdx &idx, std::size_t idxIt, SubSheet *pSubsheet) noexcept { if (idxIt == idx.size() - 1) { return pSubsheet->subsheets.erase(idx[idxIt]).error; } return rmSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]); } [[nodiscard]] constexpr auto rmSubSheet(const SubSheetIdx &idx) noexcept { return rmSubSheet(idx, 0, &subsheet); } [[nodiscard]] constexpr auto getPixel4Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept { oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp"); auto &s = this->getSubSheet(subsheetIdx); const auto idx = ptToIdx(pt, s.columns); return s.getPixel4Bpp(idx); } [[nodiscard]] constexpr auto getPixel8Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept { oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp"); auto &s = this->getSubSheet(subsheetIdx); const auto idx = ptToIdx(pt, s.columns); return s.getPixel8Bpp(idx); } [[nodiscard]] auto pixels() const noexcept { ox::Vector out; subsheet.readPixelsTo(&out); return out; } }; struct CompactTileSheet { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet"; static constexpr auto TypeVersion = 1; int8_t bpp = 0; ox::FileAddress defaultPalette; ox::Vector pixels = {}; }; oxModelBegin(NostalgiaPalette) oxModelField(colors) oxModelEnd() oxModelBegin(Palette) oxModelField(colors) oxModelEnd() oxModelBegin(NostalgiaGraphic) oxModelField(bpp) oxModelField(rows) oxModelField(columns) oxModelField(defaultPalette) oxModelField(pal) oxModelField(pixels) oxModelEnd() oxModelBegin(TileSheet::SubSheet) oxModelField(name); oxModelField(rows); oxModelField(columns); oxModelField(subsheets) oxModelField(pixels) oxModelEnd() oxModelBegin(TileSheet) oxModelField(bpp) oxModelField(defaultPalette) oxModelField(subsheet) oxModelEnd() oxModelBegin(CompactTileSheet) oxModelField(bpp) oxModelField(defaultPalette) oxModelField(pixels) oxModelEnd() struct Sprite { unsigned idx = 0; int x = 0; int y = 0; unsigned tileIdx = 0; unsigned spriteShape = 0; unsigned spriteSize = 0; unsigned flipX = 0; }; oxModelBegin(Sprite) oxModelField(idx) oxModelField(x) oxModelField(y) oxModelField(tileIdx) oxModelField(spriteShape) oxModelField(spriteSize) oxModelField(flipX) oxModelEnd() ox::Error initGfx(Context *ctx) noexcept; void addCustomDrawer(Context *ctx, Drawer *cd) noexcept; void removeCustomDrawer(Context *ctx, Drawer *cd) noexcept; void setWindowTitle(Context *ctx, ox::CRStringView title) noexcept; void focusWindow(Context *ctx) noexcept; [[nodiscard]] int getScreenWidth(Context *ctx) noexcept; [[nodiscard]] int getScreenHeight(Context *ctx) noexcept; [[nodiscard]] geo::Size getScreenSize(Context *ctx) noexcept; [[nodiscard]] uint8_t bgStatus(Context *ctx) noexcept; void setBgStatus(Context *ctx, uint32_t status) noexcept; bool bgStatus(Context *ctx, unsigned bg) noexcept; void setBgStatus(Context *ctx, unsigned bg, bool status) noexcept; ox::Error initConsole(Context *ctx) noexcept; void setBgCbb(Context *ctx, unsigned bgIdx, unsigned cbb) noexcept; /** * @param section describes which section of the selected TileSheetSpace to use (e.g. MEM_PALLETE_BG[section]) */ ox::Error loadBgTileSheet(Context *ctx, unsigned cbb, const ox::FileAddress &tilesheet, const ox::FileAddress &palette = nullptr) noexcept; ox::Error loadSpriteTileSheet(Context *ctx, const ox::FileAddress &tilesheetAddr, const ox::FileAddress &paletteAddr) noexcept; void puts(Context *ctx, int column, int row, ox::CRStringView str) noexcept; void setTile(Context *ctx, unsigned bgIdx, int column, int row, uint8_t tile) noexcept; void clearTileLayer(Context *ctx, unsigned bgIdx) noexcept; void hideSprite(Context *ctx, unsigned) noexcept; void setSprite(Context *ctx, unsigned idx, int x, int y, unsigned tileIdx, unsigned spriteShape = 0, unsigned spriteSize = 0, unsigned flipX = 0) noexcept; void setSprite(Context *ctx, const Sprite &s) noexcept; }