diff --git a/.clang-tidy b/.clang-tidy index 4d5dbae..eace73e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -57,6 +57,7 @@ misc-*, readability-duplicate-include, -misc-non-private-member-variables-in-classes, -misc-no-recursion, +-misc-include-cleaner, bugprone-*, clang-analyzer-*, modernize-*, diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a155a2..7c564a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,10 @@ endif() include(deps/nostalgia/deps/buildcore/base.cmake) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + if(BUILDCORE_TARGET STREQUAL "gba") include(deps/nostalgia/deps/gbabuildcore/base.cmake) else() diff --git a/src/jasper/modules/core/include/jasper/core/animpage.hpp b/src/jasper/modules/core/include/jasper/core/animpage.hpp index 8384528..520ff26 100644 --- a/src/jasper/modules/core/include/jasper/core/animpage.hpp +++ b/src/jasper/modules/core/include/jasper/core/animpage.hpp @@ -19,8 +19,8 @@ struct AnimPage { }; oxModelBegin(AnimPage) - oxModelFieldRename(tilesheet_path, tilesheetPath) - oxModelFieldRename(subsheet_path, subsheetPath) + oxModelFieldRename(tilesheetPath, tilesheet_path) + oxModelFieldRename(subsheetPath, subsheet_path) oxModelEnd() diff --git a/src/jasper/modules/world/include/jasper/world/prefab.hpp b/src/jasper/modules/world/include/jasper/world/prefab.hpp deleted file mode 100644 index 25d5198..0000000 --- a/src/jasper/modules/world/include/jasper/world/prefab.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#pragma once - -#include -#include -#include - -#include - -#include - -namespace jasper::world { - -struct PrefabDoc { - constexpr static auto TypeName = "net.drinkingtea.jasper.world.PrefabDoc"; - constexpr static auto TypeVersion = 1; - constexpr static auto Preloadable = true; - - ox::Size footprint; - ox::Size visibleSz; - ox::String tilesheetPath; - ox::String subsheetPath; -}; - -oxModelBegin(PrefabDoc) - oxModelField(footprint) - oxModelFieldRename(visible_size, visibleSz) - oxModelFieldRename(tilesheet_path, tilesheetPath) - oxModelFieldRename(subsheet_path, subsheetPath) -oxModelEnd() - -} diff --git a/src/jasper/modules/world/include/jasper/world/world.hpp b/src/jasper/modules/world/include/jasper/world/world.hpp index 8b06c64..3b745c3 100644 --- a/src/jasper/modules/world/include/jasper/world/world.hpp +++ b/src/jasper/modules/world/include/jasper/world/world.hpp @@ -7,7 +7,6 @@ #include #include "consts.hpp" -#include "prefab.hpp" #include "worldobject.hpp" #include "worldstatic.hpp" @@ -25,7 +24,7 @@ class World { ox::Error setupDisplay(ncore::Context &ctx) const noexcept; private: - void setupLayer(ncore::Context&, ox::Vector const&layer, unsigned layerNo) const noexcept; + void setupLayer(ncore::Context&, uint_t lyr, uint_t cbb) const noexcept; }; diff --git a/src/jasper/modules/world/include/jasper/world/worldobject.hpp b/src/jasper/modules/world/include/jasper/world/worldobject.hpp index 38abdc6..b4c2211 100644 --- a/src/jasper/modules/world/include/jasper/world/worldobject.hpp +++ b/src/jasper/modules/world/include/jasper/world/worldobject.hpp @@ -4,6 +4,8 @@ #pragma once +#include + #include #include @@ -14,7 +16,6 @@ namespace ncore = nostalgia::core; struct PaletteCycle { static constexpr auto TypeName = "net.drinkingtea.jasper.world.PaletteCycle"; static constexpr auto TypeVersion = 1; - static constexpr auto Preloadable = true; ox::FileAddress palette; uint16_t intervalMs = 1000; }; @@ -27,44 +28,68 @@ oxModelEnd() using CollisionMap = uint32_t; +enum class ObjectType: uint8_t { + None = 0, + NormalBgTile = 1, + NormalSprite = 2, + Person = 3, +}; + +using ObjectId = uint32_t; + struct WorldObject { static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObject"; static constexpr auto TypeVersion = 1; - static constexpr auto Preloadable = true; - int id{}; + ObjectId id{}; ox::String name; uint16_t palBank{}; ncore::SubSheetId subsheetId{}; CollisionMap collisionMap{}; + uint8_t objectType{}; + uint8_t ext1{}; + uint8_t ext2{}; + uint8_t ext3{}; }; oxModelBegin(WorldObject) oxModelField(id) oxModelField(name) oxModelField(palBank) - oxModelField(subsheetId) - oxModelField(collisionMap) + oxModelFieldRename(subsheetId, subsheet_id) + oxModelFieldRename(collisionMap, collision_map) + oxModelFieldRename(objectType, object_type) + oxModelField(ext1) + oxModelField(ext2) + oxModelField(ext3) oxModelEnd() +enum class TerrainType: uint8_t { + NormalLand, + NormalWater, +}; + +void bgObjSetTerrainType(WorldObject &obj, TerrainType t) noexcept; + +[[nodiscard]] +TerrainType bgObjGetTerrainType(WorldObject &obj) noexcept; struct WorldObjectSet { static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObjectSet"; static constexpr auto TypeVersion = 1; static constexpr auto Preloadable = true; - int objIdIdx = 0; + ObjectId objIdIdx = 0; ox::FileAddress tilesheet; ox::Vector palettes; ox::Vector objects; }; oxModelBegin(WorldObjectSet) - oxModelField(objIdIdx) + oxModelFieldRename(objIdIdx, obj_id_idx) oxModelField(tilesheet) oxModelField(palettes) oxModelField(objects) oxModelEnd() - ox::Error loadObjectSet(WorldObjectSet const&os) noexcept; } diff --git a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp index f73c86a..9799b01 100644 --- a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp +++ b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp @@ -12,98 +12,15 @@ #include +#include + +#include "worlddoc.hpp" +#include "worldobject.hpp" + namespace jasper::world { namespace ncore = nostalgia::core; -struct SpriteDoc { - - constexpr static auto TypeName = "net.drinkingtea.jasper.world.SpriteDoc"; - constexpr static auto TypeVersion = 1; - constexpr static auto Preloadable = true; - - ox::String tilesheetPath; - ox::Vector subsheetId; - -}; - -struct TileDoc { - - constexpr static auto TypeName = "net.drinkingtea.jasper.world.TileDoc"; - constexpr static auto TypeVersion = 1; - constexpr static auto Preloadable = true; - - ncore::SubSheetId subsheetId = -1; - ox::String subsheetPath; - uint8_t type = 0; - ox::Array layerAttachments; - - [[nodiscard]] - constexpr ox::Result getSubsheetId(ncore::TileSheet const&ts) const noexcept { - // prefer the already present ID - if (subsheetId > -1) { - return subsheetId; - } - return getIdFor(ts, subsheetPath); - } - - [[nodiscard]] - constexpr ox::Result getSubsheetPath( - ncore::TileSheet const&ts) const noexcept { - // prefer the already present path - if (!subsheetPath.len()) { - return getNameFor(ts, subsheetId); - } - return ox::StringView(subsheetPath); - } - -}; - -oxModelBegin(TileDoc) - oxModelFieldRename(subsheetId, subsheet_id) - oxModelFieldRename(subsheetPath, subsheet_path) - oxModelField(type) - oxModelFieldRename(layerAttachments, layer_attachments) -oxModelEnd() - -struct WorldDoc { - - using TileMapRow = ox::Vector; - using TileMapLayer = ox::Vector; - using TileMap = ox::Vector; - - constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldDoc"; - constexpr static auto TypeVersion = 1; - constexpr static auto Preloadable = true; - - ox::String tilesheet; // path - ox::Vector palettes; // paths - TileMap tiles; - - [[nodiscard]] - constexpr ox::Size size(std::size_t layerIdx) const noexcept { - const auto &layer = this->tiles[layerIdx]; - const auto rowCnt = static_cast(layer.size()); - if (!rowCnt) { - return {}; - } - auto colCnt = layer[0].size(); - // find shortest row (they should all be the same, but you know this data - // could come from a file) - for (auto const&row : layer) { - colCnt = ox::min(colCnt, row.size()); - } - return {static_cast(colCnt), rowCnt}; - } -}; - -oxModelBegin(WorldDoc) - oxModelField(tilesheet) - oxModelField(palettes) - oxModelField(tiles) -oxModelEnd() - - constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept { const auto val8 = static_cast(val); layerAttachments = (layerAttachments & 0b11111100) | val8; @@ -139,92 +56,69 @@ constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept { } -struct WorldStatic { - - constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldStatic"; +struct TileStatic { + constexpr static auto TypeName = "net.drinkingtea.jasper.world.TileStatic"; constexpr static auto TypeVersion = 1; - constexpr static auto Preloadable = true; - - struct Tile { - uint16_t &tileMapIdx; - uint8_t &tileType; - uint8_t &layerAttachments; - constexpr Tile(uint16_t &pTileMapIdx, uint8_t &pTileType, uint8_t &pLayerAttachments) noexcept: - tileMapIdx(pTileMapIdx), - tileType(pTileType), - layerAttachments(pLayerAttachments) { - } - }; - struct Layer { - uint16_t &columns; - uint16_t &rows; - ox::Vector &tileMapIdx; - ox::Vector &tileType; - ox::Vector &layerAttachments; - constexpr Layer( - uint16_t &pColumns, - uint16_t &pRows, - ox::Vector &pTileMapIdx, - ox::Vector &pTileType, - ox::Vector &pLayerAttachments) noexcept: - columns(pColumns), - rows(pRows), - tileMapIdx(pTileMapIdx), - tileType(pTileType), - layerAttachments(pLayerAttachments) { - } - [[nodiscard]] - constexpr Tile tile(std::size_t i) noexcept { - return {tileMapIdx[i], tileType[i], layerAttachments[i]}; - } - constexpr auto setDimensions(ox::Size dim) noexcept { - columns = static_cast(dim.width); - rows = static_cast(dim.height); - const auto tileCnt = static_cast(columns * rows); - tileMapIdx.resize(tileCnt); - tileType.resize(tileCnt); - layerAttachments.resize(tileCnt); - } - }; - - ox::FileAddress tilesheet; - ox::Vector palettes; - // tile layer data - ox::Vector columns; - ox::Vector rows; - ox::Vector> tileMapIdx; - ox::Vector> tileType; - ox::Vector> layerAttachments; - - [[nodiscard]] - constexpr Layer layer(std::size_t i) noexcept { - return { - columns[i], - rows[i], - tileMapIdx[i], - tileType[i], - layerAttachments[i], - }; - } - - constexpr auto setLayerCnt(std::size_t layerCnt) noexcept { - this->layerAttachments.resize(layerCnt); - this->columns.resize(layerCnt); - this->rows.resize(layerCnt); - this->tileMapIdx.resize(layerCnt); - this->tileType.resize(layerCnt); - } - + uint16_t tileIdx{}; + uint8_t palBank{}; + uint8_t tileType{}; + uint8_t layerAttachments{}; }; -oxModelBegin(WorldStatic) - oxModelField(tilesheet) - oxModelField(palettes) - oxModelField(columns) - oxModelField(rows) - oxModelField(tileMapIdx) +oxModelBegin(TileStatic) + oxModelField(tileIdx) + oxModelField(palBank) oxModelField(tileType) oxModelField(layerAttachments) oxModelEnd() + +struct BgLayer { + constexpr static auto TypeName = "net.drinkingtea.jasper.world.BgLayer"; + constexpr static auto TypeVersion = 1; + uint8_t cbb{}; + ox::Vector tiles; +}; + +oxModelBegin(BgLayer) + oxModelField(cbb) + oxModelField(tiles) +oxModelEnd() + + +struct WorldStatic { + constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldStatic"; + constexpr static auto TypeVersion = 1; + constexpr static auto Preloadable = true; + ncore::TileSheetSet tilesheets; + ox::Vector palettes; + int16_t columns{}; + int16_t rows{}; + ox::Array map; +}; + +oxModelBegin(WorldStatic) + oxModelField(tilesheets) + oxModelField(palettes) + oxModelField(columns) + oxModelField(rows) + oxModelField(map) +oxModelEnd() + +ox::Result loadWorldStatic(ObjectCache const&objCache, WorldDoc const&doc) noexcept; + +[[nodiscard]] +auto &tile(ox::CommonRefWith auto &ws, size_t lyr, size_t col, size_t row) noexcept { + auto const idx = row * static_cast(ws.columns) + col; + return ws.map[lyr].tiles[idx]; +} + +[[nodiscard]] +auto &cbb(ox::CommonRefWith auto &ws, size_t lyr) noexcept { + return ws.map[lyr].cbb; +} + +[[nodiscard]] +bool isValid(WorldStatic const&ws) noexcept; + } diff --git a/src/jasper/modules/world/src/CMakeLists.txt b/src/jasper/modules/world/src/CMakeLists.txt index b1ce3f1..73bdbe1 100644 --- a/src/jasper/modules/world/src/CMakeLists.txt +++ b/src/jasper/modules/world/src/CMakeLists.txt @@ -1,7 +1,10 @@ add_library( JasperWorld + objectcache.cpp world.cpp + worlddoc.cpp + worldobject.cpp worldstatic.cpp ) diff --git a/src/jasper/modules/world/src/keel/typeconv.cpp b/src/jasper/modules/world/src/keel/typeconv.cpp index 69a63db..09f28ec 100644 --- a/src/jasper/modules/world/src/keel/typeconv.cpp +++ b/src/jasper/modules/world/src/keel/typeconv.cpp @@ -5,66 +5,18 @@ #include #include +#include + #include "typeconv.hpp" namespace jasper::world { -namespace ncore = nostalgia::core; - -[[nodiscard]] -constexpr unsigned adjustLayerAttachment(unsigned layer, unsigned attachment) noexcept { - if (attachment == 0) { - return layer; - } else { - return attachment - 1; - } -} - -constexpr void setLayerAttachments(unsigned layer, TileDoc const&srcTile, WorldStatic::Tile &dstTile) noexcept { - setTopEdge( - dstTile.layerAttachments, - adjustLayerAttachment(layer, srcTile.layerAttachments[0])); - setBottomEdge( - dstTile.layerAttachments, - adjustLayerAttachment(layer, srcTile.layerAttachments[1])); - setLeftEdge( - dstTile.layerAttachments, - adjustLayerAttachment(layer, srcTile.layerAttachments[2])); - setRightEdge( - dstTile.layerAttachments, - adjustLayerAttachment(layer, srcTile.layerAttachments[3])); -} - ox::Error WorldDocToWorldStaticConverter::convert( - keel::Context &ctx, + keel::Context &kctx, WorldDoc &src, WorldStatic &dst) const noexcept { - oxRequire(ts, keel::readObj(ctx, src.tilesheet)); - const auto layerCnt = src.tiles.size(); - dst.setLayerCnt(layerCnt); - dst.tilesheet = ox::FileAddress(src.tilesheet); - dst.palettes.reserve(src.palettes.size()); - for (const auto &pal : src.palettes) { - dst.palettes.emplace_back(pal); - } - for (auto layerIdx = 0u; const auto &layer : src.tiles) { - const auto layerDim = src.size(layerIdx); - auto dstLayer = dst.layer(layerIdx); - dstLayer.setDimensions(layerDim); - for (auto tileIdx = 0u; const auto &row : layer) { - for (const auto &srcTile : row) { - auto dstTile = dstLayer.tile(tileIdx); - dstTile.tileType = srcTile.type; - oxRequire(path, srcTile.getSubsheetPath(*ts)); - oxRequire(mapIdx, getTileOffset(*ts, path)); - dstTile.tileMapIdx = static_cast(mapIdx); - setLayerAttachments(layerIdx, srcTile, dstTile); - ++tileIdx; - } - } - ++layerIdx; - } - return {}; + oxRequire(oc, buildObjCache(kctx, src)); + return loadWorldStatic(oc, src).moveTo(dst); } } diff --git a/src/jasper/modules/world/src/objectcache.cpp b/src/jasper/modules/world/src/objectcache.cpp new file mode 100644 index 0000000..01ec54a --- /dev/null +++ b/src/jasper/modules/world/src/objectcache.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +#include + +namespace jasper::world { + +void ObjectCache::clear() noexcept { + m_tileIdx = 0; + m_palBank = 0; + m_tilesheets.clear(); + m_palettes.clear(); + m_objSets.clear(); +} + +ox::Error ObjectCache::indexSet( + keel::Context &kctx, + uint64_t setId, + WorldObjectSet const&objSet) noexcept { + auto &set = m_objSets.emplace_back(ObjSet{ + .setId = setId, + .objects = {}, + }); + oxRequire(ts, readObj(kctx, objSet.tilesheet)); + for (auto const&obj : objSet.objects) { + set.objects.emplace_back(Obj{ + .id = obj.id, + .palBank = static_cast(obj.palBank + m_palBank), + .tileIdx = getTileIdx(*ts, obj.subsheetId) + m_tileIdx, + }); + } + auto const tileCnt = getTileCnt(*ts); + m_tileIdx += tileCnt; + m_tilesheets.emplace_back(objSet.tilesheet); + m_tilesheetSet.bpp = 4; + addTileSheet(objSet.tilesheet, static_cast(tileCnt)); + for (auto const&pal : objSet.palettes) { + m_palettes.emplace_back(pal); + oxRequire(p, readObj(kctx, pal.palette)); + m_palBank += largestPage(*p); + } + return {}; +} + +ox::Optional ObjectCache::obj(uint64_t setId, ObjectId objId) const noexcept { + for (auto &set : m_objSets) { + if (set.setId == setId) { + for (auto &obj : set.objects) { + if (obj.id == objId) { + return ox::Optional{ox::in_place, obj}; + } + } + break; + } + } + return {}; +} + +ncore::TileSheetSet const&ObjectCache::tilesheets() const noexcept { + return m_tilesheetSet; +} + +ox::Vector const&ObjectCache::palettes() const noexcept { + return m_palettes; +} + +void ObjectCache::addTileSheet(ox::FileAddress path, int32_t tiles) noexcept { + ncore::TileSheetSetEntry entry; + entry.tilesheet = std::move(path); + entry.sections.push_back({ + .begin = 0, + .tiles = tiles, + }); + m_tilesheetSet.entries.emplace_back(std::move(entry)); +} + +ox::Result buildObjCache(keel::Context &kctx, WorldDoc const&doc) noexcept { + ObjectCache cache; + for (auto &set : doc.objSets) { + oxRequire(s, readObj(kctx, set.path)); + oxReturnError(cache.indexSet(kctx, set.id, *s)); + } + return cache; +} + +} diff --git a/src/jasper/modules/world/src/studio/CMakeLists.txt b/src/jasper/modules/world/src/studio/CMakeLists.txt index 23aa93c..d41b7f7 100644 --- a/src/jasper/modules/world/src/studio/CMakeLists.txt +++ b/src/jasper/modules/world/src/studio/CMakeLists.txt @@ -1,6 +1,7 @@ add_library( JasperWorld-Studio studiomodule.cpp + worldeditor/commands/commands.hpp ) target_link_libraries( diff --git a/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt index c9e714c..2d54841 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt +++ b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt @@ -1,6 +1,8 @@ target_sources( JasperWorld-Studio PRIVATE + commands/addrmobjectset.cpp + commands/editsize.cpp + objectexplorer.cpp worldeditor-imgui.cpp - worldeditor.cpp worldeditorview.cpp -) \ No newline at end of file +) diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.cpp b/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.cpp new file mode 100644 index 0000000..631b9e1 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "addrmobjectset.hpp" + +namespace jasper::world { + +AddObjectSet::AddObjectSet(WorldDoc &doc, ox::StringView objSetPath): + m_doc(doc), + m_path(ox::String(objSetPath)) { + for (auto const&o : m_doc.objSets) { + if (o.path > objSetPath) { + break; + } else if (o.path == objSetPath) { + throw ox::Exception(OxError(1, "Path already exists in doc")); + } + ++m_insertIdx; + } +} + +void AddObjectSet::redo() noexcept { + m_doc.objSets.insert(m_insertIdx, { + .path = std::move(m_path), + .id = ++m_doc.objSetIdIdx, + }); +} + +void AddObjectSet::undo() noexcept { + m_path = std::move(m_doc.objSets[m_insertIdx].path); + --m_doc.objSetIdIdx; + oxIgnoreError(m_doc.objSets.erase(m_insertIdx)); +} + +int AddObjectSet::commandId() const noexcept { + return static_cast(WorldEditorCommand::AddObjectSet); +} + + +RmObjectSet::RmObjectSet(WorldDoc &doc, size_t idx): + m_doc(doc), + m_idx(idx) { +} + +void RmObjectSet::redo() noexcept { + m_entry = std::move(m_doc.objSets[m_idx]); + oxIgnoreError(m_doc.objSets.erase(m_idx)); +} + +void RmObjectSet::undo() noexcept { + m_doc.objSets.emplace(m_idx, std::move(m_entry)); +} + +int RmObjectSet::commandId() const noexcept { + return static_cast(WorldEditorCommand::RmObjectSet); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.hpp b/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.hpp new file mode 100644 index 0000000..d9846cd --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/addrmobjectset.hpp @@ -0,0 +1,45 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class AddObjectSet: public studio::UndoCommand { + private: + WorldDoc &m_doc; + size_t m_insertIdx{}; + ox::String m_path; + public: + AddObjectSet(WorldDoc &doc, ox::StringView objSetPath); + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; + [[nodiscard]] + inline size_t insertIdx() const noexcept { + return m_insertIdx; + } +}; + +class RmObjectSet: public studio::UndoCommand { + private: + WorldDoc &m_doc; + size_t m_idx{}; + ObjectSetEntry m_entry; + public: + RmObjectSet(WorldDoc &doc, size_t idx); + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/commands.hpp b/src/jasper/modules/world/src/studio/worldeditor/commands/commands.hpp new file mode 100644 index 0000000..96acb75 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/commands.hpp @@ -0,0 +1,16 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +namespace jasper::world { + +enum class WorldEditorCommand { + None, + AddObjectSet, + RmObjectSet, + EditWorldSize, +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.cpp b/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.cpp new file mode 100644 index 0000000..69fd143 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "commands.hpp" +#include "editsize.hpp" + +namespace jasper::world { + +EditWorldSize::EditWorldSize(WorldDoc &doc, ox::Size const&size): + m_doc(doc), + m_oldSize(m_doc.columns, m_doc.rows), + m_newVal(size) { +} + +void EditWorldSize::redo() noexcept { + m_oldMap = m_doc.tiles; + resize(m_doc, m_newVal); +} + +void EditWorldSize::undo() noexcept { + resize(m_doc, m_oldSize); + m_doc.tiles = std::move(m_oldMap); +} + +int EditWorldSize::commandId() const noexcept { + return static_cast(WorldEditorCommand::EditWorldSize); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.hpp b/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.hpp new file mode 100644 index 0000000..56f473d --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/commands/editsize.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include + +namespace jasper::world { + +class EditWorldSize: public studio::UndoCommand { + private: + WorldDoc &m_doc; + size_t m_insertIdx{}; + ox::Size const m_oldSize; + WorldDoc::TileMap m_oldMap; + ox::Size const m_newVal; + public: + EditWorldSize(WorldDoc &doc, ox::Size const&size); + void redo() noexcept override; + void undo() noexcept override; + [[nodiscard]] + int commandId() const noexcept override; + [[nodiscard]] + inline size_t insertIdx() const noexcept { + return m_insertIdx; + } +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.cpp b/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.cpp new file mode 100644 index 0000000..dc647f0 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.cpp @@ -0,0 +1,34 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "objectexplorer.hpp" + +namespace studio { + +ObjectExplorerModel::ObjectExplorerModel(ox::StringView name, ObjectExplorerModel *parent) noexcept: + m_parent(parent), + m_name(name), + m_fullPath(m_parent ? (m_parent->m_fullPath + "/" + m_name) : ox::String{}) { +} + +void ObjectExplorerModel::draw(turbine::Context &ctx) const noexcept { + constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (!m_children.empty()) { + if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) { + for (auto const&child : m_children) { + child->draw(ctx); + } + ImGui::TreePop(); + } + } else { + auto const name = ox::sfmt>("{}##{}", m_name, m_fullPath); + if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_Leaf)) { + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + } + ImGui::TreePop(); + } + } +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.hpp b/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.hpp new file mode 100644 index 0000000..549f73d --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/objectexplorer.hpp @@ -0,0 +1,23 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace studio { + +class ObjectExplorerModel { + private: + ObjectExplorerModel *m_parent{}; + ox::String m_name; + ox::String m_fullPath; + ox::Vector> m_children; + public: + explicit ObjectExplorerModel(ox::StringView name, ObjectExplorerModel *parent = {}) noexcept; + void draw(turbine::Context &ctx) const noexcept; + private: +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp index 5b39d81..40c37d0 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp @@ -4,23 +4,151 @@ #include +#include + #include +#include + +#include "commands/addrmobjectset.hpp" +#include "commands/editsize.hpp" + #include "worldeditor-imgui.hpp" namespace jasper::world { -WorldEditorImGui::WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path): - Editor(path), - m_ctx(ctx.tctx), - m_editor(m_ctx, path), - m_view(m_ctx, m_editor.world()) { +namespace ig = studio::ig; + +constexpr auto SqrBtnSize = ImVec2(ig::BtnSz.y, ig::BtnSz.y); + + +struct WorldTileDragDrop { + static constexpr auto TypeName = "net.drinkingtea.jasper.world.studio.WorldTileDragDrop"; + static constexpr auto TypeVersion = 1; + ox::String setPath; + ox::String objName; +}; + +oxModelBegin(WorldTileDragDrop) + oxModelField(setPath) + oxModelField(objName) +oxModelEnd() + + +static WorldDoc makeValid(WorldDoc doc) noexcept { + for (auto &lyr : doc.tiles) { + lyr.resize(static_cast(doc.columns)); + for (auto &row : lyr) { + row.resize(static_cast(doc.rows)); + } + } + return doc; +} + + +WorldEditorImGui::WorldEditorImGui(studio::StudioContext &sctx, ox::StringView path): + Editor(path), + m_sctx(sctx), + m_doc(makeValid(*readObj(keelCtx(m_sctx.tctx), path).unwrapThrow())), + m_objCache(buildObjCache(keelCtx(m_sctx.tctx), m_doc).unwrapThrow()), + m_worldStatic(loadWorldStatic(m_objCache, m_doc)), + m_view(m_sctx.tctx, m_worldStatic) { + oxThrowError(loadObjectSets()); setRequiresConstantRefresh(false); + m_objSetPicker.filePicked.connect(this, &WorldEditorImGui::addObjSet); + m_sctx.project->fileUpdated.connect(this, &WorldEditorImGui::handleObjectSetUpdate); } void WorldEditorImGui::draw(turbine::Context&) noexcept { + const auto paneSize = ImGui::GetContentRegionAvail(); + constexpr auto resourcesWidth = 300.f; + { + ig::ChildStackItem const worldView{"WorldView", ImVec2(paneSize.x - resourcesWidth, 0)}; + drawWorldView(); + } + ImGui::SameLine(); + { + ig::ChildStackItem const worldView{"RightPane"}; + drawPropEditor(); + ImGui::Separator(); + drawResources(); + } + m_objSetPicker.draw(); +} + +void WorldEditorImGui::onActivated() noexcept { + oxLogError(m_view.setupWorld()); +} + +ox::Error WorldEditorImGui::saveItem() noexcept { + return m_sctx.project->writeObj(itemPath(), m_doc, ox::ClawFormat::Organic); +} + +void WorldEditorImGui::drawObjSetSelector() noexcept { + ig::IDStackItem const idStackItem("ObjSetSelector"); + if (ig::PushButton("+", SqrBtnSize)) { + m_objSetPicker.show(); + } + ImGui::SameLine(); + if (ig::PushButton("-", SqrBtnSize)) { + rmObjSet(); + } + ig::ListBox("Object Sets", [this](size_t i) -> ox::CStringView { + return m_doc.objSets[i].path; + }, m_doc.objSets.size(), m_palMgr.selectedIdx); +} + +void WorldEditorImGui::drawObjSelector() noexcept { + ig::IDStackItem const idStackItem("ObjSelector"); + for (auto const&set : m_objSets) { + constexpr auto flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (ImGui::TreeNodeEx(set.name.c_str(), flags)) { + for (auto const&obj : set.set->objects) { + if (ImGui::TreeNodeEx(obj.name.c_str(), ImGuiTreeNodeFlags_Leaf)) { + ImGui::TreePop(); + } + ig::dragDropSource([set, obj] { + std::ignore = ig::setDragDropPayload("WorldTile", WorldTileDragDrop{ + .setPath = ox::String(set.name), + .objName = ox::String(obj.name)}); + ImGui::Text("%s", obj.name.c_str()); + }); + } + ImGui::TreePop(); + } + } +} + +void WorldEditorImGui::drawPropEditor() noexcept { + ig::IDStackItem const idStackItem("PropEditor"); + int width{m_doc.columns}; + int height{m_doc.rows}; + if (ImGui::InputInt("Map Width", &width, 1)) { + pushCommand(m_doc, ox::Size{width, m_doc.rows}); + } + if (ImGui::InputInt("Map Height", &height, 1)) { + pushCommand(m_doc, ox::Size{m_doc.columns, height}); + } +} + +void WorldEditorImGui::drawResources() noexcept { + ig::IDStackItem const idStackItem("Resources"); + drawObjSetSelector(); + drawObjSelector(); +} + +[[nodiscard]] +static ox::Vec2 dropPos(ox::Vec2 dropPos) noexcept { + auto const winPos = ImGui::GetWindowPos(); + dropPos.x -= winPos.x; + dropPos.y -= winPos.y; + return dropPos; +} + +void WorldEditorImGui::drawWorldView() noexcept { + ig::IDStackItem const idStackItem("WorldView"); auto const paneSize = ImGui::GetContentRegionAvail(); - m_view.draw(ox::Size{static_cast(paneSize.x), static_cast(paneSize.y)}); + m_view.draw({static_cast(paneSize.x), static_cast(paneSize.y)}); auto &fb = m_view.framebuffer(); auto const fbWidth = static_cast(fb.width); auto const fbHeight = static_cast(fb.height); @@ -37,22 +165,46 @@ void WorldEditorImGui::draw(turbine::Context&) noexcept { xScale = 1; yScale = srcW / dstW; } - uintptr_t const buffId = fb.color.id; ImGui::Image( - std::bit_cast(buffId), - paneSize, - ImVec2(0, 1), - ImVec2(xScale, 1 - yScale)); + ig::toImTextureID(fb.color.id), + paneSize, + ImVec2(0, 1), + ImVec2(xScale, 1 - yScale)); + std::ignore = ig::dragDropTarget([] { + oxRequire(obj, ig::getDragDropPayload("WorldTile")); + oxDebugf("{}: {}", obj.setPath, obj.objName); + auto const&io = ImGui::GetIO(); + auto const mousePos = ox::Vec2(io.MousePos); + auto const dropPos = world::dropPos(mousePos); + std::cout << dropPos.x << ", " << dropPos.y << '\n'; + return ox::Error{}; + }); } -void WorldEditorImGui::onActivated() noexcept { - oxLogError(m_view.setupWorld()); +ox::Error WorldEditorImGui::addObjSet(ox::StringView path) noexcept { + pushCommand(m_doc, path); + return {}; } -ox::Error WorldEditorImGui::saveItem() noexcept { - const auto sctx = applicationData(m_ctx); - oxReturnError(sctx->project->writeObj(itemPath(), m_editor.world(), ox::ClawFormat::Organic)); - oxReturnError(keelCtx(m_ctx).assetManager.setAsset(itemPath(), m_editor.world())); +void WorldEditorImGui::rmObjSet() noexcept { + pushCommand(m_doc, m_palMgr.selectedIdx); +} + +ox::Error WorldEditorImGui::handleObjectSetUpdate(ox::StringView path) noexcept { + if (ox::any_of(m_doc.objSets.begin(), m_doc.objSets.end(), [path](ObjectSetEntry const&set) { + return set.path == path; + })) { + oxReturnError(buildObjCache(keelCtx(m_sctx.tctx), m_doc).moveTo(m_objCache)); + } + return loadObjectSets(); +} + +ox::Error WorldEditorImGui::loadObjectSets() noexcept { + m_objSets.clear(); + for (auto const&set : m_doc.objSets) { + oxRequireM(os, readObj(keelCtx(m_sctx.tctx), set.path)); + m_objSets.emplace_back(ObjSetRef{ox::String(set.path), std::move(os)}); + } return {}; } diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp index c7a064c..c0214be 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp @@ -8,7 +8,8 @@ #include -#include "worldeditor.hpp" +#include + #include "worldeditorview.hpp" namespace jasper::world { @@ -16,9 +17,21 @@ namespace jasper::world { class WorldEditorImGui: public studio::Editor { private: - turbine::Context &m_ctx; - WorldEditor m_editor; + studio::StudioContext &m_sctx; + studio::ig::FilePicker m_objSetPicker{ + m_sctx, ox::String("Choose Object Set"), ox::String(FileExt_jwob)}; + WorldDoc m_doc; + ObjectCache m_objCache; + struct ObjSetRef { + ox::String name; + keel::AssetRef set; + }; + ox::Vector m_objSets; + WorldStatic m_worldStatic; WorldEditorView m_view; + struct { + size_t selectedIdx{}; + } m_palMgr; public: WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path); @@ -30,6 +43,26 @@ class WorldEditorImGui: public studio::Editor { protected: ox::Error saveItem() noexcept final; + private: + void drawObjSetSelector() noexcept; + + void drawObjSelector() noexcept; + + void drawPropEditor() noexcept; + + void drawResources() noexcept; + + void drawWorldView() noexcept; + + ox::Error addObjSet(ox::StringView path) noexcept; + + void rmObjSet() noexcept; + + // handles the updating of an object set in case it is one used by this world + ox::Error handleObjectSetUpdate(ox::StringView path) noexcept; + + ox::Error loadObjectSets() noexcept; + }; } diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp index c27f4bb..ea80ffd 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp @@ -8,9 +8,9 @@ namespace jasper::world { -WorldEditor::WorldEditor(turbine::Context &ctx, ox::StringView path): - m_ctx(*applicationData(ctx)), - m_world(*readObj(keelCtx(ctx), path).unwrapThrow()) { +WorldEditor::WorldEditor(studio::StudioContext &ctx, ox::StringView path): + m_ctx(ctx), + m_world(*readObj(keelCtx(m_ctx.tctx), path).unwrapThrow()) { } ox::Error WorldEditor::saveItem() noexcept { diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp index e5e8554..a5c195b 100644 --- a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp @@ -19,7 +19,7 @@ class WorldEditor { WorldStatic m_world; public: - WorldEditor(turbine::Context &ctx, ox::StringView path); + WorldEditor(studio::StudioContext &ctx, ox::StringView path); [[nodiscard]] WorldStatic const&world() const noexcept { diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp index d009dd7..9f7e8a4 100644 --- a/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/collisionmapview.hpp @@ -18,7 +18,7 @@ namespace ncore = nostalgia::core; class CollisionView { private: static const glutils::ProgramSource s_programSrc; - static constexpr int s_scale = 10; + static constexpr int s_scale = 5; ncore::ContextUPtr m_nctx; glutils::FrameBuffer m_frameBuffer; glutils::GLProgram m_shader; diff --git a/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp index ae0132c..49b3a3f 100644 --- a/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp +++ b/src/jasper/modules/world/src/studio/worldobjectseteditor/worldobjectseteditor-imgui.cpp @@ -109,18 +109,10 @@ void WorldObjectSetEditorImGui::drawObjSelector() noexcept { if (ig::PushButton("-", btnSize)) { undoStack()->push(ox::make_unique(m_doc, m_selectedObj)); } - if (ImGui::BeginListBox("Objects")) { - for (auto i = 0u; auto const&obj : m_doc.objects) { - ig::IDStackItem const idStackItem2(static_cast(i)); - if (ImGui::Selectable(obj.name.c_str(), m_selectedObj == i)) { - if (i != m_selectedObj) { - m_selectedObj = i; - loadObj(); - } - } - ++i; - } - ImGui::EndListBox(); + if (ig::ListBox("Objects", [this](size_t i) -> ox::CStringView { + return m_doc.objects[i].name; + }, m_doc.objects.size(), m_selectedObj)) { + loadObj(); } } diff --git a/src/jasper/modules/world/src/world.cpp b/src/jasper/modules/world/src/world.cpp index add1011..8500aa5 100644 --- a/src/jasper/modules/world/src/world.cpp +++ b/src/jasper/modules/world/src/world.cpp @@ -18,14 +18,14 @@ ox::Error World::setupDisplay(ncore::Context &ctx) const noexcept { if (m_worldStatic.palettes.empty()) { return OxError(1, "World has no palettes"); } - auto const&palette = m_worldStatic.palettes[0]; - oxReturnError(ncore::loadBgTileSheet( - ctx, 0, m_worldStatic.tilesheet)); - oxReturnError(ncore::loadBgPalette(ctx, 0, palette)); - // disable all backgrounds - ncore::setBgStatus(ctx, 0); - for (auto layerNo = 0u; auto const&layer : m_worldStatic.tileMapIdx) { - setupLayer(ctx, layer, layerNo); + for (auto i = 0u; auto const&pal : m_worldStatic.palettes) { + oxReturnError(ncore::loadBgPalette(ctx, i, pal.palette)); + ++i; + } + oxReturnError(ncore::loadBgTileSheet(ctx, 0, m_worldStatic.tilesheets)); + ncore::setBgStatus(ctx, 0); // disable all backgrounds + for (auto layerNo = 0u; auto const&layer : m_worldStatic.map) { + setupLayer(ctx, layerNo, layer.cbb); ++layerNo; } return {}; @@ -33,23 +33,31 @@ ox::Error World::setupDisplay(ncore::Context &ctx) const noexcept { void World::setupLayer( ncore::Context &ctx, - ox::Vector const&layer, - unsigned layerNo) const noexcept { - ncore::setBgStatus(ctx, layerNo, true); - ncore::setBgCbb(ctx, layerNo, 0); - auto x = 0; - auto y = 0; - const auto width = m_worldStatic.rows[layerNo]; - for (auto const&tile : layer) { - const auto tile8 = static_cast(tile); - ncore::setBgTile(ctx, layerNo, x + 0, y + 0, tile8 + 0); - ncore::setBgTile(ctx, layerNo, x + 1, y + 0, tile8 + 1); - ncore::setBgTile(ctx, layerNo, x + 0, y + 1, tile8 + 2); - ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3); - x += 2; - if (x >= width * 2) { - x = 0; - y += 2; + uint_t lyr, + uint_t cbb) const noexcept { + ncore::setBgStatus(ctx, lyr, true); + ncore::setBgCbb(ctx, lyr, cbb); + for (auto y = 0; y < m_worldStatic.rows; ++y) { + for (auto x = 0; x < m_worldStatic.columns; ++x) { + auto &t = tile(m_worldStatic, lyr, static_cast(x), static_cast(y)); + auto const tx = x * 2; + auto const ty = y * 2; + ncore::setBgTile(ctx, lyr, tx + 0, ty + 0, { + .tileIdx = static_cast(t.tileIdx + 0), + .palBank = t.palBank, + }); + ncore::setBgTile(ctx, lyr, tx + 1, ty + 0, { + .tileIdx = static_cast(t.tileIdx + 1), + .palBank = t.palBank, + }); + ncore::setBgTile(ctx, lyr, tx + 0, ty + 1, { + .tileIdx = static_cast(t.tileIdx + 2), + .palBank = t.palBank, + }); + ncore::setBgTile(ctx, lyr, tx + 1, ty + 1, { + .tileIdx = static_cast(t.tileIdx + 3), + .palBank = t.palBank, + }); } } } diff --git a/src/jasper/modules/world/src/worlddoc.cpp b/src/jasper/modules/world/src/worlddoc.cpp new file mode 100644 index 0000000..7b4e88b --- /dev/null +++ b/src/jasper/modules/world/src/worlddoc.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +namespace jasper::world { + +ObjectSetEntry const*objSetEntry(WorldDoc const&doc, size_t id) noexcept { + for (auto const&e : doc.objSets) { + if (e.id == id) { + return &e; + } + } + return nullptr; +} + +void resize(WorldDoc &doc, ox::Size const&sz) noexcept { + doc.columns = sz.width; + doc.rows = sz.height; + for (auto &layer : doc.tiles) { + layer.resize(static_cast(sz.height)); + for (auto &row : layer) { + row.resize(static_cast(sz.width)); + } + } +} + +} diff --git a/src/jasper/modules/world/src/worldobject.cpp b/src/jasper/modules/world/src/worldobject.cpp new file mode 100644 index 0000000..d51049c --- /dev/null +++ b/src/jasper/modules/world/src/worldobject.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +namespace jasper::world { + +void bgObjSetTerrainType(WorldObject &obj, TerrainType t) noexcept { + obj.ext1 = static_cast(t); +} + +TerrainType bgObjGetTerrainType(WorldObject &obj) noexcept { + return static_cast(obj.ext1); +} + +} \ No newline at end of file diff --git a/src/jasper/modules/world/src/worldstatic.cpp b/src/jasper/modules/world/src/worldstatic.cpp index ab74589..8146723 100644 --- a/src/jasper/modules/world/src/worldstatic.cpp +++ b/src/jasper/modules/world/src/worldstatic.cpp @@ -2,10 +2,59 @@ * Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved. */ +#include + #include namespace jasper::world { +static void loadTile(ObjectCache const&objCache, TileStatic &dst, TileDoc const&src) noexcept { + auto const obj = objCache.obj(src.obj.worldObjectSetId, src.obj.worldObjectId).or_value({}); + dst.palBank = src.palBank; + dst.tileIdx = static_cast(obj.tileIdx); + dst.palBank = static_cast(obj.palBank); + dst.tileType = src.type; + setTopEdge(dst.layerAttachments, src.topLayerAttachment); + setBottomEdge(dst.layerAttachments, src.bottomLayerAttachment); + setLeftEdge(dst.layerAttachments, src.leftLayerAttachment); + setRightEdge(dst.layerAttachments, src.rightLayerAttachment); +} +ox::Result loadWorldStatic(ObjectCache const&objCache, WorldDoc const&doc) noexcept { + auto const tileCnt = + static_cast(doc.columns) * static_cast(doc.rows); + WorldStatic out { + .tilesheets = {}, + .palettes = {}, + .columns = static_cast(doc.columns), + .rows = static_cast(doc.rows), + .map = { + {.tiles = ox::Vector(tileCnt),}, + {.tiles = ox::Vector(tileCnt),}, + {.tiles = ox::Vector(tileCnt),}, + }, + }; + // resources + out.tilesheets = objCache.tilesheets(); + out.palettes = objCache.palettes(); + // tiles + for (auto lyr = 0u; lyr < 3; ++lyr) { + for (auto x = 0u; x < static_cast(out.columns); ++x) { + for (auto y = 0u; y < static_cast(out.rows); ++y) { + auto &dst = tile(out, lyr, x, y); + auto &src = tile(doc, lyr, x, y);; + loadTile(objCache, dst, src); + } + } + } + return out; +} + +bool isValid(WorldStatic const&ws) noexcept { + auto const tileCnt = static_cast(ws.columns * ws.rows); + return ox::all_of(ws.map.begin(), ws.map.end(), [tileCnt](auto &v) { + return v.tiles.size() == tileCnt; + }); +} } diff --git a/src/jasper/player/app.cpp b/src/jasper/player/app.cpp index 8a5c402..91e96c6 100644 --- a/src/jasper/player/app.cpp +++ b/src/jasper/player/app.cpp @@ -8,12 +8,22 @@ #include #include +#include + #include +#include + +namespace ncore = nostalgia::core; namespace jasper { -ox::Error run(turbine::Context &ctx, ox::StringView, ox::SpanView) noexcept { +ox::Error run(turbine::Context &tctx, ox::StringView, ox::SpanView) noexcept { oxOut("Jasper Player\n"); - oxReturnError(turbine::run(ctx)); + oxRequire(nctx, ncore::init(tctx)); + auto constexpr worldPath = ox::FileAddress(ox::StringLiteral("/Worlds/Chester.jwld")); + oxRequire(worldStatic, readObj(keelCtx(tctx), worldPath)); + world::World const world(*worldStatic); + oxReturnError(world.setupDisplay(*nctx)); + oxReturnError(turbine::run(tctx)); oxOut("Exiting...\n"); return {}; } @@ -22,11 +32,14 @@ ox::Error run(turbine::Context &ctx, ox::StringView, ox::SpanView) n namespace olympic { ox::Error run( - [[maybe_unused]] ox::StringView project, + ox::StringView project, [[maybe_unused]] ox::StringView appName, [[maybe_unused]] ox::StringView projectDataDir, - [[maybe_unused]] int argc, - [[maybe_unused]] char const**argv) noexcept { + int argc, + char const**argv) noexcept { + if (argc < 2) { + return OxError(1, "Insufficient arguments given to olympic::run"); + } auto const path = ox::StringView(argv[1]); oxRequireM(fs, keel::loadRomFs(path)); oxRequireM(tctx, turbine::init(std::move(fs), project)); diff --git a/src/jasper/tools/Info.plist b/src/jasper/tools/Info.plist index 701c4c1..98bc96d 100644 --- a/src/jasper/tools/Info.plist +++ b/src/jasper/tools/Info.plist @@ -18,7 +18,7 @@ APPL CFBundleVersion - d2023.12.0 + dev build LSMinimumSystemVersion 12.0.0