From 624498de7da2394257bd306c74b49504daa692e8 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 23 Dec 2023 14:15:45 -0600 Subject: [PATCH] Initial commit --- .clang-tidy | 65 +++++ .gitignore | 21 ++ .gitlab-ci.yml | 24 ++ .liccor.yml | 5 + CMakeLists.txt | 35 +++ Makefile | 42 ++++ README.md | 42 ++++ developer-handbook.md | 27 ++ jenkins/gba/Jenkinsfile | 45 ++++ jenkins/linux/Jenkinsfile | 47 ++++ jenkins/mac/Jenkinsfile | 47 ++++ jenkins/shared/env.gy | 2 + project/.jasper/type_descriptors/B.bool;0 | 4 + project/.jasper/type_descriptors/B.int32;0 | 5 + project/.jasper/type_descriptors/B.int8;0 | 5 + project/.jasper/type_descriptors/B.string;0 | 4 + project/.jasper/type_descriptors/B.uint16;0 | 4 + project/.jasper/type_descriptors/B.uint64;0 | 4 + project/.jasper/type_descriptors/B.uint8;0 | 4 + .../net.drinkingtea.jasper.core.Bootfile;1 | 24 ++ ...nkingtea.nostalgia.core.CompactTileSheet;1 | 27 ++ ...nkingtea.nostalgia.core.NostalgiaGraphic;1 | 39 +++ .../net.drinkingtea.nostalgia.core.Palette;1 | 19 ++ ...ingtea.nostalgia.core.TileSheet.SubSheet;1 | 42 ++++ ...ingtea.nostalgia.core.TileSheet.SubSheet;3 | 42 ++++ ...net.drinkingtea.nostalgia.core.TileSheet;2 | 20 ++ ...net.drinkingtea.nostalgia.core.TileSheet;3 | 24 ++ ...net.drinkingtea.nostalgia.scene.SceneDoc;1 | 41 ++++ ....drinkingtea.nostalgia.scene.SceneStatic;1 | 88 +++++++ .../net.drinkingtea.nostalgia.scene.TileDoc;1 | 33 +++ ...et.drinkingtea.nostalgia.world.PrefabDoc;1 | 25 ++ .../net.drinkingtea.nostalgia.world.TileDoc;1 | 33 +++ ...net.drinkingtea.nostalgia.world.WorldDoc;1 | 41 ++++ ....drinkingtea.nostalgia.world.WorldStatic;1 | 88 +++++++ .../net.drinkingtea.ox.BasicString#8#;1 | 9 + .../net.drinkingtea.ox.FileAddress.Data;1 | 20 ++ .../net.drinkingtea.ox.FileAddress;1 | 16 ++ .../net.drinkingtea.ox.Size;1 | 16 ++ project/Bootfile | 4 + project/Palettes/Charset.npal | Bin 0 -> 94 bytes project/Palettes/Chester.npal | 1 + project/Palettes/Dirt.npal | 1 + project/Palettes/Logo.npal | 1 + project/TileSheets/Charset.ng | Bin 0 -> 949 bytes project/TileSheets/Chester.ng | Bin 0 -> 296 bytes project/TileSheets/Dirt.ng | Bin 0 -> 147 bytes project/TileSheets/Logo.ng | Bin 0 -> 241 bytes project/TileSheets/NS_Logo.ng | Bin 0 -> 225 bytes project/WorldObjects/Table.jwob | Bin 0 -> 92 bytes project/Worlds/Chester.jwld | Bin 0 -> 1289 bytes src/CMakeLists.txt | 1 + src/jasper/CMakeLists.txt | 16 ++ src/jasper/modules/CMakeLists.txt | 42 ++++ src/jasper/modules/core/CMakeLists.txt | 8 + .../core/include/jasper/core/animpage.hpp | 27 ++ .../core/include/jasper/core/bootfile.hpp | 27 ++ .../core/include/jasper/core/keelmodule.hpp | 13 + src/jasper/modules/core/src/CMakeLists.txt | 22 ++ .../modules/core/src/keel/CMakeLists.txt | 16 ++ .../modules/core/src/keel/keelmodule.cpp | 46 ++++ src/jasper/modules/keelmodules.cpp | 27 ++ src/jasper/modules/studiomodules.cpp | 25 ++ src/jasper/modules/world/CMakeLists.txt | 8 + .../world/include/jasper/world/consts.hpp | 14 ++ .../world/include/jasper/world/keelmodule.hpp | 13 + .../world/include/jasper/world/prefab.hpp | 35 +++ .../include/jasper/world/studiomodule.hpp | 13 + .../world/include/jasper/world/world.hpp | 31 +++ .../include/jasper/world/worldstatic.hpp | 230 ++++++++++++++++++ src/jasper/modules/world/src/CMakeLists.txt | 30 +++ .../modules/world/src/keel/CMakeLists.txt | 17 ++ .../modules/world/src/keel/keelmodule.cpp | 52 ++++ .../modules/world/src/keel/typeconv.cpp | 70 ++++++ .../modules/world/src/keel/typeconv.hpp | 17 ++ .../modules/world/src/studio/CMakeLists.txt | 20 ++ .../modules/world/src/studio/studiomodule.cpp | 35 +++ .../src/studio/worldeditor/CMakeLists.txt | 6 + .../studio/worldeditor/worldeditor-imgui.cpp | 59 +++++ .../studio/worldeditor/worldeditor-imgui.hpp | 35 +++ .../src/studio/worldeditor/worldeditor.cpp | 20 ++ .../src/studio/worldeditor/worldeditor.hpp | 34 +++ .../studio/worldeditor/worldeditorview.cpp | 36 +++ .../studio/worldeditor/worldeditorview.hpp | 39 +++ .../studio/worldobjecteditor/CMakeLists.txt | 5 + .../worldobjecteditor-imgui.cpp | 69 ++++++ .../worldobjecteditor-imgui.hpp | 38 +++ .../worldobjecteditor/worldobjecteditor.cpp | 29 +++ .../worldobjecteditor/worldobjecteditor.hpp | 34 +++ src/jasper/modules/world/src/world.cpp | 57 +++++ src/jasper/modules/world/src/worldstatic.cpp | 11 + src/jasper/player/CMakeLists.txt | 34 +++ src/jasper/player/app.cpp | 38 +++ src/jasper/tools/CMakeLists.txt | 57 +++++ src/jasper/tools/Info.plist | 35 +++ src/jasper/tools/js.icns | Bin 0 -> 117814 bytes src/jasper/tools/js_icon480.png | Bin 0 -> 327 bytes 96 files changed, 2577 insertions(+) create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .liccor.yml create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 developer-handbook.md create mode 100644 jenkins/gba/Jenkinsfile create mode 100644 jenkins/linux/Jenkinsfile create mode 100644 jenkins/mac/Jenkinsfile create mode 100644 jenkins/shared/env.gy create mode 100644 project/.jasper/type_descriptors/B.bool;0 create mode 100644 project/.jasper/type_descriptors/B.int32;0 create mode 100644 project/.jasper/type_descriptors/B.int8;0 create mode 100644 project/.jasper/type_descriptors/B.string;0 create mode 100644 project/.jasper/type_descriptors/B.uint16;0 create mode 100644 project/.jasper/type_descriptors/B.uint64;0 create mode 100644 project/.jasper/type_descriptors/B.uint8;0 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.jasper.core.Bootfile;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.CompactTileSheet;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.NostalgiaGraphic;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.Palette;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;2 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;3 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneDoc;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneStatic;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.TileDoc;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.PrefabDoc;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.TileDoc;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldDoc;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldStatic;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.ox.BasicString#8#;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress.Data;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress;1 create mode 100644 project/.jasper/type_descriptors/net.drinkingtea.ox.Size;1 create mode 100644 project/Bootfile create mode 100644 project/Palettes/Charset.npal create mode 100644 project/Palettes/Chester.npal create mode 100644 project/Palettes/Dirt.npal create mode 100644 project/Palettes/Logo.npal create mode 100644 project/TileSheets/Charset.ng create mode 100644 project/TileSheets/Chester.ng create mode 100644 project/TileSheets/Dirt.ng create mode 100644 project/TileSheets/Logo.ng create mode 100644 project/TileSheets/NS_Logo.ng create mode 100644 project/WorldObjects/Table.jwob create mode 100644 project/Worlds/Chester.jwld create mode 100644 src/CMakeLists.txt create mode 100644 src/jasper/CMakeLists.txt create mode 100644 src/jasper/modules/CMakeLists.txt create mode 100644 src/jasper/modules/core/CMakeLists.txt create mode 100644 src/jasper/modules/core/include/jasper/core/animpage.hpp create mode 100644 src/jasper/modules/core/include/jasper/core/bootfile.hpp create mode 100644 src/jasper/modules/core/include/jasper/core/keelmodule.hpp create mode 100644 src/jasper/modules/core/src/CMakeLists.txt create mode 100644 src/jasper/modules/core/src/keel/CMakeLists.txt create mode 100644 src/jasper/modules/core/src/keel/keelmodule.cpp create mode 100644 src/jasper/modules/keelmodules.cpp create mode 100644 src/jasper/modules/studiomodules.cpp create mode 100644 src/jasper/modules/world/CMakeLists.txt create mode 100644 src/jasper/modules/world/include/jasper/world/consts.hpp create mode 100644 src/jasper/modules/world/include/jasper/world/keelmodule.hpp create mode 100644 src/jasper/modules/world/include/jasper/world/prefab.hpp create mode 100644 src/jasper/modules/world/include/jasper/world/studiomodule.hpp create mode 100644 src/jasper/modules/world/include/jasper/world/world.hpp create mode 100644 src/jasper/modules/world/include/jasper/world/worldstatic.hpp create mode 100644 src/jasper/modules/world/src/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/keel/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/keel/keelmodule.cpp create mode 100644 src/jasper/modules/world/src/keel/typeconv.cpp create mode 100644 src/jasper/modules/world/src/keel/typeconv.hpp create mode 100644 src/jasper/modules/world/src/studio/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/studio/studiomodule.cpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditorview.cpp create mode 100644 src/jasper/modules/world/src/studio/worldeditor/worldeditorview.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt create mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp create mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp create mode 100644 src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp create mode 100644 src/jasper/modules/world/src/world.cpp create mode 100644 src/jasper/modules/world/src/worldstatic.cpp create mode 100644 src/jasper/player/CMakeLists.txt create mode 100644 src/jasper/player/app.cpp create mode 100644 src/jasper/tools/CMakeLists.txt create mode 100644 src/jasper/tools/Info.plist create mode 100644 src/jasper/tools/js.icns create mode 100644 src/jasper/tools/js_icon480.png diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..4d5dbae --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,65 @@ +# Generated from CLion Inspection settings +--- +Checks: '-*, +cppcoreguidelines-interfaces-global-init, +cppcoreguidelines-narrowing-conversions, +cppcoreguidelines-pro-type-member-init, +-cppcoreguidelines-pro-type-static-cast-downcast, +cppcoreguidelines-slicing, +google-default-arguments, +google-runtime-operator, +hicpp-exception-baseclass, +hicpp-multiway-paths-covered, +mpi-buffer-deref, +mpi-type-mismatch, +openmp-use-default-none, +performance-faster-string-find, +performance-for-range-copy, +performance-implicit-conversion-in-loop, +performance-inefficient-algorithm, +performance-inefficient-string-concatenation, +performance-inefficient-vector-operation, +performance-move-const-arg, +performance-move-constructor-init, +performance-no-automatic-move, +performance-noexcept-move-constructor, +performance-trivially-destructible, +performance-type-promotion-in-math-fn, +performance-unnecessary-copy-initialization, +performance-unnecessary-value-param, +readability-avoid-const-params-in-decls, +readability-const-return-type, +readability-container-size-empty, +readability-convert-member-functions-to-static, +readability-delete-null-pointer, +readability-deleted-default, +readability-inconsistent-declaration-parameter-name, +readability-make-member-function-const, +readability-misleading-indentation, +readability-misplaced-array-index, +readability-non-const-parameter, +readability-redundant-control-flow, +readability-redundant-declaration, +readability-redundant-function-ptr-dereference, +readability-redundant-smartptr-get, +readability-redundant-string-cstr, +readability-redundant-string-init, +readability-simplify-subscript-expr, +readability-static-accessed-through-instance, +readability-static-definition-in-anonymous-namespace, +readability-string-compare, +readability-uniqueptr-delete-release, +readability-use-anyofallof, +cert-*, +misc-*, +-misc-include-cleaner +-misc-use-anonymous-namespace, +readability-duplicate-include, +-misc-non-private-member-variables-in-classes, +-misc-no-recursion, +bugprone-*, +clang-analyzer-*, +modernize-*, +portability-*, +-modernize-use-trailing-return-type, +-bugprone-easily-swappable-parameters' \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a68aa18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.cache +.clangd +.current_build +.conanbuild +.idea +.mypy_cache +.stfolder +.stignore +scripts/__pycache__ +CMakeLists.txt.user +ROM.oxfs +Session.vim +build +compile_commands.json +dist +graph_info.json +imgui.ini +*.gba +*.sav +studio_state.json +tags diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..272afad --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,24 @@ +# This file is a template, and might need editing before it works on your project. +# You can copy and paste this template into a new `.gitlab-ci.yml` file. +# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword. +# +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/C++.gitlab-ci.yml + +# use the official gcc image, based on debian +# can use versions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ + +image: gcc + +build: + stage: build + variables: + OX_NODEBUG: 1 + before_script: + - apt update && apt -y install make cmake ninja-build pkg-config xorg-dev libgtk-3-dev python3 python3-mypy + script: + - make purge configure-release test install + - make purge configure-asan test install diff --git a/.liccor.yml b/.liccor.yml new file mode 100644 index 0000000..d6eb78d --- /dev/null +++ b/.liccor.yml @@ -0,0 +1,5 @@ +--- +source: +- src +copyright_notice: |- + Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c9f1ad4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.19) +set(CMAKE_POLICY_DEFAULT_CMP0110 NEW) # requires CMake 3.19 + +if(BUILDCORE_TARGET STREQUAL "gba") + project(jasper ASM CXX) +elseif(APPLE) + project(jasper C CXX OBJC OBJCXX) +else() + project(jasper C CXX) +endif() + +include(deps/nostalgia/deps/buildcore/base.cmake) + +set(NOSTALGIA_BUILD_PLAYER OFF) +set(NOSTALGIA_BUILD_STUDIO OFF) + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +if(APPLE) + set(CMAKE_MACOSX_RPATH OFF) +else() + if(UNIX) + set(BUILD_SHARED_LIBS ON) + endif() + set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() + +include_directories( + deps/nostalgia/deps/ox/src +) +add_subdirectory(deps/nostalgia) +add_subdirectory(src) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d7dbd03 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +BC_VAR_PROJECT_NAME=jasper +BC_VAR_PROJECT_NAME_CAP=Jasper +BUILDCORE_PATH=deps/nostalgia/deps/buildcore +include ${BUILDCORE_PATH}/base.mk + +ifeq ($(BC_VAR_OS),darwin) + PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME}.app/Contents/MacOS/${BC_VAR_PROJECT_NAME} + PROJECT_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME}Studio.app/Contents/MacOS/${BC_VAR_PROJECT_NAME_CAP}Studio + MGBA=/Applications/mGBA.app/Contents/MacOS/mGBA +else + PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} + PROJECT_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio + MGBA=mgba-qt +endif + +.PHONY: pkg-gba +pkg-gba: build + ${BC_CMD_ENVRUN} ${BC_PY3} ./deps/nostalgia/scripts/pkg-gba.py project ${BC_VAR_PROJECT_NAME} + +.PHONY: run +run: build + ${PROJECT_PLAYER} project +.PHONY: run-studio +run-studio: build + ${PROJECT_STUDIO} +.PHONY: gba-run +gba-run: pkg-gba + ${MGBA} ${BC_VAR_PROJECT_NAME}.gba +.PHONY: debug +debug: build + ${BC_CMD_HOST_DEBUGGER} ./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} sample_project +.PHONY: debug-studio +debug-studio: build + ${BC_CMD_HOST_DEBUGGER} ${PROJECT_STUDIO} + +.PHONY: configure-gba +configure-gba: + ${BC_CMD_SETUP_BUILD} --toolchain=deps/gbabuildcore/cmake/modules/GBA.cmake --target=gba --current_build=0 --build_type=release --build_root=${BC_VAR_BUILD_PATH} + +.PHONY: configure-gba-debug +configure-gba-debug: + ${BC_CMD_SETUP_BUILD} --toolchain=deps/gbabuildcore/cmake/modules/GBA.cmake --target=gba --current_build=0 --build_type=debug --build_root=${BC_VAR_BUILD_PATH} diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb03ffe --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Jasper + +## Prerequisites + +* Install GCC, Clang, or Visual Studio with C++20 support +* Install [devkitPro](https://devkitpro.org/wiki/Getting_Started) to build for GBA +* Install Python 3 +* Install Ninja, Make, and CMake +* Consider also installing ccache for faster subsequent build times + +### Debian + +For Debian (and probably other Linux distros, but the package names will +probably differ), install the following additional packages: +* pkg-config +* xorg-dev +* libgtk-3-dev + +## Build + +Build options: release, debug, asan, gba, gba-debug + + make purge configure-{gba,release,debug} install + +## Run + +### Studio + + make run-studio + +### Native Platform + + make run + +### GBA + + make gba-run + +## Contributing + +Please read the [Developer Handbook](developer-handbook.md) for information on +coding standards. diff --git a/developer-handbook.md b/developer-handbook.md new file mode 100644 index 0000000..6b2eae4 --- /dev/null +++ b/developer-handbook.md @@ -0,0 +1,27 @@ +# Jasper Developer Handbook + +## About + +The purpose of the Developer Handbook is similar to that of the README. +The README should be viewed as a prerequisite to the Developer Handbook. +The README should provide information needed to build the project, which might +be used by an advanced user or a person trying to build and package the +project. +The Developer Handbook should focus on information needed by a developer +working on the project. + +## Project Structure + +### Overview + +All components have a platform indicator next to them: + + (PG) - PC, GBA + (-G) - GBA + (P-) - PC + +With this project being based on Nostalgia, the Nostalgia [developer +handbook](https://git.drinkingtea.net/drinkingtea/nostalgia/src/branch/master/developer-handbook.md) +should be considered a prerequisite for the Jasper developer handbook. + + diff --git a/jenkins/gba/Jenkinsfile b/jenkins/gba/Jenkinsfile new file mode 100644 index 0000000..49d5f69 --- /dev/null +++ b/jenkins/gba/Jenkinsfile @@ -0,0 +1,45 @@ +pipeline { + agent { + label 'gba' + } + stages { + stage('Environment') { + steps { + load 'jenkins/shared/env.gy' + sh 'make conan-config' + sh 'make conan' + } + } + stage('Build Tools Debug') { + steps { + sh 'make purge configure-debug' + sh 'make install' + } + } + stage('Build GBA Debug') { + steps { + sh 'make configure-gba-debug' + sh 'make' + sh 'make pkg-gba' + } + } + stage('Build Tools Release') { + steps { + sh 'make purge configure-release' + sh 'make install' + } + } + stage('Build GBA Release') { + steps { + sh 'make configure-gba' + sh 'make' + sh 'make pkg-gba' + } + } + } + post { + always { + archiveArtifacts artifacts: 'nostalgia.gba', fingerprint: true + } + } +} diff --git a/jenkins/linux/Jenkinsfile b/jenkins/linux/Jenkinsfile new file mode 100644 index 0000000..3aec5c0 --- /dev/null +++ b/jenkins/linux/Jenkinsfile @@ -0,0 +1,47 @@ +pipeline { + agent { + label 'linux-x86_64' + } + stages { + stage('Environment') { + steps { + load 'jenkins/shared/env.gy' + sh 'make conan-config' + sh 'make conan' + } + } + stage('Build Asan') { + steps { + sh 'make purge configure-asan' + sh 'make' + } + } + stage('Test Asan') { + steps { + sh 'make test' + } + } + stage('Build Debug') { + steps { + sh 'make purge configure-debug' + sh 'make' + } + } + stage('Test Debug') { + steps { + sh 'make test' + } + } + stage('Build Release') { + steps { + sh 'make purge configure-release' + sh 'make' + } + } + stage('Test Release') { + steps { + sh 'make test' + } + } + } +} diff --git a/jenkins/mac/Jenkinsfile b/jenkins/mac/Jenkinsfile new file mode 100644 index 0000000..86bb827 --- /dev/null +++ b/jenkins/mac/Jenkinsfile @@ -0,0 +1,47 @@ +pipeline { + agent { + label 'mac-x86_64' + } + stages { + stage('Environment') { + steps { + load 'jenkins/shared/env.gy' + sh 'make conan-config' + sh 'make conan' + } + } + stage('Build Asan') { + steps { + sh 'make purge configure-asan' + sh 'make' + } + } + stage('Test Asan') { + steps { + sh 'make test' + } + } + stage('Build Debug') { + steps { + sh 'make purge configure-debug' + sh 'make' + } + } + stage('Test Debug') { + steps { + sh 'make test' + } + } + stage('Build Release') { + steps { + sh 'make purge configure-release' + sh 'make' + } + } + stage('Test Release') { + steps { + sh 'make test' + } + } + } +} diff --git a/jenkins/shared/env.gy b/jenkins/shared/env.gy new file mode 100644 index 0000000..f5cde3b --- /dev/null +++ b/jenkins/shared/env.gy @@ -0,0 +1,2 @@ +env.OX_NODEBUG = 1 +env.BUILDCORE_SUPPRESS_CCACHE = 1 diff --git a/project/.jasper/type_descriptors/B.bool;0 b/project/.jasper/type_descriptors/B.bool;0 new file mode 100644 index 0000000..0f93c25 --- /dev/null +++ b/project/.jasper/type_descriptors/B.bool;0 @@ -0,0 +1,4 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "primitiveType" : 2, + "typeName" : "B.bool" +} diff --git a/project/.jasper/type_descriptors/B.int32;0 b/project/.jasper/type_descriptors/B.int32;0 new file mode 100644 index 0000000..5acc44f --- /dev/null +++ b/project/.jasper/type_descriptors/B.int32;0 @@ -0,0 +1,5 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "length" : 4, + "primitiveType" : 1, + "typeName" : "B.int32" +} diff --git a/project/.jasper/type_descriptors/B.int8;0 b/project/.jasper/type_descriptors/B.int8;0 new file mode 100644 index 0000000..a011811 --- /dev/null +++ b/project/.jasper/type_descriptors/B.int8;0 @@ -0,0 +1,5 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "length" : 1, + "primitiveType" : 1, + "typeName" : "B.int8" +} diff --git a/project/.jasper/type_descriptors/B.string;0 b/project/.jasper/type_descriptors/B.string;0 new file mode 100644 index 0000000..f658c64 --- /dev/null +++ b/project/.jasper/type_descriptors/B.string;0 @@ -0,0 +1,4 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "primitiveType" : 4, + "typeName" : "B.string" +} diff --git a/project/.jasper/type_descriptors/B.uint16;0 b/project/.jasper/type_descriptors/B.uint16;0 new file mode 100644 index 0000000..1330cf9 --- /dev/null +++ b/project/.jasper/type_descriptors/B.uint16;0 @@ -0,0 +1,4 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "length" : 2, + "typeName" : "B.uint16" +} diff --git a/project/.jasper/type_descriptors/B.uint64;0 b/project/.jasper/type_descriptors/B.uint64;0 new file mode 100644 index 0000000..458ed70 --- /dev/null +++ b/project/.jasper/type_descriptors/B.uint64;0 @@ -0,0 +1,4 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "length" : 8, + "typeName" : "B.uint64" +} diff --git a/project/.jasper/type_descriptors/B.uint8;0 b/project/.jasper/type_descriptors/B.uint8;0 new file mode 100644 index 0000000..44d41b3 --- /dev/null +++ b/project/.jasper/type_descriptors/B.uint8;0 @@ -0,0 +1,4 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "length" : 1, + "typeName" : "B.uint8" +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.jasper.core.Bootfile;1 b/project/.jasper/type_descriptors/net.drinkingtea.jasper.core.Bootfile;1 new file mode 100644 index 0000000..4af35be --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.jasper.core.Bootfile;1 @@ -0,0 +1,24 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "app", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "args", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.jasper.core.Bootfile", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.CompactTileSheet;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.CompactTileSheet;1 new file mode 100644 index 0000000..2f891fa --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.CompactTileSheet;1 @@ -0,0 +1,27 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "bpp", + "typeId" : "B.int8;0" + }, + { + "fieldName" : "defaultPalette", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "pixels", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.CompactTileSheet", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.NostalgiaGraphic;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.NostalgiaGraphic;1 new file mode 100644 index 0000000..1427537 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.NostalgiaGraphic;1 @@ -0,0 +1,39 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "bpp", + "typeId" : "B.int8;0" + }, + { + "fieldName" : "rows", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "columns", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "defaultPalette", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "pal", + "typeId" : "net.drinkingtea.nostalgia.core.Palette;1" + }, + { + "fieldName" : "pixels", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.NostalgiaGraphic", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.Palette;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.Palette;1 new file mode 100644 index 0000000..0336e78 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.Palette;1 @@ -0,0 +1,19 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "colors", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.Palette", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1 new file mode 100644 index 0000000..949b965 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1 @@ -0,0 +1,42 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "name", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "rows", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "columns", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "subsheets", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1" + }, + { + "fieldName" : "pixels", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3 new file mode 100644 index 0000000..01125bb --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3 @@ -0,0 +1,42 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "name", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "rows", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "columns", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "subsheets", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3" + }, + { + "fieldName" : "pixels", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet", + "typeVersion" : 3 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;2 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;2 new file mode 100644 index 0000000..17be8f6 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;2 @@ -0,0 +1,20 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "bpp", + "typeId" : "B.int8;0" + }, + { + "fieldName" : "defaultPalette", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "subsheet", + "typeId" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet;1" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.TileSheet", + "typeVersion" : 2 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;3 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;3 new file mode 100644 index 0000000..3bb3448 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.core.TileSheet;3 @@ -0,0 +1,24 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "bpp", + "typeId" : "B.int8;0" + }, + { + "fieldName" : "idIt", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "defaultPalette", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "subsheet", + "typeId" : "net.drinkingtea.nostalgia.core.TileSheet.SubSheet;3" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.core.TileSheet", + "typeVersion" : 3 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneDoc;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneDoc;1 new file mode 100644 index 0000000..07dabd5 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneDoc;1 @@ -0,0 +1,41 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "tilesheet", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "palettes", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "tiles", + "subscriptLevels" : 3, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.nostalgia.scene.TileDoc;1" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.scene.SceneDoc", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneStatic;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneStatic;1 new file mode 100644 index 0000000..8482e49 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.SceneStatic;1 @@ -0,0 +1,88 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "tilesheet", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "palettes", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "columns", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "rows", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "tileMapIdx", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "tileType", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + }, + { + "fieldName" : "layerAttachments", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.scene.SceneStatic", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.TileDoc;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.TileDoc;1 new file mode 100644 index 0000000..3853479 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.scene.TileDoc;1 @@ -0,0 +1,33 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "subsheet_id", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "subsheet_path", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "type", + "typeId" : "B.uint8;0" + }, + { + "fieldName" : "layer_attachments", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "length" : 4, + "subscriptType" : 3 + } + ], + "typeId" : "B.uint8;0" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.scene.TileDoc", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.PrefabDoc;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.PrefabDoc;1 new file mode 100644 index 0000000..cd5bdff --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.PrefabDoc;1 @@ -0,0 +1,25 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "footprint", + "typeId" : "net.drinkingtea.ox.Size;1" + }, + { + "fieldName" : "visible_size", + "typeId" : "net.drinkingtea.ox.Size;1" + }, + { + "fieldName" : "tilesheet_path", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "subsheet_path", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.world.PrefabDoc", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.TileDoc;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.TileDoc;1 new file mode 100644 index 0000000..74dbf61 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.TileDoc;1 @@ -0,0 +1,33 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "subsheet_id", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "subsheet_path", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "type", + "typeId" : "B.uint8;0" + }, + { + "fieldName" : "layer_attachments", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "length" : 4, + "subscriptType" : 3 + } + ], + "typeId" : "B.uint8;0" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.world.TileDoc", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldDoc;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldDoc;1 new file mode 100644 index 0000000..67299d5 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldDoc;1 @@ -0,0 +1,41 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "tilesheet", + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "palettes", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.ox.BasicString#8#;1" + }, + { + "fieldName" : "tiles", + "subscriptLevels" : 3, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.nostalgia.world.TileDoc;1" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.world.WorldDoc", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldStatic;1 b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldStatic;1 new file mode 100644 index 0000000..67e78f7 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.nostalgia.world.WorldStatic;1 @@ -0,0 +1,88 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "tilesheet", + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "palettes", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "net.drinkingtea.ox.FileAddress;1" + }, + { + "fieldName" : "columns", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "rows", + "subscriptLevels" : 1, + "subscriptStack" : + [ + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "tileMapIdx", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint16;0" + }, + { + "fieldName" : "tileType", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + }, + { + "fieldName" : "layerAttachments", + "subscriptLevels" : 2, + "subscriptStack" : + [ + { + "subscriptType" : 4 + }, + { + "subscriptType" : 4 + } + ], + "typeId" : "B.uint8;0" + } + ], + "preloadable" : true, + "primitiveType" : 5, + "typeName" : "net.drinkingtea.nostalgia.world.WorldStatic", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.ox.BasicString#8#;1 b/project/.jasper/type_descriptors/net.drinkingtea.ox.BasicString#8#;1 new file mode 100644 index 0000000..dc047dc --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.ox.BasicString#8#;1 @@ -0,0 +1,9 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "primitiveType" : 4, + "typeName" : "net.drinkingtea.ox.BasicString", + "typeParams" : + [ + "8" + ], + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress.Data;1 b/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress.Data;1 new file mode 100644 index 0000000..cb981fa --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress.Data;1 @@ -0,0 +1,20 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "path", + "typeId" : "B.string" + }, + { + "fieldName" : "constPath", + "typeId" : "B.string" + }, + { + "fieldName" : "inode", + "typeId" : "B.uint64;0" + } + ], + "primitiveType" : 6, + "typeName" : "net.drinkingtea.ox.FileAddress.Data", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress;1 b/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress;1 new file mode 100644 index 0000000..43361af --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.ox.FileAddress;1 @@ -0,0 +1,16 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "type", + "typeId" : "B.int8;0" + }, + { + "fieldName" : "data", + "typeId" : "net.drinkingtea.ox.FileAddress.Data" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.ox.FileAddress", + "typeVersion" : 1 +} diff --git a/project/.jasper/type_descriptors/net.drinkingtea.ox.Size;1 b/project/.jasper/type_descriptors/net.drinkingtea.ox.Size;1 new file mode 100644 index 0000000..d1cd279 --- /dev/null +++ b/project/.jasper/type_descriptors/net.drinkingtea.ox.Size;1 @@ -0,0 +1,16 @@ +O1;net.drinkingtea.ox.TypeDescriptor;1;{ + "fieldList" : + [ + { + "fieldName" : "width", + "typeId" : "B.int32;0" + }, + { + "fieldName" : "height", + "typeId" : "B.int32;0" + } + ], + "primitiveType" : 5, + "typeName" : "net.drinkingtea.ox.Size", + "typeVersion" : 1 +} diff --git a/project/Bootfile b/project/Bootfile new file mode 100644 index 0000000..5abaf32 --- /dev/null +++ b/project/Bootfile @@ -0,0 +1,4 @@ +O1;net.drinkingtea.jasper.core.Bootfile;1;{ + "app": "World", + "args": ["Worlds/Chester.jwld"] +} diff --git a/project/Palettes/Charset.npal b/project/Palettes/Charset.npal new file mode 100644 index 0000000000000000000000000000000000000000..abb6c19ec8998ed446627bd19bde8d98580cf93f GIT binary patch literal 94 zcmV~$feJt{5CBk`QvPMjZP?a*f|Or494(sSe%v2;Zv^Z@>}hF(Ui`l4&N;6GaMTo9 sa4-z)t;5M;W-;57@|p4>OeWv2&~+p->;15q>0%K^z_L=RsMJ?CKkGFb@Bjb+ literal 0 HcmV?d00001 diff --git a/project/Palettes/Chester.npal b/project/Palettes/Chester.npal new file mode 100644 index 0000000..9880191 --- /dev/null +++ b/project/Palettes/Chester.npal @@ -0,0 +1 @@ +K1;14fc3dd8-42ff-4bf9-81f1-a010cc5ac251;M2;net.drinkingtea.nostalgia.core.Palette;1;ûÿ³Ö diff --git a/project/Palettes/Dirt.npal b/project/Palettes/Dirt.npal new file mode 100644 index 0000000..ffb649e --- /dev/null +++ b/project/Palettes/Dirt.npal @@ -0,0 +1 @@ +K1;0f75977f-1c52-45f8-9793-52ea2dc200a0;M2;net.drinkingtea.nostalgia.core.Palette;1;ûÿ³Ö diff --git a/project/Palettes/Logo.npal b/project/Palettes/Logo.npal new file mode 100644 index 0000000..d648dba --- /dev/null +++ b/project/Palettes/Logo.npal @@ -0,0 +1 @@ +K1;c79f21e2-f74f-4ad9-90ed-32b0ef7da6ed;M2;net.drinkingtea.nostalgia.core.Palette;1;PÛ{³ÖQ„ \ No newline at end of file diff --git a/project/TileSheets/Charset.ng b/project/TileSheets/Charset.ng new file mode 100644 index 0000000000000000000000000000000000000000..79da10e6ee2990a9ce25b99bfb3b27938cf19f46 GIT binary patch literal 949 zcmYjP!EVz)5OtPxokVIP_drw*V6CcfA!!@Zq){%VtO^nWAySJI5*zGoLR~ve+(UnZ zgXs!Yy|pNpo)I6x7jWedK;i^5yLFnec4y|z%$v7SUTpT-9oZ4G*{;i0vsG_Nf2+Pd z5Z!vG-Ss!S8|{s4zu9})>_tJ+@F%0_a1WD3*KScI#EH*uU4D%H|%D566`b}X>!-6!FAC|qwm4kq{V zxn-#?idP?J_I<}GU(IIrwY;oc%i5jPIj8EcDn4Y)-sAhkd)!}g9LN51p^D$W|6ER$ zqHj-}T&mucKc9NVRGd-hB?@pAORCgAs$^R9YK%NvTd`e2T$jKHJ((wjKn4aN!f5CX zhy`8Ba+U$}4vpY2FoFTG4j0#8FV>LRHT?tu9LNq<&1S#=gtPDg17e8kV7>w%&1t5Q z?6w3MEXx3m1BdqbLOsazpr+3DEfUP9jg}i_nG}=iC+P5YJ1ni1U!5Ecg2}X$&n=~2 z&Z*CNIrTqPE}ws;-fy0NaC}B<#~%H`c|IK}DkV4Kj1VmlE;Nv9E!VQ_v9-onewTAn wV~p-`PBZ^L`bG7A)PSm((Ey<)ha1&`fgFZSdLhQ9191rT6xJ}^fz4LwKUQg)2><{9 literal 0 HcmV?d00001 diff --git a/project/TileSheets/Chester.ng b/project/TileSheets/Chester.ng new file mode 100644 index 0000000000000000000000000000000000000000..6f549522ceca62231e886053ded72f3b87bf903e GIT binary patch literal 296 zcmZvWyAFat5JhFBAT*Z5mRQR2P*}`12JMWAQGZ~C5jJcV1M~krumR&KZstrTxpN44 zoFpt~@y25}5qfJD2ro^Oz+=%pU{O%yahmgE#5Eu-TB&QPO9Vl6)1gqM6f|#IpjWBj zQUM@e@tI{2W9grS0z~NiT?HLNOZ7vjxpi(DG^dW8DWPjZwv#uGwU;fLra_)RxE~gM a4kB~2f%W6hVyt8PbN_7^_w?4kXTJam4NC+7 literal 0 HcmV?d00001 diff --git a/project/TileSheets/Dirt.ng b/project/TileSheets/Dirt.ng new file mode 100644 index 0000000000000000000000000000000000000000..54f1cd1c4f6493684887a305fc50584ed04ee911 GIT binary patch literal 147 zcmW;3K@Ng25CFh-Ln845UO9Pd!9oDjiwAGU#Hb%w%7TqeYhWMX(@mVY9Bei8ExMM} zaKu_QT}=vWS1B`2t})}#^)%V5v5{Dhi;r&~XC~A!t&Cyzs9jn}KYgIbi-@hUn;hi6 nykQ__TFbLvSjP{BU4BoAvrIxEPEvr7Cn4Zlh#ZPP0NDNj&lx8< literal 0 HcmV?d00001 diff --git a/project/TileSheets/Logo.ng b/project/TileSheets/Logo.ng new file mode 100644 index 0000000000000000000000000000000000000000..161a3cbd683ec38f0b60916f5cf5ce87986017cf GIT binary patch literal 241 zcmeY^w6?G`OEgb0GS#(ANjB0oF-tViO)^PI)J-hDm^FgeIUeB_=KaqUtx! literal 0 HcmV?d00001 diff --git a/project/TileSheets/NS_Logo.ng b/project/TileSheets/NS_Logo.ng new file mode 100644 index 0000000000000000000000000000000000000000..b3f677a294a051237eb00fa605a6109610f11538 GIT binary patch literal 225 zcmYL>y$ZrG6oqS$pp)R@S|1>_X{~L{E`qqII45cD)u1$>@c{%Mz{y8-_HXc<;e+qM zS#XI(!O>KPr67=v3#?hi(+M-8G9_AGAr^9($p%7VPp&z*W*0ywjXwu{+&P`p{shU! z9bvr(2r`$w4k4}W<>KsgG^$I18HbEvIe!0ZE0{>ZvOF^>U}^OP?4YyqK2UmXNAIXd l_k&3FhctTnNZsc2EAH;gn2kBir{k84S-m0P>X0N()tBTKI z>P+z-aX;vWG~aoKeB0UiVy#D;-3Bd7Yj)dhRo7heN&-JA0*qq?Lev;Ib(m9ZbR{%; z4(w5N)_Xfg^5Kt7nA4>(0vL4;*vUx{#1T-KV67{qX+_X6(o^ur&9g(ErFDZWNQ(o_ z%PFb*7?r=Epzv)Ne6epQALFMd{%f@12B+ +#include +#include + +namespace jasper::core { + +struct AnimPage { + constexpr static auto TypeName = "net.drinkingtea.nostalgia.core.AnimPage"; + constexpr static auto TypeVersion = 1; + constexpr static auto Preloadable = true; + ox::String tilesheetPath; + ox::String subsheetPath; +}; + +oxModelBegin(AnimPage) + oxModelFieldRename(tilesheet_path, tilesheetPath) + oxModelFieldRename(subsheet_path, subsheetPath) +oxModelEnd() + + +} diff --git a/src/jasper/modules/core/include/jasper/core/bootfile.hpp b/src/jasper/modules/core/include/jasper/core/bootfile.hpp new file mode 100644 index 0000000..7e7e84e --- /dev/null +++ b/src/jasper/modules/core/include/jasper/core/bootfile.hpp @@ -0,0 +1,27 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include +#include + +namespace jasper::core { + +struct Bootfile { + static constexpr auto TypeName = "net.drinkingtea.jasper.core.Bootfile"; + static constexpr auto TypeVersion = 1; + static constexpr auto Preloadable = true; + ox::String app; + ox::Vector args; +}; + +oxModelBegin(Bootfile) + oxModelField(app) + oxModelField(args) +oxModelEnd() + + +} diff --git a/src/jasper/modules/core/include/jasper/core/keelmodule.hpp b/src/jasper/modules/core/include/jasper/core/keelmodule.hpp new file mode 100644 index 0000000..9440dad --- /dev/null +++ b/src/jasper/modules/core/include/jasper/core/keelmodule.hpp @@ -0,0 +1,13 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace jasper::core { + +keel::Module const*keelModule() noexcept; + +} diff --git a/src/jasper/modules/core/src/CMakeLists.txt b/src/jasper/modules/core/src/CMakeLists.txt new file mode 100644 index 0000000..f02bbb0 --- /dev/null +++ b/src/jasper/modules/core/src/CMakeLists.txt @@ -0,0 +1,22 @@ + +add_library(JasperCore INTERFACE) + +target_include_directories( + JasperCore INTERFACE + ../include +) + +target_link_libraries( + JasperCore INTERFACE + NostalgiaCore +) + +add_subdirectory(keel) + +install( + TARGETS + JasperCore + DESTINATION + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/src/jasper/modules/core/src/keel/CMakeLists.txt b/src/jasper/modules/core/src/keel/CMakeLists.txt new file mode 100644 index 0000000..b25972a --- /dev/null +++ b/src/jasper/modules/core/src/keel/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library( + JasperCore-Keel + keelmodule.cpp +) + +target_link_libraries( + JasperCore-Keel PUBLIC + Keel + JasperCore +) +install( + TARGETS + JasperCore-Keel + LIBRARY DESTINATION + ${NOSTALGIA_DIST_MODULE} +) diff --git a/src/jasper/modules/core/src/keel/keelmodule.cpp b/src/jasper/modules/core/src/keel/keelmodule.cpp new file mode 100644 index 0000000..805c861 --- /dev/null +++ b/src/jasper/modules/core/src/keel/keelmodule.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +namespace jasper::core { + +class CoreModule: public keel::Module { + private: + + public: + [[nodiscard]] + ox::String id() const noexcept override { + return ox::String("net.drinkingtea.jasper.core"); + } + + [[nodiscard]] + ox::Vector types() const noexcept override { + return { + keel::generateTypeDesc, + }; + } + + [[nodiscard]] + ox::Vector converters() const noexcept override { + return { + }; + } + + [[nodiscard]] + ox::Vector packTransforms() const noexcept override { + return { + }; + } + +}; + +static const CoreModule mod; +keel::Module const*keelModule() noexcept { + return &mod; +} + +} diff --git a/src/jasper/modules/keelmodules.cpp b/src/jasper/modules/keelmodules.cpp new file mode 100644 index 0000000..b06e390 --- /dev/null +++ b/src/jasper/modules/keelmodules.cpp @@ -0,0 +1,27 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include +#include + +namespace nostalgia { +void registerKeelModules() noexcept; +} + +namespace jasper { + +static bool modulesRegistered = false; +void registerKeelModules() noexcept { + if (modulesRegistered) { + return; + } + modulesRegistered = true; + nostalgia::registerKeelModules(); + keel::registerModule(core::keelModule()); + keel::registerModule(world::keelModule()); +} + +} diff --git a/src/jasper/modules/studiomodules.cpp b/src/jasper/modules/studiomodules.cpp new file mode 100644 index 0000000..12865ca --- /dev/null +++ b/src/jasper/modules/studiomodules.cpp @@ -0,0 +1,25 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +namespace nostalgia { +void registerStudioModules() noexcept; +} + +namespace jasper { + +static bool modulesRegistered = false; +void registerStudioModules() noexcept { + if (modulesRegistered) { + return; + } + modulesRegistered = true; + nostalgia::registerStudioModules(); + studio::registerModule(world::studioModule()); +} + +} diff --git a/src/jasper/modules/world/CMakeLists.txt b/src/jasper/modules/world/CMakeLists.txt new file mode 100644 index 0000000..0abe879 --- /dev/null +++ b/src/jasper/modules/world/CMakeLists.txt @@ -0,0 +1,8 @@ +add_subdirectory(src) + +install( + DIRECTORY + include/jasper + DESTINATION + include +) diff --git a/src/jasper/modules/world/include/jasper/world/consts.hpp b/src/jasper/modules/world/include/jasper/world/consts.hpp new file mode 100644 index 0000000..ae922e1 --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/consts.hpp @@ -0,0 +1,14 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace jasper::world { + +inline constexpr ox::StringLiteral FileExt_jwob("jwob"); +inline constexpr ox::StringLiteral FileExt_jwld("jwld"); + +} diff --git a/src/jasper/modules/world/include/jasper/world/keelmodule.hpp b/src/jasper/modules/world/include/jasper/world/keelmodule.hpp new file mode 100644 index 0000000..7fba482 --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/keelmodule.hpp @@ -0,0 +1,13 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace jasper::world { + +keel::Module const*keelModule() noexcept; + +} diff --git a/src/jasper/modules/world/include/jasper/world/prefab.hpp b/src/jasper/modules/world/include/jasper/world/prefab.hpp new file mode 100644 index 0000000..78188cd --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/prefab.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2023 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.nostalgia.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/studiomodule.hpp b/src/jasper/modules/world/include/jasper/world/studiomodule.hpp new file mode 100644 index 0000000..5720537 --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/studiomodule.hpp @@ -0,0 +1,13 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace jasper::world { + +studio::Module const*studioModule() noexcept; + +} diff --git a/src/jasper/modules/world/include/jasper/world/world.hpp b/src/jasper/modules/world/include/jasper/world/world.hpp new file mode 100644 index 0000000..006750d --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/world.hpp @@ -0,0 +1,31 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include "consts.hpp" +#include "prefab.hpp" +#include "worldstatic.hpp" + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +class World { + private: + WorldStatic const&m_worldStatic; + + public: + explicit World(WorldStatic const&worldStatic) noexcept; + + ox::Error setupDisplay(ncore::Context &ctx) const noexcept; + + private: + void setupLayer(ncore::Context&, ox::Vector const&layer, unsigned layerNo) const noexcept; + +}; + +} diff --git a/src/jasper/modules/world/include/jasper/world/worldstatic.hpp b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp new file mode 100644 index 0000000..bb257b4 --- /dev/null +++ b/src/jasper/modules/world/include/jasper/world/worldstatic.hpp @@ -0,0 +1,230 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +struct SpriteDoc { + + constexpr static auto TypeName = "net.drinkingtea.nostalgia.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.nostalgia.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 ts.getIdFor(subsheetPath); + } + + [[nodiscard]] + constexpr ox::Result getSubsheetPath( + ncore::TileSheet const&ts) const noexcept { + // prefer the already present path + if (!subsheetPath.len()) { + return ts.getNameFor(subsheetId); + } + return ox::StringView(subsheetPath); + } + +}; + +oxModelBegin(TileDoc) + oxModelFieldRename(subsheet_id, subsheetId) + oxModelFieldRename(subsheet_path, subsheetPath) + oxModelField(type) + oxModelFieldRename(layer_attachments, layerAttachments) +oxModelEnd() + +struct WorldDoc { + + using TileMapRow = ox::Vector; + using TileMapLayer = ox::Vector; + using TileMap = ox::Vector; + + constexpr static auto TypeName = "net.drinkingtea.nostalgia.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; +} +constexpr void setBottomEdge(uint8_t &layerAttachments, unsigned val) noexcept { + const auto val8 = static_cast(val); + layerAttachments = (layerAttachments & 0b11110011) | static_cast(val8 << 2); +} +constexpr void setLeftEdge(uint8_t &layerAttachments, unsigned val) noexcept { + const auto val8 = static_cast(val); + layerAttachments = (layerAttachments & 0b11001111) | static_cast(val8 << 4); +} +constexpr void setRightEdge(uint8_t &layerAttachments, unsigned val) noexcept { + const auto val8 = static_cast(val); + layerAttachments = (layerAttachments & 0b00111111) | static_cast(val8 << 6); +} + +[[nodiscard]] +constexpr unsigned topEdge(uint8_t layerAttachments) noexcept { + return layerAttachments & 0b11; +} +[[nodiscard]] +constexpr unsigned bottomEdge(uint8_t layerAttachments) noexcept { + return (layerAttachments >> 2) & 0b11; +} +[[nodiscard]] +constexpr unsigned leftEdge(uint8_t layerAttachments) noexcept { + return (layerAttachments >> 4) & 0b11; +} +[[nodiscard]] +constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept { + return (layerAttachments >> 6) & 0b11; +} + + +struct WorldStatic { + + constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.WorldStatic"; + 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); + } + +}; + +oxModelBegin(WorldStatic) + oxModelField(tilesheet) + oxModelField(palettes) + oxModelField(columns) + oxModelField(rows) + oxModelField(tileMapIdx) + oxModelField(tileType) + oxModelField(layerAttachments) +oxModelEnd() + +} diff --git a/src/jasper/modules/world/src/CMakeLists.txt b/src/jasper/modules/world/src/CMakeLists.txt new file mode 100644 index 0000000..73cd9e8 --- /dev/null +++ b/src/jasper/modules/world/src/CMakeLists.txt @@ -0,0 +1,30 @@ + +add_library( + JasperWorld + world.cpp + worldstatic.cpp +) + +target_include_directories( + JasperWorld PUBLIC + ../include +) + +target_link_libraries( + JasperWorld PUBLIC + NostalgiaCore + JasperCore +) + +add_subdirectory(keel) +if(NOSTALGIA_BUILD_STUDIO) + add_subdirectory(studio) +endif() + +install( + TARGETS + JasperWorld + DESTINATION + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/src/jasper/modules/world/src/keel/CMakeLists.txt b/src/jasper/modules/world/src/keel/CMakeLists.txt new file mode 100644 index 0000000..2aa42bd --- /dev/null +++ b/src/jasper/modules/world/src/keel/CMakeLists.txt @@ -0,0 +1,17 @@ +add_library( + JasperWorld-Keel + keelmodule.cpp + typeconv.cpp +) + +target_link_libraries( + JasperWorld-Keel PUBLIC + Keel + JasperWorld +) +install( + TARGETS + JasperWorld-Keel + LIBRARY DESTINATION + ${NOSTALGIA_DIST_MODULE} +) diff --git a/src/jasper/modules/world/src/keel/keelmodule.cpp b/src/jasper/modules/world/src/keel/keelmodule.cpp new file mode 100644 index 0000000..72ce3a0 --- /dev/null +++ b/src/jasper/modules/world/src/keel/keelmodule.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +#include "typeconv.hpp" + +namespace jasper::world { + +class WorldModule: public keel::Module { + private: + WorldDocToWorldStaticConverter m_worldDocToWorldStaticConverter; + + public: + [[nodiscard]] + ox::String id() const noexcept override { + return ox::String("net.drinkingtea.jasper.world"); + } + + [[nodiscard]] + ox::Vector types() const noexcept override { + return { + keel::generateTypeDesc, + keel::generateTypeDesc, + }; + } + + [[nodiscard]] + ox::Vector converters() const noexcept override { + return { + &m_worldDocToWorldStaticConverter, + }; + } + + [[nodiscard]] + ox::Vector packTransforms() const noexcept override { + return { + keel::transformRule, + }; + } + +}; + +static const WorldModule mod; +keel::Module const*keelModule() noexcept { + return &mod; +} + +} diff --git a/src/jasper/modules/world/src/keel/typeconv.cpp b/src/jasper/modules/world/src/keel/typeconv.cpp new file mode 100644 index 0000000..beeaa04 --- /dev/null +++ b/src/jasper/modules/world/src/keel/typeconv.cpp @@ -0,0 +1,70 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#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, + 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, ts->getTileOffset(path)); + dstTile.tileMapIdx = static_cast(mapIdx); + setLayerAttachments(layerIdx, srcTile, dstTile); + ++tileIdx; + } + } + ++layerIdx; + } + return {}; +} + +} diff --git a/src/jasper/modules/world/src/keel/typeconv.hpp b/src/jasper/modules/world/src/keel/typeconv.hpp new file mode 100644 index 0000000..9049ed4 --- /dev/null +++ b/src/jasper/modules/world/src/keel/typeconv.hpp @@ -0,0 +1,17 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +namespace jasper::world { + +class WorldDocToWorldStaticConverter: public keel::Converter { + ox::Error convert(keel::Context&, WorldDoc &src, WorldStatic &dst) const noexcept final; +}; + +} diff --git a/src/jasper/modules/world/src/studio/CMakeLists.txt b/src/jasper/modules/world/src/studio/CMakeLists.txt new file mode 100644 index 0000000..5ab1a07 --- /dev/null +++ b/src/jasper/modules/world/src/studio/CMakeLists.txt @@ -0,0 +1,20 @@ +add_library( + JasperWorld-Studio + studiomodule.cpp +) + +target_link_libraries( + JasperWorld-Studio PUBLIC + JasperWorld + Studio +) + +install( + TARGETS + JasperWorld-Studio + LIBRARY DESTINATION + ${NOSTALGIA_DIST_MODULE} +) + +add_subdirectory(worldobjecteditor) +add_subdirectory(worldeditor) diff --git a/src/jasper/modules/world/src/studio/studiomodule.cpp b/src/jasper/modules/world/src/studio/studiomodule.cpp new file mode 100644 index 0000000..7c7be07 --- /dev/null +++ b/src/jasper/modules/world/src/studio/studiomodule.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +#include "worldobjecteditor/worldobjecteditor-imgui.hpp" +#include "worldeditor/worldeditor-imgui.hpp" + +namespace jasper::world { + +class StudioModule: public studio::Module { + public: + ox::Vector editors(turbine::Context &ctx) const noexcept override { + return { + studio::editorMaker(ctx, FileExt_jwob), + studio::editorMaker(ctx, FileExt_jwld), + }; + } + ox::Vector> itemMakers(turbine::Context&) const noexcept override { + ox::Vector> out; + out.emplace_back(ox::make>("World Object", "WorldObjects", FileExt_jwob, ox::ClawFormat::Organic)); + out.emplace_back(ox::make>("World", "Worlds", FileExt_jwld, ox::ClawFormat::Organic)); + return out; + } +}; + +static StudioModule const mod; +studio::Module const*studioModule() noexcept { + return &mod; +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt new file mode 100644 index 0000000..c9e714c --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources( + JasperWorld-Studio PRIVATE + worldeditor-imgui.cpp + worldeditor.cpp + worldeditorview.cpp +) \ No newline at end of file diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp new file mode 100644 index 0000000..31be91f --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +#include "worldeditor-imgui.hpp" + +namespace jasper::world { + +WorldEditorImGui::WorldEditorImGui(turbine::Context &ctx, ox::StringView path): + Editor(path), + m_ctx(ctx), + m_editor(m_ctx, path), + m_view(m_ctx, m_editor.world()) { + setRequiresConstantRefresh(false); +} + +void WorldEditorImGui::draw(turbine::Context&) noexcept { + auto const paneSize = ImGui::GetContentRegionAvail(); + m_view.draw(ox::Size{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); + auto const srcH = fbHeight / fbWidth; + auto const dstH = paneSize.y / paneSize.x; + float xScale{}, yScale{}; + if (dstH > srcH) { + // crop off width + xScale = srcH / dstH; + yScale = 1; + } else { + auto const srcW = fbWidth / fbHeight; + auto const dstW = (paneSize.x / paneSize.y); + 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)); +} + +void WorldEditorImGui::onActivated() noexcept { + oxLogError(m_view.setupWorld()); +} + +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())); + 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 new file mode 100644 index 0000000..1d36208 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor-imgui.hpp @@ -0,0 +1,35 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include "worldeditor.hpp" +#include "worldeditorview.hpp" + +namespace jasper::world { + +class WorldEditorImGui: public studio::Editor { + + private: + turbine::Context &m_ctx; + WorldEditor m_editor; + WorldEditorView m_view; + + public: + WorldEditorImGui(turbine::Context &ctx, ox::StringView path); + + void draw(turbine::Context&) noexcept final; + + void onActivated() noexcept override; + + protected: + ox::Error saveItem() noexcept final; + +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp new file mode 100644 index 0000000..e3d4616 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.cpp @@ -0,0 +1,20 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "worldeditor.hpp" + +namespace jasper::world { + +WorldEditor::WorldEditor(turbine::Context &ctx, ox::StringView path): + m_ctx(*applicationData(ctx)), + m_world(*readObj(keelCtx(ctx), path).unwrapThrow()) { +} + +ox::Error WorldEditor::saveItem() noexcept { + return m_ctx.project->writeObj(m_itemPath, m_world); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp new file mode 100644 index 0000000..7cbe5a0 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditor.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +#include + +namespace jasper::world { + +class WorldEditor { + + private: + studio::StudioContext &m_ctx; + ox::String m_itemPath; + WorldStatic m_world; + + public: + WorldEditor(turbine::Context &ctx, ox::StringView path); + + [[nodiscard]] + WorldStatic const&world() const noexcept { + return m_world; + } + + protected: + ox::Error saveItem() noexcept; + +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.cpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.cpp new file mode 100644 index 0000000..4b8cfc5 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.cpp @@ -0,0 +1,36 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "worldeditorview.hpp" + +namespace jasper::world { + +WorldEditorView::WorldEditorView(turbine::Context &tctx, WorldStatic const&worldStatic): + m_cctx(ncore::init(tctx, {.glInstallDrawer = false}).unwrapThrow()), + m_worldStatic(worldStatic), + m_world(m_worldStatic) { +} + +ox::Error WorldEditorView::setupWorld() noexcept { + glutils::resizeInitFrameBuffer(m_frameBuffer, ncore::gl::drawSize(m_scale)); + return m_world.setupDisplay(*m_cctx); +} + +void WorldEditorView::draw(ox::Size const&targetSz) noexcept { + auto const scaleSz = targetSz / ncore::gl::drawSize(1); + if (m_scaleSz != scaleSz) [[unlikely]] { + m_scale = ox::max(1, ox::max(scaleSz.width, scaleSz.height)); + glutils::resizeInitFrameBuffer(m_frameBuffer, ncore::gl::drawSize(m_scale)); + } + glutils::FrameBufferBind const frameBufferBind(m_frameBuffer); + ncore::gl::draw(*m_cctx, m_scale); +} + +glutils::FrameBuffer const&WorldEditorView::framebuffer() const noexcept { + return m_frameBuffer; +} + +} diff --git a/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.hpp b/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.hpp new file mode 100644 index 0000000..e9021da --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldeditor/worldeditorview.hpp @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include +#include +#include + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +class WorldEditorView { + + private: + ncore::ContextUPtr m_cctx; + WorldStatic const&m_worldStatic; + World m_world; + glutils::FrameBuffer m_frameBuffer; + int m_scale = 1; + ox::Size m_scaleSz = ncore::gl::drawSize(m_scale); + + public: + WorldEditorView(turbine::Context &ctx, WorldStatic const&worldStatic); + + ox::Error setupWorld() noexcept; + + void draw(ox::Size const&targetSz) noexcept; + + [[nodiscard]] + glutils::FrameBuffer const&framebuffer() const noexcept; + +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt b/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt new file mode 100644 index 0000000..19ce204 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjecteditor/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( + JasperWorld-Studio PRIVATE + worldobjecteditor.cpp + worldobjecteditor-imgui.cpp +) diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp new file mode 100644 index 0000000..afc9633 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "worldobjecteditor-imgui.hpp" + +namespace jasper::world { + +WorldObjectEditorImGui::WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path): + Editor(path), + m_sctx(*applicationData(ctx)), + m_editor(ctx, path) { +} + +void WorldObjectEditorImGui::draw(turbine::Context&) noexcept { + const auto paneSize = ImGui::GetContentRegionAvail(); + constexpr auto attrEditorWidth = 400.f; + ImGui::BeginChild("Preview", ImVec2(paneSize.x - attrEditorWidth, 0)); + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::BeginChild("AttrEditor", ImVec2(attrEditorWidth, 0), true); + drawAttrEditor(); + drawTileSheetSelector(); + ImGui::EndChild(); +} + +void WorldObjectEditorImGui::onActivated() noexcept { +} + +void WorldObjectEditorImGui::drawAttrEditor() noexcept { + static constexpr auto boundDimension = [](int &v) noexcept { + v = ox::clamp(v, 1, 4); + }; + auto &doc = m_editor.doc(); + ImGui::InputInt("Footprint Width", &doc.footprint.width); + ImGui::InputInt("Footprint Height", &doc.footprint.height); + ImGui::InputInt("Visible Width", &doc.visibleSz.width); + ImGui::InputInt("Visible Height", &doc.visibleSz.height); + boundDimension(doc.footprint.width); + boundDimension(doc.footprint.height); + boundDimension(doc.visibleSz.width); + boundDimension(doc.visibleSz.height); +} + +void WorldObjectEditorImGui::drawTileSheetSelector() noexcept { + auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng); + auto const first = m_selectedTilesheetIdx < tilesheetList.size() ? + tilesheetList[m_selectedTilesheetIdx].c_str() : ""; + if (ImGui::BeginCombo("Tile Sheet", first, 0)) { + for (auto i = 0u; i < tilesheetList.size(); ++i) { + auto const selected = (m_selectedTilesheetIdx == i); + if (ImGui::Selectable(tilesheetList[i].c_str(), selected) && + m_selectedTilesheetIdx != i) { + m_selectedTilesheetIdx = i; + //oxLogError(m_model.setPalette(tilesheetList[n])); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } +} + +ox::Error WorldObjectEditorImGui::saveItem() noexcept { + return m_editor.saveItem(); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp new file mode 100644 index 0000000..45e15f3 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor-imgui.hpp @@ -0,0 +1,38 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include + +#include "worldobjecteditor.hpp" + +namespace jasper::world { + +class WorldObjectEditorImGui: public studio::Editor { + + private: + studio::StudioContext &m_sctx; + PrefabEditor m_editor; + uint_t m_selectedTilesheetIdx{}; + + public: + WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path); + + void draw(turbine::Context&) noexcept final; + + void onActivated() noexcept override; + + protected: + void drawAttrEditor() noexcept; + + void drawTileSheetSelector() noexcept; + + ox::Error saveItem() noexcept final; + +}; + +} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp new file mode 100644 index 0000000..d242c29 --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.cpp @@ -0,0 +1,29 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "worldobjecteditor.hpp" + +namespace jasper::world { + +PrefabEditor::PrefabEditor(turbine::Context &ctx, ox::StringView path): + m_ctx(*applicationData(ctx)), + m_itemPath(path), + m_doc(*readObj(keelCtx(ctx), path).unwrapThrow()) { +} + +PrefabDoc const&PrefabEditor::doc() const noexcept { + return m_doc; +} + +PrefabDoc &PrefabEditor::doc() noexcept { + return m_doc; +} + +ox::Error PrefabEditor::saveItem() noexcept { + return m_ctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic); +} + +} diff --git a/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp new file mode 100644 index 0000000..4b23bfc --- /dev/null +++ b/src/jasper/modules/world/src/studio/worldobjecteditor/worldobjecteditor.hpp @@ -0,0 +1,34 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +#include + +namespace jasper::world { + +class PrefabEditor { + + private: + studio::StudioContext &m_ctx; + ox::String m_itemPath; + PrefabDoc m_doc; + + public: + PrefabEditor(turbine::Context &ctx, ox::StringView path); + + [[nodiscard]] + PrefabDoc const&doc() const noexcept; + + [[nodiscard]] + PrefabDoc &doc() noexcept; + + ox::Error saveItem() noexcept; + +}; + +} diff --git a/src/jasper/modules/world/src/world.cpp b/src/jasper/modules/world/src/world.cpp new file mode 100644 index 0000000..b1402f8 --- /dev/null +++ b/src/jasper/modules/world/src/world.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include + +namespace jasper::world { + +namespace ncore = nostalgia::core; + +World::World(WorldStatic const&worldStatic) noexcept: + m_worldStatic(worldStatic) { +} + +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, palette)); + // disable all backgrounds + ncore::setBgStatus(ctx, 0); + for (auto layerNo = 0u; auto const&layer : m_worldStatic.tileMapIdx) { + setupLayer(ctx, layer, layerNo); + ++layerNo; + } + return {}; +} + +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, y, tile8); + ncore::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1); + ncore::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2); + ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3); + x += 2; + if (x >= width * 2) { + x = 0; + y += 2; + } + } +} + +} diff --git a/src/jasper/modules/world/src/worldstatic.cpp b/src/jasper/modules/world/src/worldstatic.cpp new file mode 100644 index 0000000..8183207 --- /dev/null +++ b/src/jasper/modules/world/src/worldstatic.cpp @@ -0,0 +1,11 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +namespace jasper::world { + + + +} diff --git a/src/jasper/player/CMakeLists.txt b/src/jasper/player/CMakeLists.txt new file mode 100644 index 0000000..23f9ffd --- /dev/null +++ b/src/jasper/player/CMakeLists.txt @@ -0,0 +1,34 @@ +add_executable( + jasper WIN32 MACOSX_BUNDLE + app.cpp +) + +if(${BUILDCORE_TARGET} STREQUAL "gba") + set(LOAD_KEEL_MODS FALSE) + set_target_properties(jasper + PROPERTIES + LINK_FLAGS ${LINKER_FLAGS} + COMPILER_FLAGS "-mthumb -mthumb-interwork" + ) + OBJCOPY_FILE(jasper) +else() + set(LOAD_KEEL_MODS TRUE) +endif() + +target_compile_definitions( + jasper PRIVATE + OLYMPIC_LOAD_KEEL_MODULES=$ +) + +# enable LTO +if(NOT WIN32) + set_property(TARGET jasper PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +target_link_libraries( + jasper PUBLIC + JasperProfile + JasperKeelModules + OlympicApplib + OxLogConn +) diff --git a/src/jasper/player/app.cpp b/src/jasper/player/app.cpp new file mode 100644 index 0000000..57b31af --- /dev/null +++ b/src/jasper/player/app.cpp @@ -0,0 +1,38 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include +#include + +#include +#include + +#include + +namespace jasper { +ox::Error run(turbine::Context &ctx, ox::StringView, ox::SpanView) noexcept { + oxOut("Jasper Player\n"); + oxReturnError(turbine::run(ctx)); + oxOut("Exiting...\n"); + return {}; +} +} + +namespace olympic { + +ox::Error run( + [[maybe_unused]] ox::StringView project, + [[maybe_unused]] ox::StringView appName, + [[maybe_unused]] ox::StringView projectDataDir, + [[maybe_unused]] int argc, + [[maybe_unused]] char const**argv) noexcept { + auto const path = ox::StringView(argv[1]); + oxRequireM(fs, keel::loadRomFs(path)); + oxRequireM(tctx, turbine::init(std::move(fs), project)); + constexpr ox::FileAddress BootfileAddr = ox::StringLiteral("/Bootfile"); + oxRequire(bootfile, keel::readObj(keelCtx(*tctx), BootfileAddr)); + return jasper::run(*tctx, bootfile->app, bootfile->args); +} + +} diff --git a/src/jasper/tools/CMakeLists.txt b/src/jasper/tools/CMakeLists.txt new file mode 100644 index 0000000..306f5a9 --- /dev/null +++ b/src/jasper/tools/CMakeLists.txt @@ -0,0 +1,57 @@ +add_executable(JasperStudio WIN32 MACOSX_BUNDLE) + +target_link_libraries( + JasperStudio + OlympicApplib + JasperStudioModules + JasperKeelModules + JasperProfile +) + +install( + TARGETS + JasperStudio + RUNTIME DESTINATION + ${JASPER_DIST_BIN} + BUNDLE DESTINATION . +) + +install( + FILES + js.icns + DESTINATION + ${JASPER_DIST_RESOURCES}/icons +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32) + # enable LTO + set_property(TARGET JasperStudio PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +if(APPLE) + set_target_properties(JasperStudio PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) +endif() + +# Pack ######################################################################## + +add_executable(jasper-pack) + +target_link_libraries( + jasper-pack + OlympicApplib + KeelPack-AppLib + JasperKeelModules + JasperProfile +) + +if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT WIN32) + # enable LTO + set_property(TARGET jasper-pack PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) +endif() + +install( + TARGETS + jasper-pack + RUNTIME DESTINATION + bin +) diff --git a/src/jasper/tools/Info.plist b/src/jasper/tools/Info.plist new file mode 100644 index 0000000..701c4c1 --- /dev/null +++ b/src/jasper/tools/Info.plist @@ -0,0 +1,35 @@ + + + + + CFBundleExecutable + JasperStudio + + CFBundleGetInfoString + Jasper Studio + + CFBundleIconFile + icons/js.icns + + CFBundleIdentifier + net.drinkingtea.jasper.studio + + CFBundlePackageType + APPL + + CFBundleVersion + d2023.12.0 + + LSMinimumSystemVersion + 12.0.0 + + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + True + + NSHumanReadableCopyright + Copyright (c) 2016-2023 Gary Talent <gary@drinkingtea.net> + + diff --git a/src/jasper/tools/js.icns b/src/jasper/tools/js.icns new file mode 100644 index 0000000000000000000000000000000000000000..1318d1a6349e698dd8cfee3cc72a7c84763822be GIT binary patch literal 117814 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

hU=tv1q+F)*deyBTZ#Fm;TYc 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 MV6?tF|2&ue13Eam`2YX_ literal 0 HcmV?d00001 diff --git a/src/jasper/tools/js_icon480.png b/src/jasper/tools/js_icon480.png new file mode 100644 index 0000000000000000000000000000000000000000..1b087ea1f7b31809fc4f39de3c7f5014ec61383f GIT binary patch literal 327 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|=Ow2$LhFxK7K$0iGC&ZP3LF2}a8w(s9Hf-3i zJJWR;Q2d*xi(^OyPw%va%0zpN$=~q8*6rYPEyIdbxTfy zhpqWwg24~1&u=-vED4@}>t`+pi9w~n)Fc(4dKg;5nq7VJ`vHiG>;r-C>Vm$oLX