[jasper/world/studio] Get a good start to world editor going
Some checks failed
Build / build (push) Failing after 46s
Some checks failed
Build / build (push) Failing after 46s
This commit is contained in:
parent
7ceee8c84d
commit
513e3360b5
@ -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-*,
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
#include <jasper/core/animpage.hpp>
|
||||
|
||||
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()
|
||||
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
#include <nostalgia/core/context.hpp>
|
||||
|
||||
#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<uint16_t> const&layer, unsigned layerNo) const noexcept;
|
||||
void setupLayer(ncore::Context&, uint_t lyr, uint_t cbb) const noexcept;
|
||||
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
@ -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<PaletteCycle> palettes;
|
||||
ox::Vector<WorldObject> objects;
|
||||
};
|
||||
|
||||
oxModelBegin(WorldObjectSet)
|
||||
oxModelField(objIdIdx)
|
||||
oxModelFieldRename(objIdIdx, obj_id_idx)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(palettes)
|
||||
oxModelField(objects)
|
||||
oxModelEnd()
|
||||
|
||||
|
||||
ox::Error loadObjectSet(WorldObjectSet const&os) noexcept;
|
||||
|
||||
}
|
||||
|
@ -12,98 +12,15 @@
|
||||
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
#include <jasper/world/objectcache.hpp>
|
||||
|
||||
#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<ncore::SubSheetId> 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<uint8_t, 4> layerAttachments;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr ox::Result<ncore::SubSheetId> 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<ox::StringView> 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<TileDoc>;
|
||||
using TileMapLayer = ox::Vector<TileMapRow>;
|
||||
using TileMap = ox::Vector<TileMapLayer>;
|
||||
|
||||
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<ox::String> 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<int>(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<int>(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<uint8_t>(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<uint16_t> &tileMapIdx;
|
||||
ox::Vector<uint8_t> &tileType;
|
||||
ox::Vector<uint8_t> &layerAttachments;
|
||||
constexpr Layer(
|
||||
uint16_t &pColumns,
|
||||
uint16_t &pRows,
|
||||
ox::Vector<uint16_t> &pTileMapIdx,
|
||||
ox::Vector<uint8_t> &pTileType,
|
||||
ox::Vector<uint8_t> &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<uint16_t>(dim.width);
|
||||
rows = static_cast<uint16_t>(dim.height);
|
||||
const auto tileCnt = static_cast<unsigned>(columns * rows);
|
||||
tileMapIdx.resize(tileCnt);
|
||||
tileType.resize(tileCnt);
|
||||
layerAttachments.resize(tileCnt);
|
||||
}
|
||||
};
|
||||
|
||||
ox::FileAddress tilesheet;
|
||||
ox::Vector<ox::FileAddress> palettes;
|
||||
// tile layer data
|
||||
ox::Vector<uint16_t> columns;
|
||||
ox::Vector<uint16_t> rows;
|
||||
ox::Vector<ox::Vector<uint16_t>> tileMapIdx;
|
||||
ox::Vector<ox::Vector<uint8_t>> tileType;
|
||||
ox::Vector<ox::Vector<uint8_t>> 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<TileStatic> 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<PaletteCycle> palettes;
|
||||
int16_t columns{};
|
||||
int16_t rows{};
|
||||
ox::Array<BgLayer, 3> map;
|
||||
};
|
||||
|
||||
oxModelBegin(WorldStatic)
|
||||
oxModelField(tilesheets)
|
||||
oxModelField(palettes)
|
||||
oxModelField(columns)
|
||||
oxModelField(rows)
|
||||
oxModelField(map)
|
||||
oxModelEnd()
|
||||
|
||||
ox::Result<WorldStatic> loadWorldStatic(ObjectCache const&objCache, WorldDoc const&doc) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
auto &tile(ox::CommonRefWith<WorldStatic> auto &ws, size_t lyr, size_t col, size_t row) noexcept {
|
||||
auto const idx = row * static_cast<size_t>(ws.columns) + col;
|
||||
return ws.map[lyr].tiles[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto &cbb(ox::CommonRefWith<WorldStatic> auto &ws, size_t lyr) noexcept {
|
||||
return ws.map[lyr].cbb;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isValid(WorldStatic const&ws) noexcept;
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
|
||||
add_library(
|
||||
JasperWorld
|
||||
objectcache.cpp
|
||||
world.cpp
|
||||
worlddoc.cpp
|
||||
worldobject.cpp
|
||||
worldstatic.cpp
|
||||
)
|
||||
|
||||
|
@ -5,66 +5,18 @@
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <jasper/world/objectcache.hpp>
|
||||
|
||||
#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<ncore::TileSheet>(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<uint16_t>(mapIdx);
|
||||
setLayerAttachments(layerIdx, srcTile, dstTile);
|
||||
++tileIdx;
|
||||
}
|
||||
}
|
||||
++layerIdx;
|
||||
}
|
||||
return {};
|
||||
oxRequire(oc, buildObjCache(kctx, src));
|
||||
return loadWorldStatic(oc, src).moveTo(dst);
|
||||
}
|
||||
|
||||
}
|
||||
|
91
src/jasper/modules/world/src/objectcache.cpp
Normal file
91
src/jasper/modules/world/src/objectcache.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include <jasper/world/objectcache.hpp>
|
||||
|
||||
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<ncore::TileSheet>(kctx, objSet.tilesheet));
|
||||
for (auto const&obj : objSet.objects) {
|
||||
set.objects.emplace_back(Obj{
|
||||
.id = obj.id,
|
||||
.palBank = static_cast<uint16_t>(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<int32_t>(tileCnt));
|
||||
for (auto const&pal : objSet.palettes) {
|
||||
m_palettes.emplace_back(pal);
|
||||
oxRequire(p, readObj<ncore::Palette>(kctx, pal.palette));
|
||||
m_palBank += largestPage(*p);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Optional<ObjectCache::Obj> 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<ObjectCache::Obj>{ox::in_place, obj};
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ncore::TileSheetSet const&ObjectCache::tilesheets() const noexcept {
|
||||
return m_tilesheetSet;
|
||||
}
|
||||
|
||||
ox::Vector<PaletteCycle> 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<ObjectCache> buildObjCache(keel::Context &kctx, WorldDoc const&doc) noexcept {
|
||||
ObjectCache cache;
|
||||
for (auto &set : doc.objSets) {
|
||||
oxRequire(s, readObj<WorldObjectSet>(kctx, set.path));
|
||||
oxReturnError(cache.indexSet(kctx, set.id, *s));
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
add_library(
|
||||
JasperWorld-Studio
|
||||
studiomodule.cpp
|
||||
worldeditor/commands/commands.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
|
@ -1,6 +1,8 @@
|
||||
target_sources(
|
||||
JasperWorld-Studio PRIVATE
|
||||
commands/addrmobjectset.cpp
|
||||
commands/editsize.cpp
|
||||
objectexplorer.cpp
|
||||
worldeditor-imgui.cpp
|
||||
worldeditor.cpp
|
||||
worldeditorview.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<int>(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<int>(WorldEditorCommand::RmObjectSet);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
#include <studio/undostack.hpp>
|
||||
|
||||
#include <jasper/world/world.hpp>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
@ -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,
|
||||
};
|
||||
|
||||
}
|
@ -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<int>(WorldEditorCommand::EditWorldSize);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
#include <studio/undostack.hpp>
|
||||
|
||||
#include <jasper/world/world.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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<ox::BasicString<255>>("{}##{}", m_name, m_fullPath);
|
||||
if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_Leaf)) {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ObjectExplorerModel {
|
||||
private:
|
||||
ObjectExplorerModel *m_parent{};
|
||||
ox::String m_name;
|
||||
ox::String m_fullPath;
|
||||
ox::Vector<ox::UPtr<ObjectExplorerModel>> m_children;
|
||||
public:
|
||||
explicit ObjectExplorerModel(ox::StringView name, ObjectExplorerModel *parent = {}) noexcept;
|
||||
void draw(turbine::Context &ctx) const noexcept;
|
||||
private:
|
||||
};
|
||||
|
||||
}
|
@ -4,23 +4,151 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <ox/std/ignore.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <studio/imguiutil.hpp>
|
||||
|
||||
#include "commands/addrmobjectset.hpp"
|
||||
#include "commands/editsize.hpp"
|
||||
|
||||
#include "worldeditor-imgui.hpp"
|
||||
|
||||
namespace jasper::world {
|
||||
|
||||
WorldEditorImGui::WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path):
|
||||
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<size_t>(doc.columns));
|
||||
for (auto &row : lyr) {
|
||||
row.resize(static_cast<size_t>(doc.rows));
|
||||
}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
WorldEditorImGui::WorldEditorImGui(studio::StudioContext &sctx, ox::StringView path):
|
||||
Editor(path),
|
||||
m_ctx(ctx.tctx),
|
||||
m_editor(m_ctx, path),
|
||||
m_view(m_ctx, m_editor.world()) {
|
||||
m_sctx(sctx),
|
||||
m_doc(makeValid(*readObj<WorldDoc>(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<EditWorldSize>(m_doc, ox::Size{width, m_doc.rows});
|
||||
}
|
||||
if (ImGui::InputInt("Map Height", &height, 1)) {
|
||||
pushCommand<EditWorldSize>(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<int>(paneSize.x), static_cast<int>(paneSize.y)});
|
||||
m_view.draw({static_cast<int>(paneSize.x), static_cast<int>(paneSize.y)});
|
||||
auto &fb = m_view.framebuffer();
|
||||
auto const fbWidth = static_cast<float>(fb.width);
|
||||
auto const fbHeight = static_cast<float>(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<void*>(buffId),
|
||||
ig::toImTextureID(fb.color.id),
|
||||
paneSize,
|
||||
ImVec2(0, 1),
|
||||
ImVec2(xScale, 1 - yScale));
|
||||
std::ignore = ig::dragDropTarget([] {
|
||||
oxRequire(obj, ig::getDragDropPayload<WorldTileDragDrop>("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<AddObjectSet>(m_doc, path);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error WorldEditorImGui::saveItem() noexcept {
|
||||
const auto sctx = applicationData<studio::StudioContext>(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<RmObjectSet>(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<WorldObjectSet>(keelCtx(m_sctx.tctx), set.path));
|
||||
m_objSets.emplace_back(ObjSetRef{ox::String(set.path), std::move(os)});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "worldeditor.hpp"
|
||||
#include <jasper/world/objectcache.hpp>
|
||||
|
||||
#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<WorldObjectSet> set;
|
||||
};
|
||||
ox::Vector<ObjSetRef> 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -8,9 +8,9 @@
|
||||
|
||||
namespace jasper::world {
|
||||
|
||||
WorldEditor::WorldEditor(turbine::Context &ctx, ox::StringView path):
|
||||
m_ctx(*applicationData<studio::StudioContext>(ctx)),
|
||||
m_world(*readObj<WorldStatic>(keelCtx(ctx), path).unwrapThrow()) {
|
||||
WorldEditor::WorldEditor(studio::StudioContext &ctx, ox::StringView path):
|
||||
m_ctx(ctx),
|
||||
m_world(*readObj<WorldStatic>(keelCtx(m_ctx.tctx), path).unwrapThrow()) {
|
||||
}
|
||||
|
||||
ox::Error WorldEditor::saveItem() noexcept {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -109,19 +109,11 @@ void WorldObjectSetEditorImGui::drawObjSelector() noexcept {
|
||||
if (ig::PushButton("-", btnSize)) {
|
||||
undoStack()->push(ox::make_unique<RmObject>(m_doc, m_selectedObj));
|
||||
}
|
||||
if (ImGui::BeginListBox("Objects")) {
|
||||
for (auto i = 0u; auto const&obj : m_doc.objects) {
|
||||
ig::IDStackItem const idStackItem2(static_cast<int>(i));
|
||||
if (ImGui::Selectable(obj.name.c_str(), m_selectedObj == i)) {
|
||||
if (i != m_selectedObj) {
|
||||
m_selectedObj = i;
|
||||
if (ig::ListBox("Objects", [this](size_t i) -> ox::CStringView {
|
||||
return m_doc.objects[i].name;
|
||||
}, m_doc.objects.size(), m_selectedObj)) {
|
||||
loadObj();
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ImGui::EndListBox();
|
||||
}
|
||||
}
|
||||
|
||||
void WorldObjectSetEditorImGui::loadObj() noexcept {
|
||||
|
@ -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<uint16_t> 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<uint8_t>(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<size_t>(x), static_cast<size_t>(y));
|
||||
auto const tx = x * 2;
|
||||
auto const ty = y * 2;
|
||||
ncore::setBgTile(ctx, lyr, tx + 0, ty + 0, {
|
||||
.tileIdx = static_cast<uint_t>(t.tileIdx + 0),
|
||||
.palBank = t.palBank,
|
||||
});
|
||||
ncore::setBgTile(ctx, lyr, tx + 1, ty + 0, {
|
||||
.tileIdx = static_cast<uint_t>(t.tileIdx + 1),
|
||||
.palBank = t.palBank,
|
||||
});
|
||||
ncore::setBgTile(ctx, lyr, tx + 0, ty + 1, {
|
||||
.tileIdx = static_cast<uint_t>(t.tileIdx + 2),
|
||||
.palBank = t.palBank,
|
||||
});
|
||||
ncore::setBgTile(ctx, lyr, tx + 1, ty + 1, {
|
||||
.tileIdx = static_cast<uint_t>(t.tileIdx + 3),
|
||||
.palBank = t.palBank,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
src/jasper/modules/world/src/worlddoc.cpp
Normal file
29
src/jasper/modules/world/src/worlddoc.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <jasper/world/worldstatic.hpp>
|
||||
|
||||
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<size_t>(sz.height));
|
||||
for (auto &row : layer) {
|
||||
row.resize(static_cast<size_t>(sz.width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
src/jasper/modules/world/src/worldobject.cpp
Normal file
17
src/jasper/modules/world/src/worldobject.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <jasper/world/worldobject.hpp>
|
||||
|
||||
namespace jasper::world {
|
||||
|
||||
void bgObjSetTerrainType(WorldObject &obj, TerrainType t) noexcept {
|
||||
obj.ext1 = static_cast<uint8_t>(t);
|
||||
}
|
||||
|
||||
TerrainType bgObjGetTerrainType(WorldObject &obj) noexcept {
|
||||
return static_cast<TerrainType>(obj.ext1);
|
||||
}
|
||||
|
||||
}
|
@ -2,10 +2,59 @@
|
||||
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
#include <jasper/world/worldstatic.hpp>
|
||||
|
||||
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<uint16_t>(obj.tileIdx);
|
||||
dst.palBank = static_cast<uint8_t>(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<WorldStatic> loadWorldStatic(ObjectCache const&objCache, WorldDoc const&doc) noexcept {
|
||||
auto const tileCnt =
|
||||
static_cast<size_t>(doc.columns) * static_cast<size_t>(doc.rows);
|
||||
WorldStatic out {
|
||||
.tilesheets = {},
|
||||
.palettes = {},
|
||||
.columns = static_cast<int16_t>(doc.columns),
|
||||
.rows = static_cast<int16_t>(doc.rows),
|
||||
.map = {
|
||||
{.tiles = ox::Vector<TileStatic>(tileCnt),},
|
||||
{.tiles = ox::Vector<TileStatic>(tileCnt),},
|
||||
{.tiles = ox::Vector<TileStatic>(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<size_t>(out.columns); ++x) {
|
||||
for (auto y = 0u; y < static_cast<size_t>(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<size_t>(ws.columns * ws.rows);
|
||||
return ox::all_of(ws.map.begin(), ws.map.end(), [tileCnt](auto &v) {
|
||||
return v.tiles.size() == tileCnt;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,12 +8,22 @@
|
||||
#include <keel/keel.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <nostalgia/core/core.hpp>
|
||||
|
||||
#include <jasper/core/bootfile.hpp>
|
||||
#include <jasper/world/world.hpp>
|
||||
|
||||
namespace ncore = nostalgia::core;
|
||||
|
||||
namespace jasper {
|
||||
ox::Error run(turbine::Context &ctx, ox::StringView, ox::SpanView<ox::String>) noexcept {
|
||||
ox::Error run(turbine::Context &tctx, ox::StringView, ox::SpanView<ox::String>) 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<world::WorldStatic>(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<ox::String>) 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));
|
||||
|
@ -18,7 +18,7 @@
|
||||
<string>APPL</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>d2023.12.0</string>
|
||||
<string>dev build</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0.0</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user