Compare commits
444 Commits
1dfde00317
...
editor
| Author | SHA1 | Date | |
|---|---|---|---|
| 888918face | |||
| 0885da8d44 | |||
| 8af961c6d3 | |||
| ef5febdde3 | |||
| 6d7fbd3926 | |||
| 2680d373d8 | |||
| 2b2ddb3cf2 | |||
| 85ff95296b | |||
| 314a2de41a | |||
| 26fafab47a | |||
| e56ff20e2d | |||
| 55d44f229d | |||
| 1c5e50cc4d | |||
| ea898da6c2 | |||
| dbb7e9f53c | |||
| cbb68a399d | |||
| 0e794f28b1 | |||
| 87d2d9123e | |||
| 6823a4ddb5 | |||
| 20a7c70081 | |||
| 9caa33b3bb | |||
| 2d7e61460a | |||
| a4b7fb3f44 | |||
| 70056cf4ca | |||
| 5f4ab71ade | |||
| f3adb3257b | |||
| 438edda7fd | |||
| d5b0441e6f | |||
| 9ba0ceb000 | |||
| 9474a68995 | |||
| 09c35f0aa6 | |||
| a2113442cb | |||
| d91808487f | |||
| 933949cc19 | |||
| 407620387d | |||
| 98947dea26 | |||
| ebff7af9b5 | |||
| b23c4b83ae | |||
| c0cff40628 | |||
| 97513e354c | |||
| c277ae7aff | |||
| e1835e6282 | |||
| 5ac21db997 | |||
| ca0e9fc3b2 | |||
| 66ebcb1608 | |||
| ff92a78dda | |||
| 7356286fe0 | |||
| 54e8e68f86 | |||
| d21cd7f78b | |||
| 1d7516982a | |||
| c77a11442c | |||
| 5bd43a4643 | |||
| 9b87dfa1a9 | |||
| 2e3173ea40 | |||
| 68eac7cf83 | |||
| 6709505630 | |||
| af6e962a5d | |||
| 18e6bdabaa | |||
| 9743942eae | |||
| 23062137a8 | |||
| 46f7fb5ccd | |||
| 9c90c49a6b | |||
| 4517b63557 | |||
| 58c239f4b4 | |||
| cc8845ba3e | |||
| 6b69ce2901 | |||
| 55300ed21c | |||
| 7346dd4339 | |||
| 2caf3b92ce | |||
| af2cd72a1f | |||
| 3d455ec1f8 | |||
| 15982d7735 | |||
| 5ae3542bd9 | |||
| b1b02ae24b | |||
| f0964e2c92 | |||
| e9661d2998 | |||
| ea6468f2a9 | |||
| a2b38d3b83 | |||
| d67ef02941 | |||
| 549ebe25d8 | |||
| 9a98348582 | |||
| c5f5b025a6 | |||
| 23eaffa3a7 | |||
| c161809248 | |||
| 4bf26dc818 | |||
| 5dd22fad6c | |||
| 2c3fdf7803 | |||
| e984b9f5d7 | |||
| a3c2e37b17 | |||
| edf1b5a0a3 | |||
| 8efdf59ebd | |||
| 5c4537b2fa | |||
| 71e6079054 | |||
| dd048d9b0d | |||
| 93074d653e | |||
| 9139c4350a | |||
| 38ce768168 | |||
| 82b3dc576c | |||
| 2167889f48 | |||
| e9b02c2acf | |||
| 9ee446431b | |||
| df106e3988 | |||
| d0a057e0ee | |||
| 8b49902bf6 | |||
| 71c1e56564 | |||
| 1b12e67de2 | |||
| 291bb4bb81 | |||
| 342ddb19f8 | |||
| 9c9d2d548e | |||
| d7a0bb4509 | |||
| 2b1a3323a8 | |||
| 99d030003c | |||
| 92a753560b | |||
| af9904c892 | |||
| e5e8c49f6c | |||
| b37e5f45ca | |||
| e1f08b07aa | |||
| 073ee8dca9 | |||
| a26e51cf46 | |||
| dfed732825 | |||
| 87aa70c6d2 | |||
| aa2979ffe7 | |||
| 236e16aa6d | |||
| 184bb970e6 | |||
| bd54469891 | |||
| 2f5dccc3ef | |||
| 592edb90a0 | |||
| 3db7e6b1b9 | |||
| 13c4df0d85 | |||
| ef25fb09da | |||
| 03cf4a9efe | |||
| 53dd36efdd | |||
| ad9e841a42 | |||
| 14f3f464c7 | |||
| cbe51cc8d0 | |||
| efaa3f6eea | |||
| 52cce9a3b0 | |||
| b7b390311e | |||
| c1eeddd14b | |||
| 1a7a55dfc3 | |||
| fe5927ea6a | |||
| 119c794ad7 | |||
| e2076b2c1c | |||
| 5208c5148e | |||
| d80660b097 | |||
| b916d0278b | |||
| d51e13e620 | |||
| 40ad4326ef | |||
| b8afc1684a | |||
| 8348b31ac8 | |||
| 411f2dbcce | |||
| ee89c08160 | |||
| 357607a89f | |||
| 8d6dc2df44 | |||
| 5207582ab3 | |||
| 71768e6154 | |||
| ecbe235523 | |||
| afef079d1e | |||
| df17696c69 | |||
| 065bf0908f | |||
| 5b6755e9cf | |||
| b08482acf1 | |||
| 80c9c1d389 | |||
| bb7db57bda | |||
| 6a83ac767c | |||
| 7e47ef9d74 | |||
| e7ec603526 | |||
| 2d8ae09bd8 | |||
| b2affbc0a7 | |||
| d50bc61ada | |||
| ec6b032b45 | |||
| bc72f48496 | |||
| dcf06fbd36 | |||
| 96311d72c2 | |||
| 07938cccc7 | |||
| 097c8c00f9 | |||
| aa5b41fe31 | |||
| 0d56859d94 | |||
| 1af5f238e4 | |||
| dd697d5650 | |||
| 5cf299a1c7 | |||
| 67bf825cc9 | |||
| 56e1696cd4 | |||
| d955fb6430 | |||
| dd910a31aa | |||
| 708c4d0ec3 | |||
| ad13d6c6a1 | |||
| 1c32158142 | |||
| 5cea284906 | |||
| 13dba8b604 | |||
| 22398ddcef | |||
| 94e2cc6210 | |||
| da3513f63d | |||
| 2c83e4ba9f | |||
| c862071126 | |||
| fed819e9b2 | |||
| c6f4518684 | |||
| 053778a502 | |||
| 78e1ae885a | |||
| 982d28a3e0 | |||
| c2cad858a5 | |||
| 794e0574ad | |||
| c190271565 | |||
| ae8a869f64 | |||
| 69d64eb8e4 | |||
| b2f2df650a | |||
| 6af570fab2 | |||
| 9ed902017c | |||
| 32b41c98e1 | |||
| 6bdb4ae30d | |||
| 25dc97e3cc | |||
| cc85983736 | |||
| 6e78ee188d | |||
| 9b73f1717f | |||
| c7b9a53535 | |||
| 2b9be6675c | |||
| fb93453482 | |||
| 81b08b2eba | |||
| d1b03c4cb3 | |||
| 9544d15a18 | |||
| 0392dd0e7f | |||
| 9c25fde548 | |||
| 2c9d0c6cff | |||
| 9897dbe031 | |||
| e78f117cfd | |||
| 07afc3813a | |||
| d788de8637 | |||
| d749ac8a91 | |||
| f71c271c97 | |||
| e1d7b7308f | |||
| 26a71a823a | |||
| 5e39097faa | |||
| 0df7845f2c | |||
| af5bf987c8 | |||
| 024ace1078 | |||
| 8d00fe9d16 | |||
| ab422b14dd | |||
| 95c0690216 | |||
| 6cb80e9e23 | |||
| 83b799caa8 | |||
| a793ac2ff7 | |||
| aec937b04b | |||
| 8ee46fd204 | |||
| 726233e55f | |||
| 7940f4c487 | |||
| b16dbaceec | |||
| f39b2060a8 | |||
| aed202ebf9 | |||
| a495179e5f | |||
| 4e1b404820 | |||
| 8c74ee31e0 | |||
| 77d3c54ebb | |||
| b5de39926b | |||
| 3a8dafbb91 | |||
| 6b22f547fe | |||
| de78be3e25 | |||
| 9f507be7bc | |||
| 9aaf271996 | |||
| b01c0d37b0 | |||
| 538079880d | |||
| fe0529d021 | |||
| d068f0f2c3 | |||
| f9a64b8d54 | |||
| 01cbfaae95 | |||
| f9006a90d5 | |||
| 7daeaee6b5 | |||
| 03218ce20f | |||
| 6f33522c1c | |||
| 3697cc3eef | |||
| 51a1077fda | |||
| 8740c2b165 | |||
| 6ed2bdd4c5 | |||
| c32df89490 | |||
| bd5a67676b | |||
| 903dab49e3 | |||
| 1668c4b0d2 | |||
| 2179a27bf5 | |||
| 6e7a0cba76 | |||
| 69b37b30bc | |||
| ae941a0fdb | |||
| 1b741a81e5 | |||
| edf321515b | |||
| c874e6c197 | |||
| 9a59c22288 | |||
| 750e8840f0 | |||
| cf59989167 | |||
| 7c194ab4b4 | |||
| be422d0a1e | |||
| 68b63d3007 | |||
| 8525138594 | |||
| 7b9f8b190e | |||
| 67f62daa9f | |||
| 0ec701f30b | |||
| c53439066e | |||
| 7278bd0c6f | |||
| b842e5821a | |||
| f7d4cce485 | |||
| 4f502b707f | |||
| 09f182228f | |||
| 69ce48a8b9 | |||
| 5c2788efe4 | |||
| 768323b5b6 | |||
| e203f225e2 | |||
| ab1e2476a0 | |||
| 312f32e786 | |||
| 397466f0a8 | |||
| 1a773cb8ba | |||
| 8441c325fa | |||
| cd4a1afbba | |||
| ae75a932bf | |||
| 0fa2beede4 | |||
| 8e5d5ca1d7 | |||
| 2a68414eec | |||
| 3f1c8e28e9 | |||
| 348531352e | |||
| 6770cc422a | |||
| 7c157e22c7 | |||
| 0cfc6d0503 | |||
| f7fbd16e57 | |||
| 542aeadf0f | |||
| 84593867dc | |||
| 9f23533069 | |||
| 4f8f6a47cb | |||
| 7d7a3f30e6 | |||
| d39ed1ea5a | |||
| 5c8b314689 | |||
| 9953d7d388 | |||
| 5adf8a0773 | |||
| 26bfb912f1 | |||
| c07d0b32a9 | |||
| 562da971e9 | |||
| 3eb24da475 | |||
| 8977d50992 | |||
| 13365dd390 | |||
| aee06f51f0 | |||
| d6c497731f | |||
| f23e26d9da | |||
| ec324e02f2 | |||
| e2ce809762 | |||
| d054cf9e36 | |||
| 943e775364 | |||
| b9ec6523d6 | |||
| 5206d47b43 | |||
| aaa8622956 | |||
| 587d716aae | |||
| 5a8710cc76 | |||
| 307f3a9dec | |||
| eff5fc3d9a | |||
| 5a3004f1d1 | |||
| db589b7d91 | |||
| 0a83175b66 | |||
| bc4776f096 | |||
| ab534bb998 | |||
| cf2aacd75b | |||
| 9f88374627 | |||
| b7d898b505 | |||
| 12c1fb6000 | |||
| 1ce1fdff8d | |||
| 7c11a7e5bc | |||
| 7d46b98310 | |||
| 68c4834a62 | |||
| c9608ad7a7 | |||
| 6ea4132ff9 | |||
| be79356f42 | |||
| d4a2e059d7 | |||
| f3d985ecbc | |||
| bcba693afb | |||
| b4fb7bf99f | |||
| 3ef6205ea3 | |||
| 3feb43fdad | |||
| d74226dab1 | |||
| 5c3db5d991 | |||
| bcb8bea0fe | |||
| 0c0650a2c3 | |||
| 2c0fd84c72 | |||
| 81cd03e0c3 | |||
| 349e6e7c94 | |||
| c4c43b23ad | |||
| 7622f81309 | |||
| c31bcf7f6a | |||
| fef31b9102 | |||
| 20cf016b06 | |||
| 67604eca8d | |||
| 46f820690d | |||
| e36256abe3 | |||
| b00ca3d48c | |||
| cf2e6bf382 | |||
| fc52afdb00 | |||
| 85434b4edb | |||
| f3a6c8df71 | |||
| 6e5c5f61db | |||
| 12c31ba9d1 | |||
| ea50d893d4 | |||
| bacd0e6e39 | |||
| c0cd4ead04 | |||
| 4b04fc65ad | |||
| 83243ba32f | |||
| a734ecaa10 | |||
| 22e2f703db | |||
| 28174b8dc8 | |||
| 061352bcff | |||
| 2f40724258 | |||
| a45a2a5bd7 | |||
| 08221af3f8 | |||
| f799690d3c | |||
| 07ab2b4b02 | |||
| cafeda4368 | |||
| 517b39649c | |||
| 067b0d2e9f | |||
| 9b98181d28 | |||
| 46a94ecacd | |||
| 964a9f64f2 | |||
| b4d94c2cbe | |||
| 268e9ffefd | |||
| c8f8170ec2 | |||
| 8b20f0bf31 | |||
| fe9af039fc | |||
| 6fad5bef4a | |||
| 16a0403fd4 | |||
| e32d1f0900 | |||
| 3f37b7cdb5 | |||
| 059ccf41b6 | |||
| 1af2b8f47b | |||
| 87f18d0e13 | |||
| 8de12da1ec | |||
| 71080682cc | |||
| f915a4208b | |||
| 14c41d33a7 | |||
| 3e61d6f84d | |||
| 4541d5219b | |||
| 3ce1566a2e | |||
| 368729f0f3 | |||
| 127392a1ae | |||
| af1329710d | |||
| 30232d1275 | |||
| 31fa4948d5 | |||
| 6c11096fd2 | |||
| 7a90d2d38f | |||
| a543bc7c00 | |||
| 8af2f044ed | |||
| 947f21cac7 | |||
| 479aad2f06 | |||
| 329925ea54 | |||
| c8a3ebfcec |
117
.github/workflows/build.yml
vendored
Normal file
117
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
name: Build Dusk
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Run tests in Docker
|
||||
run: ./scripts/test-linux-docker.sh
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Build Linux
|
||||
run: ./scripts/build-linux-docker.sh
|
||||
- name: Upload Linux binary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dusk-linux
|
||||
path: build-linux/Dusk
|
||||
if-no-files-found: error
|
||||
|
||||
build-psp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Build psp
|
||||
run: ./scripts/build-psp-docker.sh
|
||||
- name: Move EBOOT.PBP to Dusk subfolder
|
||||
run: |
|
||||
mkdir -p ./git-artifcats/Dusk/PSP/GAME/Dusk
|
||||
cp build-psp/EBOOT.PBP ./git-artifcats/Dusk/PSP/GAME/Dusk/EBOOT.PBP
|
||||
- name: Upload psp binary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dusk-psp
|
||||
path: ./git-artifcats/Dusk
|
||||
if-no-files-found: error
|
||||
|
||||
build-knulli:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Build knulli
|
||||
run: ./scripts/build-knulli-docker.sh
|
||||
- name: Move output to Dusk subfolder
|
||||
run: |
|
||||
mkdir -p ./git-artifcats/Dusk
|
||||
cp -r build-knulli/dusk ./git-artifcats/Dusk
|
||||
- name: Upload knulli binary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dusk-knulli
|
||||
path: ./git-artifcats/Dusk
|
||||
if-no-files-found: error
|
||||
|
||||
build-gamecube:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Build GameCube
|
||||
run: ./scripts/build-gamecube-docker.sh
|
||||
- name: Copy output files.
|
||||
run: |
|
||||
mkdir -p ./git-artifcats/Dusk
|
||||
cp build-gamecube/Dusk.dol ./git-artifcats/Dusk/Dusk.dol
|
||||
cp build-gamecube/dusk.dsk ./git-artifcats/Dusk/dusk.dsk
|
||||
- name: Upload GameCube binary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dusk-gamecube
|
||||
path: ./git-artifcats/Dusk
|
||||
if-no-files-found: error
|
||||
|
||||
build-wii:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Docker
|
||||
uses: docker/setup-docker-action@v5
|
||||
- name: Build Wii
|
||||
run: ./scripts/build-wii-docker.sh
|
||||
- name: Copy output files.
|
||||
run: |
|
||||
mkdir -p ./git-artifcats/Dusk/apps/Dusk
|
||||
cp build-wii/Dusk.dol ./git-artifcats/Dusk/apps/Dusk/boot.dol
|
||||
cp build-wii/dusk.dsk ./git-artifcats/Dusk/apps/Dusk/dusk.dsk
|
||||
cp docker/dolphin/meta.xml ./git-artifcats/Dusk/apps/Dusk/meta.xml
|
||||
- name: Upload Wii binary
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dusk-wii
|
||||
path: ./git-artifcats/Dusk
|
||||
if-no-files-found: error
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -95,3 +95,13 @@ assets/borrowed
|
||||
# /archive
|
||||
|
||||
__pycache__
|
||||
|
||||
package-lock.json
|
||||
yarn-error.log
|
||||
yarn.lock
|
||||
|
||||
.editor
|
||||
.venv
|
||||
|
||||
/build2
|
||||
/build*
|
||||
126
CMakeLists.txt
126
CMakeLists.txt
@@ -4,15 +4,13 @@
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Setup
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
|
||||
|
||||
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
||||
set(DUSK_TARGET_SYSTEM "linux")
|
||||
# set(DUSK_TARGET_SYSTEM "psp")
|
||||
endif()
|
||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||
|
||||
# Prep cache
|
||||
set(DUSK_CACHE_TARGET "dusk-target")
|
||||
@@ -21,76 +19,96 @@ set(DUSK_CACHE_TARGET "dusk-target")
|
||||
set(DUSK_ROOT_DIR "${CMAKE_SOURCE_DIR}")
|
||||
set(DUSK_BUILD_DIR "${CMAKE_BINARY_DIR}")
|
||||
set(DUSK_SOURCES_DIR "${DUSK_ROOT_DIR}/src")
|
||||
set(DUSK_TEST_DIR "${DUSK_ROOT_DIR}/test")
|
||||
set(DUSK_TEMP_DIR "${DUSK_BUILD_DIR}/temp")
|
||||
set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools")
|
||||
set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data")
|
||||
set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets")
|
||||
set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated")
|
||||
set(DUSK_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_ASSETS "" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_LIBRARY_TARGET_NAME "DuskCore" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_BINARY_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
set(DUSK_ASSETS_ZIP "${DUSK_BUILD_DIR}/dusk.dsk" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
|
||||
# Toolchain
|
||||
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
||||
set(DUSK_TARGET_SYSTEM "linux")
|
||||
endif()
|
||||
|
||||
# Create directories
|
||||
file(MAKE_DIRECTORY ${DUSK_GENERATED_HEADERS_DIR})
|
||||
file(MAKE_DIRECTORY ${DUSK_ASSETS_BUILD_DIR})
|
||||
file(MAKE_DIRECTORY ${DUSK_TEMP_DIR})
|
||||
file(MAKE_DIRECTORY ${DUSK_BUILT_ASSETS_DIR})
|
||||
|
||||
# Compilers
|
||||
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
||||
find_package(pspsdk REQUIRED)
|
||||
endif()
|
||||
# Required build packages
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
|
||||
# Init Project
|
||||
project(${DUSK_TARGET_NAME}
|
||||
# Init Project.
|
||||
project(${DUSK_LIBRARY_TARGET_NAME}
|
||||
VERSION 1.0.0
|
||||
LANGUAGES C
|
||||
)
|
||||
|
||||
# Executable
|
||||
add_executable(${DUSK_TARGET_NAME})
|
||||
# Either, create library and binary separately (used for tests), or make them
|
||||
# one in the same so all code is in the binary only.
|
||||
# Binary Executable
|
||||
add_executable(${DUSK_BINARY_TARGET_NAME} ${DUSK_SOURCES_DIR}/dusk/null.c)
|
||||
|
||||
if(DUSK_BUILD_TESTS)
|
||||
# MainLibrary
|
||||
add_library(${DUSK_LIBRARY_TARGET_NAME} STATIC)
|
||||
|
||||
# Link library to binary
|
||||
target_link_libraries(${DUSK_BINARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
${DUSK_LIBRARY_TARGET_NAME}
|
||||
)
|
||||
else()
|
||||
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_BINARY_TARGET_NAME}" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||
endif()
|
||||
|
||||
# Definitions
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
DUSK_TARGET_SYSTEM="${DUSK_TARGET_SYSTEM}"
|
||||
)
|
||||
|
||||
# Toolchains
|
||||
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
|
||||
|
||||
# Add tools
|
||||
add_subdirectory(tools)
|
||||
|
||||
# Add libraries
|
||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
target_link_libraries(${DUSK_TARGET_NAME} PRIVATE
|
||||
SDL2::SDL2
|
||||
OpenGL::GL
|
||||
GL
|
||||
)
|
||||
|
||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
||||
find_package(SDL2 REQUIRED)
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
# pspsdk
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
target_include_directories(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Add code
|
||||
add_subdirectory(src)
|
||||
|
||||
# Include generated headers
|
||||
target_include_directories(${DUSK_TARGET_NAME} PUBLIC
|
||||
# Include generated headers from tools.
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
${DUSK_GENERATED_HEADERS_DIR}
|
||||
)
|
||||
|
||||
# Postbuild, create PBP file for PSP.
|
||||
if(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
||||
create_pbp_file(
|
||||
TARGET "${DUSK_TARGET_NAME}"
|
||||
ICON_PATH NULL
|
||||
BACKGROUND_PATH NULL
|
||||
PREVIEW_PATH NULL
|
||||
TITLE "${DUSK_TARGET_NAME}"
|
||||
VERSION 01.00
|
||||
# Add main code
|
||||
add_subdirectory(${DUSK_SOURCES_DIR})
|
||||
|
||||
# Include generated headers
|
||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
${DUSK_GENERATED_HEADERS_DIR}
|
||||
)
|
||||
|
||||
# Handle tests
|
||||
if(DUSK_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
# Build assets
|
||||
file(GLOB_RECURSE DUSK_ASSET_FILES CONFIGURE_DEPENDS "${DUSK_ASSETS_DIR}/*")
|
||||
add_custom_command(
|
||||
OUTPUT "${DUSK_ASSETS_ZIP}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${DUSK_ASSETS_DIR}"
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -f "${DUSK_ASSETS_ZIP}"
|
||||
COMMAND ${CMAKE_COMMAND} -E tar "cf" "${DUSK_ASSETS_ZIP}" --format=zip -- .
|
||||
WORKING_DIRECTORY "${DUSK_ASSETS_DIR}"
|
||||
DEPENDS ${DUSK_ASSET_FILES}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(DUSK_ASSETS_BUILT DEPENDS "${DUSK_ASSETS_ZIP}")
|
||||
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} DUSK_ASSETS_BUILT)
|
||||
|
||||
23
README.md
Normal file
23
README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Dusk
|
||||
RPG Game Project, small and able to run on a PSP.
|
||||
|
||||
# Building
|
||||
Each build target has different requirements. You can take a look at the git
|
||||
workflow to see how the builds are done for each target. In addition, for
|
||||
accessing the editor and building the game on your host system, install the
|
||||
following packages, depending on your system;
|
||||
|
||||
Fedora;
|
||||
```
|
||||
sudo dnf install git make gcc cmake python python-polib python3-pillow python3-dotenv python3-numpy python-qt5 python3-pyopengl SDL2-devel zlib-devel libzip-devel bzip2-devel openssl-devel lzma-sdk-devel xz xz-devel lua-devel
|
||||
```
|
||||
|
||||
Ubuntu;
|
||||
```
|
||||
sudo apt-get install git build-essential gcc python python-polib python3-pillow python3-dotenv python3-numpy python3-pyqt5 python3-opengl
|
||||
```
|
||||
|
||||
Arch Linux;
|
||||
```
|
||||
sudo pacman -S git base-devel gcc python python-polib python-pillow python-dotenv python-numpy python-pyqt5 python-opengl
|
||||
```
|
||||
12
archive/asset/args.py
Normal file
12
archive/asset/args.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import sys, os
|
||||
import argparse
|
||||
|
||||
# Check if the script is run with the correct arguments
|
||||
parser = argparse.ArgumentParser(description="Generate chunk header files")
|
||||
parser.add_argument('--assets', required=True, help='Dir to output built assets')
|
||||
parser.add_argument('--headers-dir', required=True, help='Directory to output individual asset headers (required for header build)')
|
||||
parser.add_argument('--output-headers', help='Output header file for built assets (required for header build)')
|
||||
parser.add_argument('--output-assets', required=True, help='Output directory for built assets')
|
||||
parser.add_argument('--output-file', required=True, help='Output file for built assets (required for wad build)')
|
||||
parser.add_argument('--input', required=True, help='Input assets to process', nargs='+')
|
||||
args = parser.parse_args()
|
||||
66
archive/asset/bundle/__main__.py
Normal file
66
archive/asset/bundle/__main__.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import sys, os
|
||||
from tools.asset.args import args
|
||||
from tools.asset.process.asset import processAsset
|
||||
from tools.asset.process.palette import processPaletteList
|
||||
from tools.asset.process.tileset import processTilesetList
|
||||
from tools.asset.process.language import processLanguageList
|
||||
from tools.asset.path import getBuiltAssetsRelativePath
|
||||
import zipfile
|
||||
|
||||
# Parse input file args.
|
||||
inputAssets = []
|
||||
for inputArg in args.input:
|
||||
files = inputArg.split('$')
|
||||
for file in files:
|
||||
if str(file).strip() == '':
|
||||
continue
|
||||
|
||||
pieces = file.split('#')
|
||||
|
||||
if len(pieces) < 2:
|
||||
print(f"Error: Invalid input asset format '{file}'. Expected format: type#path[#option1%option2...]")
|
||||
sys.exit(1)
|
||||
|
||||
options = {}
|
||||
if len(pieces) > 2:
|
||||
optionParts = pieces[2].split('%')
|
||||
for part in optionParts:
|
||||
partSplit = part.split('=')
|
||||
|
||||
if len(partSplit) < 1:
|
||||
continue
|
||||
if len(partSplit) == 2:
|
||||
options[partSplit[0]] = partSplit[1]
|
||||
else:
|
||||
options[partSplit[0]] = True
|
||||
|
||||
inputAssets.append({
|
||||
'type': pieces[0],
|
||||
'path': pieces[1],
|
||||
'options': options
|
||||
})
|
||||
|
||||
if not inputAssets:
|
||||
print("Error: No input assets provided.")
|
||||
sys.exit(1)
|
||||
|
||||
# Process each asset.
|
||||
files = []
|
||||
for asset in inputAssets:
|
||||
asset = processAsset(asset)
|
||||
files.extend(asset['files'])
|
||||
|
||||
# Generate additional files
|
||||
files.extend(processLanguageList()['files'])
|
||||
|
||||
# Take assets and add to a zip archive.
|
||||
outputFileName = args.output_file
|
||||
print(f"Creating output file: {outputFileName}")
|
||||
with zipfile.ZipFile(outputFileName, 'w') as zipf:
|
||||
for file in files:
|
||||
relativeOutputPath = getBuiltAssetsRelativePath(file)
|
||||
zipf.write(file, arcname=relativeOutputPath)
|
||||
|
||||
# Generate additional headers.
|
||||
processPaletteList()
|
||||
processTilesetList()
|
||||
12
archive/asset/cache.py
Normal file
12
archive/asset/cache.py
Normal file
@@ -0,0 +1,12 @@
|
||||
processedAssets = {}
|
||||
|
||||
def assetGetCache(assetPath):
|
||||
if assetPath in processedAssets:
|
||||
return processedAssets[assetPath]
|
||||
return None
|
||||
|
||||
def assetCache(assetPath, processedData):
|
||||
if assetPath in processedAssets:
|
||||
return processedAssets[assetPath]
|
||||
processedAssets[assetPath] = processedData
|
||||
return processedData
|
||||
10
archive/asset/path.py
Normal file
10
archive/asset/path.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
from tools.asset.args import args
|
||||
|
||||
def getAssetRelativePath(fullPath):
|
||||
# Get the relative path to the asset
|
||||
return os.path.relpath(fullPath, start=args.assets).replace('\\', '/')
|
||||
|
||||
def getBuiltAssetsRelativePath(fullPath):
|
||||
# Get the relative path to the built asset
|
||||
return os.path.relpath(fullPath, start=args.output_assets).replace('\\', '/')
|
||||
33
archive/asset/process/asset.py
Normal file
33
archive/asset/process/asset.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import sys
|
||||
# from processtileset import processTileset
|
||||
from tools.asset.process.image import processImage
|
||||
from tools.asset.process.palette import processPalette
|
||||
from tools.asset.process.tileset import processTileset
|
||||
from tools.asset.process.map import processMap
|
||||
from tools.asset.process.language import processLanguage
|
||||
from tools.asset.process.script import processScript
|
||||
|
||||
processedAssets = []
|
||||
|
||||
def processAsset(asset):
|
||||
if asset['path'] in processedAssets:
|
||||
return
|
||||
processedAssets.append(asset['path'])
|
||||
|
||||
# Handle tiled tilesets
|
||||
t = asset['type'].lower()
|
||||
if t == 'palette':
|
||||
return processPalette(asset)
|
||||
elif t == 'image':
|
||||
return processImage(asset)
|
||||
elif t == 'tileset':
|
||||
return processTileset(asset)
|
||||
elif t == 'map':
|
||||
return processMap(asset)
|
||||
elif t == 'language':
|
||||
return processLanguage(asset)
|
||||
elif t == 'script':
|
||||
return processScript(asset)
|
||||
else:
|
||||
print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'")
|
||||
sys.exit(1)
|
||||
134
archive/asset/process/image.py
Normal file
134
archive/asset/process/image.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import os
|
||||
import sys
|
||||
from PIL import Image
|
||||
from tools.asset.process.palette import extractPaletteFromImage, palettes
|
||||
from tools.asset.args import args
|
||||
from tools.asset.path import getAssetRelativePath
|
||||
from tools.asset.cache import assetGetCache, assetCache
|
||||
|
||||
images = []
|
||||
|
||||
def processImage(asset):
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
type = None
|
||||
if 'type' in asset['options']:
|
||||
type = asset['options'].get('type', 'PALETTIZED').upper()
|
||||
|
||||
if type == 'PALETTIZED' or type is None:
|
||||
return assetCache(asset['path'], processPalettizedImage(asset))
|
||||
elif type == 'ALPHA':
|
||||
return assetCache(asset['path'], processAlphaImage(asset))
|
||||
else:
|
||||
print(f"Error: Unknown image type {type} for asset {asset['path']}")
|
||||
sys.exit(1)
|
||||
|
||||
def processPalettizedImage(asset):
|
||||
assetPath = asset['path']
|
||||
cache = assetGetCache(assetPath)
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
image = Image.open(assetPath)
|
||||
imagePalette = extractPaletteFromImage(image)
|
||||
|
||||
# Find palette that contains every color
|
||||
palette = None
|
||||
for p in palettes:
|
||||
hasAllColors = True
|
||||
for color in imagePalette:
|
||||
for palColor in p['pixels']:
|
||||
if color[0] == palColor[0] and color[1] == palColor[1] and color[2] == palColor[2] and color[3] == palColor[3]:
|
||||
break
|
||||
elif color[3] == 0 and palColor[3] == 0:
|
||||
break
|
||||
else:
|
||||
print('Pallete {} does not contain color #{}'.format(p['paletteName'], '{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3])))
|
||||
hasAllColors = False
|
||||
break
|
||||
if hasAllColors:
|
||||
palette = p
|
||||
break
|
||||
|
||||
if palette is None:
|
||||
palette = palettes[0] # Just to avoid reference error
|
||||
print(f"No matching palette found for {assetPath}!")
|
||||
# Find which pixel is missing
|
||||
for color in imagePalette:
|
||||
if color in palette['pixels']:
|
||||
continue
|
||||
# Convert to hex (with alpha)
|
||||
hexColor = '#{:02x}{:02x}{:02x}{:02x}'.format(color[0], color[1], color[2], color[3])
|
||||
print(f"Missing color: {hexColor} in palette {palette['paletteName']}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Converting image {assetPath} to use palette")
|
||||
|
||||
paletteIndexes = []
|
||||
for pixel in list(image.getdata()):
|
||||
if pixel[3] == 0:
|
||||
pixel = (0, 0, 0, 0)
|
||||
paletteIndex = palette['pixels'].index(pixel)
|
||||
paletteIndexes.append(paletteIndex)
|
||||
|
||||
data = bytearray()
|
||||
data.extend(b"DPI") # Dusk Palettized Image
|
||||
data.extend(image.width.to_bytes(4, 'little')) # Width
|
||||
data.extend(image.height.to_bytes(4, 'little')) # Height
|
||||
data.append(palette['paletteIndex']) # Palette index
|
||||
for paletteIndex in paletteIndexes:
|
||||
if paletteIndex > 255 or paletteIndex < 0:
|
||||
print(f"Error: Palette index {paletteIndex} exceeds 255!")
|
||||
sys.exit(1)
|
||||
data.append(paletteIndex.to_bytes(1, 'little')[0]) # Pixel index
|
||||
|
||||
relative = getAssetRelativePath(assetPath)
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0]
|
||||
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dpi")
|
||||
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
||||
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
||||
with open(outputFilePath, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
outImage = {
|
||||
"imagePath": outputFileRelative,
|
||||
"files": [ outputFilePath ],
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
}
|
||||
return assetCache(assetPath, outImage)
|
||||
|
||||
def processAlphaImage(asset):
|
||||
assetPath = asset['path']
|
||||
cache = assetGetCache(assetPath)
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
print(f"Processing alpha image: {assetPath}")
|
||||
|
||||
data = bytearray()
|
||||
data.extend(b"DAI") # Dusk Alpha Image
|
||||
image = Image.open(assetPath).convert("RGBA")
|
||||
data.extend(image.width.to_bytes(4, 'little')) # Width
|
||||
data.extend(image.height.to_bytes(4, 'little')) # Height
|
||||
for pixel in list(image.getdata()):
|
||||
# Only write alpha channel
|
||||
data.append(pixel[3].to_bytes(1, 'little')[0]) # Pixel alpha
|
||||
|
||||
relative = getAssetRelativePath(assetPath)
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0]
|
||||
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dai")
|
||||
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
||||
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
||||
with open(outputFilePath, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
outImage = {
|
||||
"imagePath": outputFileRelative,
|
||||
"files": [ outputFilePath ],
|
||||
'width': image.width,
|
||||
'height': image.height,
|
||||
}
|
||||
return assetCache(assetPath, outImage)
|
||||
228
archive/asset/process/language.py
Normal file
228
archive/asset/process/language.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import sys
|
||||
import os
|
||||
from tools.asset.args import args
|
||||
from tools.asset.cache import assetCache, assetGetCache
|
||||
from tools.asset.path import getAssetRelativePath
|
||||
from tools.dusk.defs import defs
|
||||
import polib
|
||||
import re
|
||||
|
||||
LANGUAGE_CHUNK_CHAR_COUNT = int(defs.get('ASSET_LANG_CHUNK_CHAR_COUNT'))
|
||||
|
||||
LANGUAGE_DATA = {}
|
||||
LANGUAGE_KEYS = []
|
||||
|
||||
def processLanguageList():
|
||||
# Language keys header data
|
||||
headerKeys = "// Auto-generated language keys header file.\n"
|
||||
headerKeys += "#pragma once\n"
|
||||
headerKeys += "#include \"dusk.h\"\n\n"
|
||||
|
||||
# This is the desired chunk groups list.. if a language key STARTS with any
|
||||
# of the keys in this list we would "like to" put it in that chunk group.
|
||||
# If there is no match, or the list is full then we will add it to the next
|
||||
# available chunk group (that isn't a 'desired' one). If the chunk becomes
|
||||
# full, then we attempt to make another chunk with the same prefix so that
|
||||
# a second batching can occur.
|
||||
desiredChunkGroups = {
|
||||
'ui': 0
|
||||
}
|
||||
|
||||
# Now, for each language key, create the header reference and index.
|
||||
keyIndex = 0
|
||||
languageKeyIndexes = {}
|
||||
languageKeyChunk = {}
|
||||
languageKeyChunkIndexes = {}
|
||||
languageKeyChunkOffsets = {}
|
||||
for key in LANGUAGE_KEYS:
|
||||
headerKeys += f"#define {getLanguageVariableName(key)} {keyIndex}\n"
|
||||
languageKeyIndexes[key] = keyIndex
|
||||
keyIndex += 1
|
||||
|
||||
# Find desired chunk group
|
||||
assignedChunk = None
|
||||
for desiredKey in desiredChunkGroups:
|
||||
if key.lower().startswith(desiredKey):
|
||||
assignedChunk = desiredChunkGroups[desiredKey]
|
||||
break
|
||||
# If no desired chunk group matched, assign to -1
|
||||
if assignedChunk is None:
|
||||
assignedChunk = -1
|
||||
languageKeyChunk[key] = assignedChunk
|
||||
|
||||
# Setup header.
|
||||
for lang in LANGUAGE_DATA:
|
||||
if key not in LANGUAGE_DATA[lang]:
|
||||
print(f"Warning: Missing translation for key '{key}' in language '{lang}'")
|
||||
sys.exit(1)
|
||||
|
||||
# Seal the header.
|
||||
headerKeys += f"\n#define LANG_KEY_COUNT {len(LANGUAGE_KEYS)}\n"
|
||||
|
||||
# Now we can generate the language string chunks.
|
||||
nextChunkIndex = max(desiredChunkGroups.values()) + 1
|
||||
files = []
|
||||
|
||||
for lang in LANGUAGE_DATA:
|
||||
langData = LANGUAGE_DATA[lang]
|
||||
|
||||
# Key = chunkIndex, value = chunkInfo
|
||||
languageChunks = {}
|
||||
for key in LANGUAGE_KEYS:
|
||||
keyIndex = languageKeyIndexes[key]
|
||||
chunkIndex = languageKeyChunk[key]
|
||||
wasSetChunk = chunkIndex != -1
|
||||
|
||||
# This will keep looping until we find a chunk
|
||||
while True:
|
||||
# Determine the next chunkIndex IF chunkIndex is -1
|
||||
if chunkIndex == -1:
|
||||
chunkIndex = nextChunkIndex
|
||||
|
||||
# Is the chunk full?
|
||||
curLen = languageChunks.get(chunkIndex, {'len': 0})['len']
|
||||
newLen = curLen + len(langData[key])
|
||||
if newLen > LANGUAGE_CHUNK_CHAR_COUNT:
|
||||
# Chunk is full, need to create a new chunk.
|
||||
chunkIndex = -1
|
||||
if wasSetChunk:
|
||||
wasSetChunk = False
|
||||
else:
|
||||
nextChunkIndex += 1
|
||||
continue
|
||||
|
||||
# Chunk is not full, we can use it.
|
||||
if chunkIndex not in languageChunks:
|
||||
languageChunks[chunkIndex] = {
|
||||
'len': 0,
|
||||
'keys': []
|
||||
}
|
||||
languageChunks[chunkIndex]['len'] = newLen
|
||||
languageChunks[chunkIndex]['keys'].append(key)
|
||||
languageKeyChunkIndexes[key] = chunkIndex
|
||||
languageKeyChunkOffsets[key] = curLen
|
||||
break
|
||||
|
||||
# We have now chunked all the keys for this language!
|
||||
langBuffer = b""
|
||||
|
||||
# Write header info
|
||||
langBuffer += b'DLF' # Dusk Language File
|
||||
|
||||
for key in LANGUAGE_KEYS:
|
||||
# Write the chunk that this key belongs to as uint32_t
|
||||
chunkIndex = languageKeyChunkIndexes[key]
|
||||
langBuffer += chunkIndex.to_bytes(4, byteorder='little')
|
||||
|
||||
# Write the offset for this key as uint32_t
|
||||
offset = languageKeyChunkOffsets[key]
|
||||
langBuffer += offset.to_bytes(4, byteorder='little')
|
||||
|
||||
# Write the length of the string as uint32_t
|
||||
strData = langData[key].encode('utf-8')
|
||||
langBuffer += len(strData).to_bytes(4, byteorder='little')
|
||||
|
||||
# Now write out each chunk's string data, packed tight and no null term.
|
||||
for chunkIndex in sorted(languageChunks.keys()):
|
||||
chunkInfo = languageChunks[chunkIndex]
|
||||
for key in chunkInfo['keys']:
|
||||
strData = langData[key].encode('utf-8')
|
||||
langBuffer += strData
|
||||
|
||||
# Now pad the chunk to full size
|
||||
curLen = chunkInfo['len']
|
||||
if curLen < LANGUAGE_CHUNK_CHAR_COUNT:
|
||||
padSize = LANGUAGE_CHUNK_CHAR_COUNT - curLen
|
||||
langBuffer += b'\0' * padSize
|
||||
|
||||
# Write out the language data file
|
||||
outputFile = os.path.join(args.output_assets, "language", f"{lang}.dlf")
|
||||
files.append(outputFile)
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, "wb") as f:
|
||||
f.write(langBuffer)
|
||||
|
||||
# Write out the language keys header file
|
||||
outputFile = os.path.join(args.headers_dir, "locale", "language", "keys.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, "w") as f:
|
||||
f.write(headerKeys)
|
||||
|
||||
# Generate language list.
|
||||
langValues = {}
|
||||
headerLocale = "#pragma once\n#include \"locale/localeinfo.h\"\n\n"
|
||||
headerLocale += "typedef enum {\n"
|
||||
count = 0
|
||||
headerLocale += f" DUSK_LOCALE_NULL = {count},\n"
|
||||
count += 1
|
||||
for lang in LANGUAGE_DATA:
|
||||
langKey = lang.replace('-', '_').replace(' ', '_').upper()
|
||||
langValues[lang] = count
|
||||
headerLocale += f" DUSK_LOCALE_{langKey} = {count},\n"
|
||||
count += 1
|
||||
headerLocale += f" DUSK_LOCALE_COUNT = {count}\n"
|
||||
headerLocale += "} dusklocale_t;\n\n"
|
||||
|
||||
headerLocale += f"static const localeinfo_t LOCALE_INFOS[DUSK_LOCALE_COUNT] = {{\n"
|
||||
for lang in LANGUAGE_DATA:
|
||||
langKey = lang.replace('-', '_').replace(' ', '_').upper()
|
||||
headerLocale += f" [DUSK_LOCALE_{langKey}] = {{\n"
|
||||
headerLocale += f" .file = \"{lang}\"\n"
|
||||
headerLocale += f" }},\n"
|
||||
headerLocale += "};\n"
|
||||
|
||||
headerLocale += f"static const char_t *LOCALE_SCRIPT = \n"
|
||||
for lang in LANGUAGE_DATA:
|
||||
langKey = lang.replace('-', '_').replace(' ', '_').upper()
|
||||
langValue = langValues[lang]
|
||||
headerLocale += f" \"DUSK_LOCALE_{langKey} = {langValue}\\n\"\n"
|
||||
headerLocale += ";\n"
|
||||
|
||||
# Write out the locale enum header file
|
||||
outputFile = os.path.join(args.headers_dir, "locale", "locale.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, "w") as f:
|
||||
f.write(headerLocale)
|
||||
|
||||
return {
|
||||
'files': files
|
||||
}
|
||||
|
||||
def getLanguageVariableName(languageKey):
|
||||
# Take the language key, prepend LANG_, uppercase, replace any non symbols
|
||||
# with _
|
||||
key = languageKey.strip().upper()
|
||||
key = re.sub(r'[^A-Z0-9]', '_', key)
|
||||
return f"LANG_{key}"
|
||||
|
||||
def processLanguage(asset):
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
# Load PO File
|
||||
po = polib.pofile(asset['path'])
|
||||
|
||||
langName = po.metadata.get('Language')
|
||||
if langName not in LANGUAGE_DATA:
|
||||
LANGUAGE_DATA[langName] = {}
|
||||
|
||||
for entry in po:
|
||||
key = entry.msgid
|
||||
val = entry.msgstr
|
||||
|
||||
if key not in LANGUAGE_KEYS:
|
||||
LANGUAGE_KEYS.append(key)
|
||||
|
||||
if key not in LANGUAGE_DATA[langName]:
|
||||
LANGUAGE_DATA[langName][key] = val
|
||||
else:
|
||||
print(f"Error: Duplicate translation key '{key}' in language '{langName}'")
|
||||
sys.exit(1)
|
||||
|
||||
outLanguageData = {
|
||||
'data': po,
|
||||
'path': asset['path'],
|
||||
'files': []
|
||||
}
|
||||
return assetCache(asset['path'], outLanguageData)
|
||||
154
archive/asset/process/map.py
Normal file
154
archive/asset/process/map.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from tools.asset.args import args
|
||||
from tools.asset.cache import assetCache, assetGetCache
|
||||
from tools.asset.path import getAssetRelativePath
|
||||
from tools.dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
|
||||
from tools.dusk.map import Map
|
||||
from tools.dusk.chunk import Chunk
|
||||
|
||||
def convertModelData(modelData):
|
||||
# TLDR; Model data stores things efficiently with indices, but we buffer it
|
||||
# out to 6 vertex quads for simplicity.
|
||||
outVertices = []
|
||||
outUVs = []
|
||||
outColors = []
|
||||
for indice in modelData['indices']:
|
||||
vertex = modelData['vertices'][indice]
|
||||
uv = modelData['uvs'][indice]
|
||||
color = modelData['colors'][indice]
|
||||
outVertices.append(vertex)
|
||||
outUVs.append(uv)
|
||||
outColors.append(color)
|
||||
|
||||
return {
|
||||
'vertices': outVertices,
|
||||
'uvs': outUVs,
|
||||
'colors': outColors
|
||||
}
|
||||
|
||||
def processChunk(chunk):
|
||||
cache = assetGetCache(chunk.getFilename())
|
||||
if cache:
|
||||
return cache
|
||||
|
||||
baseModel = {
|
||||
'vertices': [],
|
||||
'colors': [],
|
||||
'uvs': []
|
||||
}
|
||||
models = [ baseModel ]
|
||||
|
||||
for tileIndex, tile in chunk.tiles.items():
|
||||
tileBase = tile.getBaseTileModel()
|
||||
|
||||
convertedBase = convertModelData(tileBase)
|
||||
baseModel['vertices'].extend(convertedBase['vertices'])
|
||||
baseModel['colors'].extend(convertedBase['colors'])
|
||||
baseModel['uvs'].extend(convertedBase['uvs'])
|
||||
|
||||
# Generate binary buffer for efficient output
|
||||
buffer = bytearray()
|
||||
buffer.extend(b'DMC')# Header
|
||||
buffer.extend(len(chunk.tiles).to_bytes(4, 'little')) # Number of tiles
|
||||
buffer.extend(len(models).to_bytes(1, 'little')) # Number of models
|
||||
buffer.extend(len(chunk.entities).to_bytes(1, 'little')) # Number of entities
|
||||
|
||||
# Buffer tile data as array of uint8_t
|
||||
for tileIndex, tile in chunk.tiles.items():
|
||||
buffer.extend(tile.shape.to_bytes(1, 'little'))
|
||||
|
||||
# # For each model
|
||||
for model in models:
|
||||
vertexCount = len(model['vertices'])
|
||||
buffer.extend(vertexCount.to_bytes(4, 'little'))
|
||||
for i in range(vertexCount):
|
||||
vertex = model['vertices'][i]
|
||||
uv = model['uvs'][i]
|
||||
color = model['colors'][i]
|
||||
|
||||
buffer.extend(color[0].to_bytes(1, 'little'))
|
||||
buffer.extend(color[1].to_bytes(1, 'little'))
|
||||
buffer.extend(color[2].to_bytes(1, 'little'))
|
||||
buffer.extend(color[3].to_bytes(1, 'little'))
|
||||
|
||||
buffer.extend(bytearray(struct.pack('<f', uv[0])))
|
||||
buffer.extend(bytearray(struct.pack('<f', uv[1])))
|
||||
|
||||
buffer.extend(bytearray(struct.pack('<f', vertex[0])))
|
||||
buffer.extend(bytearray(struct.pack('<f', vertex[1])))
|
||||
buffer.extend(bytearray(struct.pack('<f', vertex[2])))
|
||||
|
||||
# For each entity
|
||||
for entity in chunk.entities.values():
|
||||
buffer.extend(entity.type.to_bytes(1, 'little'))
|
||||
buffer.extend(entity.localX.to_bytes(1, 'little'))
|
||||
buffer.extend(entity.localY.to_bytes(1, 'little'))
|
||||
buffer.extend(entity.localZ.to_bytes(1, 'little'))
|
||||
pass
|
||||
|
||||
# Write out map file
|
||||
relative = getAssetRelativePath(chunk.getFilename())
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(relative))[0]
|
||||
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dmc")
|
||||
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
||||
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
||||
with open(outputFilePath, "wb") as f:
|
||||
f.write(buffer)
|
||||
|
||||
outChunk = {
|
||||
'files': [ outputFilePath ],
|
||||
'chunk': chunk
|
||||
}
|
||||
return assetCache(chunk.getFilename(), outChunk)
|
||||
|
||||
def processMap(asset):
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
map = Map(None)
|
||||
map.load(asset['path'])
|
||||
chunksDir = map.getChunkDirectory()
|
||||
|
||||
files = os.listdir(chunksDir)
|
||||
if len(files) == 0:
|
||||
print(f"Error: No chunk files found in {chunksDir}.")
|
||||
sys.exit(1)
|
||||
|
||||
chunkFiles = []
|
||||
for fileName in files:
|
||||
if not fileName.endswith('.json'):
|
||||
continue
|
||||
|
||||
fNameNoExt = os.path.splitext(fileName)[0]
|
||||
fnPieces = fNameNoExt.split('_')
|
||||
if len(fnPieces) != 3:
|
||||
print(f"Error: Chunk filename {fileName} does not contain valid chunk coordinates.")
|
||||
sys.exit(1)
|
||||
chunk = Chunk(map, int(fnPieces[0]), int(fnPieces[1]), int(fnPieces[2]))
|
||||
chunk.load()
|
||||
result = processChunk(chunk)
|
||||
chunkFiles.extend(result['files'])
|
||||
|
||||
# Map file
|
||||
outBuffer = bytearray()
|
||||
outBuffer.extend(b'DMF')
|
||||
outBuffer.extend(len(chunkFiles).to_bytes(4, 'little'))
|
||||
|
||||
# DMF (Dusk Map file)
|
||||
fileRelative = getAssetRelativePath(asset['path'])
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(fileRelative))[0]
|
||||
outputMapRelative = os.path.join(os.path.dirname(fileRelative), f"{fileNameWithoutExt}.dmf")
|
||||
outputMapPath = os.path.join(args.output_assets, outputMapRelative)
|
||||
os.makedirs(os.path.dirname(outputMapPath), exist_ok=True)
|
||||
with open(outputMapPath, "wb") as f:
|
||||
f.write(outBuffer)
|
||||
|
||||
outMap = {
|
||||
'files': chunkFiles
|
||||
}
|
||||
outMap['files'].append(outputMapPath)
|
||||
return assetCache(asset['path'], outMap)
|
||||
96
archive/asset/process/palette.py
Normal file
96
archive/asset/process/palette.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import json
|
||||
import os
|
||||
from PIL import Image
|
||||
import datetime
|
||||
from tools.asset.args import args
|
||||
from tools.asset.cache import assetCache, assetGetCache
|
||||
|
||||
palettes = []
|
||||
|
||||
def extractPaletteFromImage(image):
|
||||
# goes through and finds all unique colors in the image
|
||||
if image.mode != 'RGBA':
|
||||
image = image.convert('RGBA')
|
||||
pixels = list(image.getdata())
|
||||
uniqueColors = []
|
||||
for color in pixels:
|
||||
# We treat all alpha 0 as rgba(0,0,0,0) for palette purposes
|
||||
if color[3] == 0:
|
||||
color = (0, 0, 0, 0)
|
||||
if color not in uniqueColors:
|
||||
uniqueColors.append(color)
|
||||
return uniqueColors
|
||||
|
||||
def processPalette(asset):
|
||||
print(f"Processing palette: {asset['path']}")
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
paletteIndex = len(palettes)
|
||||
image = Image.open(asset['path'])
|
||||
pixels = extractPaletteFromImage(image)
|
||||
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]
|
||||
fileNameWithoutPalette = os.path.splitext(fileNameWithoutExt)[0]
|
||||
|
||||
# PSP requires that the palette size be a power of two, so we will pad the
|
||||
# palette with transparent colors if needed.
|
||||
def mathNextPowTwo(x):
|
||||
return 1 << (x - 1).bit_length()
|
||||
|
||||
nextPowTwo = mathNextPowTwo(len(pixels))
|
||||
while len(pixels) < nextPowTwo:
|
||||
pixels.append((0, 0, 0, 0))
|
||||
|
||||
# Header
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
data = f"// Palette Generated for {asset['path']} at {now}\n"
|
||||
data += f"#include \"display/palette/palette.h\"\n\n"
|
||||
data += f"#define PALETTE_{paletteIndex}_COLOR_COUNT {len(pixels)}\n\n"
|
||||
data += f"#pragma pack(push, 1)\n"
|
||||
data += f"static const color_t PALETTE_{paletteIndex}_COLORS[PALETTE_{paletteIndex}_COLOR_COUNT] = {{\n"
|
||||
for pixel in pixels:
|
||||
data += f" {{ 0x{pixel[0]:02X}, 0x{pixel[1]:02X}, 0x{pixel[2]:02X}, 0x{pixel[3]:02X} }},\n"
|
||||
data += f"}};\n"
|
||||
data += f"#pragma pack(pop)\n\n"
|
||||
data += f"static const palette_t PALETTE_{paletteIndex} = {{\n"
|
||||
data += f" .colorCount = PALETTE_{paletteIndex}_COLOR_COUNT,\n"
|
||||
data += f" .colors = PALETTE_{paletteIndex}_COLORS,\n"
|
||||
data += f"}};\n"
|
||||
|
||||
# Write Header
|
||||
outputFile = os.path.join(args.headers_dir, "display", "palette", f"palette_{paletteIndex}.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, "w") as f:
|
||||
f.write(data)
|
||||
|
||||
palette = {
|
||||
"paletteIndex": paletteIndex,
|
||||
"paletteName": fileNameWithoutPalette,
|
||||
"pixels": pixels,
|
||||
"headerFile": os.path.relpath(outputFile, args.headers_dir),
|
||||
"asset": asset,
|
||||
"files": [ ],# No zippable files.
|
||||
}
|
||||
|
||||
palettes.append(palette)
|
||||
return assetCache(asset['path'], palette)
|
||||
|
||||
def processPaletteList():
|
||||
data = f"// Auto-generated palette list\n"
|
||||
print(f"Generating palette list with {len(palettes)} palettes.")
|
||||
for palette in palettes:
|
||||
data += f"#include \"{palette['headerFile']}\"\n"
|
||||
data += f"\n"
|
||||
data += f"#define PALETTE_LIST_COUNT {len(palettes)}\n\n"
|
||||
data += f"static const palette_t* PALETTE_LIST[PALETTE_LIST_COUNT] = {{\n"
|
||||
for palette in palettes:
|
||||
data += f" &PALETTE_{palette['paletteIndex']},\n"
|
||||
data += f"}};\n"
|
||||
|
||||
# Write the palette list to a header file
|
||||
outputFile = os.path.join(args.headers_dir, "display", "palette", "palettelist.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, "w") as f:
|
||||
f.write(data)
|
||||
43
archive/asset/process/script.py
Normal file
43
archive/asset/process/script.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import sys
|
||||
import os
|
||||
from tools.asset.args import args
|
||||
from tools.asset.cache import assetCache, assetGetCache
|
||||
from tools.asset.path import getAssetRelativePath
|
||||
from tools.dusk.defs import fileDefs
|
||||
|
||||
def processScript(asset):
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
# Load the lua file as a string
|
||||
with open(asset['path'], 'r', encoding='utf-8') as f:
|
||||
luaCode = f.read()
|
||||
|
||||
# TODO: I will precompile or minify the Lua code here in the future
|
||||
|
||||
# Replace all definitions in the code
|
||||
for key, val in fileDefs.items():
|
||||
luaCode = luaCode.replace(key, str(val))
|
||||
|
||||
# Create output Dusk Script File (DSF) data
|
||||
data = ""
|
||||
data += "DSF"
|
||||
data += luaCode
|
||||
|
||||
# Write to relative output file path.
|
||||
relative = getAssetRelativePath(asset['path'])
|
||||
fileNameWithoutExt = os.path.splitext(os.path.basename(asset['path']))[0]
|
||||
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dsf")
|
||||
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
||||
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
||||
with open(outputFilePath, "wb") as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
outScript = {
|
||||
'data': data,
|
||||
'path': asset['path'],
|
||||
'files': [ outputFilePath ],
|
||||
'scriptPath': outputFileRelative,
|
||||
}
|
||||
return assetCache(asset['path'], outScript)
|
||||
178
archive/asset/process/tileset.py
Normal file
178
archive/asset/process/tileset.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
from xml.etree import ElementTree
|
||||
from tools.asset.process.image import processImage
|
||||
from tools.asset.path import getAssetRelativePath
|
||||
from tools.asset.args import args
|
||||
from tools.asset.cache import assetGetCache, assetCache
|
||||
|
||||
tilesets = []
|
||||
|
||||
def loadTilesetFromTSX(asset):
|
||||
# Load the TSX file
|
||||
tree = ElementTree.parse(asset['path'])
|
||||
root = tree.getroot()
|
||||
|
||||
# Expect tileheight, tilewidth, columns and tilecount attributes
|
||||
if 'tilewidth' not in root.attrib or 'tileheight' not in root.attrib or 'columns' not in root.attrib or 'tilecount' not in root.attrib:
|
||||
print(f"Error: TSX file {asset['path']} is missing required attributes (tilewidth, tileheight, columns, tilecount)")
|
||||
sys.exit(1)
|
||||
|
||||
tileWidth = int(root.attrib['tilewidth'])
|
||||
tileHeight = int(root.attrib['tileheight'])
|
||||
columns = int(root.attrib['columns'])
|
||||
tileCount = int(root.attrib['tilecount'])
|
||||
rows = (tileCount + columns - 1) // columns # Calculate rows based on tileCount and columns
|
||||
|
||||
# Find the image element
|
||||
imageElement = root.find('image')
|
||||
if imageElement is None or 'source' not in imageElement.attrib:
|
||||
print(f"Error: TSX file {asset['path']} is missing an image element with a source attribute")
|
||||
sys.exit(1)
|
||||
|
||||
imagePath = imageElement.attrib['source']
|
||||
|
||||
# Image is relative to the TSX file
|
||||
imageAssetPath = os.path.join(os.path.dirname(asset['path']), imagePath)
|
||||
|
||||
image = processImage({
|
||||
'path': imageAssetPath,
|
||||
'options': asset['options'],
|
||||
})
|
||||
|
||||
return {
|
||||
"image": image,
|
||||
"tileWidth": tileWidth,
|
||||
"tileHeight": tileHeight,
|
||||
"columns": columns,
|
||||
"rows": rows,
|
||||
"originalWidth": tileWidth * columns,
|
||||
"originalHeight": tileHeight * rows,
|
||||
}
|
||||
|
||||
def loadTilesetFromArgs(asset):
|
||||
# We need to determine how big each tile is. This can either be provided as
|
||||
# an arg of tileWidth/tileHeight or as a count of rows/columns.
|
||||
# Additionally, if the image has been factored, then the user can provide both
|
||||
# tile sizes AND cols/rows to indicate the original size of the image.
|
||||
image = processImage(asset)
|
||||
|
||||
tileWidth, tileHeight = None, None
|
||||
columns, rows = None, None
|
||||
originalWidth, originalHeight = image['width'], image['height']
|
||||
|
||||
if 'tileWidth' in asset['options'] and 'columns' in asset['options']:
|
||||
tileWidth = int(asset['options']['tileWidth'])
|
||||
columns = int(asset['options']['columns'])
|
||||
originalWidth = tileWidth * columns
|
||||
elif 'tileWidth' in asset['options']:
|
||||
tileWidth = int(asset['options']['tileWidth'])
|
||||
columns = image['width'] // tileWidth
|
||||
elif 'columns' in asset['options']:
|
||||
columns = int(asset['options']['columns'])
|
||||
tileWidth = image['width'] // columns
|
||||
else:
|
||||
print(f"Error: Tileset {asset['path']} must specify either tileWidth or columns")
|
||||
sys.exit(1)
|
||||
|
||||
if 'tileHeight' in asset['options'] and 'rows' in asset['options']:
|
||||
tileHeight = int(asset['options']['tileHeight'])
|
||||
rows = int(asset['options']['rows'])
|
||||
originalHeight = tileHeight * rows
|
||||
elif 'tileHeight' in asset['options']:
|
||||
tileHeight = int(asset['options']['tileHeight'])
|
||||
rows = image['height'] // tileHeight
|
||||
elif 'rows' in asset['options']:
|
||||
rows = int(asset['options']['rows'])
|
||||
tileHeight = image['height'] // rows
|
||||
else:
|
||||
print(f"Error: Tileset {asset['path']} must specify either tileHeight or rows")
|
||||
sys.exit(1)
|
||||
|
||||
return {
|
||||
"image": image,
|
||||
"tileWidth": tileWidth,
|
||||
"tileHeight": tileHeight,
|
||||
"columns": columns,
|
||||
"rows": rows,
|
||||
"originalWidth": originalWidth,
|
||||
"originalHeight": originalHeight,
|
||||
}
|
||||
|
||||
def processTileset(asset):
|
||||
cache = assetGetCache(asset['path'])
|
||||
if cache is not None:
|
||||
return cache
|
||||
|
||||
print(f"Processing tileset: {asset['path']}")
|
||||
tilesetData = None
|
||||
if asset['path'].endswith('.tsx'):
|
||||
tilesetData = loadTilesetFromTSX(asset)
|
||||
else:
|
||||
tilesetData = loadTilesetFromArgs(asset)
|
||||
|
||||
fileNameWithoutExtension = os.path.splitext(os.path.basename(asset['path']))[0]
|
||||
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
tilesetName = fileNameWithoutExtension
|
||||
tilesetNameUpper = tilesetName.upper()
|
||||
|
||||
widthScale = tilesetData['originalWidth'] / tilesetData['image']['width']
|
||||
heightScale = tilesetData['originalHeight'] / tilesetData['image']['height']
|
||||
|
||||
# Create header
|
||||
data = f"// Tileset Generated for {asset['path']} at {now}\n"
|
||||
data += f"#pragma once\n"
|
||||
data += f"#include \"display/tileset/tileset.h\"\n\n"
|
||||
data += f"static const tileset_t TILESET_{tilesetNameUpper} = {{\n"
|
||||
data += f" .name = {json.dumps(tilesetName)},\n"
|
||||
data += f" .tileWidth = {tilesetData['tileWidth']},\n"
|
||||
data += f" .tileHeight = {tilesetData['tileHeight']},\n"
|
||||
data += f" .tileCount = {tilesetData['columns'] * tilesetData['rows']},\n"
|
||||
data += f" .columns = {tilesetData['columns']},\n"
|
||||
data += f" .rows = {tilesetData['rows']},\n"
|
||||
data += f" .uv = {{ {widthScale / tilesetData['columns']}f, {heightScale / tilesetData['rows']}f }},\n"
|
||||
data += f" .image = {json.dumps(tilesetData['image']['imagePath'])},\n"
|
||||
data += f"}};\n"
|
||||
|
||||
|
||||
# Write Header
|
||||
outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tileset_{tilesetName}.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, 'w') as f:
|
||||
f.write(data)
|
||||
|
||||
print(f"Write header for tileset: {outputFile}")
|
||||
|
||||
tileset = {
|
||||
"files": [],
|
||||
"image": tilesetData['image'],
|
||||
"headerFile": os.path.relpath(outputFile, args.headers_dir),
|
||||
"tilesetName": tilesetName,
|
||||
"tilesetNameUpper": tilesetNameUpper,
|
||||
"tilesetIndex": len(tilesets),
|
||||
"tilesetData": tilesetData,
|
||||
"files": tilesetData['image']['files'],
|
||||
}
|
||||
|
||||
tilesets.append(tileset)
|
||||
return assetCache(asset['path'], tileset)
|
||||
|
||||
def processTilesetList():
|
||||
data = f"// Tileset List Generated at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
||||
data += f"#pragma once\n"
|
||||
for tileset in tilesets:
|
||||
data += f"#include \"{tileset['headerFile']}\"\n"
|
||||
data += f"\n"
|
||||
data += f"#define TILESET_LIST_COUNT {len(tilesets)}\n\n"
|
||||
data += f"static const tileset_t* TILESET_LIST[TILESET_LIST_COUNT] = {{\n"
|
||||
for tileset in tilesets:
|
||||
data += f" &TILESET_{tileset['tilesetNameUpper']},\n"
|
||||
data += f"}};\n"
|
||||
|
||||
# Write header.
|
||||
outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tilesetlist.h")
|
||||
os.makedirs(os.path.dirname(outputFile), exist_ok=True)
|
||||
with open(outputFile, 'w') as f:
|
||||
f.write(data)
|
||||
47
archive/assetpalette.c
Normal file
47
archive/assetpalette.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetpalette.h"
|
||||
#include "asset/assettype.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
errorret_t assetPaletteLoad(assetentire_t entire) {
|
||||
assertNotNull(entire.data, "Data pointer cannot be NULL.");
|
||||
assertNotNull(entire.output, "Output pointer cannot be NULL.");
|
||||
|
||||
assetpalette_t *assetData = (assetpalette_t *)entire.data;
|
||||
palette_t *palette = (palette_t *)entire.output;
|
||||
|
||||
// Read header and version (first 4 bytes)
|
||||
if(
|
||||
assetData->header[0] != 'D' ||
|
||||
assetData->header[1] != 'P' ||
|
||||
assetData->header[2] != 'F'
|
||||
) {
|
||||
errorThrow("Invalid palette header");
|
||||
}
|
||||
|
||||
// Version (can only be 1 atm)
|
||||
if(assetData->version != 0x01) {
|
||||
errorThrow("Unsupported palette version");
|
||||
}
|
||||
|
||||
// Check color count.
|
||||
if(
|
||||
assetData->colorCount == 0 ||
|
||||
assetData->colorCount > PALETTE_COLOR_COUNT_MAX
|
||||
) {
|
||||
errorThrow("Invalid palette color count");
|
||||
}
|
||||
|
||||
paletteInit(
|
||||
palette,
|
||||
assetData->colorCount,
|
||||
assetData->colors
|
||||
);
|
||||
errorOk();
|
||||
}
|
||||
30
archive/assetpalette.h
Normal file
30
archive/assetpalette.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "display/texture/palette.h"
|
||||
|
||||
typedef struct assetentire_s assetentire_t;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
char_t header[3];
|
||||
uint8_t version;
|
||||
|
||||
uint8_t colorCount;
|
||||
color_t colors[PALETTE_COLOR_COUNT_MAX];
|
||||
} assetpalette_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
/**
|
||||
* Loads a palette from the given data pointer into the output palette.
|
||||
*
|
||||
* @param entire Data received from the asset loader system.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t assetPaletteLoad(assetentire_t entire);
|
||||
@@ -1,37 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Libs
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
m
|
||||
)
|
||||
|
||||
# Includes
|
||||
target_include_directories(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
game.c
|
||||
input.c
|
||||
time.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(assert)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(event)
|
||||
add_subdirectory(item)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(util)
|
||||
add_subdirectory(world)
|
||||
@@ -1,85 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assert.h"
|
||||
|
||||
#ifndef ASSERTIONS_FAKED
|
||||
void assertTrueImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const bool x,
|
||||
const char *message
|
||||
) {
|
||||
if(x != true) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Assertion Failed in %s:%i\n\n%s\n",
|
||||
file,
|
||||
line,
|
||||
message
|
||||
);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void assertFalseImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
bool x,
|
||||
const char *message
|
||||
) {
|
||||
assertTrueImpl(file, line, !x, message);
|
||||
}
|
||||
|
||||
void assertUnreachableImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
) {
|
||||
assertTrueImpl(file, line, false, message);
|
||||
}
|
||||
|
||||
void assertNotNullImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const void *pointer,
|
||||
const char *message
|
||||
) {
|
||||
assertTrueImpl(
|
||||
file,
|
||||
line,
|
||||
pointer != NULL,
|
||||
message
|
||||
);
|
||||
|
||||
// Ensure we can touch it
|
||||
volatile char temp;
|
||||
temp = *((char*)pointer);
|
||||
}
|
||||
|
||||
void assertNullImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const void *pointer,
|
||||
const char *message
|
||||
) {
|
||||
assertTrueImpl(
|
||||
file,
|
||||
line,
|
||||
pointer == NULL,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
void assertDeprecatedImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
) {
|
||||
assertUnreachableImpl(file, line, message);
|
||||
}
|
||||
#endif
|
||||
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#ifndef ASSERTIONS_FAKED
|
||||
/**
|
||||
* Assert a given value to be true.
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param x Value to assert as true.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertTrueImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const bool_t x,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts a given statement to be false.
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param x Value to assert as false.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertFalseImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const bool_t x,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts that a given line of code is unreachable. Essentially a forced
|
||||
* assertion failure, good for "edge cases"
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertUnreachableImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Assert a given pointer to not point to a null pointer.
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param pointer Pointer to assert is not a null pointer.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertNotNullImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const void *pointer,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts a given pointer to be a nullptr.
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param pointer Pointer to assert is nullptr.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertNullImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const void *pointer,
|
||||
const char *message
|
||||
);
|
||||
|
||||
/**
|
||||
* Asserts a function as being deprecated.
|
||||
*
|
||||
* @param file File that the assertion is being made from.
|
||||
* @param line Line that the assertion is being made from.
|
||||
* @param message Message to throw against assertion failure.
|
||||
*/
|
||||
void assertDeprecatedImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const char *message
|
||||
);
|
||||
|
||||
void assertMemoryRangeMatchesImpl(
|
||||
const char *file,
|
||||
const int32_t line,
|
||||
const void *start,
|
||||
const void *end,
|
||||
const size_t size,
|
||||
const char *message
|
||||
);
|
||||
|
||||
#define assertTrue(x, message) \
|
||||
assertTrueImpl(__FILE__, __LINE__, x, message)
|
||||
|
||||
#define assertFalse(x, message) \
|
||||
assertFalseImpl(__FILE__, __LINE__, x, message)
|
||||
|
||||
#define assertUnreachable(message) \
|
||||
assertUnreachableImpl(__FILE__, __LINE__, message)
|
||||
|
||||
#define assertNotNull(pointer, message) \
|
||||
assertNotNullImpl(__FILE__, __LINE__, pointer, message)
|
||||
|
||||
#define assertNull(pointer, message) \
|
||||
assertNullImpl(__FILE__, __LINE__, pointer, message)
|
||||
|
||||
#define assertDeprecated(message) \
|
||||
assertDeprecatedImpl(__FILE__, __LINE__, message)
|
||||
|
||||
#define assertStrLenMax(str, len, message) \
|
||||
assertTrue(strlen(str) < len, message)
|
||||
|
||||
#define assertStrLenMin(str, len, message) \
|
||||
assertTrue(strlen(str) >= len, message)
|
||||
|
||||
#else
|
||||
// If assertions are faked, we define the macros to do nothing.
|
||||
#define assertTrue(x, message) ((void)0)
|
||||
#define assertFalse(x, message) ((void)0)
|
||||
#define assertUnreachable(message) ((void)0)
|
||||
#define assertNotNull(pointer, message) ((void)0)
|
||||
#define assertNull(pointer, message) ((void)0)
|
||||
#define assertDeprecated(message) ((void)0)
|
||||
#define assertStrLenMax(str, len, message) ((void)0)
|
||||
#define assertStrLenMin(str, len, message) ((void)0)
|
||||
|
||||
#endif
|
||||
158
archive/dusk/chunk.py
Normal file
158
archive/dusk/chunk.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import json
|
||||
import os
|
||||
from tools.dusk.event import Event
|
||||
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
|
||||
from tools.dusk.tile import Tile
|
||||
from tools.dusk.entity import Entity
|
||||
from tools.dusk.region import Region
|
||||
from tools.editor.map.vertexbuffer import VertexBuffer
|
||||
from OpenGL.GL import *
|
||||
|
||||
class Chunk:
|
||||
def __init__(self, map, x, y, z):
|
||||
self.map = map
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.current = {}
|
||||
self.original = {}
|
||||
self.entities = {}
|
||||
self.regions = {}
|
||||
self.onChunkData = Event()
|
||||
self.dirty = False
|
||||
|
||||
self.tiles = {}
|
||||
self.vertexBuffer = VertexBuffer()
|
||||
|
||||
# Test Region
|
||||
region = self.regions[0] = Region(self)
|
||||
region.minX = 0
|
||||
region.minY = 0
|
||||
region.minZ = 0
|
||||
region.maxX = 32
|
||||
region.maxY = 32
|
||||
region.maxZ = 32
|
||||
region.updateVertexs()
|
||||
|
||||
# Gen tiles.
|
||||
tileIndex = 0
|
||||
for tz in range(CHUNK_DEPTH):
|
||||
for ty in range(CHUNK_HEIGHT):
|
||||
for tx in range(CHUNK_WIDTH):
|
||||
self.tiles[tileIndex] = Tile(self, tx, ty, tz, tileIndex)
|
||||
tileIndex += 1
|
||||
|
||||
# Update vertices
|
||||
self.tileUpdateVertices()
|
||||
|
||||
def reload(self, newX, newY, newZ):
|
||||
self.x = newX
|
||||
self.y = newY
|
||||
self.z = newZ
|
||||
self.entities = {}
|
||||
for tile in self.tiles.values():
|
||||
tile.chunkReload(newX, newY, newZ)
|
||||
self.load()
|
||||
|
||||
def tileUpdateVertices(self):
|
||||
self.vertexBuffer.clear()
|
||||
for tile in self.tiles.values():
|
||||
tile.buffer(self.vertexBuffer)
|
||||
self.vertexBuffer.buildData()
|
||||
|
||||
def load(self):
|
||||
fname = self.getFilename()
|
||||
if not fname or not os.path.exists(fname):
|
||||
self.new()
|
||||
return
|
||||
try:
|
||||
with open(fname, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not 'shapes' in data:
|
||||
data['shapes'] = []
|
||||
|
||||
# For each tile.
|
||||
for tile in self.tiles.values():
|
||||
tile.load(data)
|
||||
|
||||
# For each entity.
|
||||
self.entities = {}
|
||||
if 'entities' in data:
|
||||
for id, entData in enumerate(data['entities']):
|
||||
ent = Entity(self)
|
||||
ent.load(entData)
|
||||
self.entities[id] = ent
|
||||
|
||||
self.tileUpdateVertices()
|
||||
self.dirty = False
|
||||
self.onChunkData.invoke(self)
|
||||
self.map.onEntityData.invoke()
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to load chunk file: {e}")
|
||||
|
||||
def save(self):
|
||||
if not self.isDirty():
|
||||
return
|
||||
|
||||
dataOut = {
|
||||
'shapes': [],
|
||||
'entities': []
|
||||
}
|
||||
|
||||
for tile in self.tiles.values():
|
||||
dataOut['shapes'].append(tile.shape)
|
||||
|
||||
for ent in self.entities.values():
|
||||
entData = {}
|
||||
ent.save(entData)
|
||||
dataOut['entities'].append(entData)
|
||||
|
||||
fname = self.getFilename()
|
||||
if not fname:
|
||||
raise ValueError("No filename specified for saving chunk.")
|
||||
try:
|
||||
with open(fname, 'w') as f:
|
||||
json.dump(dataOut, f)
|
||||
self.dirty = False
|
||||
self.onChunkData.invoke(self)
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to save chunk file: {e}")
|
||||
|
||||
def new(self):
|
||||
for tile in self.tiles.values():
|
||||
tile.shape = TILE_SHAPE_NULL
|
||||
|
||||
self.tileUpdateVertices()
|
||||
self.dirty = False
|
||||
self.onChunkData.invoke(self)
|
||||
|
||||
def isDirty(self):
|
||||
return self.dirty
|
||||
|
||||
def getFilename(self):
|
||||
if not self.map or not hasattr(self.map, 'getChunkDirectory'):
|
||||
return None
|
||||
dirPath = self.map.getChunkDirectory()
|
||||
if dirPath is None:
|
||||
return None
|
||||
return f"{dirPath}/{self.x}_{self.y}_{self.z}.json"
|
||||
|
||||
def draw(self):
|
||||
self.vertexBuffer.draw()
|
||||
|
||||
def addEntity(self, localX=0, localY=0, localZ=0):
|
||||
ent = Entity(self, localX, localY, localZ)
|
||||
self.entities[len(self.entities)] = ent
|
||||
self.map.onEntityData.invoke()
|
||||
self.dirty = True
|
||||
return ent
|
||||
|
||||
def removeEntity(self, entity):
|
||||
for key, val in list(self.entities.items()):
|
||||
if val == entity:
|
||||
del self.entities[key]
|
||||
self.map.onEntityData.invoke()
|
||||
self.dirty = True
|
||||
return True
|
||||
return False
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "console/console.h"
|
||||
|
||||
void cmdEcho(const consolecmdexec_t *exec) {
|
||||
assertTrue(
|
||||
exec->argc >= 1,
|
||||
"echo command requires 1 argument."
|
||||
);
|
||||
|
||||
consolePrint("%s", exec->argv[0]);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "console/console.h"
|
||||
|
||||
void cmdGet(const consolecmdexec_t *exec) {
|
||||
assertTrue(
|
||||
exec->argc >= 1,
|
||||
"Get command requires 1 argument."
|
||||
);
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
|
||||
consolevar_t *var = &CONSOLE.variables[i];
|
||||
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
|
||||
consolePrint("%s", var->value);
|
||||
return;
|
||||
}
|
||||
|
||||
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "console/console.h"
|
||||
#include "game.h"
|
||||
|
||||
void cmdQuit(const consolecmdexec_t *exec) {
|
||||
consolePrint("Quitting application...");
|
||||
GAME.running = false;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "console/console.h"
|
||||
|
||||
void cmdSet(const consolecmdexec_t *exec) {
|
||||
assertTrue(exec->argc >= 2, "set command requires 2 arguments.");
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE.variableCount; i++) {
|
||||
consolevar_t *var = &CONSOLE.variables[i];
|
||||
if(stringCompare(var->name, exec->argv[0]) != 0) continue;
|
||||
consoleVarSetValue(var, exec->argv[1]);
|
||||
consolePrint("%s %s", var->name, var->value);
|
||||
for(i = 0; i < var->eventCount; i++) {
|
||||
assertNotNull(var->events[i], "Event is NULL");
|
||||
var->events[i](var);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
consolePrint("Error: Variable '%s' not found.", exec->argv[0]);
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "console.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "console/cmd/cmdquit.h"
|
||||
#include "console/cmd/cmdecho.h"
|
||||
#include "console/cmd/cmdset.h"
|
||||
#include "console/cmd/cmdget.h"
|
||||
|
||||
#include "input.h"
|
||||
|
||||
console_t CONSOLE;
|
||||
|
||||
void consoleInit() {
|
||||
memoryZero(&CONSOLE, sizeof(console_t));
|
||||
|
||||
// Register the get and set command.
|
||||
CONSOLE.cmdGet = consoleRegCmd("get", cmdGet);
|
||||
CONSOLE.cmdSet = consoleRegCmd("set", cmdSet);
|
||||
consoleRegCmd("quit", cmdQuit);
|
||||
consoleRegCmd("echo", cmdEcho);
|
||||
|
||||
consolePrint(" = Dawn Console = ");
|
||||
}
|
||||
|
||||
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function) {
|
||||
consolecmd_t *cmd = &CONSOLE.commands[CONSOLE.commandCount++];
|
||||
consoleCmdInit(cmd, name, function);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
consolevar_t * consoleRegVar(
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
) {
|
||||
consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++];
|
||||
consoleVarInitListener(var, name, value, event);
|
||||
return var;
|
||||
}
|
||||
|
||||
void consolePrint(const char_t *message, ...) {
|
||||
char_t buffer[CONSOLE_LINE_MAX];
|
||||
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
int32_t len = stringFormatVA(buffer, CONSOLE_LINE_MAX, message, args);
|
||||
va_end(args);
|
||||
|
||||
// Move all lines back
|
||||
memoryMove(
|
||||
CONSOLE.line[0],
|
||||
CONSOLE.line[1],
|
||||
(CONSOLE_HISTORY_MAX - 1) * CONSOLE_LINE_MAX
|
||||
);
|
||||
|
||||
// Copy the new line
|
||||
memoryCopy(
|
||||
CONSOLE.line[CONSOLE_HISTORY_MAX - 1],
|
||||
buffer,
|
||||
len + 1
|
||||
);
|
||||
printf("%s\n", buffer);
|
||||
}
|
||||
|
||||
void consoleExec(const char_t *line) {
|
||||
assertNotNull(line, "line must not be NULL");
|
||||
assertTrue(
|
||||
CONSOLE.execBufferCount < CONSOLE_EXEC_BUFFER_MAX,
|
||||
"Too many commands in the buffer."
|
||||
);
|
||||
|
||||
char_t buffer[CONSOLE_LINE_MAX];
|
||||
size_t i = 0, j = 0;
|
||||
char_t c;
|
||||
consoleexecstate_t state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
consolecmdexec_t *exec = NULL;
|
||||
|
||||
while(state != CONSOLE_EXEC_STATE_FULLY_PARSED) {
|
||||
c = line[i];
|
||||
|
||||
switch(state) {
|
||||
case CONSOLE_EXEC_STATE_INITIAL:
|
||||
assertTrue(j == 0, "Buffer not empty?");
|
||||
|
||||
if(c == '\0') {
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
break;
|
||||
}
|
||||
|
||||
if(stringIsWhitespace(c) || c == ';') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
state = CONSOLE_EXEC_STATE_PARSE_CMD;
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_CMD:
|
||||
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_CMD_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '"') {
|
||||
// Can't handle quotes within the command.
|
||||
consolePrint("Invalid command");
|
||||
while(c != '\0' && c != ';') c = line[++i];
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Command too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_CMD_PARSED:
|
||||
if(j == 0) {
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create exec
|
||||
assertNull(exec, "Existing command parsing?");
|
||||
|
||||
exec = &CONSOLE.execBuffer[CONSOLE.execBufferCount];
|
||||
memoryZero(exec, sizeof(consolecmdexec_t));
|
||||
|
||||
buffer[j] = '\0';
|
||||
stringCopy(exec->command, buffer, CONSOLE_LINE_MAX);
|
||||
state = CONSOLE_EXEC_STATE_FIND_ARG;
|
||||
|
||||
j = 0;// Free up buffer
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_FIND_ARG:
|
||||
if(c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_CMD_FINISHED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(stringIsWhitespace(c)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '"') {
|
||||
state = CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED;
|
||||
i++;
|
||||
} else {
|
||||
state = CONSOLE_EXEC_STATE_PARSE_ARG;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_ARG:
|
||||
if(stringIsWhitespace(c) || c == '\0' || c == ';') {
|
||||
state = CONSOLE_EXEC_STATE_ARG_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Arg too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED:
|
||||
if(c == '"') {
|
||||
state = CONSOLE_EXEC_STATE_ARG_PARSED;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '\0' || c == ';') {
|
||||
consolePrint("Unterminated quote");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(c == '\\') {
|
||||
c = line[++i];
|
||||
|
||||
if(c == '\0' || c == ';') {
|
||||
consolePrint("Unterminated quote");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[j++] = c;
|
||||
i++;
|
||||
|
||||
if(j >= CONSOLE_LINE_MAX) {
|
||||
consolePrint("Arg too long");
|
||||
state = CONSOLE_EXEC_STATE_FULLY_PARSED;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_ARG_PARSED:
|
||||
buffer[j] = '\0';
|
||||
stringCopy(exec->argv[exec->argc++], buffer, CONSOLE_LINE_MAX);
|
||||
state = CONSOLE_EXEC_STATE_FIND_ARG;
|
||||
j = 0;// Free up buffer
|
||||
break;
|
||||
|
||||
case CONSOLE_EXEC_STATE_CMD_FINISHED:
|
||||
assertNotNull(exec, "No command found?");
|
||||
|
||||
// Now, is there a command that matches?
|
||||
for(uint32_t k = 0; k < CONSOLE.commandCount; k++) {
|
||||
consolecmd_t *cmd = &CONSOLE.commands[k];
|
||||
if(stringCompare(cmd->name, exec->command) != 0) continue;
|
||||
exec->cmd = cmd;
|
||||
break;
|
||||
}
|
||||
|
||||
if(exec->cmd == NULL) {
|
||||
// Command wasn't found, is there a variable that matches?
|
||||
for(uint32_t k = 0; k < CONSOLE.variableCount; k++) {
|
||||
consolevar_t *var = &CONSOLE.variables[k];
|
||||
if(stringCompare(var->name, exec->command) != 0) continue;
|
||||
|
||||
// Matching variable found, is this a GET or a SET?
|
||||
if(exec->argc == 0) {
|
||||
exec->cmd = CONSOLE.cmdGet;
|
||||
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||
exec->argc = 1;
|
||||
} else {
|
||||
exec->cmd = CONSOLE.cmdSet;
|
||||
stringCopy(exec->argv[1], exec->argv[0], CONSOLE_LINE_MAX);
|
||||
stringCopy(exec->argv[0], exec->command, CONSOLE_LINE_MAX);
|
||||
exec->argc = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(exec->cmd == NULL) {
|
||||
consolePrint("Command not found", exec->command);
|
||||
exec = NULL;
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prep for next command.
|
||||
exec = NULL;
|
||||
state = CONSOLE_EXEC_STATE_INITIAL;
|
||||
CONSOLE.execBufferCount++;
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid state.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// May move these later
|
||||
void consoleUpdate() {
|
||||
if(inputPressed(INPUT_BIND_CONSOLE)) {
|
||||
CONSOLE.visible = !CONSOLE.visible;
|
||||
if(CONSOLE.visible) {
|
||||
consolePrint("Console opened.");
|
||||
} else {
|
||||
consolePrint("Console closed.");
|
||||
}
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < CONSOLE.execBufferCount; i++) {
|
||||
consolecmdexec_t *exec = &CONSOLE.execBuffer[i];
|
||||
assertNotNull(exec->cmd, "Command execution has no command.");
|
||||
exec->cmd->function(exec);
|
||||
}
|
||||
|
||||
// #if KEYBOARD_SUPPORT == 1
|
||||
// uint8_t key;
|
||||
// while((key = inputKeyboardPop()) != 0) {
|
||||
// printf("Key pressed: %c\n", key);
|
||||
// switch(key) {
|
||||
// case 0:
|
||||
// break;
|
||||
|
||||
// case INPUT_KEY_ENTER:
|
||||
// consoleExec(CONSOLE.inputBuffer);
|
||||
// CONSOLE.inputIndex = 0;
|
||||
// CONSOLE.inputBuffer[0] = '\0';
|
||||
// break;
|
||||
|
||||
// case INPUT_KEY_BACKSPACE:
|
||||
// if(CONSOLE.inputIndex > 0) {
|
||||
// CONSOLE.inputIndex--;
|
||||
// CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
|
||||
// }
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// if(
|
||||
// key >= INPUT_KEY_ASCII_START && key <= INPUT_KEY_ASCII_END &&
|
||||
// CONSOLE.inputIndex < CONSOLE_LINE_MAX - 1
|
||||
// ) {
|
||||
// CONSOLE.inputBuffer[CONSOLE.inputIndex++] = key;
|
||||
// CONSOLE.inputBuffer[CONSOLE.inputIndex] = '\0';
|
||||
// }
|
||||
// break;
|
||||
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
|
||||
// Clear the exec buffer
|
||||
CONSOLE.execBufferCount = 0;
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "consolevar.h"
|
||||
#include "consolecmd.h"
|
||||
|
||||
typedef enum {
|
||||
CONSOLE_EXEC_STATE_INITIAL,
|
||||
CONSOLE_EXEC_STATE_PARSE_CMD,
|
||||
CONSOLE_EXEC_STATE_CMD_PARSED,
|
||||
|
||||
CONSOLE_EXEC_STATE_FIND_ARG,
|
||||
CONSOLE_EXEC_STATE_PARSE_ARG,
|
||||
CONSOLE_EXEC_STATE_PARSE_ARG_QUOTED,
|
||||
CONSOLE_EXEC_STATE_ARG_PARSED,
|
||||
|
||||
CONSOLE_EXEC_STATE_CMD_FINISHED,
|
||||
CONSOLE_EXEC_STATE_FULLY_PARSED
|
||||
} consoleexecstate_t;
|
||||
|
||||
typedef struct {
|
||||
consolecmd_t commands[CONSOLE_COMMANDS_MAX];
|
||||
uint32_t commandCount;
|
||||
|
||||
consolevar_t variables[CONSOLE_VARIABLES_MAX];
|
||||
uint32_t variableCount;
|
||||
|
||||
char_t line[CONSOLE_HISTORY_MAX][CONSOLE_LINE_MAX];
|
||||
|
||||
consolecmdexec_t execBuffer[CONSOLE_EXEC_BUFFER_MAX];
|
||||
uint32_t execBufferCount;
|
||||
|
||||
consolecmd_t *cmdGet;
|
||||
consolecmd_t *cmdSet;
|
||||
|
||||
bool_t visible;
|
||||
|
||||
// May move these later
|
||||
// #if KEYBOARD_SUPPORT == 1
|
||||
// char_t inputBuffer[CONSOLE_LINE_MAX];
|
||||
// int32_t inputIndex;
|
||||
// #endif
|
||||
} console_t;
|
||||
|
||||
extern console_t CONSOLE;
|
||||
|
||||
/**
|
||||
* Initializes the console.
|
||||
*/
|
||||
void consoleInit();
|
||||
|
||||
/**
|
||||
* Registers a console command.
|
||||
*
|
||||
* @param name The name of the command.
|
||||
* @param function The function to execute when the command is called.
|
||||
* @return The registered command.
|
||||
*/
|
||||
consolecmd_t * consoleRegCmd(const char_t *name, consolecmdfunc_t function);
|
||||
|
||||
/**
|
||||
* Registers a console variable.
|
||||
*
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
* @param event The event to register.
|
||||
* @return The registered variable.
|
||||
*/
|
||||
consolevar_t * consoleRegVar(
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the value of a console variable.
|
||||
*
|
||||
* @param name The name of the variable.
|
||||
* @param value The new value of the variable.
|
||||
*/
|
||||
void consolePrint(
|
||||
const char_t *message,
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* Executes a console command.
|
||||
*
|
||||
* @param line The line to execute.
|
||||
*/
|
||||
void consoleExec(const char_t *line);
|
||||
|
||||
/**
|
||||
* Processes the console's pending commands.
|
||||
*/
|
||||
void consoleUpdate();
|
||||
|
||||
void cmdGet(const consolecmdexec_t *exec);
|
||||
void cmdSet(const consolecmdexec_t *exec);
|
||||
void cmdEcho(const consolecmdexec_t *exec);
|
||||
void cmdQuit(const consolecmdexec_t *exec);
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "consolecmd.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void consoleCmdInit(
|
||||
consolecmd_t *cmd,
|
||||
const char_t *name,
|
||||
consolecmdfunc_t function
|
||||
) {
|
||||
assertNotNull(cmd, "Command is NULL.");
|
||||
assertNotNull(name, "Name is NULL.");
|
||||
assertNotNull(function, "Function is NULL.");
|
||||
assertStrLenMin(name, 1, "Name is empty.");
|
||||
assertStrLenMax(name, CONSOLE_CMD_NAME_MAX, "Name is too long.");
|
||||
|
||||
memoryZero(cmd, sizeof(consolecmd_t));
|
||||
stringCopy(cmd->name, name, CONSOLE_CMD_NAME_MAX);
|
||||
cmd->function = function;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "consoledefs.h"
|
||||
|
||||
typedef struct consolecmd_s consolecmd_t;
|
||||
|
||||
typedef struct {
|
||||
consolecmd_t *cmd;
|
||||
char_t command[CONSOLE_LINE_MAX];
|
||||
char_t argv[CONSOLE_CMD_ARGC_MAX][CONSOLE_LINE_MAX];
|
||||
uint32_t argc;
|
||||
} consolecmdexec_t;
|
||||
|
||||
typedef void (*consolecmdfunc_t)(const consolecmdexec_t *exec);
|
||||
|
||||
typedef struct consolecmd_s {
|
||||
char_t name[CONSOLE_CMD_NAME_MAX];
|
||||
consolecmdfunc_t function;
|
||||
} consolecmd_t;
|
||||
|
||||
/**
|
||||
* Initializes a console command.
|
||||
*
|
||||
* @param cmd Pointer to the console command.
|
||||
* @param name The name of the command.
|
||||
* @param function The function to execute when the command is called.
|
||||
*/
|
||||
void consoleCmdInit(
|
||||
consolecmd_t *cmd,
|
||||
const char_t *name,
|
||||
consolecmdfunc_t function
|
||||
);
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CONSOLE_CMD_NAME_MAX 32
|
||||
#define CONSOLE_CMD_ARGC_MAX 16
|
||||
|
||||
#define CONSOLE_COMMANDS_MAX 128
|
||||
#define CONSOLE_VARIABLES_MAX 128
|
||||
#define CONSOLE_LINE_MAX 256
|
||||
#define CONSOLE_HISTORY_MAX 32
|
||||
#define CONSOLE_EXEC_BUFFER_MAX 16
|
||||
|
||||
#define CONSOLE_VAR_NAME_MAX 32
|
||||
#define CONSOLE_VAR_VALUE_MAX 128
|
||||
#define CONSOLE_VAR_EVENTS_MAX 8
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "consolevar.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
void consoleVarInit(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(name, "name must not be NULL");
|
||||
assertNotNull(value, "value must not be NULL");
|
||||
|
||||
assertStrLenMin(name, 1, "name must not be empty");
|
||||
assertStrLenMax(name, CONSOLE_VAR_NAME_MAX, "name is too long");
|
||||
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
|
||||
|
||||
memoryZero(var, sizeof(consolevar_t));
|
||||
stringCopy(var->name, name, CONSOLE_VAR_NAME_MAX);
|
||||
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
|
||||
}
|
||||
|
||||
void consoleVarInitListener(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
) {
|
||||
consoleVarInit(var, name, value);
|
||||
if(event) consoleVarListen(var, event);
|
||||
}
|
||||
|
||||
void consoleVarSetValue(consolevar_t *var, const char_t *value) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(value, "value must not be NULL");
|
||||
assertStrLenMax(value, CONSOLE_VAR_VALUE_MAX, "value is too long");
|
||||
|
||||
stringCopy(var->value, value, CONSOLE_VAR_VALUE_MAX);
|
||||
|
||||
uint8_t i = 0;
|
||||
while (i < var->eventCount) {
|
||||
var->events[i](var);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void consoleVarListen(consolevar_t *var, consolevarchanged_t event) {
|
||||
assertNotNull(var, "var must not be NULL");
|
||||
assertNotNull(event, "event must not be NULL");
|
||||
assertTrue(
|
||||
var->eventCount < CONSOLE_VAR_EVENTS_MAX,
|
||||
"Event count is too high"
|
||||
);
|
||||
var->events[var->eventCount++] = event;
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "consoledefs.h"
|
||||
|
||||
typedef struct consolevar_s consolevar_t;
|
||||
|
||||
typedef void (*consolevarchanged_t)(const consolevar_t *var);
|
||||
|
||||
typedef struct consolevar_s {
|
||||
char_t name[CONSOLE_VAR_NAME_MAX];
|
||||
char_t value[CONSOLE_VAR_VALUE_MAX];
|
||||
consolevarchanged_t events[CONSOLE_VAR_EVENTS_MAX];
|
||||
uint8_t eventCount;
|
||||
} consolevar_t;
|
||||
|
||||
/**
|
||||
* Initializes a console variable.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
*/
|
||||
void consoleVarInit(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes a console variable with a listener.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param name The name of the variable.
|
||||
* @param value The initial value of the variable.
|
||||
* @param event The event to register.
|
||||
*/
|
||||
void consoleVarInitListener(
|
||||
consolevar_t *var,
|
||||
const char_t *name,
|
||||
const char_t *value,
|
||||
consolevarchanged_t event
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the value of a console variable.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param value The new value of the variable.
|
||||
*/
|
||||
void consoleVarSetValue(consolevar_t *var, const char_t *value);
|
||||
|
||||
/**
|
||||
* Registers an event to be called when the value of a console variable changes.
|
||||
*
|
||||
* @param var Pointer to the console variable.
|
||||
* @param event The event to register.
|
||||
*/
|
||||
void consoleVarListen(consolevar_t *var, consolevarchanged_t event);
|
||||
49
archive/dusk/defs.py
Normal file
49
archive/dusk/defs.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from dotenv import load_dotenv, dotenv_values
|
||||
import os
|
||||
import sys
|
||||
|
||||
current_file_path = os.path.abspath(__file__)
|
||||
duskDefsPath = os.path.join(os.path.dirname(current_file_path), "..", "..", "src", "duskdefs.env")
|
||||
|
||||
# Ensure the .env file exists
|
||||
if not os.path.isfile(duskDefsPath):
|
||||
print(f"Error: .env file not found at {duskDefsPath}")
|
||||
sys.exit(1)
|
||||
|
||||
load_dotenv(dotenv_path=duskDefsPath)
|
||||
defs = {key: os.getenv(key) for key in os.environ.keys()}
|
||||
|
||||
fileDefs = dotenv_values(dotenv_path=duskDefsPath)
|
||||
|
||||
# Parsed out definitions
|
||||
CHUNK_WIDTH = int(defs.get('CHUNK_WIDTH'))
|
||||
CHUNK_HEIGHT = int(defs.get('CHUNK_HEIGHT'))
|
||||
CHUNK_DEPTH = int(defs.get('CHUNK_DEPTH'))
|
||||
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH
|
||||
CHUNK_VERTEX_COUNT_MAX = int(defs.get('CHUNK_VERTEX_COUNT_MAX'))
|
||||
|
||||
TILE_WIDTH = float(defs.get('TILE_WIDTH'))
|
||||
TILE_HEIGHT = float(defs.get('TILE_HEIGHT'))
|
||||
TILE_DEPTH = float(defs.get('TILE_DEPTH'))
|
||||
|
||||
RPG_CAMERA_PIXELS_PER_UNIT = float(defs.get('RPG_CAMERA_PIXELS_PER_UNIT'))
|
||||
RPG_CAMERA_Z_OFFSET = float(defs.get('RPG_CAMERA_Z_OFFSET'))
|
||||
RPG_CAMERA_FOV = float(defs.get('RPG_CAMERA_FOV'))
|
||||
|
||||
MAP_WIDTH = 5
|
||||
MAP_HEIGHT = 5
|
||||
MAP_DEPTH = 3
|
||||
MAP_CHUNK_COUNT = MAP_WIDTH * MAP_HEIGHT * MAP_DEPTH
|
||||
|
||||
TILE_SHAPES = {}
|
||||
for key in defs.keys():
|
||||
if key.startswith('TILE_SHAPE_'):
|
||||
globals()[key] = int(defs.get(key))
|
||||
TILE_SHAPES[key] = int(defs.get(key))
|
||||
|
||||
ENTITY_TYPES = {}
|
||||
for key in defs.keys():
|
||||
if key.startswith('ENTITY_TYPE_'):
|
||||
globals()[key] = int(defs.get(key))
|
||||
if key != 'ENTITY_TYPE_COUNT':
|
||||
ENTITY_TYPES[key] = int(defs.get(key))
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
#ifndef RENDER_WIDTH
|
||||
#define RENDER_WIDTH 320
|
||||
#endif
|
||||
#ifndef RENDER_HEIGHT
|
||||
#define RENDER_HEIGHT 240
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes the rendering system.
|
||||
*/
|
||||
errorret_t renderInit(void);
|
||||
|
||||
/**
|
||||
* Tells the rendering system to actually draw the frame.
|
||||
*/
|
||||
errorret_t renderDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the rendering system.
|
||||
*/
|
||||
errorret_t renderDispose(void);
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scene.h"
|
||||
#include "world/overworld.h"
|
||||
|
||||
scene_t SCENE_CURRENT;
|
||||
scenecallback_t SCENE_CALLBACKS[SCENE_COUNT] = {
|
||||
[SCENE_INITIAL] = {
|
||||
.init = NULL,
|
||||
.update = NULL
|
||||
},
|
||||
|
||||
[SCENE_OVERWORLD] = {
|
||||
.init = overworldInit,
|
||||
.update = overworldUpdate,
|
||||
.dispose = NULL
|
||||
}
|
||||
};
|
||||
|
||||
void sceneInit(void) {
|
||||
for(uint8_t i = 0; i < SCENE_COUNT; i++) {
|
||||
if(SCENE_CALLBACKS[i].init) {
|
||||
SCENE_CALLBACKS[i].init();
|
||||
}
|
||||
}
|
||||
|
||||
SCENE_CURRENT = SCENE_OVERWORLD;
|
||||
}
|
||||
|
||||
void sceneSet(const scene_t scene) {
|
||||
SCENE_CURRENT = scene;
|
||||
}
|
||||
|
||||
void sceneUpdate(void) {
|
||||
if(SCENE_CALLBACKS[SCENE_CURRENT].update) {
|
||||
SCENE_CALLBACKS[SCENE_CURRENT].update();
|
||||
}
|
||||
}
|
||||
|
||||
void sceneDispose(void) {
|
||||
for(uint8_t i = 0; i < SCENE_COUNT; i++) {
|
||||
if(SCENE_CALLBACKS[i].dispose) {
|
||||
SCENE_CALLBACKS[i].dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
SCENE_INITIAL,
|
||||
SCENE_OVERWORLD,
|
||||
|
||||
SCENE_COUNT
|
||||
} scene_t;
|
||||
|
||||
typedef struct {
|
||||
void (*init)(void);
|
||||
void (*update)(void);
|
||||
void (*dispose)(void);
|
||||
} scenecallback_t;
|
||||
|
||||
extern scene_t SCENE_CURRENT;
|
||||
extern scenecallback_t SCENE_CALLBACKS[SCENE_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes the scene module.
|
||||
*/
|
||||
void sceneInit(void);
|
||||
|
||||
/**
|
||||
* Sets the current scene.
|
||||
*
|
||||
* @param scene The scene to set.
|
||||
*/
|
||||
void sceneSet(const scene_t scene);
|
||||
|
||||
/**
|
||||
* Updates the current scene.
|
||||
*/
|
||||
void sceneUpdate(void);
|
||||
|
||||
/**
|
||||
* Disposes of the current scene.
|
||||
*/
|
||||
void sceneDispose(void);
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <float.h>
|
||||
|
||||
typedef bool bool_t;
|
||||
typedef int int_t;
|
||||
typedef float float_t;
|
||||
typedef char char_t;
|
||||
90
archive/dusk/entity.py
Normal file
90
archive/dusk/entity.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from tools.dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
|
||||
from tools.editor.map.vertexbuffer import VertexBuffer
|
||||
|
||||
class Entity:
|
||||
def __init__(self, chunk, localX=0, localY=0, localZ=0):
|
||||
self.type = ENTITY_TYPE_NPC
|
||||
self.name = "Unititled"
|
||||
self.localX = localX % CHUNK_WIDTH
|
||||
self.localY = localY % CHUNK_HEIGHT
|
||||
self.localZ = localZ % CHUNK_DEPTH
|
||||
|
||||
self.chunk = chunk
|
||||
self.vertexBuffer = VertexBuffer()
|
||||
pass
|
||||
|
||||
def load(self, obj):
|
||||
self.type = obj.get('type', ENTITY_TYPE_NULL)
|
||||
self.localX = obj.get('x', 0)
|
||||
self.localY = obj.get('y', 0)
|
||||
self.localZ = obj.get('z', 0)
|
||||
self.name = obj.get('name', "Untitled")
|
||||
pass
|
||||
|
||||
def save(self, obj):
|
||||
obj['type'] = self.type
|
||||
obj['name'] = self.name
|
||||
obj['x'] = self.localX
|
||||
obj['y'] = self.localY
|
||||
obj['z'] = self.localZ
|
||||
pass
|
||||
|
||||
def setType(self, entityType):
|
||||
if self.type == entityType:
|
||||
return
|
||||
self.type = entityType
|
||||
self.chunk.dirty = True
|
||||
self.chunk.map.onEntityData.invoke()
|
||||
|
||||
def setName(self, name):
|
||||
if self.name == name:
|
||||
return
|
||||
self.name = name
|
||||
self.chunk.dirty = True
|
||||
self.chunk.map.onEntityData.invoke()
|
||||
|
||||
def draw(self):
|
||||
self.vertexBuffer.clear()
|
||||
|
||||
startX = (self.chunk.x * CHUNK_WIDTH + self.localX) * TILE_WIDTH
|
||||
startY = (self.chunk.y * CHUNK_HEIGHT + self.localY) * TILE_HEIGHT
|
||||
startZ = (self.chunk.z * CHUNK_DEPTH + self.localZ) * TILE_DEPTH
|
||||
w = TILE_WIDTH
|
||||
h = TILE_HEIGHT
|
||||
d = TILE_DEPTH
|
||||
|
||||
# Center
|
||||
startX -= w / 2
|
||||
startY -= h / 2
|
||||
startZ -= d / 2
|
||||
|
||||
# Offset upwards a little
|
||||
startZ += 1
|
||||
|
||||
# Buffer simple quad at current position (need 6 positions)
|
||||
self.vertexBuffer.vertices = [
|
||||
startX, startY, startZ,
|
||||
startX + w, startY, startZ,
|
||||
startX + w, startY + h, startZ,
|
||||
startX, startY, startZ,
|
||||
startX + w, startY + h, startZ,
|
||||
startX, startY + h, startZ,
|
||||
]
|
||||
self.vertexBuffer.colors = [
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
1.0, 0.0, 1.0, 1.0,
|
||||
]
|
||||
self.vertexBuffer.uvs = [
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
]
|
||||
self.vertexBuffer.buildData()
|
||||
self.vertexBuffer.draw()
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "direction.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
float_t directionToAngle(const direction_t dir) {
|
||||
switch(dir) {
|
||||
case DIRECTION_NORTH: return (M_PI_2);
|
||||
case DIRECTION_SOUTH: return -(M_PI_2);
|
||||
case DIRECTION_EAST: return 0;
|
||||
case DIRECTION_WEST: return (M_PI);
|
||||
default: return 0; // Should never happen
|
||||
}
|
||||
}
|
||||
|
||||
void directionGetCoordinates(
|
||||
const direction_t dir,
|
||||
int8_t *x, int8_t *y
|
||||
) {
|
||||
assertNotNull(x, "X coordinate pointer cannot be NULL");
|
||||
assertNotNull(y, "Y coordinate pointer cannot be NULL");
|
||||
|
||||
switch(dir) {
|
||||
case DIRECTION_NORTH:
|
||||
*x = 0;
|
||||
*y = -1;
|
||||
break;
|
||||
|
||||
case DIRECTION_SOUTH:
|
||||
*x = 0;
|
||||
*y = 1;
|
||||
break;
|
||||
|
||||
case DIRECTION_EAST:
|
||||
*x = 1;
|
||||
*y = 0;
|
||||
break;
|
||||
|
||||
case DIRECTION_WEST:
|
||||
*x = -1;
|
||||
*y = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid direction");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
DIRECTION_SOUTH = 0,
|
||||
DIRECTION_EAST = 1,
|
||||
DIRECTION_WEST = 2,
|
||||
DIRECTION_NORTH = 3,
|
||||
|
||||
DIRECTION_UP = DIRECTION_NORTH,
|
||||
DIRECTION_DOWN = DIRECTION_SOUTH,
|
||||
DIRECTION_LEFT = DIRECTION_WEST,
|
||||
DIRECTION_RIGHT = DIRECTION_EAST,
|
||||
} direction_t;
|
||||
|
||||
/**
|
||||
* Converts a direction to an angle in float_t format.
|
||||
*
|
||||
* @param dir The direction to convert.
|
||||
* @return The angle corresponding to the direction.
|
||||
*/
|
||||
float_t directionToAngle(const direction_t dir);
|
||||
|
||||
/**
|
||||
* Gets the relative coordinates for a given direction.
|
||||
*
|
||||
* @param dir The direction to get coordinates for.
|
||||
* @param x Pointer to store the x coordinate.
|
||||
* @param y Pointer to store the y coordinate.
|
||||
*/
|
||||
void directionGetCoordinates(
|
||||
const direction_t dir,
|
||||
int8_t *x, int8_t *y
|
||||
);
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "world/world.h"
|
||||
#include "world/tiledata.h"
|
||||
#include "time.h"
|
||||
|
||||
entity_t ENTITIES[ENTITY_COUNT_MAX] = {0};
|
||||
|
||||
entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
{NULL}, // ENTITY_TYPE_NULL
|
||||
{
|
||||
.load = playerEntityLoad,
|
||||
.update = playerEntityUpdate,
|
||||
},
|
||||
{
|
||||
.load = npcLoad,
|
||||
.update = npcUpdate,
|
||||
.interact = npcInteract,
|
||||
},
|
||||
};
|
||||
|
||||
void entityLoad(entity_t *entity, const entity_t *source) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertNotNull(source, "Source entity pointer cannot be NULL");
|
||||
assertTrue(source->type != ENTITY_TYPE_NULL, "Source entity type NULL");
|
||||
assertTrue(source->type < ENTITY_TYPE_COUNT, "Source entity type bad");
|
||||
assertNotNull(
|
||||
ENTITY_CALLBACKS[source->type].load,
|
||||
"Entity type has no i nit callback"
|
||||
);
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
|
||||
entity->type = source->type;
|
||||
entity->x = source->x;
|
||||
entity->y = source->y;
|
||||
entity->dir = source->dir;
|
||||
entity->id = source->id;
|
||||
|
||||
ENTITY_CALLBACKS[entity->type].load(entity, source);
|
||||
}
|
||||
|
||||
void entityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
assertNotNull(
|
||||
ENTITY_CALLBACKS[entity->type].update,
|
||||
"Entity type has no update callback"
|
||||
);
|
||||
|
||||
ENTITY_CALLBACKS[entity->type].update(entity);
|
||||
|
||||
if(entity->subX > 0) {
|
||||
entity->subX -= entity->moveSpeed;
|
||||
} else if(entity->subX < 0) {
|
||||
entity->subX += entity->moveSpeed;
|
||||
}
|
||||
|
||||
if(entity->subY > 0) {
|
||||
entity->subY -= entity->moveSpeed;
|
||||
} else if(entity->subY < 0) {
|
||||
entity->subY += entity->moveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
void entityMove(entity_t *entity, const uint8_t moveSpeed) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
assertFalse(
|
||||
entityIsMoving(entity),
|
||||
"Entity is already moving, cannot move again"
|
||||
);
|
||||
|
||||
int8_t x = 0, y = 0;
|
||||
directionGetCoordinates(entity->dir, &x, &y);
|
||||
|
||||
// entity in way?
|
||||
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
|
||||
if(ent != NULL) return;
|
||||
|
||||
entity->x += x;
|
||||
entity->y += y;
|
||||
entity->subX = TILE_WIDTH_HEIGHT * -x;
|
||||
entity->subY = TILE_WIDTH_HEIGHT * -y;
|
||||
entity->moveSpeed = moveSpeed;
|
||||
}
|
||||
|
||||
void entityTurn(entity_t *entity, const direction_t dir) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
assertTrue(
|
||||
dir >= DIRECTION_SOUTH && dir <= DIRECTION_NORTH, "Invalid direction"
|
||||
);
|
||||
assertFalse(
|
||||
entityIsMoving(entity), "Entity is already moving, cannot turn"
|
||||
);
|
||||
|
||||
entity->dir = dir;
|
||||
}
|
||||
|
||||
bool_t entityIsMoving(const entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
return entity->subX != 0 || entity->subY != 0;
|
||||
}
|
||||
|
||||
entity_t * entityGetAt(
|
||||
const uint32_t tileX,
|
||||
const uint32_t tileY
|
||||
) {
|
||||
entity_t *entity = ENTITIES;
|
||||
|
||||
do {
|
||||
if(entity->type == ENTITY_TYPE_NULL) continue;
|
||||
if(entity->x == tileX && entity->y == tileY) return entity;
|
||||
} while((entity++) < &ENTITIES[ENTITY_COUNT_MAX - 1]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "direction.h"
|
||||
#include "player.h"
|
||||
#include "npc.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 32
|
||||
#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds
|
||||
#define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds.
|
||||
|
||||
typedef enum {
|
||||
ENTITY_TYPE_NULL = 0,
|
||||
ENTITY_TYPE_PLAYER = 1,
|
||||
ENTITY_TYPE_NPC = 2,
|
||||
} entitytype_t;
|
||||
#define ENTITY_TYPE_COUNT 3
|
||||
|
||||
typedef struct _entity_t {
|
||||
uint32_t id;// Completely unique ID for this entity.
|
||||
uint32_t x, y;
|
||||
int8_t subX, subY;
|
||||
uint8_t moveSpeed;
|
||||
|
||||
entitytype_t type;
|
||||
direction_t dir;
|
||||
|
||||
|
||||
union {
|
||||
npc_t npc;
|
||||
playerentity_t player;
|
||||
};
|
||||
} entity_t;
|
||||
|
||||
typedef struct {
|
||||
void (*load) (entity_t *entity, const entity_t *source);
|
||||
void (*update) (entity_t *entity);
|
||||
void (*interact)(entity_t *player, entity_t *self);
|
||||
} entitycallback_t;
|
||||
|
||||
extern entity_t ENTITIES[ENTITY_COUNT_MAX];
|
||||
extern entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT];
|
||||
|
||||
/**
|
||||
* Loads an entity from the generated entity data.
|
||||
*
|
||||
* @param entity Pointer to the entity to initialize.
|
||||
* @param source Pointer to the source entity data.
|
||||
*/
|
||||
void entityLoad(entity_t *entity, const entity_t *source);
|
||||
|
||||
/**
|
||||
* Updates the entity's state.
|
||||
*
|
||||
* @param entity Pointer to the entity to update.
|
||||
*/
|
||||
void entityUpdate(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Moves the entity by the specified x and y offsets.
|
||||
*
|
||||
* @param entity Pointer to the entity to move.
|
||||
* @param moveSpeed The speed at which to move the entity.
|
||||
*/
|
||||
void entityMove(entity_t *entity, const uint8_t moveSpeed);
|
||||
|
||||
/**
|
||||
* Turns the entity to face the specified direction.
|
||||
*
|
||||
* @param entity Pointer to the entity to turn.
|
||||
* @param dir The direction to turn the entity to.
|
||||
*/
|
||||
void entityTurn(entity_t *entity, const direction_t dir);
|
||||
|
||||
/**
|
||||
* Returns whether or not an entity is currently moving.
|
||||
*
|
||||
* @param entity Pointer to the entity to check.
|
||||
* @return True if the entity is moving, false otherwise.
|
||||
*/
|
||||
bool_t entityIsMoving(const entity_t *entity);
|
||||
|
||||
/**
|
||||
* Gets the entity at the specified tile coordinates.
|
||||
*
|
||||
* @param tileX The x coordinate of the tile to get the entity from.
|
||||
* @param tileY The y coordinate of the tile to get the entity from.
|
||||
* @return Pointer to the entity at the specified coordinates, or NULL if no
|
||||
* entity exists there.
|
||||
*/
|
||||
entity_t *entityGetAt(
|
||||
const uint32_t tileX,
|
||||
const uint32_t tileY
|
||||
);
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "locale/language.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void npcLoad(entity_t *entity, const entity_t *source) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertNotNull(source, "Source entity pointer cannot be NULL");
|
||||
assertTrue(source->type == ENTITY_TYPE_NPC, "Source entity type must be NPC");
|
||||
|
||||
entity->npc = source->npc;
|
||||
}
|
||||
|
||||
void npcUpdate(entity_t *entity) {
|
||||
}
|
||||
|
||||
void npcInteract(entity_t *player, entity_t *self) {
|
||||
assertTrue(self->type == ENTITY_TYPE_NPC, "Entity must be of type NPC");
|
||||
|
||||
switch(self->npc.interactType) {
|
||||
case NPC_INTERACT_TYPE_NONE:
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_TEXT:
|
||||
uiTextboxSetText(languageGet(self->npc.text));
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_CONVO:
|
||||
break;
|
||||
|
||||
case NPC_INTERACT_TYPE_EVENT:
|
||||
eventSetActive(self->npc.eventData);
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown NPC interaction type");
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
#include "event/eventlist.h"
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
|
||||
typedef enum {
|
||||
NPC_INTERACT_TYPE_NONE = 0,
|
||||
NPC_INTERACT_TYPE_TEXT = 1,
|
||||
NPC_INTERACT_TYPE_CONVO = 2,
|
||||
NPC_INTERACT_TYPE_EVENT = 3,
|
||||
} npcinteracttype_t;
|
||||
|
||||
typedef struct {
|
||||
npcinteracttype_t interactType;
|
||||
|
||||
union {
|
||||
const char_t* text;
|
||||
const eventdata_t *eventData;
|
||||
};
|
||||
} npc_t;
|
||||
|
||||
/**
|
||||
* Initializes the NPC entity.
|
||||
*
|
||||
* @param entity The entity to initialize.
|
||||
* @param source The source entity to copy data from.
|
||||
*/
|
||||
void npcLoad(entity_t *entity, const entity_t *source);
|
||||
|
||||
/**
|
||||
* Updates the NPC entity.
|
||||
*
|
||||
* @param entity The entity to update.
|
||||
*/
|
||||
void npcUpdate(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Handles interaction between the player and the NPC.
|
||||
*
|
||||
* @param player The player entity interacting with the NPC.
|
||||
* @param self The NPC entity being interacted with.
|
||||
*/
|
||||
void npcInteract(entity_t *player, entity_t *self);
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "input.h"
|
||||
#include "display/render.h"
|
||||
#include "world/world.h"
|
||||
|
||||
#include "ui/uitextbox.h"
|
||||
|
||||
inventory_t PLAYER_INVENTORY;
|
||||
|
||||
void playerInit() {
|
||||
entity_t *ent = &ENTITIES[0];
|
||||
|
||||
entity_t playerEntityData = {
|
||||
.id = PLAYER_ENTITY_ID,
|
||||
.type = ENTITY_TYPE_PLAYER,
|
||||
.x = WORLD_PLAYER_SPAWN_X,
|
||||
.y = WORLD_PLAYER_SPAWN_Y,
|
||||
};
|
||||
|
||||
entityLoad(ent, &playerEntityData);
|
||||
inventoryInit(&PLAYER_INVENTORY, INVENTORY_SIZE_MAX);
|
||||
}
|
||||
|
||||
void playerEntityLoad(entity_t *entity, const entity_t *source) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertNotNull(source, "Source entity pointer cannot be NULL");
|
||||
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
|
||||
assertTrue(source->type == entity->type, "Source/Entity type mismatch");
|
||||
}
|
||||
|
||||
void playerEntityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER");
|
||||
|
||||
// TODO: make this just a method somewhere.
|
||||
if(UI_TEXTBOX.visible) return;
|
||||
if(entityIsMoving(entity)) return;
|
||||
|
||||
const uint8_t moveSpeed = inputIsDown(INPUT_BIND_CANCEL) ? PLAYER_SPEED_RUN : PLAYER_SPEED_WALK;
|
||||
|
||||
if(inputIsDown(INPUT_BIND_UP)) {
|
||||
if(entity->dir != DIRECTION_NORTH) {
|
||||
entityTurn(entity, DIRECTION_NORTH);
|
||||
return;
|
||||
}
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
|
||||
} else if(inputIsDown(INPUT_BIND_DOWN)) {
|
||||
if(entity->dir != DIRECTION_SOUTH) {
|
||||
entityTurn(entity, DIRECTION_SOUTH);
|
||||
return;
|
||||
}
|
||||
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
} else if(inputIsDown(INPUT_BIND_LEFT)) {
|
||||
if(entity->dir != DIRECTION_WEST) {
|
||||
entityTurn(entity, DIRECTION_WEST);
|
||||
return;
|
||||
}
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
|
||||
} else if(inputIsDown(INPUT_BIND_RIGHT)) {
|
||||
if(entity->dir != DIRECTION_EAST) {
|
||||
entityTurn(entity, DIRECTION_EAST);
|
||||
return;
|
||||
}
|
||||
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Interact
|
||||
if(inputPressed(INPUT_BIND_ACTION)) {
|
||||
int8_t x, y;
|
||||
directionGetCoordinates(entity->dir, &x, &y);
|
||||
entity_t *ent = entityGetAt(entity->x + x, entity->y + y);
|
||||
|
||||
if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) {
|
||||
assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds");
|
||||
ENTITY_CALLBACKS[ent->type].interact(entity, ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "item/inventory.h"
|
||||
|
||||
#define PLAYER_SPEED_WALK 1
|
||||
#define PLAYER_SPEED_RUN 2
|
||||
|
||||
typedef struct _entity_t entity_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t nothing;
|
||||
} playerentity_t;
|
||||
|
||||
#define PLAYER_ENTITY_ID (UINT32_MAX-1)
|
||||
|
||||
extern inventory_t PLAYER_INVENTORY;
|
||||
|
||||
/**
|
||||
* Initializes the player and all player-related entities.
|
||||
*/
|
||||
void playerInit(void);
|
||||
|
||||
/**
|
||||
* Loads the player entity.
|
||||
*
|
||||
* @param entity The entity to initialize.
|
||||
* @param source The source entity to copy data from.
|
||||
*/
|
||||
void playerEntityLoad(entity_t *entity, const entity_t *source);
|
||||
|
||||
/**
|
||||
* Updates the player entity.
|
||||
*
|
||||
* @param entity The entity to update.
|
||||
*/
|
||||
void playerEntityUpdate(entity_t *entity);
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assert/assert.h"
|
||||
#include "error.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
errorret_t errorThrowImpl(
|
||||
errorstate_t *state,
|
||||
errorcode_t code,
|
||||
const char_t *file,
|
||||
const char_t *function,
|
||||
const int32_t line,
|
||||
const char_t *message,
|
||||
...
|
||||
) {
|
||||
assertNotNull(state, "Error state cannot be NULL");
|
||||
assertTrue(code != ERROR_OK, "Error code must not be OK");
|
||||
assertNotNull(file, "File cannot be NULL");
|
||||
assertNotNull(function, "Function cannot be NULL");
|
||||
assertTrue(line >= 0, "File pointer must be valid");
|
||||
assertNotNull(message, "Message cannot be NULL");
|
||||
|
||||
memoryZero(state, sizeof(errorstate_t));
|
||||
state->code = code;
|
||||
|
||||
// Format args.
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
|
||||
// Get length of formatted message
|
||||
va_list argsCopy;
|
||||
va_copy(argsCopy, args);
|
||||
int32_t len = stringFormatVA(NULL, 0, message, argsCopy);
|
||||
va_end(argsCopy);
|
||||
|
||||
// Create string to hold the formatted message
|
||||
state->message = (char_t *)memoryAllocate(len + 1);
|
||||
stringFormatVA(state->message, len + 1, message, args);
|
||||
va_end(args);
|
||||
|
||||
// Format lines
|
||||
len = stringFormat(NULL, 0, ERROR_LINE_FORMAT, file, line, function);
|
||||
assertTrue(len >= 0, "Line formatting failed");
|
||||
state->lines = (char_t *)memoryAllocate(len + 1);
|
||||
stringFormat(state->lines, len + 1, ERROR_LINE_FORMAT, file, line, function);
|
||||
|
||||
return (errorret_t) {
|
||||
.code = code,
|
||||
.state = state
|
||||
};
|
||||
}
|
||||
|
||||
errorret_t errorOkImpl() {
|
||||
return (errorret_t) {
|
||||
.code = ERROR_OK,
|
||||
.state = NULL
|
||||
};
|
||||
}
|
||||
|
||||
errorret_t errorChainImpl(
|
||||
const errorret_t retval,
|
||||
const char_t *file,
|
||||
const char_t *function,
|
||||
const int32_t line
|
||||
) {
|
||||
if (retval.code == ERROR_OK) return retval;
|
||||
|
||||
assertNotNull(retval.state, "Error state cannot be NULL");
|
||||
assertNotNull(retval.state->message, "Message cannot be NULL");
|
||||
|
||||
// Create a new line string.
|
||||
int32_t newLineLen = snprintf(NULL, 0, ERROR_LINE_FORMAT, file, line, function);
|
||||
assertTrue(newLineLen >= 0, "Line formatting failed");
|
||||
char_t *newLine = (char_t *)memoryAllocate(newLineLen + 1);
|
||||
snprintf(newLine, newLineLen + 1, ERROR_LINE_FORMAT, file, line, function);
|
||||
|
||||
// Resize the existing lines to accommodate the new line
|
||||
size_t existingLen = strlen(retval.state->lines);
|
||||
memoryResize(
|
||||
(void**)&retval.state->lines,
|
||||
existingLen,
|
||||
existingLen + newLineLen + 1
|
||||
);
|
||||
|
||||
// Now append the new line to the existing lines
|
||||
memoryCopy(
|
||||
retval.state->lines + existingLen,
|
||||
newLine,
|
||||
newLineLen + 1
|
||||
);
|
||||
|
||||
// Cleanup the temporary new line
|
||||
memoryFree(newLine);
|
||||
return retval;
|
||||
}
|
||||
|
||||
void errorCatch(const errorret_t retval) {
|
||||
if (retval.code == ERROR_OK) return;
|
||||
|
||||
assertNotNull(retval.state, "Error state cannot be NULL");
|
||||
assertNotNull(retval.state->message, "Message cannot be NULL");
|
||||
|
||||
memoryFree((void*)retval.state->message);
|
||||
}
|
||||
|
||||
errorret_t errorPrint(const errorret_t retval) {
|
||||
if (retval.code == ERROR_OK) return retval;
|
||||
|
||||
assertNotNull(retval.state, "Error state cannot be NULL");
|
||||
assertNotNull(retval.state->message, "Message cannot be NULL");
|
||||
|
||||
printf(
|
||||
ERROR_PRINT_FORMAT,
|
||||
retval.state->code,
|
||||
retval.state->message,
|
||||
retval.state->lines
|
||||
);
|
||||
return retval;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef uint8_t errorcode_t;
|
||||
|
||||
typedef struct {
|
||||
errorcode_t code;
|
||||
char_t *message;
|
||||
char_t *lines;
|
||||
} errorstate_t;
|
||||
|
||||
typedef struct {
|
||||
errorcode_t code;
|
||||
errorstate_t *state;
|
||||
} errorret_t;
|
||||
|
||||
static const errorcode_t ERROR_OK = 0;
|
||||
static const errorcode_t ERROR_NOT_OK = 1;
|
||||
static const char_t *ERROR_PRINT_FORMAT = "Error (%d): %s\n%s";
|
||||
static const char_t *ERROR_LINE_FORMAT = " at %s:%d in function %s\n";
|
||||
static errorstate_t ERROR_STATE = {
|
||||
.code = ERROR_OK,
|
||||
.message = NULL,
|
||||
.lines = NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the error state with the provided code and message.
|
||||
*
|
||||
* @param state The error state to initialize.
|
||||
* @param code The error code to set.
|
||||
* @param file The file where the error occurred.
|
||||
* @param function The function where the error occurred.
|
||||
* @param line The line number where the error occurred.
|
||||
* @param message The error message.
|
||||
* @param args The arguments for the error message.
|
||||
* @return The error code.
|
||||
*/
|
||||
errorret_t errorThrowImpl(
|
||||
errorstate_t *state,
|
||||
errorcode_t code,
|
||||
const char_t *file,
|
||||
const char_t *function,
|
||||
const int32_t line,
|
||||
const char_t *message,
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns an error state with no error.
|
||||
*
|
||||
* @return An error state with code ERROR_OK.
|
||||
*/
|
||||
errorret_t errorOkImpl();
|
||||
|
||||
/**
|
||||
* Chains an error state, allowing for error propagation.
|
||||
*
|
||||
* @param retval The return value containing the error state.
|
||||
* @param file The file where the error occurred.
|
||||
* @param function The function where the error occurred.
|
||||
* @param line The line number where the error occurred.
|
||||
* @return The error code if an error occurred, otherwise continues execution.
|
||||
*/
|
||||
errorret_t errorChainImpl(
|
||||
const errorret_t retval,
|
||||
const char_t *file,
|
||||
const char_t *function,
|
||||
const int32_t line
|
||||
);
|
||||
|
||||
/**
|
||||
* Catches an error and handles it.
|
||||
*
|
||||
* @param retval The return value containing the error state.
|
||||
*/
|
||||
void errorCatch(const errorret_t retval);
|
||||
|
||||
/**
|
||||
* Prints the error state to the console.
|
||||
*
|
||||
* @param retval The return value containing the error state.
|
||||
* @return Passed retval for chaining.
|
||||
*/
|
||||
errorret_t errorPrint(const errorret_t retval);
|
||||
|
||||
/**
|
||||
* Throws an error with a formatted message.
|
||||
*
|
||||
* @param code The error code to throw.
|
||||
* @param message The format string for the error message.
|
||||
* @param ... Additional arguments for the format string.
|
||||
* @return The error code.
|
||||
*/
|
||||
#define errorThrowWithCode(code, message, ... ) \
|
||||
return errorThrowImpl(\
|
||||
&ERROR_STATE, (code), __FILE__, __func__, __LINE__, (message), \
|
||||
__VA_ARGS__ \
|
||||
)
|
||||
|
||||
/**
|
||||
* Throws an error with a default error code of ERROR_NOT_OK.
|
||||
*
|
||||
* @param message The format string for the error message.
|
||||
* @param ... Additional arguments for the format string.
|
||||
* @return The error code.
|
||||
*/
|
||||
#define errorThrow(message, ...) \
|
||||
return errorThrowImpl(\
|
||||
&ERROR_STATE, ERROR_NOT_OK, __FILE__, __func__, __LINE__, (message), \
|
||||
__VA_ARGS__ \
|
||||
)
|
||||
|
||||
/**
|
||||
* Checks if a child method errored, and if it did, then send it up the chain.
|
||||
* @param retval The return value containing the error state.
|
||||
* @return The error code if an error occurred, otherwise continues execution.
|
||||
*/
|
||||
#define errorChain(retval) \
|
||||
if ((retval).code != ERROR_OK) { \
|
||||
return errorChainImpl(retval, __FILE__, __func__, __LINE__); \
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns without an error.
|
||||
*/
|
||||
#define errorOk() \
|
||||
return errorOkImpl()
|
||||
|
||||
// EOF
|
||||
18
archive/dusk/event.py
Normal file
18
archive/dusk/event.py
Normal file
@@ -0,0 +1,18 @@
|
||||
class Event:
|
||||
def __init__(self):
|
||||
self._subscribers = []
|
||||
|
||||
def sub(self, callback):
|
||||
"""Subscribe a callback to the event."""
|
||||
if callback not in self._subscribers:
|
||||
self._subscribers.append(callback)
|
||||
|
||||
def unsub(self, callback):
|
||||
"""Unsubscribe a callback from the event."""
|
||||
if callback in self._subscribers:
|
||||
self._subscribers.remove(callback)
|
||||
|
||||
def invoke(self, *args, **kwargs):
|
||||
"""Invoke all subscribers with the given arguments."""
|
||||
for callback in self._subscribers:
|
||||
callback(*args, **kwargs)
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "event.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
eventcallback_t EVENT_CALLBACKS[] = {
|
||||
[EVENT_TYPE_NULL] = { NULL, NULL },
|
||||
[EVENT_TYPE_TEXT] = { eventTextStart, eventTextUpdate },
|
||||
};
|
||||
|
||||
event_t EVENT;
|
||||
|
||||
void eventInit() {
|
||||
memoryZero(&EVENT, sizeof(event_t));
|
||||
}
|
||||
|
||||
void eventUpdate() {
|
||||
if(EVENT.active == NULL) {
|
||||
return; // No active event to update
|
||||
}
|
||||
|
||||
const eventitem_t *item = &EVENT.active->items[EVENT.item];
|
||||
assertNotNull(
|
||||
EVENT_CALLBACKS[item->type].update,
|
||||
"Event type does not have an update callback"
|
||||
);
|
||||
|
||||
EVENT_CALLBACKS[item->type].update(item);
|
||||
}
|
||||
|
||||
void eventSetActive(const eventdata_t *event) {
|
||||
assertNotNull(event, "Event data cannot be NULL");
|
||||
assertTrue(
|
||||
event->itemCount <= EVENT_ITEM_COUNT_MAX,
|
||||
"Event count too high"
|
||||
);
|
||||
assertTrue(event->itemCount > 0, "Event must have at least one item");
|
||||
|
||||
EVENT.active = event;
|
||||
EVENT.item = 0;
|
||||
|
||||
const eventitem_t *firstItem = &EVENT.active->items[EVENT.item];
|
||||
|
||||
assertNotNull(
|
||||
EVENT_CALLBACKS[firstItem->type].start,
|
||||
"Event type does not have a start callback"
|
||||
);
|
||||
EVENT_CALLBACKS[firstItem->type].start(firstItem);
|
||||
}
|
||||
|
||||
void eventNext() {
|
||||
assertNotNull(EVENT.active, "No active event to proceed with");
|
||||
assertTrue(EVENT.item < EVENT.active->itemCount, "No more items in the event");
|
||||
|
||||
EVENT.item++;
|
||||
if (EVENT.item >= EVENT.active->itemCount) {
|
||||
EVENT.active = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
const eventitem_t *nextItem = &EVENT.active->items[EVENT.item];
|
||||
assertNotNull(
|
||||
EVENT_CALLBACKS[nextItem->type].start,
|
||||
"Event type does not have a start callback"
|
||||
);
|
||||
EVENT_CALLBACKS[nextItem->type].start(nextItem);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "eventdata.h"
|
||||
|
||||
typedef struct {
|
||||
eventdata_t data;
|
||||
const eventdata_t *active;
|
||||
uint8_t item;
|
||||
} event_t;
|
||||
|
||||
typedef struct {
|
||||
void (*start)(const eventitem_t *item);
|
||||
void (*update)(const eventitem_t *item);
|
||||
} eventcallback_t;
|
||||
|
||||
extern eventcallback_t EVENT_CALLBACKS[EVENT_TYPE_COUNT];
|
||||
extern event_t EVENT;
|
||||
|
||||
/**
|
||||
* Initializes the event system.
|
||||
*/
|
||||
void eventInit();
|
||||
|
||||
/**
|
||||
* Updates the active event.
|
||||
*/
|
||||
void eventUpdate();
|
||||
|
||||
/**
|
||||
* Sets the active event.
|
||||
*
|
||||
* @param event The event to set as active.
|
||||
*/
|
||||
void eventSetActive(const eventdata_t *eventData);
|
||||
|
||||
/**
|
||||
* Goes to the next item in the active event. Only meant to be called by
|
||||
* event items.
|
||||
*/
|
||||
void eventNext();
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma ocne
|
||||
#include "eventtext.h"
|
||||
|
||||
typedef enum {
|
||||
EVENT_TYPE_NULL = 0,
|
||||
EVENT_TYPE_TEXT,
|
||||
} eventtype_t;
|
||||
|
||||
#define EVENT_TYPE_COUNT 2
|
||||
|
||||
typedef struct _eventitem_t {
|
||||
eventtype_t type;
|
||||
|
||||
union {
|
||||
eventtext_t text;
|
||||
};
|
||||
} eventitem_t;
|
||||
|
||||
#define EVENT_ITEM_COUNT_MAX 32
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "event.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void eventTextStart(const eventitem_t *item) {
|
||||
assertNotNull(item, "Event item cannot be NULL");
|
||||
assertTrue(item->type == EVENT_TYPE_TEXT, "Event item must be of type TEXT");
|
||||
assertNotNull(item->text, "Event item must have at least one text");
|
||||
uiTextboxSetText(item->text);
|
||||
}
|
||||
|
||||
void eventTextUpdate(const eventitem_t *item) {
|
||||
assertNotNull(item, "Event item cannot be NULL");
|
||||
assertTrue(item->type == EVENT_TYPE_TEXT, "Event item must be of type TEXT");
|
||||
|
||||
if(!UI_TEXTBOX.visible) {
|
||||
eventNext();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct _eventitem_t eventitem_t;
|
||||
|
||||
#define EVENT_TEXT_STRING_COUNT_MAX 8
|
||||
|
||||
typedef const char_t* eventtext_t;
|
||||
|
||||
/**
|
||||
* Starts the text event for the given item.
|
||||
*
|
||||
* @param item The event item to start.
|
||||
*/
|
||||
void eventTextStart(const eventitem_t *item);
|
||||
|
||||
/**
|
||||
* Updates the text event for the given item.
|
||||
*
|
||||
* @param item The event item to update.
|
||||
*/
|
||||
void eventTextUpdate(const eventitem_t *item);
|
||||
|
||||
/**
|
||||
* Query whether the text event is done or not.
|
||||
*
|
||||
* @param item The event item to check.
|
||||
*/
|
||||
bool_t eventTextIsDone(const eventitem_t *item);
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "game.h"
|
||||
#include "util/memory.h"
|
||||
#include "world/chunk.h"
|
||||
#include "display/scene.h"
|
||||
#include "world/overworld.h"
|
||||
#include "input.h"
|
||||
#include "event/event.h"
|
||||
#include "ui/uitextbox.h"
|
||||
#include "console/console.h"
|
||||
#include "util/memory.h"
|
||||
#include "time.h"
|
||||
|
||||
game_t GAME;
|
||||
|
||||
void gameInit(void) {
|
||||
memoryZero(&GAME, sizeof(game_t));
|
||||
GAME.running = true;
|
||||
|
||||
timeInit();
|
||||
consoleInit();
|
||||
inputInit();
|
||||
eventInit();
|
||||
uiTextboxInit();
|
||||
sceneInit();
|
||||
}
|
||||
|
||||
void gameUpdate(void) {
|
||||
timeUpdate();
|
||||
|
||||
// Game logic is tied to 60FPS for now, saves me a lot of hassle with float
|
||||
// issues
|
||||
float_t timeSinceLastTick = TIME.time - TIME.lastTick;
|
||||
while(timeSinceLastTick >= TIME_STEP) {
|
||||
|
||||
sceneUpdate();
|
||||
uiTextboxUpdate();
|
||||
eventUpdate();
|
||||
inputUpdate();
|
||||
|
||||
timeSinceLastTick -= TIME_STEP;
|
||||
TIME.lastTick = TIME.time;
|
||||
}
|
||||
|
||||
if(inputPressed(INPUT_BIND_QUIT)) consoleExec("quit");
|
||||
consoleUpdate();
|
||||
}
|
||||
|
||||
void gameDispose(void) {
|
||||
sceneDispose();
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct {
|
||||
bool_t running;
|
||||
} game_t;
|
||||
|
||||
extern game_t GAME;
|
||||
|
||||
/**
|
||||
* Initializes the game, this should be called before any other game functions.
|
||||
* This should be called by the parent platform at a time that it deems
|
||||
* appropriate. Any game systems cannot be used until this function has
|
||||
* been called.
|
||||
*
|
||||
* By the point this is called, we expect;
|
||||
* - Rendering has initialized and is ready to draw.
|
||||
* - Input has been initialized and is ready to be read.
|
||||
* - If your system handles time dynamically, it should be ready to be used.
|
||||
*
|
||||
* The systems called (in order) are;
|
||||
* - Console.
|
||||
* - Input system (Not the platforms input, but the game's input system).
|
||||
* - Time system (if applicable).
|
||||
* - Event System
|
||||
* - UI Systems.
|
||||
* - Gameplay systems.
|
||||
*/
|
||||
void gameInit(void);
|
||||
|
||||
/**
|
||||
* Asks the game to update, this will not do any drawing and should be called
|
||||
* in the main loop of the system, ideally either after or before the rendering
|
||||
* has occured.
|
||||
*/
|
||||
void gameUpdate(void);
|
||||
|
||||
/**
|
||||
* Cleans up resources used by the game, rendering really should still be
|
||||
* available at this point because we want to cleanup nicely.
|
||||
*/
|
||||
void gameDispose(void);
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "input.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
input_t INPUT;
|
||||
|
||||
void inputInit(void) {
|
||||
memoryZero(&INPUT, sizeof(input_t));
|
||||
}
|
||||
|
||||
void inputUpdate(void) {
|
||||
INPUT.previous = INPUT.current;
|
||||
INPUT.current = inputStateGet();
|
||||
}
|
||||
|
||||
bool_t inputIsDown(const inputbind_t bind) {
|
||||
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
|
||||
return (INPUT.current & bind) != 0;
|
||||
}
|
||||
|
||||
bool_t inputWasDown(const inputbind_t bind) {
|
||||
assertTrue(bind < INPUT_BIND_COUNT, "Input bind out of bounds");
|
||||
return (INPUT.previous & bind) != 0;
|
||||
}
|
||||
|
||||
bool_t inputPressed(const inputbind_t bind) {
|
||||
return inputIsDown(bind) && !inputWasDown(bind);
|
||||
}
|
||||
|
||||
bool_t inputReleased(const inputbind_t bind) {
|
||||
return !inputIsDown(bind) && inputWasDown(bind);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef uint8_t inputbind_t;
|
||||
typedef inputbind_t inputstate_t;
|
||||
|
||||
#define INPUT_BIND_UP (1 << 0)
|
||||
#define INPUT_BIND_DOWN (1 << 1)
|
||||
#define INPUT_BIND_LEFT (1 << 2)
|
||||
#define INPUT_BIND_RIGHT (1 << 3)
|
||||
#define INPUT_BIND_ACTION (1 << 4)
|
||||
#define INPUT_BIND_CANCEL (1 << 5)
|
||||
#define INPUT_BIND_CONSOLE (1 << 6)
|
||||
#define INPUT_BIND_QUIT (1 << 7)
|
||||
|
||||
#define INPUT_BIND_COUNT (INPUT_BIND_QUIT + 1)
|
||||
|
||||
typedef struct {
|
||||
uint8_t current;
|
||||
uint8_t previous;
|
||||
} input_t;
|
||||
|
||||
extern input_t INPUT;
|
||||
|
||||
/**
|
||||
* Initialize the input system.
|
||||
*/
|
||||
void inputInit(void);
|
||||
|
||||
/**
|
||||
* Updates the input state.
|
||||
*/
|
||||
void inputUpdate(void);
|
||||
|
||||
/**
|
||||
* Gets the current input state as a bitmask.
|
||||
*
|
||||
* @return The current input state as a bitmask.
|
||||
*/
|
||||
inputstate_t inputStateGet(void);
|
||||
|
||||
/**
|
||||
* Checks if a specific input bind is currently pressed.
|
||||
*
|
||||
* @param bind The input bind to check.
|
||||
* @return true if the bind is currently pressed, false otherwise.
|
||||
*/
|
||||
bool_t inputIsDown(const inputbind_t bind);
|
||||
|
||||
/**
|
||||
* Checks if a specific input bind was pressed in the last update.
|
||||
*
|
||||
* @param bind The input bind to check.
|
||||
* @return true if the bind was pressed in the last update, false otherwise.
|
||||
*/
|
||||
bool_t inputWasDown(const inputbind_t bind);
|
||||
|
||||
/**
|
||||
* Checks if a specific input bind was down this frame but not in the the
|
||||
* previous frame.
|
||||
*
|
||||
* @param bind The input bind to check.
|
||||
* @return true if the bind is currently pressed, false otherwise.
|
||||
*/
|
||||
bool_t inputPressed(const inputbind_t bind);
|
||||
|
||||
/**
|
||||
* Checks if a specific input bind was released this frame.
|
||||
*
|
||||
* @param bind The input bind to check.
|
||||
* @return true if the bind was released this frame, false otherwise.
|
||||
*/
|
||||
bool_t inputReleased(const inputbind_t bind);
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "inventory.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void inventoryInit(inventory_t *inventory, const uint8_t size) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(size <= INVENTORY_SIZE_MAX, "size exceeding INVENTORY_SIZE_MAX");
|
||||
assertTrue(size > 0, "size must be greater than 0");
|
||||
|
||||
memoryZero(inventory, sizeof(inventory_t));
|
||||
inventory->size = size;
|
||||
}
|
||||
|
||||
uint8_t inventoryItemIndexByType(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
|
||||
uint8_t item = inventory->itemCount;
|
||||
while(item--) {
|
||||
if(inventory->items[item].type == type) return item;
|
||||
}
|
||||
return INVENTORY_SIZE_MAX;
|
||||
}
|
||||
|
||||
uint8_t inventoryItemCount(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
|
||||
const uint8_t index = inventoryItemIndexByType(inventory, type);
|
||||
if(index == INVENTORY_SIZE_MAX) return 0;
|
||||
return inventory->items[index].count;
|
||||
}
|
||||
|
||||
void inventoryItemSet(
|
||||
inventory_t *inventory,
|
||||
const itemtype_t type,
|
||||
const uint8_t count
|
||||
) {
|
||||
assertNotNull(inventory, "inventory must not be NULL");
|
||||
assertTrue(type > ITEM_TYPE_NULL, "type must be greater than ITEM_TYPE_NULL");
|
||||
assertTrue(count > 0, "count must be greater than 0");
|
||||
|
||||
const uint8_t index = inventoryItemIndexByType(inventory, type);
|
||||
|
||||
if(index == INVENTORY_SIZE_MAX) {
|
||||
// Item does not exist, add it
|
||||
assertTrue(inventory->itemCount < inventory->size, "inventory is full");
|
||||
inventory->items[inventory->itemCount].type = type;
|
||||
inventory->items[inventory->itemCount].count = count;
|
||||
inventory->itemCount++;
|
||||
} else {
|
||||
// Item exists, update the count
|
||||
inventory->items[index].count = count;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "itemstack.h"
|
||||
|
||||
#define INVENTORY_SIZE_MAX UINT8_MAX
|
||||
|
||||
typedef struct {
|
||||
itemstack_t items[INVENTORY_SIZE_MAX];
|
||||
uint8_t itemCount;
|
||||
uint8_t size;
|
||||
} inventory_t;
|
||||
|
||||
/**
|
||||
* Initializes an inventory with a specified size.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to initialize.
|
||||
* @param size The size of the inventory (maximum is INVENTORY_SIZE_MAX).
|
||||
*/
|
||||
void inventoryInit(inventory_t *inventory, const uint8_t size);
|
||||
|
||||
/**
|
||||
* Finds the index of the item of a specified type in the inventory.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to search.
|
||||
* @param type The type of item to find.
|
||||
* @return The index of the item, or INVENTORY_SIZE_MAX if not found.
|
||||
*/
|
||||
uint8_t inventoryItemIndexByType(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the count of items of a specified type in the inventory.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to check.
|
||||
* @param type The type of item to count.
|
||||
* @return The count of items of the specified type.
|
||||
*/
|
||||
uint8_t inventoryItemCount(
|
||||
const inventory_t *inventory,
|
||||
const itemtype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the count of items of a specified type in the inventory.
|
||||
* If the item does not exist, it will be added.
|
||||
*
|
||||
* @param inventory Pointer to the inventory to modify.
|
||||
* @param type The type of item to set.
|
||||
* @param count The count of items to set.
|
||||
*/
|
||||
void inventoryItemSet(
|
||||
inventory_t *inventory,
|
||||
const itemtype_t type,
|
||||
const uint8_t count
|
||||
);
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
ITEM_TYPE_NULL = 0,
|
||||
|
||||
// MEDICINE
|
||||
ITEM_TYPE_POTION,
|
||||
|
||||
// INGREDIENTS
|
||||
ITEM_TYPE_ONION,
|
||||
ITEM_TYPE_SWEET_POTATO,
|
||||
ITEM_TYPE_CARROT,
|
||||
|
||||
// COOKED FOOD
|
||||
ITEM_TYPE_BAKED_SWEET_POTATO,
|
||||
} itemtype_t;
|
||||
@@ -1,145 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "language.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
language_t LANGUAGE;
|
||||
|
||||
void languageInit(void) {
|
||||
LANGUAGE.current = LANGUAGE_EN;
|
||||
}
|
||||
|
||||
const char_t * languageGet(const char_t *key) {
|
||||
assertNotNull(key, "Key cannot be NULL");
|
||||
assertTrue(LANGUAGE.current < LANGUAGE_COUNT, "Invalid language index");
|
||||
|
||||
int16_t keyIndex = -1;
|
||||
for(uint16_t i = 0; i < LANGUAGE_COUNTS[LANGUAGE.current]; i++) {
|
||||
if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) continue;
|
||||
keyIndex = i;
|
||||
break;
|
||||
}
|
||||
assertTrue(keyIndex != -1, "Key not found in language");
|
||||
return LANGUAGE_VALUES[LANGUAGE.current][keyIndex];
|
||||
}
|
||||
|
||||
uint16_t langaugeGetLength(const char_t *key) {
|
||||
assertNotNull(key, "Key cannot be NULL");
|
||||
assertTrue(LANGUAGE.current < LANGUAGE_COUNT, "Invalid language index");
|
||||
|
||||
int16_t keyIndex = -1;
|
||||
for(uint16_t i = 0; i < LANGUAGE_COUNTS[LANGUAGE.current]; i++) {
|
||||
if(strcmp(LANGUAGE_KEYS[LANGUAGE.current][i], key) != 0) continue;
|
||||
keyIndex = i;
|
||||
break;
|
||||
}
|
||||
assertTrue(keyIndex != -1, "Key not found in language");
|
||||
return strlen(LANGUAGE_VALUES[LANGUAGE.current][keyIndex]);
|
||||
}
|
||||
|
||||
uint16_t languageFormat(
|
||||
const char_t *key,
|
||||
char_t *buffer,
|
||||
uint16_t buffSize,
|
||||
const char_t **keys,
|
||||
const char_t **values,
|
||||
const uint16_t valueCount
|
||||
) {
|
||||
if(buffer != NULL) {
|
||||
assertTrue(buffSize > 0, "Buffer size must be greater than 0");
|
||||
} else {
|
||||
assertTrue(buffSize == 0, "Buffer size must be 0 if buffer is NULL");
|
||||
}
|
||||
|
||||
assertNotNull(key, "Key cannot be NULL");
|
||||
assertNotNull(keys, "Keys cannot be NULL");
|
||||
assertNotNull(values, "Values cannot be NULL");
|
||||
|
||||
const char_t *val = languageGet(key);
|
||||
assertNotNull(val, "Value for key cannot be NULL");
|
||||
|
||||
char_t c;
|
||||
uint16_t i = 0;
|
||||
uint16_t j = 0;
|
||||
uint8_t k = 0;
|
||||
bool_t inBraces = false;
|
||||
char_t braceBuffer[64] = {0};
|
||||
|
||||
#define bufferChar(c) ( \
|
||||
(buffer ? (buffer[j++] = c) : (j++)), \
|
||||
assertTrue(buffer ? j < buffSize : true, "Buffer overflow") \
|
||||
)
|
||||
|
||||
while((c = val[i++]) != '\0') {
|
||||
if(c == '{' && val[i] == '{') {
|
||||
goto startBraces;
|
||||
} else if(c == '}' && val[i] == '}') {
|
||||
goto endBraces;
|
||||
} else if(inBraces) {
|
||||
goto braceBuffering;
|
||||
} else {
|
||||
goto character;
|
||||
}
|
||||
|
||||
character: {
|
||||
bufferChar(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
braceBuffering: {
|
||||
assertFalse(val[i] == '\0', "Unexpected end of string.");
|
||||
braceBuffer[k++] = c;
|
||||
assertTrue(k < sizeof(braceBuffer), "Brace buffer overflow");
|
||||
if(val[i] == ' ') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
startBraces: {
|
||||
assertFalse(inBraces, "Nested braces are not allowed");
|
||||
|
||||
inBraces = true;
|
||||
i++;
|
||||
k = 0;
|
||||
assertFalse(val[i] == '\0', "Unexpected end of string.");
|
||||
if(val[i] == ' ') i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
endBraces: {
|
||||
assertTrue(inBraces, "Unmatched closing brace found");
|
||||
|
||||
inBraces = false;
|
||||
i++;
|
||||
braceBuffer[k] = '\0';
|
||||
|
||||
uint16_t l;
|
||||
for(l = 0; l < valueCount; l++) {
|
||||
if(strcmp(braceBuffer, keys[l]) != 0) {
|
||||
continue;
|
||||
}
|
||||
const char_t *replacement = values[l];
|
||||
|
||||
uint16_t r = 0;
|
||||
while((c = replacement[r++]) != '\0') {
|
||||
bufferChar(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
assertTrue(l < valueCount, "No string replacement found!");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer){
|
||||
assertTrue(j < buffSize, "Buffer overflow");
|
||||
buffer[j] = '\0';
|
||||
}
|
||||
|
||||
assertFalse(inBraces, "Unmatched opening brace found");
|
||||
return j;
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "locale/language/languages.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t current;
|
||||
} language_t;
|
||||
|
||||
extern language_t LANGUAGE;
|
||||
|
||||
/**
|
||||
* Initializes the language system.
|
||||
*
|
||||
* This function sets up the language system, loading the default language
|
||||
* and preparing any necessary resources for language handling.
|
||||
*/
|
||||
void languageInit(void);
|
||||
|
||||
/**
|
||||
* Gets a language string by its key.
|
||||
*
|
||||
* @param key The key for the language string.
|
||||
* @return The language string associated with the key.
|
||||
*/
|
||||
const char_t * languageGet(const char_t *key);
|
||||
|
||||
/**
|
||||
* Gets the length of a language string by its key.
|
||||
*
|
||||
* @param key The key for the language string.
|
||||
* @return The length of the language string associated with the key.
|
||||
*/
|
||||
uint16_t langaugeGetLength(const char_t *key);
|
||||
|
||||
/**
|
||||
* Formats a language string with given keys and values.
|
||||
*
|
||||
* This function replaces placeholders in the language string with the provided
|
||||
* values based on the keys.
|
||||
*
|
||||
* If buffer is NULL, the function will instead calculate the length of the
|
||||
* formatted string.
|
||||
*
|
||||
* @param key The key for the language string to format.
|
||||
* @param buffer The buffer to store the formatted string.
|
||||
* @param buffSize The size of the buffer.
|
||||
* @param keys An array of keys to replace in the language string.
|
||||
* @param values An array of values corresponding to the keys.
|
||||
* @param valueCount The number of key-value pairs.
|
||||
* @return The number of characters written to the buffer.
|
||||
*/
|
||||
uint16_t languageFormat(
|
||||
const char_t *key,
|
||||
char_t *buffer,
|
||||
uint16_t buffSize,
|
||||
const char_t **keys,
|
||||
const char_t **values,
|
||||
const uint16_t valueCount
|
||||
);
|
||||
259
archive/dusk/map.py
Normal file
259
archive/dusk/map.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import json
|
||||
import sys
|
||||
from tools.dusk.event import Event
|
||||
from PyQt5.QtWidgets import QFileDialog, QMessageBox
|
||||
from PyQt5.QtCore import QTimer
|
||||
import os
|
||||
from tools.dusk.chunk import Chunk
|
||||
from tools.dusk.defs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH
|
||||
import traceback
|
||||
|
||||
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../assets/map/')
|
||||
EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor')
|
||||
|
||||
class Map:
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.data = {}
|
||||
self.dataOriginal = {}
|
||||
self.position = [None, None, None] # x, y, z
|
||||
self.topLeftX = None
|
||||
self.topLeftY = None
|
||||
self.topLeftZ = None
|
||||
self.chunks = {}
|
||||
self.onMapData = Event()
|
||||
self.onPositionChange = Event()
|
||||
self.onEntityData = Event()
|
||||
self.mapFileName = None
|
||||
self.lastFile = None
|
||||
self.firstLoad = True
|
||||
|
||||
index = 0
|
||||
for x in range(MAP_WIDTH):
|
||||
for y in range(MAP_HEIGHT):
|
||||
for z in range(MAP_DEPTH):
|
||||
self.chunks[index] = Chunk(self, x, y, z)
|
||||
index += 1
|
||||
|
||||
# Only in editor instances:
|
||||
self.moveTo(0, 0, 0)
|
||||
if parent is not None:
|
||||
QTimer.singleShot(16, self.loadLastFile)
|
||||
|
||||
def loadLastFile(self):
|
||||
if not os.path.exists(EDITOR_CONFIG_PATH):
|
||||
return
|
||||
try:
|
||||
with open(EDITOR_CONFIG_PATH, 'r') as f:
|
||||
config = json.load(f)
|
||||
lastFile = config.get('lastFile')
|
||||
lastPosition = config.get('lastPosition')
|
||||
leftPanelIndex = config.get('leftPanelIndex')
|
||||
if lastFile and os.path.exists(lastFile):
|
||||
self.load(lastFile)
|
||||
if lastPosition and isinstance(lastPosition, list) and len(lastPosition) == 3:
|
||||
self.moveTo(*lastPosition)
|
||||
if leftPanelIndex is not None:
|
||||
self.parent.leftPanel.tabs.setCurrentIndex(leftPanelIndex)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
def updateEditorConfig(self):
|
||||
if self.parent is None:
|
||||
return
|
||||
try:
|
||||
mapFileName = self.getMapFilename()
|
||||
config = {
|
||||
'lastFile': mapFileName if mapFileName else "",
|
||||
'lastPosition': self.position,
|
||||
'leftPanelIndex': self.parent.leftPanel.tabs.currentIndex()
|
||||
}
|
||||
config_dir = os.path.dirname(EDITOR_CONFIG_PATH)
|
||||
if not os.path.exists(config_dir):
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
with open(EDITOR_CONFIG_PATH, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
def newFile(self):
|
||||
self.data = {}
|
||||
self.dataOriginal = {}
|
||||
self.mapFileName = None
|
||||
self.lastFile = None
|
||||
for chunk in self.chunks.values():
|
||||
chunk.new()
|
||||
self.moveTo(0, 0, 0)
|
||||
self.onMapData.invoke(self.data)
|
||||
self.updateEditorConfig()
|
||||
|
||||
def save(self, fname=None):
|
||||
if not self.getMapFilename() and fname is None:
|
||||
filePath, _ = QFileDialog.getSaveFileName(None, "Save Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
|
||||
if not filePath:
|
||||
return
|
||||
self.mapFileName = filePath
|
||||
if fname:
|
||||
self.mapFileName = fname
|
||||
try:
|
||||
with open(self.getMapFilename(), 'w') as f:
|
||||
json.dump(self.data, f, indent=2)
|
||||
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
|
||||
for chunk in self.chunks.values():
|
||||
chunk.save()
|
||||
self.updateEditorConfig()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(None, "Save Error", f"Failed to save map file:\n{e}")
|
||||
|
||||
def load(self, fileName):
|
||||
try:
|
||||
with open(fileName, 'r') as f:
|
||||
self.data = json.load(f)
|
||||
self.mapFileName = fileName
|
||||
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
|
||||
for chunk in self.chunks.values():
|
||||
chunk.load()
|
||||
self.onMapData.invoke(self.data)
|
||||
self.updateEditorConfig()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(None, "Load Error", f"Failed to load map file:\n{e}")
|
||||
|
||||
def isMapFileDirty(self):
|
||||
return json.dumps(self.data, sort_keys=True) != json.dumps(self.dataOriginal, sort_keys=True)
|
||||
|
||||
def isDirty(self):
|
||||
return self.isMapFileDirty() or self.anyChunksDirty()
|
||||
|
||||
def getMapFilename(self):
|
||||
return self.mapFileName if self.mapFileName and os.path.exists(self.mapFileName) else None
|
||||
|
||||
def getMapDirectory(self):
|
||||
if self.mapFileName is None:
|
||||
return None
|
||||
dirname = os.path.dirname(self.mapFileName)
|
||||
return dirname
|
||||
|
||||
def getChunkDirectory(self):
|
||||
dirName = self.getMapDirectory()
|
||||
if dirName is None:
|
||||
return None
|
||||
return os.path.join(dirName, 'chunks')
|
||||
|
||||
def anyChunksDirty(self):
|
||||
for chunk in self.chunks.values():
|
||||
if chunk.isDirty():
|
||||
return True
|
||||
return False
|
||||
|
||||
def moveTo(self, x, y, z):
|
||||
if self.position == [x, y, z]:
|
||||
return
|
||||
|
||||
# We need to decide if the chunks should be unloaded here or not.
|
||||
newTopLeftChunkX = x // CHUNK_WIDTH - (MAP_WIDTH // 2)
|
||||
newTopLeftChunkY = y // CHUNK_HEIGHT - (MAP_HEIGHT // 2)
|
||||
newTopLeftChunkZ = z // CHUNK_DEPTH - (MAP_DEPTH // 2)
|
||||
|
||||
if(newTopLeftChunkX != self.topLeftX or
|
||||
newTopLeftChunkY != self.topLeftY or
|
||||
newTopLeftChunkZ != self.topLeftZ):
|
||||
|
||||
chunksToUnload = []
|
||||
chunksToKeep = []
|
||||
for chunk in self.chunks.values():
|
||||
chunkWorldX = chunk.x
|
||||
chunkWorldY = chunk.y
|
||||
chunkWorldZ = chunk.z
|
||||
if(chunkWorldX < newTopLeftChunkX or
|
||||
chunkWorldX >= newTopLeftChunkX + MAP_WIDTH or
|
||||
chunkWorldY < newTopLeftChunkY or
|
||||
chunkWorldY >= newTopLeftChunkY + MAP_HEIGHT or
|
||||
chunkWorldZ < newTopLeftChunkZ or
|
||||
chunkWorldZ >= newTopLeftChunkZ + MAP_DEPTH):
|
||||
chunksToUnload.append(chunk)
|
||||
else:
|
||||
chunksToKeep.append(chunk)
|
||||
|
||||
# Unload chunks that are out of the new bounds.
|
||||
for chunk in chunksToUnload:
|
||||
if chunk.isDirty():
|
||||
print(f"Can't move map, some chunks are dirty: ({chunk.x}, {chunk.y}, {chunk.z})")
|
||||
return
|
||||
|
||||
# Now we can safely unload the chunks.
|
||||
chunkIndex = 0
|
||||
newChunks = {}
|
||||
for chunk in chunksToKeep:
|
||||
newChunks[chunkIndex] = chunk
|
||||
chunkIndex += 1
|
||||
|
||||
for xPos in range(newTopLeftChunkX, newTopLeftChunkX + MAP_WIDTH):
|
||||
for yPos in range(newTopLeftChunkY, newTopLeftChunkY + MAP_HEIGHT):
|
||||
for zPos in range(newTopLeftChunkZ, newTopLeftChunkZ + MAP_DEPTH):
|
||||
# Check if we already have this chunk.
|
||||
found = False
|
||||
for chunk in chunksToKeep:
|
||||
if chunk.x == xPos and chunk.y == yPos and chunk.z == zPos:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
# Create a new chunk.
|
||||
newChunk = chunksToUnload.pop()
|
||||
newChunk.reload(xPos, yPos, zPos)
|
||||
newChunks[chunkIndex] = newChunk
|
||||
chunkIndex += 1
|
||||
|
||||
self.chunks = newChunks
|
||||
self.topLeftX = newTopLeftChunkX
|
||||
self.topLeftY = newTopLeftChunkY
|
||||
self.topLeftZ = newTopLeftChunkZ
|
||||
|
||||
self.position = [x, y, z]
|
||||
self.onPositionChange.invoke(self.position)
|
||||
if not self.firstLoad:
|
||||
self.updateEditorConfig()
|
||||
self.firstLoad = False
|
||||
|
||||
def moveRelative(self, x, y, z):
|
||||
self.moveTo(
|
||||
self.position[0] + x,
|
||||
self.position[1] + y,
|
||||
self.position[2] + z
|
||||
)
|
||||
|
||||
def draw(self):
|
||||
for chunk in self.chunks.values():
|
||||
chunk.draw()
|
||||
|
||||
for chunk in self.chunks.values():
|
||||
for entity in chunk.entities.values():
|
||||
entity.draw()
|
||||
|
||||
# Only render on Region tab
|
||||
if self.parent.leftPanel.tabs.currentWidget() == self.parent.leftPanel.regionPanel:
|
||||
for chunk in self.chunks.values():
|
||||
for region in chunk.regions.values():
|
||||
region.draw()
|
||||
|
||||
def getChunkAtWorldPos(self, x, y, z):
|
||||
chunkX = x // CHUNK_WIDTH
|
||||
chunkY = y // CHUNK_HEIGHT
|
||||
chunkZ = z // CHUNK_DEPTH
|
||||
for chunk in self.chunks.values():
|
||||
if chunk.x == chunkX and chunk.y == chunkY and chunk.z == chunkZ:
|
||||
return chunk
|
||||
return None
|
||||
|
||||
def getTileAtWorldPos(self, x, y, z):
|
||||
chunk = self.getChunkAtWorldPos(x, y, z)
|
||||
if not chunk:
|
||||
print("No chunk found at position:", (x, y, z))
|
||||
return None
|
||||
|
||||
tileX = x % CHUNK_WIDTH
|
||||
tileY = y % CHUNK_HEIGHT
|
||||
tileZ = z % CHUNK_DEPTH
|
||||
tileIndex = tileX + tileY * CHUNK_WIDTH + tileZ * CHUNK_WIDTH * CHUNK_HEIGHT
|
||||
return chunk.tiles.get(tileIndex)
|
||||
141
archive/dusk/region.py
Normal file
141
archive/dusk/region.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from tools.dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
|
||||
from tools.editor.map.vertexbuffer import VertexBuffer
|
||||
from OpenGL.GL import *
|
||||
from OpenGL.GLU import *
|
||||
|
||||
class Region:
|
||||
def __init__(self, chunk):
|
||||
self.minX = 0
|
||||
self.minY = 0
|
||||
self.minZ = 0
|
||||
self.maxX = 0
|
||||
self.maxY = 0
|
||||
self.maxZ = 0
|
||||
self.chunk = chunk
|
||||
self.vertexBuffer = VertexBuffer()
|
||||
self.color = (1.0, 0.0, 0.0)
|
||||
self.updateVertexs()
|
||||
pass
|
||||
|
||||
def updateVertexs(self):
|
||||
# Draw a quad, semi transparent with solid outlines
|
||||
vminX = (self.minX * CHUNK_WIDTH) * TILE_WIDTH
|
||||
vminY = (self.minY * CHUNK_HEIGHT) * TILE_HEIGHT
|
||||
vminZ = (self.minZ * CHUNK_DEPTH) * TILE_DEPTH
|
||||
vmaxX = (self.maxX * CHUNK_WIDTH) * TILE_WIDTH
|
||||
vmaxY = (self.maxY * CHUNK_HEIGHT) * TILE_HEIGHT
|
||||
vmaxZ = (self.maxZ * CHUNK_DEPTH) * TILE_DEPTH
|
||||
alpha = 0.25
|
||||
|
||||
# Move back half a tile width
|
||||
vminX -= TILE_WIDTH / 2
|
||||
vmaxX -= TILE_WIDTH / 2
|
||||
vminY -= TILE_HEIGHT / 2
|
||||
vmaxY -= TILE_HEIGHT / 2
|
||||
vminZ -= TILE_DEPTH / 2
|
||||
vmaxZ -= TILE_DEPTH / 2
|
||||
|
||||
# Cube (6 verts per face)
|
||||
self.vertexBuffer.vertices = [
|
||||
# Front face
|
||||
vminX, vminY, vmaxZ,
|
||||
vmaxX, vminY, vmaxZ,
|
||||
vmaxX, vmaxY, vmaxZ,
|
||||
vminX, vminY, vmaxZ,
|
||||
vmaxX, vmaxY, vmaxZ,
|
||||
vminX, vmaxY, vmaxZ,
|
||||
|
||||
# Back face
|
||||
vmaxX, vminY, vminZ,
|
||||
vminX, vminY, vminZ,
|
||||
vminX, vmaxY, vminZ,
|
||||
vmaxX, vminY, vminZ,
|
||||
vminX, vmaxY, vminZ,
|
||||
vmaxX, vmaxY, vminZ,
|
||||
|
||||
# Left face
|
||||
vminX, vminY, vminZ,
|
||||
vminX, vminY, vmaxZ,
|
||||
vminX, vmaxY, vmaxZ,
|
||||
vminX, vminY, vminZ,
|
||||
vminX, vmaxY, vmaxZ,
|
||||
vminX, vmaxY, vminZ,
|
||||
|
||||
# Right face
|
||||
vmaxX, vminY, vmaxZ,
|
||||
vmaxX, vminY, vminZ,
|
||||
vmaxX, vmaxY, vminZ,
|
||||
vmaxX, vminY, vmaxZ,
|
||||
vmaxX, vmaxY, vminZ,
|
||||
vmaxX, vmaxY, vmaxZ,
|
||||
|
||||
# Top face
|
||||
vminX, vmaxY, vmaxZ,
|
||||
vmaxX, vmaxY, vmaxZ,
|
||||
vmaxX, vmaxY, vminZ,
|
||||
vminX, vmaxY, vmaxZ,
|
||||
vmaxX, vmaxY, vminZ,
|
||||
vminX, vmaxY, vminZ,
|
||||
|
||||
# Bottom face
|
||||
vminX, vminY, vminZ,
|
||||
vmaxX, vminY, vminZ,
|
||||
vmaxX, vminY, vmaxZ,
|
||||
vminX, vminY, vminZ,
|
||||
vmaxX, vminY, vmaxZ,
|
||||
vminX, vminY, vmaxZ,
|
||||
]
|
||||
|
||||
self.vertexBuffer.colors = [
|
||||
# Front face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
|
||||
# Back face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
|
||||
# Left face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
|
||||
# Right face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
|
||||
# Top face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
|
||||
# Bottom face
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
self.color[0], self.color[1], self.color[2], alpha,
|
||||
]
|
||||
self.vertexBuffer.buildData()
|
||||
|
||||
def draw(self):
|
||||
self.vertexBuffer.draw()
|
||||
206
archive/dusk/tile.py
Normal file
206
archive/dusk/tile.py
Normal file
@@ -0,0 +1,206 @@
|
||||
from OpenGL.GL import *
|
||||
from tools.dusk.defs import (
|
||||
TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH,
|
||||
CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH,
|
||||
TILE_SHAPE_NULL, TILE_SHAPE_FLOOR,
|
||||
TILE_SHAPE_RAMP_NORTH, TILE_SHAPE_RAMP_SOUTH,
|
||||
TILE_SHAPE_RAMP_EAST, TILE_SHAPE_RAMP_WEST,
|
||||
TILE_SHAPE_RAMP_SOUTHWEST, TILE_SHAPE_RAMP_SOUTHEAST,
|
||||
TILE_SHAPE_RAMP_NORTHWEST, TILE_SHAPE_RAMP_NORTHEAST
|
||||
)
|
||||
|
||||
def getItem(arr, index, default):
|
||||
if index < len(arr):
|
||||
return arr[index]
|
||||
return default
|
||||
|
||||
class Tile:
|
||||
def __init__(self, chunk, x, y, z, tileIndex):
|
||||
self.shape = TILE_SHAPE_NULL
|
||||
|
||||
self.chunk = chunk
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.index = tileIndex
|
||||
|
||||
self.posX = x * TILE_WIDTH + chunk.x * CHUNK_WIDTH * TILE_WIDTH
|
||||
self.posY = y * TILE_HEIGHT + chunk.y * CHUNK_HEIGHT * TILE_HEIGHT
|
||||
self.posZ = z * TILE_DEPTH + chunk.z * CHUNK_DEPTH * TILE_DEPTH
|
||||
|
||||
def chunkReload(self, newX, newY, newZ):
|
||||
self.posX = self.x * TILE_WIDTH + newX * CHUNK_WIDTH * TILE_WIDTH
|
||||
self.posY = self.y * TILE_HEIGHT + newY * CHUNK_HEIGHT * TILE_HEIGHT
|
||||
self.posZ = self.z * TILE_DEPTH + newZ * CHUNK_DEPTH * TILE_DEPTH
|
||||
|
||||
def load(self, chunkData):
|
||||
self.shape = getItem(chunkData['shapes'], self.index, TILE_SHAPE_NULL)
|
||||
|
||||
def setShape(self, shape):
|
||||
if shape == self.shape:
|
||||
return
|
||||
|
||||
self.shape = shape
|
||||
self.chunk.dirty = True
|
||||
self.chunk.tileUpdateVertices()
|
||||
self.chunk.onChunkData.invoke(self.chunk)
|
||||
|
||||
def getBaseTileModel(self):
|
||||
vertices = []
|
||||
indices = []
|
||||
uvs = []
|
||||
colors = []
|
||||
|
||||
if self.shape == TILE_SHAPE_NULL:
|
||||
pass
|
||||
|
||||
elif self.shape == TILE_SHAPE_FLOOR:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (255, 255, 255, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_NORTH:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (255, 0, 0, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_SOUTH:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (0, 255, 0, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_EAST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (0, 0, 255, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_WEST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (255, 255, 0, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_SOUTHWEST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (255, 128, 0, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_NORTHWEST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (128, 255, 0, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_NORTHEAST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (0, 255, 128, 255) ] * 4
|
||||
|
||||
elif self.shape == TILE_SHAPE_RAMP_SOUTHEAST:
|
||||
vertices = [
|
||||
(self.posX, self.posY, self.posZ),
|
||||
(self.posX + TILE_WIDTH, self.posY, self.posZ + TILE_DEPTH),
|
||||
(self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH),
|
||||
(self.posX, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH)
|
||||
]
|
||||
indices = [0, 1, 2, 0, 2, 3]
|
||||
uvs = [ (0, 0), (1, 0), (1, 1), (0, 1) ]
|
||||
colors = [ (255, 128, 255, 255) ] * 4
|
||||
|
||||
else:
|
||||
# Solid black cube for unknown shape
|
||||
x0, y0, z0 = self.posX, self.posY, self.posZ
|
||||
x1, y1, z1 = self.posX + TILE_WIDTH, self.posY + TILE_HEIGHT, self.posZ + TILE_DEPTH
|
||||
vertices = [
|
||||
(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), # bottom
|
||||
(x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1) # top
|
||||
]
|
||||
indices = [
|
||||
0,1,2, 0,2,3, # bottom
|
||||
4,5,6, 4,6,7, # top
|
||||
0,1,5, 0,5,4, # front
|
||||
2,3,7, 2,7,6, # back
|
||||
1,2,6, 1,6,5, # right
|
||||
3,0,4, 3,4,7 # left
|
||||
]
|
||||
uvs = [ (0,0) ] * 8
|
||||
colors = [ (0,0,0,255) ] * 8
|
||||
|
||||
return {
|
||||
'vertices': vertices,
|
||||
'indices': indices,
|
||||
'uvs': uvs,
|
||||
'colors': colors
|
||||
}
|
||||
|
||||
def buffer(self, vertexBuffer):
|
||||
if self.shape == TILE_SHAPE_NULL:
|
||||
return
|
||||
|
||||
# New code:
|
||||
baseData = self.getBaseTileModel()
|
||||
|
||||
# Base data is indiced but we need to buffer unindiced data
|
||||
for index in baseData['indices']:
|
||||
verts = baseData['vertices'][index]
|
||||
uv = baseData['uvs'][index]
|
||||
color = baseData['colors'][index]
|
||||
|
||||
vertexBuffer.vertices.extend([
|
||||
verts[0] - (TILE_WIDTH / 2.0),
|
||||
verts[1] - (TILE_HEIGHT / 2.0),
|
||||
verts[2] - (TILE_DEPTH / 2.0)
|
||||
])
|
||||
|
||||
vertexBuffer.colors.extend([
|
||||
color[0] / 255.0,
|
||||
color[1] / 255.0,
|
||||
color[2] / 255.0,
|
||||
color[3] / 255.0
|
||||
])
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "time.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
dusktime_t TIME;
|
||||
|
||||
void timeInit(void) {
|
||||
memoryZero(&TIME, sizeof(TIME));
|
||||
|
||||
// Set these to something non-zero.
|
||||
TIME.lastTick = TIME_STEP;
|
||||
TIME.delta = TIME.realDelta = TIME_STEP;
|
||||
TIME.realTime = TIME.time = TIME_STEP * 2;
|
||||
}
|
||||
|
||||
void timeUpdate(void) {
|
||||
TIME.realDelta = timeDeltaGet();
|
||||
|
||||
#if TIME_DYNAMIC
|
||||
TIME.delta = TIME.realDelta;
|
||||
#else
|
||||
TIME.delta = TIME_PLATFORM_STEP;
|
||||
#endif
|
||||
|
||||
assertTrue(TIME.delta >= 0.0f, "Time delta is negative");
|
||||
assertTrue(TIME.realDelta >= 0.0f, "Real time delta is negative");
|
||||
|
||||
TIME.time += TIME.delta;
|
||||
TIME.realTime += TIME.realDelta;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct {
|
||||
float_t delta;
|
||||
float_t lastTick;
|
||||
float_t time;
|
||||
float_t realDelta;
|
||||
float_t realTime;
|
||||
} dusktime_t;
|
||||
|
||||
extern dusktime_t TIME;
|
||||
|
||||
#define TIME_STEP (1.0f / 60.0f) // Default to 60FPS
|
||||
|
||||
#ifndef TIME_DYNAMIC
|
||||
#define TIME_DYNAMIC 1
|
||||
#endif
|
||||
|
||||
#if TIME_DYNAMIC == 0
|
||||
#ifndef TIME_PLATFORM_STEP
|
||||
#define TIME_PLATFORM_STEP TIME_STEP
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes the time system.
|
||||
*/
|
||||
void timeInit(void);
|
||||
|
||||
/**
|
||||
* Updates the time system
|
||||
*/
|
||||
void timeUpdate(void);
|
||||
|
||||
#if TIME_DYNAMIC == 1
|
||||
/**
|
||||
* Gets the time delta since the last frame, in seconds. Tied to the
|
||||
* platform.
|
||||
*
|
||||
* This will only get called once per gameUpdate.
|
||||
*/
|
||||
float_t timeDeltaGet(void);
|
||||
#endif
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "uitextbox.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "input.h"
|
||||
|
||||
uitextbox_t UI_TEXTBOX;
|
||||
|
||||
void uiTextboxInit() {
|
||||
memoryZero(&UI_TEXTBOX, sizeof(uitextbox_t));
|
||||
}
|
||||
|
||||
void uiTextboxUpdate() {
|
||||
if(UI_TEXTBOX.visible == false) return;
|
||||
|
||||
if(UI_TEXTBOX.charsRevealed < UI_TEXTBOX.pageChars[UI_TEXTBOX.page]) {
|
||||
UI_TEXTBOX.charsRevealed++;
|
||||
if(inputIsDown(INPUT_BIND_ACTION)) {
|
||||
UI_TEXTBOX.charsRevealed++;
|
||||
}
|
||||
|
||||
} else {
|
||||
if(inputPressed(INPUT_BIND_ACTION)) {
|
||||
if(UI_TEXTBOX.page < UI_TEXTBOX.pageCount - 1) {
|
||||
UI_TEXTBOX.page++;
|
||||
UI_TEXTBOX.charsRevealed = 0;
|
||||
} else {
|
||||
// Close the textbox
|
||||
UI_TEXTBOX.visible = false;
|
||||
UI_TEXTBOX.page = 0;
|
||||
UI_TEXTBOX.charsRevealed = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uiTextboxSetText(const char_t *text) {
|
||||
assertNotNull(text, "Text pointer cannot be NULL, call uiTextboxClose()");
|
||||
|
||||
memoryZero(UI_TEXTBOX.text, sizeof(UI_TEXTBOX.text));
|
||||
memoryZero(UI_TEXTBOX.lineLengths, sizeof(UI_TEXTBOX.lineLengths));
|
||||
memoryZero(UI_TEXTBOX.pageChars, sizeof(UI_TEXTBOX.pageChars));
|
||||
UI_TEXTBOX.pageCount = 1;// Always at least one page.
|
||||
UI_TEXTBOX.totalChars = 0;
|
||||
UI_TEXTBOX.page = 0;
|
||||
UI_TEXTBOX.charsRevealed = 0;
|
||||
UI_TEXTBOX.visible = true;
|
||||
|
||||
char_t c;// current char
|
||||
uint16_t i = 0;// Index of character we are pulling
|
||||
uint16_t lastWordStart = 0;// Index of the last word start (in src string).
|
||||
uint8_t line = 0;// Which line we are currently writing to.
|
||||
uint8_t page = 0;
|
||||
bool_t startOfLine = true;// Are we at the start of a line?
|
||||
|
||||
while((c = text[i++]) != '\0') {
|
||||
// HARD disallowed characters.
|
||||
assertTrue(c != '\r', "Carriage return characters not allowed.");
|
||||
assertTrue(c != '\t', "Tab characters not allowed.");
|
||||
|
||||
// Is this the beginning of a new line?
|
||||
if(startOfLine) {
|
||||
// Yes, we are at the start of a new line.
|
||||
startOfLine = false;
|
||||
// Is this the first line?
|
||||
if(line == 0) {
|
||||
// nothing to do, just continue.
|
||||
} else {
|
||||
// Yes, start of new line. Is this line the first line on a new page?
|
||||
if((line % UI_TEXTBOX_LINES_PER_PAGE) == 0) {
|
||||
// Yes, start a new page.
|
||||
i--;// Rewind so that this character can go through the loop again.
|
||||
goto newPage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change what we do depending on the character.
|
||||
if(c == '\n') {
|
||||
goto newline;
|
||||
} else if(c == ' ') {
|
||||
goto whitespace;
|
||||
} else {
|
||||
goto character;
|
||||
}
|
||||
|
||||
// Handle whitespace characters (not newlines)
|
||||
whitespace: {
|
||||
// Is this whitespace the last char of the line?
|
||||
if(UI_TEXTBOX.lineLengths[line] == UI_TEXTBOX_CHARS_PER_LINE) {
|
||||
goto newline;
|
||||
} else if(UI_TEXTBOX.lineLengths[line] == 0) {
|
||||
// If this is the first character of the line, we can just ignore it.
|
||||
continue;
|
||||
}
|
||||
|
||||
lastWordStart = i;
|
||||
goto appendCharacter;
|
||||
}
|
||||
|
||||
// Handle regular characters
|
||||
character: {
|
||||
// Is this character going to cause a wrap to occur?
|
||||
if(UI_TEXTBOX.lineLengths[line] == UI_TEXTBOX_CHARS_PER_LINE) {
|
||||
// How long ago was the last whitespace?
|
||||
uint16_t charsSinceLastSpace = i - lastWordStart;
|
||||
|
||||
// Is the word longer than a line can possibly hold?
|
||||
assertTrue(
|
||||
charsSinceLastSpace < UI_TEXTBOX_CHARS_PER_LINE,
|
||||
"Word longer than a line can hold."
|
||||
);
|
||||
|
||||
// Undo appending of all characters since the last whitespace.
|
||||
UI_TEXTBOX.totalChars -= charsSinceLastSpace;
|
||||
UI_TEXTBOX.lineLengths[line] -= charsSinceLastSpace;
|
||||
UI_TEXTBOX.pageChars[page] -= charsSinceLastSpace;
|
||||
|
||||
// Rewind the loop so that printing will begin on the new line at the
|
||||
// start of the last word.
|
||||
i -= charsSinceLastSpace;
|
||||
|
||||
// Newline.
|
||||
goto newline;
|
||||
}
|
||||
|
||||
// Append the character to the textbox.
|
||||
goto appendCharacter;
|
||||
}
|
||||
|
||||
// Handle newlines
|
||||
newline: {
|
||||
// Ensure we don't exceed the maximum number of lines.
|
||||
assertTrue(
|
||||
line < UI_TEXTBOX_LINE_COUNT,
|
||||
"Exceeded maximum number of lines in textbox."
|
||||
);
|
||||
|
||||
// Add a line to the textbox.
|
||||
line++;
|
||||
startOfLine = true;// Next iteration will be a start of line.
|
||||
lastWordStart = i;// We also mark this as the last word start.
|
||||
continue;
|
||||
}
|
||||
|
||||
newPage: {
|
||||
// Make sure we don't exceed the maximum number of pages.
|
||||
assertTrue(
|
||||
UI_TEXTBOX.pageCount < UI_TEXTBOX_PAGE_COUNT_MAX,
|
||||
"Exceeded maximum number of pages in textbox."
|
||||
);
|
||||
UI_TEXTBOX.pageCount++;
|
||||
page++;
|
||||
continue;
|
||||
}
|
||||
|
||||
appendCharacter: {
|
||||
assertTrue(
|
||||
UI_TEXTBOX.totalChars < UI_TEXTBOX_CHARS_MAX,
|
||||
"Exceeded maximum number of characters in textbox."
|
||||
);
|
||||
assertTrue(
|
||||
line < UI_TEXTBOX_LINE_COUNT,
|
||||
"Exceeded maximum number of lines in textbox."
|
||||
);
|
||||
assertTrue(
|
||||
UI_TEXTBOX.lineLengths[line] < UI_TEXTBOX_CHARS_PER_LINE,
|
||||
"Exceeded maximum number of chars per line in textbox."
|
||||
);
|
||||
|
||||
// Push the character to the textbox.
|
||||
UI_TEXTBOX.text[
|
||||
line * UI_TEXTBOX_CHARS_PER_LINE + UI_TEXTBOX.lineLengths[line]
|
||||
] = c;
|
||||
|
||||
// Increment the line length and page character count.
|
||||
UI_TEXTBOX.totalChars++;
|
||||
UI_TEXTBOX.lineLengths[line]++;
|
||||
UI_TEXTBOX.pageChars[page]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
assertUnreachable("Code should not reach here, all cases handled.");
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/renderbase.h"
|
||||
#include "ui/font.h"
|
||||
|
||||
#define UI_TEXTBOX_LINES_PER_PAGE 4
|
||||
#define UI_TEXTBOX_WIDTH RENDER_WIDTH
|
||||
#define UI_TEXTBOX_HEIGHT_INNER ( \
|
||||
FONT_TILE_HEIGHT * UI_TEXTBOX_LINES_PER_PAGE \
|
||||
)
|
||||
|
||||
#define UI_TEXTBOX_BORDER_WIDTH 4
|
||||
#define UI_TEXTBOX_BORDER_HEIGHT UI_TEXTBOX_BORDER_WIDTH
|
||||
#define UI_TEXTBOX_PADDING_X 2
|
||||
#define UI_TEXTBOX_PADDING_Y UI_TEXTBOX_PADDING_X
|
||||
#define UI_TEXTBOX_WIDTH_INNER ( \
|
||||
UI_TEXTBOX_WIDTH - (UI_TEXTBOX_BORDER_WIDTH * 2) - \
|
||||
(UI_TEXTBOX_PADDING_X * 2) \
|
||||
)
|
||||
#define UI_TEXTBOX_HEIGHT ( \
|
||||
UI_TEXTBOX_HEIGHT_INNER + (UI_TEXTBOX_BORDER_HEIGHT * 2) + \
|
||||
(UI_TEXTBOX_PADDING_Y * 2) \
|
||||
)
|
||||
|
||||
#define UI_TEXTBOX_CHARS_PER_LINE (UI_TEXTBOX_WIDTH_INNER / FONT_TILE_WIDTH)
|
||||
#define UI_TEXTBOX_CHARS_PER_PAGE ( \
|
||||
UI_TEXTBOX_CHARS_PER_LINE * UI_TEXTBOX_LINES_PER_PAGE \
|
||||
)
|
||||
#define UI_TEXTBOX_PAGE_COUNT_MAX 6
|
||||
#define UI_TEXTBOX_LINE_COUNT ( \
|
||||
UI_TEXTBOX_LINES_PER_PAGE * UI_TEXTBOX_PAGE_COUNT_MAX \
|
||||
)
|
||||
#define UI_TEXTBOX_CHARS_MAX ( \
|
||||
UI_TEXTBOX_CHARS_PER_PAGE * UI_TEXTBOX_PAGE_COUNT_MAX \
|
||||
)
|
||||
|
||||
#define UI_TEXTBOX_REVEAL_RATE 2
|
||||
|
||||
typedef struct {
|
||||
char_t text[UI_TEXTBOX_CHARS_MAX];
|
||||
uint8_t lineLengths[UI_TEXTBOX_LINE_COUNT];
|
||||
uint8_t pageCount;
|
||||
uint8_t pageChars[UI_TEXTBOX_PAGE_COUNT_MAX];
|
||||
uint16_t totalChars;
|
||||
|
||||
uint8_t page;
|
||||
uint8_t charsRevealed;
|
||||
|
||||
bool_t visible;
|
||||
} uitextbox_t;
|
||||
|
||||
extern uitextbox_t UI_TEXTBOX;
|
||||
|
||||
/**
|
||||
* Initializes the UI textbox.
|
||||
*/
|
||||
void uiTextboxInit(void);
|
||||
|
||||
/**
|
||||
* Updates the UI textbox, handling text input and scrolling.
|
||||
*/
|
||||
void uiTextboxUpdate(void);
|
||||
|
||||
/**
|
||||
* Sets the text for the UI textbox.
|
||||
*
|
||||
* @param text The text to display in the textbox.
|
||||
*/
|
||||
void uiTextboxSetText(
|
||||
const char_t *text
|
||||
);
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
/**
|
||||
* Finds the next power of two greater than or equal to the given value.
|
||||
*
|
||||
* @param value The value to find the next power of two for.
|
||||
* @return The next power of two greater than or equal to the value.
|
||||
*/
|
||||
uint32_t mathNextPowTwo(uint32_t value);
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void * memoryAllocate(const size_t size) {
|
||||
assertTrue(size > 0, "Cannot allocate 0 bytes of memory.");
|
||||
void *ptr = malloc(size);
|
||||
assertNotNull(ptr, "Memory allocation failed.");
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void memoryFree(void *ptr) {
|
||||
assertNotNull(ptr, "Cannot free NULL memory.");
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
||||
void memoryCopy(void *dest, const void *src, const size_t size) {
|
||||
assertNotNull(dest, "Cannot copy to NULL memory.");
|
||||
assertNotNull(src, "Cannot copy from NULL memory.");
|
||||
assertTrue(size > 0, "Cannot copy 0 bytes of memory.");
|
||||
assertTrue(dest != src, "Cannot copy memory to itself.");
|
||||
memcpy(dest, src, size);
|
||||
}
|
||||
|
||||
void memorySet(void *dest, const uint8_t value, const size_t size) {
|
||||
assertNotNull(dest, "Cannot set NULL memory.");
|
||||
assertTrue(size > 0, "Cannot set 0 bytes of memory.");
|
||||
memset(dest, value, size);
|
||||
}
|
||||
|
||||
void memoryZero(void *dest, const size_t size) {
|
||||
memorySet(dest, 0, size);
|
||||
}
|
||||
|
||||
void memoryCopyRangeSafe(
|
||||
void *dest,
|
||||
const void *start,
|
||||
const void *end,
|
||||
const size_t sizeMax
|
||||
) {
|
||||
assertFalse(start == end, "Start and end pointers are the same.");
|
||||
assertTrue(end > start, "End pointer is not after start pointer.");
|
||||
|
||||
size_t copy = (size_t)end - (size_t)start;
|
||||
assertTrue(copy <= sizeMax, "Size of memory to copy is too large.");
|
||||
memoryCopy(dest, start, copy);
|
||||
}
|
||||
|
||||
void memoryMove(void *dest, const void *src, const size_t size) {
|
||||
assertNotNull(dest, "Cannot move to NULL memory.");
|
||||
assertNotNull(src, "Cannot move from NULL memory.");
|
||||
assertTrue(size > 0, "Cannot move 0 bytes of memory.");
|
||||
assertTrue(dest != src, "Cannot move memory to itself.");
|
||||
memmove(dest, src, size);
|
||||
}
|
||||
|
||||
int_t memoryCompare(
|
||||
const void *a,
|
||||
const void *b,
|
||||
const size_t size
|
||||
) {
|
||||
assertNotNull(a, "Cannot compare NULL memory.");
|
||||
assertNotNull(b, "Cannot compare NULL memory.");
|
||||
assertTrue(size > 0, "Cannot compare 0 bytes of memory.");
|
||||
return memcmp(a, b, size);
|
||||
}
|
||||
|
||||
void memoryReallocate(void **ptr, const size_t size) {
|
||||
assertNotNull(ptr, "Cannot reallocate NULL pointer.");
|
||||
assertTrue(size > 0, "Cannot reallocate to 0 bytes of memory.");
|
||||
void *newPointer = memoryAllocate(size);
|
||||
assertNotNull(newPointer, "Memory reallocation failed.");
|
||||
memoryFree(*ptr);
|
||||
*ptr = newPointer;
|
||||
}
|
||||
|
||||
void memoryResize(void **ptr, const size_t oldSize, const size_t newSize) {
|
||||
assertNotNull(ptr, "Cannot resize NULL pointer.");
|
||||
if(newSize == oldSize) return;
|
||||
if(oldSize == 0) return memoryReallocate(ptr, newSize);
|
||||
|
||||
assertTrue(newSize > oldSize, "New size must be greater than old size.");
|
||||
|
||||
void *newPointer = memoryAllocate(newSize);
|
||||
assertNotNull(newPointer, "Memory resizing failed.");
|
||||
memoryCopy(newPointer, *ptr, oldSize);
|
||||
memoryFree(*ptr);
|
||||
*ptr = newPointer;
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "string.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
bool_t stringIsWhitespace(const char_t c) {
|
||||
return isspace(c);
|
||||
}
|
||||
|
||||
void stringCopy(char_t *dest, const char_t *src, const size_t destSize) {
|
||||
assertNotNull(dest, "dest must not be NULL");
|
||||
assertNotNull(src, "src must not be NULL");
|
||||
assertTrue(destSize > 0, "destSize must be greater than 0");
|
||||
assertStrLenMax(src, destSize, "src is too long");
|
||||
memoryCopy(dest, src, strlen(src) + 1);
|
||||
}
|
||||
|
||||
int stringCompare(const char_t *str1, const char_t *str2) {
|
||||
assertNotNull(str1, "str1 must not be NULL");
|
||||
assertNotNull(str2, "str2 must not be NULL");
|
||||
return strcmp(str1, str2);
|
||||
}
|
||||
|
||||
void stringTrim(char_t *str) {
|
||||
assertNotNull(str, "str must not be NULL");
|
||||
|
||||
// Trim leading whitespace
|
||||
char_t *start = str;
|
||||
while(stringIsWhitespace(*start)) start++;
|
||||
|
||||
// Trim trailing whitespace
|
||||
char_t *end = start + strlen(start) - 1;
|
||||
while (end >= start && stringIsWhitespace(*end)) end--;
|
||||
|
||||
// Null-terminate the string
|
||||
*(end + 1) = '\0';
|
||||
|
||||
// Move trimmed string to the original buffer
|
||||
if (start != str) memmove(str, start, end - start + 2);
|
||||
}
|
||||
|
||||
char_t * stringToken(char_t *str, const char_t *delim) {
|
||||
assertNotNull(str, "str must not be NULL");
|
||||
assertNotNull(delim, "delim must not be NULL");
|
||||
return strtok(str, delim);
|
||||
}
|
||||
|
||||
int32_t stringFormat(
|
||||
char_t *dest,
|
||||
const size_t destSize,
|
||||
const char_t *format,
|
||||
...
|
||||
) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int32_t len = stringFormatVA(dest, destSize, format, args);
|
||||
va_end(args);
|
||||
return len;
|
||||
}
|
||||
|
||||
int32_t stringFormatVA(
|
||||
char_t *dest,
|
||||
const size_t destSize,
|
||||
const char_t *format,
|
||||
const va_list args
|
||||
) {
|
||||
assertNotNull(format, "format must not be NULL");
|
||||
|
||||
if(dest == NULL) {
|
||||
int32_t ret = vsnprintf(NULL, 0, format, args);
|
||||
assertTrue(ret >= 0, "Failed to format string.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
assertTrue(destSize > 0, "destSize must be greater than 0");
|
||||
int32_t ret = vsnprintf(dest, destSize, format, args);
|
||||
assertTrue(ret >= 0, "Failed to format string.");
|
||||
assertTrue(ret < destSize, "Formatted string is too long.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool_t stringToI32(const char_t *str, int32_t *out) {
|
||||
assertNotNull(str, "str must not be NULL");
|
||||
assertNotNull(out, "out must not be NULL");
|
||||
|
||||
char_t *endptr;
|
||||
errno = 0;
|
||||
long int result = strtol(str, &endptr, 10);
|
||||
if (errno != 0 || *endptr != '\0') {
|
||||
return false;
|
||||
}
|
||||
*out = (int32_t)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t stringToI64(const char_t *str, int64_t *out) {
|
||||
assertNotNull(str, "str must not be NULL");
|
||||
assertNotNull(out, "out must not be NULL");
|
||||
|
||||
char_t *endptr;
|
||||
errno = 0;
|
||||
long long int result = strtoll(str, &endptr, 10);
|
||||
if (errno != 0 || *endptr != '\0') {
|
||||
return false;
|
||||
}
|
||||
*out = (int64_t)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool_t stringToU16(const char_t *str, uint16_t *out) {
|
||||
assertNotNull(str, "str must not be NULL");
|
||||
assertNotNull(out, "out must not be NULL");
|
||||
|
||||
char_t *endptr;
|
||||
errno = 0;
|
||||
unsigned long int result = strtoul(str, &endptr, 10);
|
||||
if (errno != 0 || *endptr != '\0' || result > UINT16_MAX) {
|
||||
return false;
|
||||
}
|
||||
*out = (uint16_t)result;
|
||||
return true;
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
/**
|
||||
* Determines if a character is whitespace.
|
||||
*
|
||||
* @param c The character to check.
|
||||
* @return TRUE if the character is whitespace, FALSE otherwise.
|
||||
*/
|
||||
bool_t stringIsWhitespace(const char_t c);
|
||||
|
||||
/**
|
||||
* Copies a string from src to dest, ensuring the dest string is null-terminated
|
||||
* and does not exceed the specified size.
|
||||
*
|
||||
* @param dest The destination string.
|
||||
* @param src The source string.
|
||||
* @param destSize The size of the destination string exc. null terminator.
|
||||
*/
|
||||
void stringCopy(char_t *dest, const char_t *src, const size_t destSize);
|
||||
|
||||
/**
|
||||
* Compares two strings.
|
||||
*
|
||||
* @param str1 The first string.
|
||||
* @param str2 The second string.
|
||||
* @return 0 if the strings are equal, -1 if str1 is less than str2, 1 if str1
|
||||
* is greater than str2.
|
||||
*/
|
||||
int stringCompare(const char_t *str1, const char_t *str2);
|
||||
|
||||
/**
|
||||
* Trims whitespace from the beginning and end of a string.
|
||||
*
|
||||
* @param str The string to trim.
|
||||
*/
|
||||
void stringTrim(char_t *str);
|
||||
|
||||
/**
|
||||
* Gets the next token in a string using a delimiter.
|
||||
* e.g. input: "Hello, World, Happy Monday!" with stringToken(input, ",") will
|
||||
* return "Hello" then " World" then " Happy Monday!" on each subsequent call.
|
||||
*
|
||||
* @param str The string to split.
|
||||
* @param delim The delimiter to split by.
|
||||
* @return A pointer to the next token in the string.
|
||||
*/
|
||||
char_t * stringToken(char_t *str, const char_t *delim);
|
||||
|
||||
/**
|
||||
* Formats a string.
|
||||
*
|
||||
* @param dest The destination string, or NULL to get the length of the
|
||||
* formatted string.
|
||||
* @param destSize The size of the destination string exc. null terminator, can
|
||||
* be anything if dest is NULL.
|
||||
* @param format The format string.
|
||||
* @param ... The arguments to format.
|
||||
* @return The number of characters written.
|
||||
*/
|
||||
int32_t stringFormat(
|
||||
char_t *dest,
|
||||
const size_t destSize,
|
||||
const char_t *format,
|
||||
...
|
||||
);
|
||||
|
||||
/**
|
||||
* Formats a string using a va_list.
|
||||
*
|
||||
* @param dest The destination string, or NULL to get the length of the
|
||||
* formatted string.
|
||||
* @param destSize The size of the destination string exc. null terminator, can
|
||||
* be anything if dest is NULL.
|
||||
* @param format The format string.
|
||||
* @param args The va_list of arguments.
|
||||
* @return The number of characters written.
|
||||
*/
|
||||
int32_t stringFormatVA(
|
||||
char_t *dest,
|
||||
const size_t destSize,
|
||||
const char_t *format,
|
||||
const va_list args
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts a string to an integer.
|
||||
*
|
||||
* @param str The string to convert.
|
||||
* @param out The output integer.
|
||||
* @return TRUE if the conversion was successful, FALSE otherwise.
|
||||
*/
|
||||
bool_t stringToI32(const char_t *str, int32_t *out);
|
||||
|
||||
/**
|
||||
* Converts a string to a signed 16-bit integer.
|
||||
*
|
||||
* @param str The string to convert.
|
||||
* @param out The output signed integer.
|
||||
* @return TRUE if the conversion was successful, FALSE otherwise.
|
||||
*/
|
||||
bool_t stringToI16(const char_t *str, int16_t *out);
|
||||
|
||||
/**
|
||||
* Converts a string to an unsigned 16-bit integer.
|
||||
*
|
||||
* @param str The string to convert.
|
||||
* @param out The output unsigned integer.
|
||||
* @return TRUE if the conversion was successful, FALSE otherwise.
|
||||
*/
|
||||
bool_t stringToU16(const char_t *str, uint16_t *out);
|
||||
@@ -1,321 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "chunk.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "world/world.h"
|
||||
|
||||
void renderChunkUpdated(chunk_t *chunk);
|
||||
|
||||
chunkmap_t CHUNK_MAP;
|
||||
|
||||
void chunkMapInit() {
|
||||
memoryZero(&CHUNK_MAP, sizeof(chunkmap_t));
|
||||
|
||||
// Load default chunks, YX order.
|
||||
uint16_t i = 0;
|
||||
chunk_t *chunk;
|
||||
for(uint8_t y = 0; y < CHUNK_MAP_HEIGHT; y++) {
|
||||
for(uint8_t x = 0; x < CHUNK_MAP_WIDTH; x++) {
|
||||
assertTrue(i < CHUNK_MAP_COUNT, "Chunk index out of bounds");
|
||||
|
||||
chunk = CHUNK_MAP.chunks + i;
|
||||
CHUNK_MAP.chunkOrder[i] = chunk;
|
||||
|
||||
chunkLoad(chunk, x, y);
|
||||
assertTrue(
|
||||
chunk->x == x && chunk->y == y,
|
||||
"Chunk coordinates do not match expected values"
|
||||
);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void chunkMapShift(const int16_t x, const int16_t y) {
|
||||
if(x == 0 && y == 0) assertUnreachable("ChunkMapShift called with no shift");
|
||||
|
||||
chunk_t *newChunkOrder[CHUNK_MAP_COUNT];
|
||||
chunk_t *unloadedChunks[CHUNK_MAP_COUNT];
|
||||
chunk_t *chunk;
|
||||
uint8_t i, j;
|
||||
uint16_t
|
||||
/** New Map Coordinates */
|
||||
newX, newY,
|
||||
newChunkX, newChunkY
|
||||
;
|
||||
|
||||
// Calculate the new map coordinates
|
||||
newX = CHUNK_MAP.topLeftX + x;
|
||||
newY = CHUNK_MAP.topLeftY + y;
|
||||
|
||||
// Zero the new chunk order
|
||||
memoryZero(newChunkOrder, sizeof(newChunkOrder));
|
||||
|
||||
// For each chunk...
|
||||
j = 0;
|
||||
chunk = CHUNK_MAP.chunks;
|
||||
do {
|
||||
// Is this chunk still going to be within the map bounds?
|
||||
if(
|
||||
chunk->x < newX || chunk->y < newY ||
|
||||
chunk->x >= newX + CHUNK_MAP_WIDTH ||
|
||||
chunk->y >= newY + CHUNK_MAP_HEIGHT
|
||||
) {
|
||||
// No, it's not, let's unload it and make it available for reuse.
|
||||
chunkUnload(chunk);
|
||||
assertTrue(
|
||||
j < CHUNK_MAP_COUNT,
|
||||
"Unloaded chunk index out of bounds"
|
||||
);
|
||||
unloadedChunks[j++] = chunk;
|
||||
chunk++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Yes it is still valid, determine the new index that it will be at
|
||||
i = (chunk->y - newY) * CHUNK_MAP_WIDTH + (chunk->x - newX);
|
||||
assertTrue(
|
||||
i < CHUNK_MAP_COUNT,
|
||||
"Chunk index out of bounds after shifting"
|
||||
);
|
||||
assertNull(
|
||||
newChunkOrder[i],
|
||||
"New chunk order index is already occupied"
|
||||
);
|
||||
|
||||
// Set the new chunk order
|
||||
newChunkOrder[i] = chunk;
|
||||
chunk++;
|
||||
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
|
||||
|
||||
// Now check the new chunk order list for missing chunks.
|
||||
i = 0;
|
||||
do {
|
||||
assertTrue(
|
||||
i < CHUNK_MAP_COUNT,
|
||||
"New chunk order index out of bounds after shifting"
|
||||
);
|
||||
|
||||
// Is this chunk loaded still?
|
||||
chunk = newChunkOrder[i];
|
||||
if(chunk != NULL) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine the new chunk coordinates.
|
||||
newChunkX = i % CHUNK_MAP_WIDTH + newX;
|
||||
newChunkY = i / CHUNK_MAP_WIDTH + newY;
|
||||
assertTrue(
|
||||
newChunkX >= newX && newChunkX < newX + CHUNK_MAP_WIDTH,
|
||||
"New chunk X coordinate out of bounds after shifting"
|
||||
);
|
||||
assertTrue(
|
||||
newChunkY >= newY && newChunkY < newY + CHUNK_MAP_HEIGHT,
|
||||
"New chunk Y coordinate out of bounds after shifting"
|
||||
);
|
||||
|
||||
// Pop a chunk from the unloaded chunks list.
|
||||
assertTrue(j > 0, "No unloaded chunks available to reuse");
|
||||
chunk = unloadedChunks[--j];
|
||||
assertNotNull(chunk, "Unloaded chunk pointer is null");
|
||||
|
||||
// Load the chunk at the new coordinates.
|
||||
chunkLoad(chunk, newChunkX, newChunkY);
|
||||
assertTrue(
|
||||
chunk->x == newChunkX && chunk->y == newChunkY,
|
||||
"Chunk coordinates do not match expected values after shifting"
|
||||
);
|
||||
|
||||
// Set it in order.
|
||||
newChunkOrder[i] = chunk;
|
||||
i++;
|
||||
} while(i < CHUNK_MAP_COUNT);
|
||||
|
||||
// Update Absolutes.
|
||||
CHUNK_MAP.topLeftX = newX;
|
||||
CHUNK_MAP.topLeftY = newY;
|
||||
|
||||
// Update the chunk order.
|
||||
memoryCopy(
|
||||
CHUNK_MAP.chunkOrder,
|
||||
newChunkOrder,
|
||||
sizeof(CHUNK_MAP.chunkOrder)
|
||||
);
|
||||
}
|
||||
|
||||
void chunkMapSetPosition(const uint16_t x, const uint16_t y) {
|
||||
if(x == CHUNK_MAP.topLeftX && y == CHUNK_MAP.topLeftY) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t shiftX = x - CHUNK_MAP.topLeftX;
|
||||
int16_t shiftY = y - CHUNK_MAP.topLeftY;
|
||||
|
||||
// Are we shifting the entire map?
|
||||
if(
|
||||
shiftX >= CHUNK_MAP_WIDTH || shiftX < -CHUNK_MAP_WIDTH ||
|
||||
shiftY >= CHUNK_MAP_HEIGHT || shiftY < -CHUNK_MAP_HEIGHT
|
||||
) {
|
||||
printf("Shifting chunk map to new position (%u, %u)\n", x, y);
|
||||
}
|
||||
|
||||
// Shift the chunk map by the specified offsets.
|
||||
chunkMapShift(shiftX, shiftY);
|
||||
}
|
||||
|
||||
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY) {
|
||||
assertTrue(
|
||||
chunkX < WORLD_WIDTH && chunkY < WORLD_HEIGHT,
|
||||
"Chunk coordinates out of bounds"
|
||||
);
|
||||
|
||||
chunk_t *chunk = CHUNK_MAP.chunks;
|
||||
do {
|
||||
if(chunk->x == chunkX && chunk->y == chunkY) return chunk;
|
||||
chunk++;
|
||||
} while(chunk < CHUNK_MAP.chunks + CHUNK_MAP_COUNT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y) {
|
||||
assertNotNull(chunk, "Chunk pointer is null");
|
||||
|
||||
// Zero out the chunk data.
|
||||
memoryZero(chunk, sizeof(chunk_t));
|
||||
|
||||
// Set the chunk coordinates.
|
||||
chunk->x = x;
|
||||
chunk->y = y;
|
||||
|
||||
// Only load data if the chunk is within bounds.
|
||||
if(x >= WORLD_WIDTH || y >= WORLD_HEIGHT) {
|
||||
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
|
||||
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
|
||||
return;
|
||||
}
|
||||
|
||||
// Is chunk data defined?
|
||||
const chunkdata_t *chunkData = WORLD_CHUNKS[y * WORLD_WIDTH + x];
|
||||
if(chunkData == NULL) {
|
||||
memorySet(chunk->tilesBase, 0, sizeof(chunk->tilesBase));
|
||||
memorySet(chunk->tilesBaseOverlay, 0, sizeof(chunk->tilesBaseOverlay));
|
||||
return;
|
||||
}
|
||||
|
||||
// Load tile data into chunk
|
||||
// printf("Loading chunk at (%u, %u)\n", x, y);
|
||||
memoryCopy(
|
||||
chunk->tilesBase,
|
||||
chunkData->layerBase,
|
||||
sizeof(chunk->tilesBase)
|
||||
);
|
||||
memoryCopy(
|
||||
chunk->tilesBaseOverlay,
|
||||
chunkData->layerBaseOverlay,
|
||||
sizeof(chunk->tilesBaseOverlay)
|
||||
);
|
||||
|
||||
// Load chunk entities
|
||||
const entity_t *data;
|
||||
entity_t *entity;
|
||||
data = chunkData->entities;
|
||||
while(data < chunkData->entities + CHUNK_ENTITY_COUNT_MAX) {
|
||||
if(data->type == ENTITY_TYPE_NULL) break;
|
||||
|
||||
// Store that this chunk owns this entity ID.
|
||||
chunk->entityIDs[chunk->entityCount++] = data->id;
|
||||
|
||||
// Check entity isn't loaded (still).
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
if(entity->type != ENTITY_TYPE_NULL && entity->id == data->id) break;
|
||||
entity++;
|
||||
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
|
||||
|
||||
if(entity != ENTITIES + ENTITY_COUNT_MAX) {
|
||||
// Entity is already loaded, skip it.
|
||||
printf("Entity ID %u already loaded, skipping...\n", data->id);
|
||||
data++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find an empty entity slot.
|
||||
entity = ENTITIES;
|
||||
while(true) {
|
||||
assertTrue(
|
||||
entity < ENTITIES + ENTITY_COUNT_MAX,
|
||||
"Out of available entities"
|
||||
);
|
||||
|
||||
if(entity->type == ENTITY_TYPE_NULL) break;
|
||||
entity++;
|
||||
};
|
||||
|
||||
// Load this entity.
|
||||
entityLoad(entity, data);
|
||||
data++;
|
||||
}
|
||||
|
||||
// Allow the rendering platform to know this chunk is loaded.
|
||||
renderChunkUpdated(chunk);
|
||||
}
|
||||
|
||||
void chunkUnload(chunk_t *chunk) {
|
||||
uint8_t i;
|
||||
entity_t *entity;
|
||||
uint32_t id;
|
||||
assertNotNull(chunk, "Chunk pointer is null");
|
||||
|
||||
// Iterate over each entity this chunk owns.
|
||||
i = 0;
|
||||
while(i < chunk->entityCount) {
|
||||
id = chunk->entityIDs[i++];
|
||||
|
||||
// Now, do we need to unload this entity?
|
||||
bool_t shouldUnload = false;
|
||||
|
||||
// Now, find the entity loaded with this ID. It should be impossible for
|
||||
// this entity to be unloaded (but may change in future).
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
if(entity->type != ENTITY_TYPE_NULL && entity->id == id) break;
|
||||
entity++;
|
||||
} while(entity < ENTITIES + ENTITY_COUNT_MAX);
|
||||
|
||||
assertTrue(
|
||||
entity < ENTITIES + ENTITY_COUNT_MAX,
|
||||
"Entity ID not found in ENTITIES array, cannot unload"
|
||||
);
|
||||
|
||||
// If the entity is still within our chunk bounds, it's getting unloaded
|
||||
if(
|
||||
floorf(entity->x) >= chunk->x * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
|
||||
ceilf(entity->x) < (chunk->x + 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT &&
|
||||
floorf(entity->y) >= chunk->y * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT &&
|
||||
ceilf(entity->y) < (chunk->y + 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT
|
||||
) {
|
||||
shouldUnload = true;
|
||||
} else {
|
||||
assertUnreachable(
|
||||
"Entity has left its chunk bounds, we should not be unloading it but "
|
||||
"I have yet to implement that properly. It will need to self-manage "
|
||||
"its own unloading somehow, and also not be in a null chunk "
|
||||
"when it does so."
|
||||
);
|
||||
}
|
||||
|
||||
// This entity is still in use, leave it loaded.
|
||||
if(!shouldUnload) continue;
|
||||
|
||||
// NULL the entity type, effectively unloading it.
|
||||
entity->type = ENTITY_TYPE_NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "tile.h"
|
||||
#include "display/render.h"
|
||||
|
||||
#define CHUNK_WIDTH 8
|
||||
#define CHUNK_HEIGHT 8
|
||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT)
|
||||
#define CHUNK_ENTITY_COUNT_MAX 8
|
||||
|
||||
#define CHUNK_MAP_WIDTH (((RENDER_WIDTH / TILE_WIDTH_HEIGHT)/CHUNK_WIDTH)+2)
|
||||
#define CHUNK_MAP_HEIGHT (((RENDER_HEIGHT / TILE_WIDTH_HEIGHT)/CHUNK_HEIGHT)+2)
|
||||
#define CHUNK_MAP_COUNT (CHUNK_MAP_WIDTH * CHUNK_MAP_HEIGHT)
|
||||
|
||||
typedef struct {
|
||||
uint16_t x, y;
|
||||
tile_t tilesBase[CHUNK_TILE_COUNT];
|
||||
tile_t tilesBaseOverlay[CHUNK_TILE_COUNT];
|
||||
uint32_t entityIDs[CHUNK_ENTITY_COUNT_MAX];
|
||||
uint8_t entityCount;
|
||||
} chunk_t;
|
||||
|
||||
typedef struct {
|
||||
chunk_t chunks[CHUNK_MAP_COUNT];
|
||||
chunk_t *chunkOrder[CHUNK_MAP_COUNT];
|
||||
|
||||
uint16_t topLeftX;
|
||||
uint16_t topLeftY;
|
||||
} chunkmap_t;
|
||||
|
||||
extern chunkmap_t CHUNK_MAP;
|
||||
|
||||
/**
|
||||
* Initializes the chunk map.
|
||||
*/
|
||||
void chunkMapInit();
|
||||
|
||||
/**
|
||||
* Shifts the chunk map by the specified x and y offsets.
|
||||
*
|
||||
* @param x The x offset to shift the chunk map.
|
||||
* @param y The y offset to shift the chunk map.
|
||||
*/
|
||||
void chunkMapShift(const int16_t x, const int16_t y);
|
||||
|
||||
/**
|
||||
* Sets the position of the chunk map to the specified coordinates.
|
||||
*
|
||||
* @param x The x coordinate of the top-left chunk.
|
||||
* @param y The y coordinate of the top-left chunk.
|
||||
*/
|
||||
void chunkMapSetPosition(const uint16_t x, const uint16_t y);
|
||||
|
||||
/**
|
||||
* Gets the chunk at the specified chunk coordinates.
|
||||
*
|
||||
* @param chunkX The x coordinate of the chunk.
|
||||
* @param chunkY The y coordinate of the chunk.
|
||||
* @return A pointer to the chunk at the specified chunk coordinates, or NULL if
|
||||
* no chunk exists at those coordinates.
|
||||
*/
|
||||
chunk_t * chunkGetChunkAt(const uint16_t chunkX, const uint16_t chunkY);
|
||||
|
||||
/**
|
||||
* Loads a chunk at the specified coordinates.
|
||||
*
|
||||
* @param chunk The chunk to load.
|
||||
* @param x The x coordinate of the chunk.
|
||||
* @param y The y coordinate of the chunk.
|
||||
*/
|
||||
void chunkLoad(chunk_t *chunk, const uint16_t x, const uint16_t y);
|
||||
|
||||
/**
|
||||
* Unloads a chunk (that is currently loaded).
|
||||
*
|
||||
* @param chunk The chunk to unload.
|
||||
*/
|
||||
void chunkUnload(chunk_t *chunk);
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "chunk.h"
|
||||
#include "entity/entity.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t layerBase[CHUNK_TILE_COUNT];
|
||||
uint8_t layerBaseOverlay[CHUNK_TILE_COUNT];
|
||||
entity_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||
} chunkdata_t;
|
||||
@@ -1,72 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworld.h"
|
||||
#include "chunk.h"
|
||||
#include "display/render.h"
|
||||
#include "assert/assert.h"
|
||||
#include "entity/entity.h"
|
||||
|
||||
uint32_t OVERWORLD_CAMERA_X;
|
||||
uint32_t OVERWORLD_CAMERA_Y;
|
||||
overworldcameratype_t OVERWORLD_CAMERA_TYPE;
|
||||
|
||||
void overworldInit(void) {
|
||||
playerInit();
|
||||
chunkMapInit();
|
||||
|
||||
OVERWORLD_CAMERA_X = 0;
|
||||
OVERWORLD_CAMERA_Y = 0;
|
||||
OVERWORLD_CAMERA_TYPE = OVERWORLD_CAMERA_TYPE_CENTERED_POSITION;
|
||||
}
|
||||
|
||||
void overworldUpdate() {
|
||||
entity_t *entity;
|
||||
|
||||
assertTrue(
|
||||
OVERWORLD_CAMERA_X < OVERWORLD_CAMERA_LIMIT_X,
|
||||
"Camera position limit (just because I haven't tested properly)"
|
||||
);
|
||||
assertTrue(
|
||||
OVERWORLD_CAMERA_Y < OVERWORLD_CAMERA_LIMIT_Y,
|
||||
"Camera position limit (just because I haven't tested properly)"
|
||||
);
|
||||
|
||||
entity = ENTITIES;
|
||||
do {
|
||||
entityUpdate(entity++);
|
||||
} while(entity->type != ENTITY_TYPE_NULL);
|
||||
|
||||
// Testing, follow player
|
||||
entity = &ENTITIES[0]; // Player entity
|
||||
assertTrue(
|
||||
entity->type == ENTITY_TYPE_PLAYER,
|
||||
"First entity must be player"
|
||||
);
|
||||
OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX;
|
||||
OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY;
|
||||
|
||||
uint16_t x, y;
|
||||
uint16_t halfWidth, halfHeight;
|
||||
halfWidth = ((CHUNK_MAP_WIDTH - 1) * CHUNK_WIDTH * TILE_WIDTH_HEIGHT) / 2;
|
||||
halfHeight = ((CHUNK_MAP_HEIGHT - 1) * CHUNK_HEIGHT * TILE_WIDTH_HEIGHT) / 2;
|
||||
|
||||
// Calculate the chunk map position based on the camera position.
|
||||
if(OVERWORLD_CAMERA_X < halfWidth) {
|
||||
x = 0;
|
||||
} else {
|
||||
x = (OVERWORLD_CAMERA_X - halfWidth) / (CHUNK_WIDTH*TILE_WIDTH_HEIGHT);
|
||||
}
|
||||
|
||||
if(OVERWORLD_CAMERA_Y < halfHeight) {
|
||||
y = 0;
|
||||
} else {
|
||||
y = (OVERWORLD_CAMERA_Y - halfHeight) / (CHUNK_HEIGHT*TILE_WIDTH_HEIGHT);
|
||||
}
|
||||
|
||||
chunkMapSetPosition(x, y);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
OVERWORLD_CAMERA_TYPE_CENTERED_POSITION,
|
||||
} overworldcameratype_t;
|
||||
|
||||
extern uint32_t OVERWORLD_CAMERA_X;
|
||||
extern uint32_t OVERWORLD_CAMERA_Y;
|
||||
extern overworldcameratype_t OVERWORLD_CAMERA_TYPE;
|
||||
|
||||
#define OVERWORLD_CAMERA_LIMIT_X (UINT32_MAX / 4)
|
||||
#define OVERWORLD_CAMERA_LIMIT_Y (UINT32_MAX / 4)
|
||||
|
||||
/**
|
||||
* Initializes the overworld.
|
||||
*/
|
||||
void overworldInit(void);
|
||||
|
||||
/**
|
||||
* Updates the overworld.
|
||||
*/
|
||||
void overworldUpdate(void);
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define TILE_WIDTH_HEIGHT 16
|
||||
|
||||
typedef uint8_t tile_t;
|
||||
|
||||
typedef enum {
|
||||
TILE_SOLID_NONE = 0,
|
||||
TILE_SOLID_FULL = 1,
|
||||
TILE_SOLID_TRIANGLE_TOP_RIGHT = 2,
|
||||
TILE_SOLID_TRIANGLE_TOP_LEFT = 3,
|
||||
TILE_SOLID_TRIANGLE_BOTTOM_RIGHT = 4,
|
||||
TILE_SOLID_TRIANGLE_BOTTOM_LEFT = 5,
|
||||
} tilesolidtype_t;
|
||||
|
||||
typedef struct {
|
||||
tilesolidtype_t solidType;
|
||||
} tilemetadata_t;
|
||||
@@ -1,40 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Libs
|
||||
find_package(pspsdk REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
# pspsdk
|
||||
${SDL2_LIBRARIES}
|
||||
)
|
||||
|
||||
# Compile definitions
|
||||
target_compile_definitions(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
RENDER_WIDTH=480
|
||||
RENDER_HEIGHT=272
|
||||
RENDER_WINDOW_WIDTH_DEFAULT=480
|
||||
RENDER_WINDOW_HEIGHT_DEFAULT=272
|
||||
# DUSK_TIME_DYNAMIC=0
|
||||
DUSK_TIME_DYNAMIC=1
|
||||
)
|
||||
|
||||
# Includes
|
||||
target_include_directories(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${SDL2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
duskpsp.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "duskpsp.h"
|
||||
|
||||
// PSP_MODULE_INFO("Dusk", 0, 1, 0);
|
||||
// PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER | THREAD_ATTR_VFPU);
|
||||
@@ -1,41 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Compile defs
|
||||
target_compile_definitions(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
# DUSK_KEYBOARD_SUPPORT=1
|
||||
)
|
||||
|
||||
# Libs
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(cglm REQUIRED)
|
||||
|
||||
target_link_libraries(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
SDL2::SDL2
|
||||
OpenGL::GL
|
||||
GL
|
||||
cglm
|
||||
)
|
||||
|
||||
|
||||
# Includes
|
||||
target_include_directories(${DUSK_TARGET_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
)
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
dusksdl2input.c
|
||||
main.c
|
||||
time.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(display)
|
||||
@@ -1,21 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
render.c
|
||||
renderbackbuffer.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(camera)
|
||||
add_subdirectory(framebuffer)
|
||||
add_subdirectory(mesh)
|
||||
add_subdirectory(overworld)
|
||||
add_subdirectory(texture)
|
||||
add_subdirectory(spritebatch)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(ui)
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "camera.h"
|
||||
#include "display/render.h"
|
||||
|
||||
#include "world/overworld.h"
|
||||
|
||||
void cameraUIPush(void) {
|
||||
glPushMatrix();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT);
|
||||
|
||||
mat4 ortho;
|
||||
glm_ortho(
|
||||
0.0f, (float_t)RENDER_WIDTH,
|
||||
(float_t)RENDER_HEIGHT, 0.0f,
|
||||
-1.0f, 1.0f,
|
||||
ortho
|
||||
);
|
||||
glLoadMatrixf((const GLfloat*)ortho);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
}
|
||||
|
||||
void cameraUIPop(void) {
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void cameraScreenPush(void) {
|
||||
glPushMatrix();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
mat4 ortho;
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
int32_t windowWidth, windowHeight;
|
||||
SDL_GetWindowSize(RENDER_WINDOW, &windowWidth, &windowHeight);
|
||||
|
||||
glViewport(0, 0, windowWidth, windowHeight);
|
||||
glm_ortho(
|
||||
0.0f, (float_t) windowWidth,
|
||||
(float_t)windowHeight, 0.0f,
|
||||
-1.0f, 1.0f,
|
||||
ortho
|
||||
);
|
||||
#else
|
||||
glm_ortho(
|
||||
0.0f, (float_t)RENDER_WIDTH,
|
||||
(float_t)RENDER_HEIGHT, 0.0f,
|
||||
-1.0f, 1.0f,
|
||||
ortho
|
||||
);
|
||||
#endif
|
||||
glLoadMatrixf((const GLfloat*)ortho);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
}
|
||||
|
||||
void cameraScreenPop(void) {
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void cameraOverworldPush(void) {
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
glViewport(0, 0, RENDER_WIDTH, RENDER_HEIGHT);
|
||||
#endif
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
const float_t fov = glm_rad(75.0f);
|
||||
const float_t camOffset = 12.0f;
|
||||
const float_t aspect = (float_t)RENDER_WIDTH / (float_t)RENDER_HEIGHT;
|
||||
const float_t pixelPerfectOffset = (
|
||||
tanf((glm_rad(180) - fov) / 2.0f) *
|
||||
((float_t)RENDER_HEIGHT/ 2.0f)
|
||||
);
|
||||
|
||||
vec3 look = {
|
||||
OVERWORLD_CAMERA_X,
|
||||
OVERWORLD_CAMERA_Y,
|
||||
0.0f
|
||||
};
|
||||
vec3 eye = {
|
||||
look[0],
|
||||
look[1] + camOffset,
|
||||
look[2] + pixelPerfectOffset
|
||||
};
|
||||
vec3 up = { 0.0f, 1.0f, 0.0f };
|
||||
|
||||
mat4 proj;
|
||||
glm_perspective(fov, aspect, 0.1f, 1000.0f, proj);
|
||||
|
||||
// Flips rendering on the Y axis, so that it is still right-down even in 3D;
|
||||
proj[1][1] = -proj[1][1];
|
||||
|
||||
mat4 view;
|
||||
glm_lookat(eye, look, up, view);
|
||||
|
||||
mat4 pv;
|
||||
glm_mat4_mul(proj, view, pv);
|
||||
|
||||
glLoadMatrixf((const GLfloat*)pv);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
}
|
||||
|
||||
void cameraOverworldPop(void) {
|
||||
glPopMatrix();
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusksdl2.h"
|
||||
|
||||
/**
|
||||
* Pushes the UI camera matrix onto the stack.
|
||||
*/
|
||||
void cameraUIPush(void);
|
||||
|
||||
/**
|
||||
* Pops the UI camera matrix from the stack.
|
||||
*/
|
||||
void cameraUIPop(void);
|
||||
|
||||
/**
|
||||
* Pushes the screen space camera matrix onto the stack.
|
||||
*/
|
||||
void cameraScreenPush(void);
|
||||
|
||||
/**
|
||||
* Pops the screen space camera matrix.
|
||||
*/
|
||||
void cameraScreenPop(void);
|
||||
|
||||
/**
|
||||
* Pushes the overworld camera matrix onto the stack.
|
||||
*/
|
||||
void cameraOverworldPush(void);
|
||||
|
||||
/**
|
||||
* Pops the overworld camera matrix.
|
||||
*/
|
||||
void cameraOverworldPop(void);
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
void frameBufferInit(
|
||||
framebuffer_t *framebuffer,
|
||||
const uint32_t width,
|
||||
const uint32_t height
|
||||
) {
|
||||
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
|
||||
assertTrue(width > 0 && height > 0, "Width & height must be greater than 0");
|
||||
|
||||
memoryZero(framebuffer, sizeof(framebuffer_t));
|
||||
textureInit(&framebuffer->texture, width, height, GL_RGBA, NULL);
|
||||
|
||||
// Generate the framebuffer object using EXT
|
||||
glGenFramebuffersEXT(1, &framebuffer->id);
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
|
||||
|
||||
// Attach the texture to the framebuffer
|
||||
glFramebufferTexture2DEXT(
|
||||
GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
|
||||
GL_TEXTURE_2D, framebuffer->texture.id, 0
|
||||
);
|
||||
|
||||
// Check if the framebuffer is complete
|
||||
if(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT) {
|
||||
assertUnreachable("Framebuffer is not complete");
|
||||
}
|
||||
|
||||
// Unbind the framebuffer
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
||||
}
|
||||
|
||||
void frameBufferBind(const framebuffer_t *framebuffer) {
|
||||
if(framebuffer == NULL) {
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind the framebuffer for rendering
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->id);
|
||||
}
|
||||
|
||||
void frameBufferDispose(framebuffer_t *framebuffer) {
|
||||
assertNotNull(framebuffer, "Framebuffer cannot be NULL");
|
||||
|
||||
glDeleteFramebuffersEXT(1, &framebuffer->id);
|
||||
textureDispose(&framebuffer->texture);
|
||||
}
|
||||
#endif
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/render.h"
|
||||
#include "display/texture/texture.h"
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
typedef struct {
|
||||
GLuint id;
|
||||
texture_t texture;
|
||||
} framebuffer_t;
|
||||
|
||||
/**
|
||||
* Initializes a framebuffer using EXT methods.
|
||||
*
|
||||
* @param framebuffer The framebuffer to initialize.
|
||||
* @param width The width of the framebuffer.
|
||||
* @param height The height of the framebuffer.
|
||||
* @return An error code indicating success or failure.
|
||||
*/
|
||||
void frameBufferInit(
|
||||
framebuffer_t *framebuffer,
|
||||
const uint32_t width,
|
||||
const uint32_t height
|
||||
);
|
||||
|
||||
/**
|
||||
* Binds the framebuffer for rendering using EXT methods.
|
||||
*
|
||||
* @param framebuffer The framebuffer to bind, or NULL to unbind.
|
||||
*/
|
||||
void frameBufferBind(const framebuffer_t *framebuffer);
|
||||
|
||||
/**
|
||||
* Disposes of the framebuffer using EXT methods.
|
||||
*
|
||||
* @param framebuffer The framebuffer to dispose of.
|
||||
*/
|
||||
void frameBufferDispose(framebuffer_t *framebuffer);
|
||||
#endif
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "mesh.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
#include "console/console.h"
|
||||
|
||||
void meshInit(
|
||||
mesh_t *mesh,
|
||||
const GLenum primitiveType,
|
||||
const int32_t vertexCount,
|
||||
const meshvertex_t *vertices
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
assertTrue(vertexCount > 0, "Vertex count must be greater than 0");
|
||||
|
||||
memoryZero(mesh, sizeof(mesh_t));
|
||||
|
||||
mesh->primitiveType = primitiveType;
|
||||
mesh->vertexCount = vertexCount;
|
||||
mesh->vertices = vertices;
|
||||
}
|
||||
|
||||
void meshDraw(
|
||||
const mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
) {
|
||||
const int32_t offset = vertexOffset == -1 ? 0 : vertexOffset;
|
||||
const int32_t count = vertexCount == -1 ? mesh->vertexCount : vertexCount;
|
||||
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
assertTrue(offset >= 0, "Vertex offset must be non-negative");
|
||||
assertTrue(count >= 0, "Vertex count must be non-negative");
|
||||
assertTrue(offset + count <= mesh->vertexCount,
|
||||
"Vertex offset + count must not exceed vertex count"
|
||||
);
|
||||
|
||||
#if 1
|
||||
// PSP style pointer legacy OpenGL
|
||||
const GLsizei stride = sizeof(meshvertex_t);
|
||||
|
||||
glColorPointer(
|
||||
MESH_VERTEX_COLOR_SIZE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].color[0]
|
||||
);
|
||||
glTexCoordPointer(
|
||||
MESH_VERTEX_UV_SIZE,
|
||||
GL_FLOAT,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].uv[0]
|
||||
);
|
||||
glVertexPointer(
|
||||
MESH_VERTEX_POS_SIZE,
|
||||
GL_FLOAT,
|
||||
stride,
|
||||
(const GLvoid*)&mesh->vertices[offset].pos[0]
|
||||
);
|
||||
|
||||
glDrawArrays(
|
||||
mesh->primitiveType,
|
||||
0,
|
||||
count
|
||||
);
|
||||
#else
|
||||
#error "Need to support modern OpenGL with VAOs and VBOs"
|
||||
#endif
|
||||
}
|
||||
|
||||
void meshDispose(mesh_t *mesh) {
|
||||
assertNotNull(mesh, "Mesh cannot be NULL");
|
||||
memoryZero(mesh, sizeof(mesh_t));
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2025 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "dusksdl2.h"
|
||||
|
||||
#define MESH_VERTEX_COLOR_SIZE 4
|
||||
#define MESH_VERTEX_UV_SIZE 2
|
||||
#define MESH_VERTEX_POS_SIZE 3
|
||||
|
||||
typedef struct {
|
||||
GLubyte color[MESH_VERTEX_COLOR_SIZE];
|
||||
GLfloat uv[MESH_VERTEX_UV_SIZE];
|
||||
GLfloat pos[MESH_VERTEX_POS_SIZE];
|
||||
} meshvertex_t;
|
||||
|
||||
typedef struct {
|
||||
const meshvertex_t *vertices;
|
||||
int32_t vertexCount;
|
||||
GLenum primitiveType;
|
||||
} mesh_t;
|
||||
|
||||
/**
|
||||
* Initializes a mesh.
|
||||
*
|
||||
* @param mesh The mesh to initialize.
|
||||
* @param primitiveType The OpenGL primitive type (e.g., GL_TRIANGLES).
|
||||
* @param vertexCount The number of vertices in the mesh.
|
||||
* @param vertices The vertex data for the mesh.
|
||||
*/
|
||||
void meshInit(
|
||||
mesh_t *mesh,
|
||||
const GLenum primitiveType,
|
||||
const int32_t vertexCount,
|
||||
const meshvertex_t *vertices
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws a mesh.
|
||||
*
|
||||
* @param mesh The mesh to draw.
|
||||
* @param vertexOffset The offset in the vertex array to start drawing from.
|
||||
* @param vertexCount The number of vertices to draw. If -1, draws all vertices.
|
||||
*/
|
||||
void meshDraw(
|
||||
const mesh_t *mesh,
|
||||
const int32_t vertexOffset,
|
||||
const int32_t vertexCount
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes a mesh.
|
||||
*
|
||||
* @param mesh The mesh to dispose.
|
||||
*/
|
||||
void meshDispose(mesh_t *mesh);
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "quad.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void quadBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t minX,
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
const uint8_t r,
|
||||
const uint8_t g,
|
||||
const uint8_t b,
|
||||
const uint8_t a,
|
||||
const float_t u0,
|
||||
const float_t v0,
|
||||
const float_t u1,
|
||||
const float_t v1
|
||||
) {
|
||||
const float_t z = 0.0f; // Z coordinate for 2D rendering
|
||||
assertNotNull(vertices, "Vertices cannot be NULL");
|
||||
|
||||
// First triangle
|
||||
vertices[0] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u0, v0 }, // UV
|
||||
{ minX, minY, z } // Position
|
||||
};
|
||||
vertices[1] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u1, v0 }, // UV
|
||||
{ maxX, minY, z } // Position
|
||||
};
|
||||
vertices[2] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u1, v1 }, // UV
|
||||
{ maxX, maxY, z } // Position
|
||||
};
|
||||
|
||||
// Second triangle
|
||||
vertices[3] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u0, v0 }, // UV
|
||||
{ minX, minY, z } // Position
|
||||
};
|
||||
vertices[4] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u1, v1 }, // UV
|
||||
{ maxX, maxY, z } // Position
|
||||
};
|
||||
vertices[5] = (meshvertex_t) {
|
||||
{ r, g, b, a }, // Color
|
||||
{ u0, v1 }, // UV
|
||||
{ minX, maxY, z } // Position
|
||||
};
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "mesh.h"
|
||||
|
||||
#define QUAD_VERTEX_COUNT 6
|
||||
|
||||
/**
|
||||
* Buffers a quad into the provided vertex array.
|
||||
*
|
||||
* @param vertices The vertex array to buffer into.
|
||||
* @param minX The minimum X coordinate of the quad.
|
||||
* @param minY The minimum Y coordinate of the quad.
|
||||
* @param maxX The maximum X coordinate of the quad.
|
||||
* @param maxY The maximum Y coordinate of the quad.
|
||||
* @param r The red color component (0-255).
|
||||
* @param g The green color component (0-255).
|
||||
* @param b The blue color component (0-255).
|
||||
* @param a The alpha color component (0-255).
|
||||
* @param u0 The U texture coordinate for the first vertex.
|
||||
* @param v0 The V texture coordinate for the first vertex.
|
||||
* @param u1 The U texture coordinate for the second vertex.
|
||||
* @param v1 The V texture coordinate for the second vertex.
|
||||
*/
|
||||
void quadBuffer(
|
||||
meshvertex_t *vertices,
|
||||
const float_t minX,
|
||||
const float_t minY,
|
||||
const float_t maxX,
|
||||
const float_t maxY,
|
||||
const uint8_t r,
|
||||
const uint8_t g,
|
||||
const uint8_t b,
|
||||
const uint8_t a,
|
||||
const float_t u0,
|
||||
const float_t v0,
|
||||
const float_t u1,
|
||||
const float_t v1
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
renderoverworld.c
|
||||
)
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "renderoverworld.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "display/camera/camera.h"
|
||||
#include "entity/entity.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
renderoverworld_t RENDER_OVERWORLD;
|
||||
|
||||
void renderOverworldInit(void) {
|
||||
memoryZero(&RENDER_OVERWORLD, sizeof(RENDER_OVERWORLD));
|
||||
|
||||
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
|
||||
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
|
||||
|
||||
meshInit(
|
||||
&chunk->meshBase,
|
||||
GL_TRIANGLES,
|
||||
CHUNK_TILE_COUNT * QUAD_VERTEX_COUNT,
|
||||
chunk->verticesBase
|
||||
);
|
||||
|
||||
meshInit(
|
||||
&chunk->meshBaseOverlay,
|
||||
GL_TRIANGLES,
|
||||
CHUNK_TILE_COUNT,
|
||||
chunk->verticesBaseOverlay
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void renderOverworldDraw(void) {
|
||||
cameraOverworldPush();
|
||||
|
||||
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
|
||||
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
|
||||
meshDraw(&chunk->meshBase, -1, -1);
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
entity_t *entity = &ENTITIES[i];
|
||||
if(entity->type == ENTITY_TYPE_NULL) continue;
|
||||
|
||||
float_t x = (entity->x * TILE_WIDTH_HEIGHT) + entity->subX;
|
||||
float_t y = (entity->y * TILE_WIDTH_HEIGHT) + entity->subY;
|
||||
|
||||
// Draw the entity
|
||||
spriteBatchPush(
|
||||
NULL,
|
||||
x, y,
|
||||
x + TILE_WIDTH_HEIGHT, y + TILE_WIDTH_HEIGHT,
|
||||
0xFF, 0x00, 0xFF, 0XFF,
|
||||
0.0f, 0.0f, 1.0f, 1.0f
|
||||
);
|
||||
}
|
||||
|
||||
spriteBatchFlush();
|
||||
cameraOverworldPop();
|
||||
}
|
||||
|
||||
void renderChunkUpdated(chunk_t *chunk) {
|
||||
uint8_t r, g, b;
|
||||
assertNotNull(chunk, "Chunk pointer is null");
|
||||
|
||||
int32_t chunkIndex = chunk - CHUNK_MAP.chunks;
|
||||
assertTrue(
|
||||
chunkIndex >= 0 && chunkIndex < CHUNK_MAP_COUNT,
|
||||
"Chunk index out of bounds"
|
||||
);
|
||||
|
||||
for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) {
|
||||
tile_t base = chunk->tilesBase[i];
|
||||
tile_t overlay = chunk->tilesBaseOverlay[i];
|
||||
|
||||
float_t posX = (i % CHUNK_WIDTH) + (chunk->x * CHUNK_WIDTH);
|
||||
float_t posY = (i / CHUNK_WIDTH) + (chunk->y * CHUNK_HEIGHT);
|
||||
|
||||
switch(base) {
|
||||
case 0:
|
||||
r = 0; g = 0; b = 0; // Black for empty
|
||||
break;
|
||||
case 1:
|
||||
r = 34; g = 139; b = 34; // Forest Green
|
||||
break;
|
||||
case 2:
|
||||
r = 0; g = 191; b = 255; // Deep Sky Blue
|
||||
break;
|
||||
case 3:
|
||||
r = 139; g = 69; b = 19; // Saddle Brown
|
||||
break;
|
||||
case 4:
|
||||
r = 255; g = 255; b = 0; // Yellow
|
||||
break;
|
||||
default:
|
||||
r = 255; g = 20; b = 147; // Pink for unknown
|
||||
break;
|
||||
}
|
||||
|
||||
quadBuffer(
|
||||
&RENDER_OVERWORLD.chunks[chunkIndex].verticesBase[i * QUAD_VERTEX_COUNT],
|
||||
posX * TILE_WIDTH_HEIGHT,
|
||||
posY * TILE_WIDTH_HEIGHT,
|
||||
(posX + 1) * TILE_WIDTH_HEIGHT,
|
||||
(posY + 1) * TILE_WIDTH_HEIGHT,
|
||||
r, g, b, 255,
|
||||
0, 0, 1, 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void renderOverworldDispose(void) {
|
||||
// Clean up overworld rendering resources here
|
||||
for(uint8_t i = 0; i < CHUNK_MAP_COUNT; i++) {
|
||||
renderchunk_t *chunk = &RENDER_OVERWORLD.chunks[i];
|
||||
meshDispose(&chunk->meshBase);
|
||||
meshDispose(&chunk->meshBaseOverlay);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "world/chunk.h"
|
||||
#include "display/mesh/quad.h"
|
||||
|
||||
typedef struct {
|
||||
mesh_t meshBase;
|
||||
meshvertex_t verticesBase[CHUNK_TILE_COUNT * QUAD_VERTEX_COUNT];
|
||||
|
||||
mesh_t meshBaseOverlay;
|
||||
meshvertex_t verticesBaseOverlay[CHUNK_TILE_COUNT];
|
||||
} renderchunk_t;
|
||||
|
||||
typedef struct {
|
||||
renderchunk_t chunks[CHUNK_MAP_COUNT];
|
||||
} renderoverworld_t;
|
||||
|
||||
extern renderoverworld_t RENDER_OVERWORLD;
|
||||
|
||||
/**
|
||||
* Initializes the render overworld.
|
||||
*/
|
||||
void renderOverworldInit(void);
|
||||
|
||||
/**
|
||||
* Draws the render overworld.
|
||||
*/
|
||||
void renderOverworldDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the render overworld.
|
||||
*/
|
||||
void renderOverworldDispose(void);
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusksdl2input.h"
|
||||
#include "render.h"
|
||||
#include "renderbackbuffer.h"
|
||||
#include "display/scene/renderscene.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/ui/renderui.h"
|
||||
|
||||
SDL_Window *RENDER_WINDOW;
|
||||
SDL_GLContext RENDER_GL_CONTEXT;
|
||||
bool_t RENDER_RUNNING;
|
||||
|
||||
errorret_t renderInit(void) {
|
||||
// Init SDL
|
||||
uint32_t flags = SDL_INIT_VIDEO;
|
||||
#if INPUT_SUPPORT_GAMEPAD
|
||||
flags |= SDL_INIT_GAMECONTROLLER;
|
||||
#endif
|
||||
|
||||
if(SDL_Init(flags) != 0) {
|
||||
errorThrow(
|
||||
"SDL Failed to Initialize: %s",
|
||||
SDL_GetError()
|
||||
);
|
||||
}
|
||||
|
||||
// Set OpenGL attributes (Needs to be done now or later?)
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
||||
// Create window with OpenGL flag.
|
||||
RENDER_WINDOW = SDL_CreateWindow(
|
||||
"DuskSDL2",
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
RENDER_WINDOW_WIDTH_DEFAULT,
|
||||
RENDER_WINDOW_HEIGHT_DEFAULT,
|
||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI |
|
||||
SDL_WINDOW_OPENGL
|
||||
);
|
||||
if(!RENDER_WINDOW) {
|
||||
errorThrow("SDL_CreateWindow failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
// Create OpenGL context
|
||||
RENDER_GL_CONTEXT = SDL_GL_CreateContext(RENDER_WINDOW);
|
||||
if(!RENDER_GL_CONTEXT) {
|
||||
errorThrow("SDL_GL_CreateContext failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_LIGHTING);// PSP defaults this on?
|
||||
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
glEnableClientState(GL_COLOR_ARRAY);// To confirm: every frame on PSP?
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
spriteBatchInit();
|
||||
renderBackBufferInit();
|
||||
renderSceneInit();
|
||||
renderUIInit();
|
||||
|
||||
RENDER_RUNNING = true;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t renderDraw(void) {
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
switch(event.type) {
|
||||
case SDL_QUIT:
|
||||
RENDER_RUNNING = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the state
|
||||
spriteBatchClear();
|
||||
renderBackBufferBind();
|
||||
|
||||
renderSceneDraw();
|
||||
renderUIDraw();
|
||||
|
||||
// Finish rendering, now render back buffer.
|
||||
renderBackBufferUnbind();
|
||||
renderBackBufferDraw();
|
||||
textureBind(NULL);
|
||||
|
||||
SDL_GL_SwapWindow(RENDER_WINDOW);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t renderDispose(void) {
|
||||
renderUIDispose();
|
||||
renderSceneDispose();
|
||||
renderBackBufferDispose();
|
||||
spriteBatchDispose();
|
||||
|
||||
// Destroy OpenGL context
|
||||
SDL_GL_DeleteContext(RENDER_GL_CONTEXT);
|
||||
SDL_DestroyWindow(RENDER_WINDOW);
|
||||
SDL_Quit();
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusksdl2.h"
|
||||
#include "display/renderbase.h"
|
||||
|
||||
#ifndef RENDER_WINDOW_WIDTH_DEFAULT
|
||||
#define RENDER_WINDOW_WIDTH_DEFAULT RENDER_WIDTH * 3
|
||||
#endif
|
||||
#ifndef RENDER_WINDOW_HEIGHT_DEFAULT
|
||||
#define RENDER_WINDOW_HEIGHT_DEFAULT RENDER_HEIGHT * 3
|
||||
#endif
|
||||
|
||||
#if RENDER_WIDTH == RENDER_WINDOW_WIDTH_DEFAULT && RENDER_HEIGHT == RENDER_WINDOW_HEIGHT_DEFAULT
|
||||
#define RENDER_USE_FRAMEBUFFER 0
|
||||
#else
|
||||
#define RENDER_USE_FRAMEBUFFER 1
|
||||
#endif
|
||||
|
||||
extern SDL_Window *RENDER_WINDOW;
|
||||
extern SDL_Renderer *RENDER_RENDERER;
|
||||
extern bool_t RENDER_RUNNING;
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "renderbackbuffer.h"
|
||||
#include "render.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/camera/camera.h"
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
framebuffer_t RENDER_BACKBUFFER;
|
||||
#endif
|
||||
|
||||
errorret_t renderBackBufferInit(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
frameBufferInit(
|
||||
&RENDER_BACKBUFFER,
|
||||
RENDER_WIDTH,
|
||||
RENDER_HEIGHT
|
||||
);
|
||||
#else
|
||||
// No back buffer needed for window rendering
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void renderBackBufferBind(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
frameBufferBind(&RENDER_BACKBUFFER);
|
||||
#endif
|
||||
|
||||
// Fill background with cornflower blue.
|
||||
glClearColor(0.392f, 0.584f, 0.929f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void renderBackBufferUnbind(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
frameBufferBind(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void renderBackBufferDraw(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
int32_t windowWidth, windowHeight;
|
||||
SDL_GetWindowSize(RENDER_WINDOW, &windowWidth, &windowHeight);
|
||||
|
||||
// Set viewport to match window size
|
||||
cameraScreenPush();
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// Create a quad that is scaled to fit but maintain original aspect ratio
|
||||
int32_t renderWidth, renderHeight, renderX, renderY;
|
||||
if(RENDER_WIDTH * windowHeight > RENDER_HEIGHT * windowWidth) {
|
||||
renderWidth = windowWidth;
|
||||
renderHeight = (RENDER_HEIGHT * windowWidth) / RENDER_WIDTH;
|
||||
renderX = 0;
|
||||
renderY = (windowHeight - renderHeight) / 2;
|
||||
} else {
|
||||
renderWidth = (RENDER_WIDTH * windowHeight) / RENDER_HEIGHT;
|
||||
renderHeight = windowHeight;
|
||||
renderX = (windowWidth - renderWidth) / 2;
|
||||
renderY = 0;
|
||||
}
|
||||
|
||||
// Draw the back buffer texture
|
||||
spriteBatchClear();
|
||||
spriteBatchPush(
|
||||
&RENDER_BACKBUFFER.texture,
|
||||
renderX, renderY,
|
||||
renderX+renderWidth, renderY+renderHeight,
|
||||
0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0, 1, 1, 0
|
||||
);
|
||||
spriteBatchFlush();
|
||||
cameraScreenPop();
|
||||
#else
|
||||
// No back buffer to draw
|
||||
#endif
|
||||
}
|
||||
|
||||
errorret_t renderBackBufferDispose(void) {
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
frameBufferDispose(&RENDER_BACKBUFFER);
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "display/renderbase.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
|
||||
#if RENDER_USE_FRAMEBUFFER
|
||||
extern framebuffer_t RENDER_BACKBUFFER;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes the render back buffer. May be either a framebuffer or a texture
|
||||
* depending on the render settings.
|
||||
*/
|
||||
errorret_t renderBackBufferInit(void);
|
||||
|
||||
/**
|
||||
* Binds the render back buffer as the current render target.
|
||||
*/
|
||||
void renderBackBufferBind(void);
|
||||
|
||||
/**
|
||||
* Unbinds the render back buffer, returning to the default render target.
|
||||
*/
|
||||
void renderBackBufferUnbind(void);
|
||||
|
||||
/**
|
||||
* Draws the render back buffer to the screen, scaling it to fit the window.
|
||||
*/
|
||||
void renderBackBufferDraw(void);
|
||||
|
||||
/**
|
||||
* Disposes of the render back buffer, freeing any resources it holds.
|
||||
*
|
||||
* @return An error state if an error occurred, otherwise OK.
|
||||
*/
|
||||
errorret_t renderBackBufferDispose(void);
|
||||
@@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "renderscene.h"
|
||||
#include "display/overworld/renderoverworld.h"
|
||||
|
||||
renderscenecallback_t RENDER_SCENE_CALLBACKS[SCENE_COUNT] = {
|
||||
[SCENE_INITIAL] = {
|
||||
.init = NULL,
|
||||
.draw = NULL,
|
||||
.dispose = NULL
|
||||
},
|
||||
|
||||
[SCENE_OVERWORLD] = {
|
||||
.init = renderOverworldInit,
|
||||
.draw = renderOverworldDraw,
|
||||
.dispose = renderOverworldDispose
|
||||
},
|
||||
};
|
||||
|
||||
void renderSceneInit(void) {
|
||||
for(int32_t i = 0; i < SCENE_COUNT; i++) {
|
||||
if(!RENDER_SCENE_CALLBACKS[i].init) continue;
|
||||
RENDER_SCENE_CALLBACKS[i].init();
|
||||
}
|
||||
}
|
||||
|
||||
void renderSceneDraw(void) {
|
||||
if(!RENDER_SCENE_CALLBACKS[SCENE_CURRENT].draw) return;
|
||||
RENDER_SCENE_CALLBACKS[SCENE_CURRENT].draw();
|
||||
}
|
||||
|
||||
void renderSceneDispose(void) {
|
||||
for(int32_t i = 0; i < SCENE_COUNT; i++) {
|
||||
if(!RENDER_SCENE_CALLBACKS[i].dispose) continue;
|
||||
RENDER_SCENE_CALLBACKS[i].dispose();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user