Compare commits
53 Commits
69ce48a8b9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev
|
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev python3-dotenv python3-pyqt5 python3-opengl liblua5.3-dev
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=linux
|
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=linux
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -39,7 +39,7 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev
|
apt-get install -y build-essential cmake python3 python3-pip python3-polib python3-pil libsdl2-dev libgl1-mesa-dev libzip-dev python3-dotenv python3-pyqt5 python3-opengl liblua5.3-dev
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=psp
|
run: cmake -S . -B build -DDUSK_TARGET_SYSTEM=psp
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -56,27 +56,3 @@ jobs:
|
|||||||
name: dusk-psp
|
name: dusk-psp
|
||||||
path: build/gitea/
|
path: build/gitea/
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- build-linux
|
|
||||||
- build-psp
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Download Linux binary
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dusk-linux
|
|
||||||
path: ./release-assets/linux
|
|
||||||
- name: Download PSP binary
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: dusk-psp
|
|
||||||
path: ./release-assets/psp
|
|
||||||
- name: Create Gitea Release
|
|
||||||
uses: akkuman/gitea-release-action@v1
|
|
||||||
env:
|
|
||||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
|
||||||
files: ./release-assets/*
|
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -95,3 +95,10 @@ assets/borrowed
|
|||||||
# /archive
|
# /archive
|
||||||
|
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
.editor
|
||||||
|
.venv
|
||||||
@@ -11,7 +11,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/mod
|
|||||||
|
|
||||||
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
if(NOT DEFINED DUSK_TARGET_SYSTEM)
|
||||||
set(DUSK_TARGET_SYSTEM "linux")
|
set(DUSK_TARGET_SYSTEM "linux")
|
||||||
#set(DUSK_TARGET_SYSTEM "psp")
|
# set(DUSK_TARGET_SYSTEM "psp")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Prep cache
|
# Prep cache
|
||||||
@@ -92,9 +92,8 @@ add_subdirectory(src)
|
|||||||
# Build assets
|
# Build assets
|
||||||
add_custom_target(DUSK_ASSETS_BUILT ALL
|
add_custom_target(DUSK_ASSETS_BUILT ALL
|
||||||
COMMAND
|
COMMAND
|
||||||
${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assetstool/main.py
|
${Python3_EXECUTABLE} ${DUSK_TOOLS_DIR}/assettool.py
|
||||||
--assets ${DUSK_GAME_ASSETS_DIR}
|
--assets ${DUSK_GAME_ASSETS_DIR}
|
||||||
--build-type wad
|
|
||||||
--output-assets ${DUSK_BUILT_ASSETS_DIR}
|
--output-assets ${DUSK_BUILT_ASSETS_DIR}
|
||||||
--output-file ${DUSK_BUILD_DIR}/dusk.dsk
|
--output-file ${DUSK_BUILD_DIR}/dusk.dsk
|
||||||
--headers-dir ${DUSK_GENERATED_HEADERS_DIR}
|
--headers-dir ${DUSK_GENERATED_HEADERS_DIR}
|
||||||
|
|||||||
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 python python-polib python3-pillow python3-dotenv python3-numpy python-qt5 python3-pyopengl
|
||||||
|
```
|
||||||
|
|
||||||
|
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,5 +12,6 @@ add_subdirectory(palette)
|
|||||||
add_subdirectory(locale)
|
add_subdirectory(locale)
|
||||||
|
|
||||||
add_subdirectory(entity)
|
add_subdirectory(entity)
|
||||||
|
add_subdirectory(script)
|
||||||
add_subdirectory(map)
|
add_subdirectory(map)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Language: en_US\n"
|
"Language: en_US\n"
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
msgid "ui.test"
|
msgid "ui.test"
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
# This software is released under the MIT License.
|
# This software is released under the MIT License.
|
||||||
# https://opensource.org/licenses/MIT
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
add_asset(MAP map)
|
add_asset(MAP map.json)
|
||||||
3
assets/map/map.json
Normal file
3
assets/map/map.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"mapName": "Test"
|
||||||
|
}
|
||||||
1
assets/map/map/-1_-1_0.json
Normal file
1
assets/map/map/-1_-1_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/-1_-1_1.json
Normal file
1
assets/map/map/-1_-1_1.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/-1_0_0.json
Normal file
1
assets/map/map/-1_0_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/-2_1_0.json
Normal file
1
assets/map/map/-2_1_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/0_-1_0.json
Normal file
1
assets/map/map/0_-1_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/0_-1_1.json
Normal file
1
assets/map/map/0_-1_1.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
@@ -1,37 +1 @@
|
|||||||
{
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 2, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
"tiles": [
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
|
|
||||||
1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,37 +1 @@
|
|||||||
{
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
"tiles": [
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1
assets/map/map/2_0_0.json
Normal file
1
assets/map/map/2_0_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/3_0_0.json
Normal file
1
assets/map/map/3_0_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/4_0_0.json
Normal file
1
assets/map/map/4_0_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "entities": []}
|
||||||
1
assets/map/map/5_0_0.json
Normal file
1
assets/map/map/5_0_0.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"shapes": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
|
||||||
8
assets/script/CMakeLists.txt
Normal file
8
assets/script/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Copyright (c) 2025 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
add_asset(SCRIPT test.lua)
|
||||||
|
|
||||||
|
add_subdirectory(scene)
|
||||||
5
assets/script/init.lua
Normal file
5
assets/script/init.lua
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
print('Init')
|
||||||
|
|
||||||
|
-- Load map scene
|
||||||
|
setScene('map')
|
||||||
|
setMap()
|
||||||
4
assets/script/scene/CMakeLists.txt
Normal file
4
assets/script/scene/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright (c) 2025 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
64
cmake/modules/envtoh.cmake
Normal file
64
cmake/modules/envtoh.cmake
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
if(NOT DEFINED ENV_FILE)
|
||||||
|
message(FATAL_ERROR "ENV_FILE is not set")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED OUT_HEADER)
|
||||||
|
message(FATAL_ERROR "OUT_HEADER is not set")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${ENV_FILE}")
|
||||||
|
message(FATAL_ERROR ".env file not found: ${ENV_FILE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(STRINGS "${ENV_FILE}" ENV_LINES)
|
||||||
|
|
||||||
|
set(HEADER_CONTENT "#pragma once\n#include \"dusk.h\"\n\n")
|
||||||
|
|
||||||
|
foreach(line IN LISTS ENV_LINES)
|
||||||
|
# Skip comments and empty lines (allow whitespace before # or ;)
|
||||||
|
if(line STREQUAL "" OR line MATCHES "^[ \t]*[#;]")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Expect KEY=VALUE (allow spaces around '=')
|
||||||
|
if(NOT line MATCHES "^[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*")
|
||||||
|
message(WARNING "Skipping invalid line in ${ENV_FILE}: '${line}'")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Extract key
|
||||||
|
string(REGEX MATCH "^[A-Za-z_][A-Za-z0-9_]*" KEY "${line}")
|
||||||
|
string(LENGTH "${KEY}" key_len)
|
||||||
|
|
||||||
|
# Extract value (allow spaces around '=')
|
||||||
|
string(REGEX REPLACE "^[A-Za-z_][A-Za-z0-9_]*[ \t]*=[ \t]*" "" RAW_VALUE "${line}")
|
||||||
|
|
||||||
|
# Lowercase copy for boolean detection
|
||||||
|
string(TOLOWER "${RAW_VALUE}" VALUE_LC)
|
||||||
|
set(VALUE "${RAW_VALUE}")
|
||||||
|
|
||||||
|
# Determine type and format accordingly
|
||||||
|
if(VALUE_LC STREQUAL "true")
|
||||||
|
set(HEADER_CONTENT "${HEADER_CONTENT}#define ${KEY} true\n")
|
||||||
|
|
||||||
|
elseif(VALUE_LC STREQUAL "false")
|
||||||
|
set(HEADER_CONTENT "${HEADER_CONTENT}#define ${KEY} false\n")
|
||||||
|
|
||||||
|
elseif(VALUE MATCHES "^[+-]?[0-9]+$")
|
||||||
|
# Integer
|
||||||
|
set(HEADER_CONTENT "${HEADER_CONTENT}#define ${KEY} ${VALUE}\n")
|
||||||
|
|
||||||
|
elseif(VALUE MATCHES "^[+-]?[0-9]*\\.[0-9]+$")
|
||||||
|
# Float → append "f"
|
||||||
|
set(HEADER_CONTENT "${HEADER_CONTENT}#define ${KEY} ${VALUE}f\n")
|
||||||
|
|
||||||
|
else()
|
||||||
|
# String → escape for C literal
|
||||||
|
string(REPLACE "\\" "\\\\" VALUE_ESC "${VALUE}")
|
||||||
|
string(REPLACE "\"" "\\\"" VALUE_ESC "${VALUE_ESC}")
|
||||||
|
set(HEADER_CONTENT "${HEADER_CONTENT}#define ${KEY} \"${VALUE_ESC}\"\n")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
file(WRITE "${OUT_HEADER}" "${HEADER_CONTENT}")
|
||||||
|
message(STATUS "Generated header: ${OUT_HEADER}")
|
||||||
@@ -5,6 +5,17 @@
|
|||||||
|
|
||||||
find_package(cglm REQUIRED)
|
find_package(cglm REQUIRED)
|
||||||
find_package(libzip REQUIRED)
|
find_package(libzip REQUIRED)
|
||||||
|
find_package(Lua REQUIRED)
|
||||||
|
|
||||||
|
if(Lua_FOUND AND NOT TARGET Lua::Lua)
|
||||||
|
add_library(Lua::Lua INTERFACE IMPORTED)
|
||||||
|
set_target_properties(
|
||||||
|
Lua::Lua
|
||||||
|
PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Libs
|
# Libs
|
||||||
target_link_libraries(${DUSK_TARGET_NAME}
|
target_link_libraries(${DUSK_TARGET_NAME}
|
||||||
@@ -13,6 +24,7 @@ target_link_libraries(${DUSK_TARGET_NAME}
|
|||||||
cglm
|
cglm
|
||||||
zip
|
zip
|
||||||
pthread
|
pthread
|
||||||
|
Lua::Lua
|
||||||
)
|
)
|
||||||
|
|
||||||
# Includes
|
# Includes
|
||||||
@@ -27,6 +39,9 @@ target_sources(${DUSK_TARGET_NAME}
|
|||||||
main.c
|
main.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Defs
|
||||||
|
add_defs(duskdefs.env duskdefs.h)
|
||||||
|
|
||||||
# Subdirs
|
# Subdirs
|
||||||
add_subdirectory(assert)
|
add_subdirectory(assert)
|
||||||
add_subdirectory(asset)
|
add_subdirectory(asset)
|
||||||
@@ -38,6 +53,7 @@ add_subdirectory(input)
|
|||||||
add_subdirectory(locale)
|
add_subdirectory(locale)
|
||||||
add_subdirectory(rpg)
|
add_subdirectory(rpg)
|
||||||
add_subdirectory(scene)
|
add_subdirectory(scene)
|
||||||
|
add_subdirectory(script)
|
||||||
add_subdirectory(thread)
|
add_subdirectory(thread)
|
||||||
add_subdirectory(time)
|
add_subdirectory(time)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "type/assetlanguage.h"
|
#include "type/assetlanguage.h"
|
||||||
#include "type/assetmap.h"
|
#include "type/assetmap.h"
|
||||||
#include "type/assetchunk.h"
|
#include "type/assetchunk.h"
|
||||||
|
#include "type/assetscript.h"
|
||||||
#include <zip.h>
|
#include <zip.h>
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -21,6 +22,7 @@ typedef enum {
|
|||||||
ASSET_TYPE_LANGUAGE,
|
ASSET_TYPE_LANGUAGE,
|
||||||
ASSET_TYPE_MAP,
|
ASSET_TYPE_MAP,
|
||||||
ASSET_TYPE_CHUNK,
|
ASSET_TYPE_CHUNK,
|
||||||
|
ASSET_TYPE_SCRIPT,
|
||||||
|
|
||||||
ASSET_TYPE_COUNT,
|
ASSET_TYPE_COUNT,
|
||||||
} assettype_t;
|
} assettype_t;
|
||||||
@@ -81,5 +83,11 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
|
|||||||
.header = "DCF",
|
.header = "DCF",
|
||||||
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
|
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
|
||||||
.custom = assetChunkLoad
|
.custom = assetChunkLoad
|
||||||
|
},
|
||||||
|
|
||||||
|
[ASSET_TYPE_SCRIPT] = {
|
||||||
|
.header = "DSF",
|
||||||
|
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
|
||||||
|
.custom = assetScriptHandler
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -11,4 +11,5 @@ target_sources(${DUSK_TARGET_NAME}
|
|||||||
assetlanguage.c
|
assetlanguage.c
|
||||||
assetmap.c
|
assetmap.c
|
||||||
assetchunk.c
|
assetchunk.c
|
||||||
|
assetscript.c
|
||||||
)
|
)
|
||||||
@@ -7,11 +7,13 @@
|
|||||||
|
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t tileCount;
|
uint32_t tileCount;
|
||||||
uint8_t modelCount;
|
uint8_t modelCount;
|
||||||
|
uint8_t entityCount;
|
||||||
} assetchunkheader_t;
|
} assetchunkheader_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
@@ -27,17 +29,21 @@ typedef struct {
|
|||||||
} assetchunkmodelheader_t;
|
} assetchunkmodelheader_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
typedef struct {
|
||||||
|
entitytype_t entityType;
|
||||||
|
uint8_t localX;
|
||||||
|
uint8_t localY;
|
||||||
|
uint8_t localZ;
|
||||||
|
} assetchunkentityheader_t;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
errorret_t assetChunkLoad(assetcustom_t custom) {
|
errorret_t assetChunkLoad(assetcustom_t custom) {
|
||||||
assertNotNull(custom.output, "Output pointer cannot be NULL");
|
assertNotNull(custom.output, "Output pointer cannot be NULL");
|
||||||
assertNotNull(custom.zipFile, "Zip file pointer cannot be NULL");
|
assertNotNull(custom.zipFile, "Zip file pointer cannot be NULL");
|
||||||
|
|
||||||
chunk_t *chunk = (chunk_t *)custom.output;
|
chunk_t *chunk = (chunk_t *)custom.output;
|
||||||
printf(
|
assertTrue(chunk->meshCount == 0, "Chunk is not in a good state");
|
||||||
"Loading chunk asset at position (%d, %d, %d)...\n",
|
|
||||||
chunk->position.x,
|
|
||||||
chunk->position.y,
|
|
||||||
chunk->position.z
|
|
||||||
);
|
|
||||||
|
|
||||||
// Read header
|
// Read header
|
||||||
assetchunkheader_t header;
|
assetchunkheader_t header;
|
||||||
@@ -67,6 +73,15 @@ errorret_t assetChunkLoad(assetcustom_t custom) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(header.entityCount > CHUNK_ENTITY_COUNT_MAX) {
|
||||||
|
zip_fclose(custom.zipFile);
|
||||||
|
errorThrow(
|
||||||
|
"Chunk asset has too many entities: %d (max %d).",
|
||||||
|
header.entityCount,
|
||||||
|
CHUNK_ENTITY_COUNT_MAX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
chunk->meshCount = header.modelCount;
|
chunk->meshCount = header.modelCount;
|
||||||
|
|
||||||
// Read tile data
|
// Read tile data
|
||||||
@@ -112,6 +127,7 @@ errorret_t assetChunkLoad(assetcustom_t custom) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init the mesh
|
// Init the mesh
|
||||||
|
if(modelHeader.vertexCount > 0) {
|
||||||
mesh_t *mesh = &chunk->meshes[i];
|
mesh_t *mesh = &chunk->meshes[i];
|
||||||
meshInit(
|
meshInit(
|
||||||
mesh,
|
mesh,
|
||||||
@@ -121,6 +137,41 @@ errorret_t assetChunkLoad(assetcustom_t custom) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
vertexIndex += modelHeader.vertexCount;
|
vertexIndex += modelHeader.vertexCount;
|
||||||
|
} else {
|
||||||
|
chunk->meshes[i].vertexCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read entity data
|
||||||
|
for(uint8_t i = 0; i < header.entityCount; i++) {
|
||||||
|
assetchunkentityheader_t entityHeader;
|
||||||
|
bytesRead = zip_fread(
|
||||||
|
custom.zipFile, &entityHeader, sizeof(assetchunkentityheader_t)
|
||||||
|
);
|
||||||
|
if(bytesRead != sizeof(assetchunkentityheader_t)) {
|
||||||
|
zip_fclose(custom.zipFile);
|
||||||
|
errorThrow("Failed to read chunk entity header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t entityIndex = entityGetAvailable();
|
||||||
|
if(entityIndex == 0xFF) {
|
||||||
|
zip_fclose(custom.zipFile);
|
||||||
|
errorThrow("No available entity slots.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_t *entity = &ENTITIES[entityIndex];
|
||||||
|
entityInit(entity, (entitytype_t)entityHeader.entityType);
|
||||||
|
entity->position.x = (
|
||||||
|
(chunk->position.x * CHUNK_WIDTH) + entityHeader.localX
|
||||||
|
);
|
||||||
|
entity->position.y = (
|
||||||
|
(chunk->position.y * CHUNK_HEIGHT) + entityHeader.localY
|
||||||
|
);
|
||||||
|
entity->position.z = (
|
||||||
|
(chunk->position.z * CHUNK_DEPTH) + entityHeader.localZ
|
||||||
|
);
|
||||||
|
|
||||||
|
chunk->entities[i] = entityIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "locale/language/keys.h"
|
#include "locale/language/keys.h"
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
#include "duskdefs.h"
|
||||||
#include <zip.h>
|
#include <zip.h>
|
||||||
|
|
||||||
#define ASSET_LANG_CHUNK_CHAR_COUNT 6 * 1024 // 6 KB per chunk
|
|
||||||
#define ASSET_LANG_CHUNK_CACHE 4 // Number of chunks to cache in memory
|
#define ASSET_LANG_CHUNK_CACHE 4 // Number of chunks to cache in memory
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
|
|||||||
58
src/asset/type/assetscript.c
Normal file
58
src/asset/type/assetscript.c
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
errorret_t assetScriptHandler(assetcustom_t custom) {
|
||||||
|
assertNotNull(custom.zipFile, "Custom asset zip file cannot be NULL");
|
||||||
|
assertNotNull(custom.output, "Custom asset output cannot be NULL");
|
||||||
|
|
||||||
|
assetscript_t *script = (assetscript_t *)custom.output;
|
||||||
|
errorChain(assetScriptInit(script, custom.zipFile));
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetScriptInit(
|
||||||
|
assetscript_t *script,
|
||||||
|
zip_file_t *zipFile
|
||||||
|
) {
|
||||||
|
assertNotNull(script, "Script asset cannot be NULL");
|
||||||
|
assertNotNull(zipFile, "Zip file cannot be NULL");
|
||||||
|
|
||||||
|
// We now own the zip file handle.
|
||||||
|
script->zip = zipFile;
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char_t * assetScriptReader(lua_State* lState, void* data, size_t* size) {
|
||||||
|
assetscript_t *script = (assetscript_t *)data;
|
||||||
|
zip_int64_t bytesRead = zip_fread(
|
||||||
|
script->zip, script->buffer, sizeof(script->buffer)
|
||||||
|
);
|
||||||
|
|
||||||
|
if(bytesRead < 0) {
|
||||||
|
*size = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*size = (size_t)bytesRead;
|
||||||
|
return script->buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t assetScriptDispose(assetscript_t *script) {
|
||||||
|
assertNotNull(script, "Script asset cannot be NULL");
|
||||||
|
|
||||||
|
if(script->zip != NULL) {
|
||||||
|
zip_fclose(script->zip);
|
||||||
|
script->zip = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
57
src/asset/type/assetscript.h
Normal file
57
src/asset/type/assetscript.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "duskdefs.h"
|
||||||
|
#include <zip.h>
|
||||||
|
#include <lua.h>
|
||||||
|
|
||||||
|
#define ASSET_SCRIPT_BUFFER_SIZE 1024
|
||||||
|
|
||||||
|
typedef struct assetscript_s {
|
||||||
|
zip_file_t *zip;
|
||||||
|
char_t buffer[ASSET_SCRIPT_BUFFER_SIZE];
|
||||||
|
} assetscript_t;
|
||||||
|
|
||||||
|
typedef struct assetcustom_s assetcustom_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receiving function from the asset manager to handle script assets.
|
||||||
|
*
|
||||||
|
* @param custom Custom asset loading data.
|
||||||
|
* @return Error code.
|
||||||
|
*/
|
||||||
|
errorret_t assetScriptHandler(assetcustom_t custom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a script asset.
|
||||||
|
*
|
||||||
|
* @param script Script asset to initialize.
|
||||||
|
* @param zipFile Zip file handle for the script asset.
|
||||||
|
* @return Error code.
|
||||||
|
*/
|
||||||
|
errorret_t assetScriptInit(assetscript_t *script, zip_file_t *zipFile);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reader function for Lua to read script data from the asset.
|
||||||
|
*
|
||||||
|
* @param L Lua state.
|
||||||
|
* @param data Pointer to the assetscript_t structure.
|
||||||
|
* @param size Pointer to store the size of the read data.
|
||||||
|
* @return Pointer to the read data buffer.
|
||||||
|
*/
|
||||||
|
const char_t * assetScriptReader(lua_State* L, void* data, size_t* size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes of a script asset, freeing any allocated resources.
|
||||||
|
*
|
||||||
|
* @param script Script asset to dispose of.
|
||||||
|
* @return Error code.
|
||||||
|
*/
|
||||||
|
errorret_t assetScriptDispose(assetscript_t *script);
|
||||||
|
|
||||||
@@ -12,9 +12,7 @@ void debugPrint(const char_t *message, ...) {
|
|||||||
va_start(args, message);
|
va_start(args, message);
|
||||||
vprintf(message, args);
|
vprintf(message, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
|
fflush(stdout);
|
||||||
// For the time being just use standard printing functions.
|
|
||||||
printf(message, args);
|
|
||||||
|
|
||||||
#if PSP
|
#if PSP
|
||||||
FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a");
|
FILE *file = fopen("ms0:/PSP/GAME/Dusk/debug.log", "a");
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "display/display.h"
|
#include "display/display.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "display/framebuffer.h"
|
#include "display/framebuffer.h"
|
||||||
|
#include "display/screen.h"
|
||||||
|
|
||||||
void cameraInit(camera_t *camera) {
|
void cameraInit(camera_t *camera) {
|
||||||
cameraInitPerspective(camera);
|
cameraInitPerspective(camera);
|
||||||
@@ -112,9 +113,10 @@ void cameraPushMatrix(camera_t *camera) {
|
|||||||
"Pixel perfect camera view requires perspective projection"
|
"Pixel perfect camera view requires perspective projection"
|
||||||
);
|
);
|
||||||
|
|
||||||
const float_t viewportHeight = (
|
// const float_t viewportHeight = (
|
||||||
(float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
|
// (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND)
|
||||||
);
|
// );
|
||||||
|
const float_t viewportHeight = (float_t)SCREEN.height;
|
||||||
const float_t z = (viewportHeight / 2.0f) / (
|
const float_t z = (viewportHeight / 2.0f) / (
|
||||||
camera->lookatPixelPerfect.pixelsPerUnit *
|
camera->lookatPixelPerfect.pixelsPerUnit *
|
||||||
tanf(camera->perspective.fov / 2.0f)
|
tanf(camera->perspective.fov / 2.0f)
|
||||||
|
|||||||
@@ -51,10 +51,15 @@ errorret_t displayInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SDL_GL_SetSwapInterval(1);
|
SDL_GL_SetSwapInterval(1);
|
||||||
glDisable(GL_DEPTH_TEST);
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
glDisable(GL_LIGHTING);// PSP defaults this on?
|
glDisable(GL_LIGHTING);// PSP defaults this on?
|
||||||
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
|
glShadeModel(GL_SMOOTH); // Fixes color on PSP?
|
||||||
|
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(GL_LEQUAL);
|
||||||
|
glClearDepth(1.0f);
|
||||||
|
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ void screenInit() {
|
|||||||
|
|
||||||
|
|
||||||
#if DISPLAY_SIZE_DYNAMIC == 1
|
#if DISPLAY_SIZE_DYNAMIC == 1
|
||||||
SCREEN.mode = SCREEN_MODE_FIXED_HEIGHT;
|
SCREEN.mode = SCREEN_MODE_FIXED_VIEWPORT_HEIGHT;
|
||||||
SCREEN.fixedHeight.height = DISPLAY_SCREEN_HEIGHT_DEFAULT;
|
SCREEN.fixedHeight.height = DISPLAY_SCREEN_HEIGHT_DEFAULT;
|
||||||
|
|
||||||
cameraInitOrthographic(&SCREEN.framebufferCamera);
|
cameraInitOrthographic(&SCREEN.framebufferCamera);
|
||||||
@@ -242,6 +242,17 @@ void screenBind() {
|
|||||||
frameBufferBind(&SCREEN.framebuffer);
|
frameBufferBind(&SCREEN.framebuffer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT: {
|
||||||
|
SCREEN.height = SCREEN.fixedViewportHeight.height;
|
||||||
|
float_t fbWidth = (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND);
|
||||||
|
float_t fbHeight = (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND);
|
||||||
|
float_t fbAspect = fbWidth / fbHeight;
|
||||||
|
SCREEN.width = (int32_t)floorf(SCREEN.height * fbAspect);
|
||||||
|
SCREEN.aspect = (float_t)SCREEN.width / (float_t)SCREEN.height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
@@ -253,10 +264,9 @@ void screenBind() {
|
|||||||
|
|
||||||
void screenUnbind() {
|
void screenUnbind() {
|
||||||
switch(SCREEN.mode) {
|
switch(SCREEN.mode) {
|
||||||
case SCREEN_MODE_BACKBUFFER: {
|
|
||||||
// Nothing to do here.
|
// Nothing to do here.
|
||||||
|
case SCREEN_MODE_BACKBUFFER:
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
#if DISPLAY_SIZE_DYNAMIC == 1
|
#if DISPLAY_SIZE_DYNAMIC == 1
|
||||||
case SCREEN_MODE_ASPECT_RATIO:
|
case SCREEN_MODE_ASPECT_RATIO:
|
||||||
@@ -265,13 +275,15 @@ void screenUnbind() {
|
|||||||
case SCREEN_MODE_FIXED_WIDTH:
|
case SCREEN_MODE_FIXED_WIDTH:
|
||||||
if(SCREEN.framebufferReady) frameBufferBind(NULL);
|
if(SCREEN.framebufferReady) frameBufferBind(NULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SCREEN_MODE_FIXED_VIEWPORT_HEIGHT:
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
default: {
|
default:
|
||||||
assertUnreachable("Invalid screen mode.");
|
assertUnreachable("Invalid screen mode.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void screenRender() {
|
void screenRender() {
|
||||||
@@ -280,6 +292,11 @@ void screenRender() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if DISPLAY_SIZE_DYNAMIC == 1
|
#if DISPLAY_SIZE_DYNAMIC == 1
|
||||||
|
if(SCREEN.mode == SCREEN_MODE_FIXED_VIEWPORT_HEIGHT) {
|
||||||
|
glViewport(0, 0, SCREEN.width, SCREEN.height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
SCREEN.mode == SCREEN_MODE_ASPECT_RATIO ||
|
SCREEN.mode == SCREEN_MODE_ASPECT_RATIO ||
|
||||||
SCREEN.mode == SCREEN_MODE_FIXED_HEIGHT ||
|
SCREEN.mode == SCREEN_MODE_FIXED_HEIGHT ||
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ typedef enum {
|
|||||||
SCREEN_MODE_ASPECT_RATIO,// Maintains aspect at all cost
|
SCREEN_MODE_ASPECT_RATIO,// Maintains aspect at all cost
|
||||||
SCREEN_MODE_FIXED_HEIGHT, // Fixed height, width expands/contracts as needed
|
SCREEN_MODE_FIXED_HEIGHT, // Fixed height, width expands/contracts as needed
|
||||||
SCREEN_MODE_FIXED_WIDTH, // Fixed width, height expands/contracts as needed
|
SCREEN_MODE_FIXED_WIDTH, // Fixed width, height expands/contracts as needed
|
||||||
|
// Fixed viewport height. Fixed height but higher resolution.
|
||||||
|
SCREEN_MODE_FIXED_VIEWPORT_HEIGHT,
|
||||||
#endif
|
#endif
|
||||||
} screenmode_t;
|
} screenmode_t;
|
||||||
|
|
||||||
@@ -68,6 +70,10 @@ typedef struct {
|
|||||||
struct {
|
struct {
|
||||||
int32_t width;
|
int32_t width;
|
||||||
} fixedWidth;
|
} fixedWidth;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int32_t height;
|
||||||
|
} fixedViewportHeight;
|
||||||
};
|
};
|
||||||
} screen_t;
|
} screen_t;
|
||||||
|
|
||||||
|
|||||||
45
src/duskdefs.env
Normal file
45
src/duskdefs.env
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (c) 2025 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
ENTITY_DIR_SOUTH = 0
|
||||||
|
ENTITY_DIR_WEST = 1
|
||||||
|
ENTITY_DIR_EAST = 2
|
||||||
|
ENTITY_DIR_NORTH = 3
|
||||||
|
|
||||||
|
ENTITY_COUNT = 128
|
||||||
|
|
||||||
|
ENTITY_TYPE_NULL = 0
|
||||||
|
ENTITY_TYPE_PLAYER = 1
|
||||||
|
ENTITY_TYPE_NPC = 2
|
||||||
|
ENTITY_TYPE_COUNT = 3
|
||||||
|
|
||||||
|
CHUNK_WIDTH = 16
|
||||||
|
CHUNK_HEIGHT = 16
|
||||||
|
CHUNK_DEPTH = 4
|
||||||
|
# CHUNK_VERTEX_COUNT_MAX = QUAD_VERTEXES * CHUNK_WIDTH * CHUNK_HEIGHT * 4
|
||||||
|
CHUNK_VERTEX_COUNT_MAX=6144
|
||||||
|
CHUNK_MESH_COUNT_MAX = 14
|
||||||
|
CHUNK_ENTITY_COUNT_MAX = 8
|
||||||
|
|
||||||
|
TILE_WIDTH = 16.0
|
||||||
|
TILE_HEIGHT = 16.0
|
||||||
|
TILE_DEPTH = 16.0
|
||||||
|
|
||||||
|
TILE_SHAPE_NULL = 0
|
||||||
|
TILE_SHAPE_FLOOR = 1
|
||||||
|
TILE_SHAPE_RAMP_SOUTH = 2
|
||||||
|
TILE_SHAPE_RAMP_WEST = 3
|
||||||
|
TILE_SHAPE_RAMP_EAST = 4
|
||||||
|
TILE_SHAPE_RAMP_NORTH = 5
|
||||||
|
TILE_SHAPE_RAMP_SOUTHWEST = 6
|
||||||
|
TILE_SHAPE_RAMP_SOUTHEAST = 7
|
||||||
|
TILE_SHAPE_RAMP_NORTHWEST = 8
|
||||||
|
TILE_SHAPE_RAMP_NORTHEAST = 9
|
||||||
|
|
||||||
|
RPG_CAMERA_FOV = 70
|
||||||
|
RPG_CAMERA_PIXELS_PER_UNIT = 1.0
|
||||||
|
RPG_CAMERA_Z_OFFSET = 24.0
|
||||||
|
|
||||||
|
ASSET_LANG_CHUNK_CHAR_COUNT = 6144
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "ui/ui.h"
|
#include "ui/ui.h"
|
||||||
#include "rpg/rpg.h"
|
#include "rpg/rpg.h"
|
||||||
|
#include "script/scriptmanager.h"
|
||||||
#include "debug/debug.h"
|
#include "debug/debug.h"
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
@@ -31,11 +32,17 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
inputInit();
|
inputInit();
|
||||||
errorChain(assetInit());
|
errorChain(assetInit());
|
||||||
errorChain(localeManagerInit());
|
errorChain(localeManagerInit());
|
||||||
|
errorChain(scriptManagerInit());
|
||||||
errorChain(displayInit());
|
errorChain(displayInit());
|
||||||
errorChain(uiInit());
|
errorChain(uiInit());
|
||||||
errorChain(rpgInit());
|
errorChain(rpgInit());
|
||||||
errorChain(sceneManagerInit());
|
errorChain(sceneManagerInit());
|
||||||
|
|
||||||
|
// Run the initial script.
|
||||||
|
errorChain(scriptContextInit(&ENGINE.mainScriptContext));
|
||||||
|
errorChain(scriptContextExecFile(&ENGINE.mainScriptContext, "script/test.dsf"));
|
||||||
|
scriptContextDispose(&ENGINE.mainScriptContext);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +55,6 @@ errorret_t engineUpdate(void) {
|
|||||||
sceneManagerUpdate();
|
sceneManagerUpdate();
|
||||||
errorChain(displayUpdate());
|
errorChain(displayUpdate());
|
||||||
|
|
||||||
|
|
||||||
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
if(inputPressed(INPUT_ACTION_RAGEQUIT)) ENGINE.running = false;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "display/display.h"// Important to be included first.
|
#include "display/display.h"// Important to be included first.
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
#include "script/scriptcontext.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool_t running;
|
bool_t running;
|
||||||
int32_t argc;
|
int32_t argc;
|
||||||
const char_t **argv;
|
const char_t **argv;
|
||||||
|
scriptcontext_t mainScriptContext;
|
||||||
} engine_t;
|
} engine_t;
|
||||||
|
|
||||||
extern engine_t ENGINE;
|
extern engine_t ENGINE;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "input/input.h"
|
#include "input/input.h"
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
// Main applet
|
||||||
errorret_t ret;
|
errorret_t ret;
|
||||||
|
|
||||||
// Init engine
|
// Init engine
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
#include "cutscenewait.h"
|
#include "cutscenewait.h"
|
||||||
#include "cutscenecallback.h"
|
#include "cutscenecallback.h"
|
||||||
#include "cutscenetext.h"
|
#include "cutscenetext.h"
|
||||||
#include "cutscenecutscene.h"
|
|
||||||
|
typedef struct cutscene_s cutscene_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CUTSCENE_ITEM_TYPE_NULL,
|
CUTSCENE_ITEM_TYPE_NULL,
|
||||||
@@ -27,7 +28,7 @@ typedef struct cutsceneitem_s {
|
|||||||
cutscenetext_t text;
|
cutscenetext_t text;
|
||||||
cutscenecallback_t callback;
|
cutscenecallback_t callback;
|
||||||
cutscenewait_t wait;
|
cutscenewait_t wait;
|
||||||
const cutscenecutscene_t cutscene;
|
const cutscene_t *cutscene;
|
||||||
};
|
};
|
||||||
} cutsceneitem_t;
|
} cutsceneitem_t;
|
||||||
|
|
||||||
|
|||||||
@@ -81,30 +81,74 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
bool_t fall = false;
|
bool_t fall = false;
|
||||||
bool_t raise = false;
|
bool_t raise = false;
|
||||||
|
|
||||||
// Are we walking up stairs?
|
// Are we walking up a ramp?
|
||||||
if(
|
if(
|
||||||
tileIsStairs(tileCurrent) &&
|
tileIsRamp(tileCurrent) &&
|
||||||
(direction+TILE_STAIRS_SOUTH) == tileCurrent &&
|
(
|
||||||
newPos.z < (MAP_CHUNK_DEPTH - 1)
|
// Can only walk UP the direction the ramp faces.
|
||||||
|
(direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent ||
|
||||||
|
// If diagonal ramp, can go up one of two ways only.
|
||||||
|
(
|
||||||
|
(
|
||||||
|
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST &&
|
||||||
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST &&
|
||||||
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST &&
|
||||||
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST &&
|
||||||
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// Must be able to walk up.
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
tileNew = TILE_NULL;// Force check for stairs above.
|
tileNew = TILE_SHAPE_NULL;// Force check for ramp above.
|
||||||
worldpos_t abovePos = newPos;
|
worldpos_t abovePos = newPos;
|
||||||
abovePos.z += 1;
|
abovePos.z += 1;
|
||||||
tile_t tileAbove = mapGetTile(abovePos);
|
tile_t tileAbove = mapGetTile(abovePos);
|
||||||
|
|
||||||
if(tileAbove != TILE_NULL && tileIsWalkable(tileAbove)) {
|
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
|
||||||
// We can go up the stairs.
|
// We can go up the ramp.
|
||||||
raise = true;
|
raise = true;
|
||||||
}
|
}
|
||||||
} else if(tileNew == TILE_NULL && newPos.z > 0) {
|
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
|
||||||
// Falling down?
|
// Falling down?
|
||||||
worldpos_t belowPos = newPos;
|
worldpos_t belowPos = newPos;
|
||||||
belowPos.z -= 1;
|
belowPos.z -= 1;
|
||||||
tile_t tileBelow = mapGetTile(belowPos);
|
tile_t tileBelow = mapGetTile(belowPos);
|
||||||
if(
|
if(
|
||||||
tileBelow != TILE_NULL &&
|
tileBelow != TILE_SHAPE_NULL &&
|
||||||
tileIsStairs(tileBelow) &&
|
tileIsRamp(tileBelow) &&
|
||||||
(entityDirGetOpposite(direction)+TILE_STAIRS_SOUTH) == tileBelow
|
(
|
||||||
|
// This handles regular cardinal ramps
|
||||||
|
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow ||
|
||||||
|
// This handles diagonal ramps
|
||||||
|
(
|
||||||
|
(
|
||||||
|
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST &&
|
||||||
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST &&
|
||||||
|
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileBelow == TILE_SHAPE_RAMP_NORTHEAST &&
|
||||||
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
tileBelow == TILE_SHAPE_RAMP_NORTHWEST &&
|
||||||
|
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
// We will fall to this tile.
|
// We will fall to this tile.
|
||||||
fall = true;
|
fall = true;
|
||||||
@@ -145,3 +189,12 @@ entity_t * entityGetAt(const worldpos_t position) {
|
|||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t entityGetAvailable() {
|
||||||
|
entity_t *ent = ENTITIES;
|
||||||
|
do {
|
||||||
|
if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES;
|
||||||
|
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||||
|
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
@@ -11,14 +11,12 @@
|
|||||||
#include "entitytype.h"
|
#include "entitytype.h"
|
||||||
#include "npc.h"
|
#include "npc.h"
|
||||||
|
|
||||||
#define ENTITY_COUNT 256
|
|
||||||
|
|
||||||
typedef struct map_s map_t;
|
typedef struct map_s map_t;
|
||||||
|
|
||||||
typedef struct entity_s {
|
typedef struct entity_s {
|
||||||
uint8_t id;
|
uint8_t id;
|
||||||
entitytype_t type;
|
entitytype_t type;
|
||||||
entitytypedata_t;
|
entitytypedata_t data;
|
||||||
|
|
||||||
// Movement
|
// Movement
|
||||||
entitydir_t direction;
|
entitydir_t direction;
|
||||||
@@ -70,3 +68,10 @@ void entityWalk(entity_t *entity, const entitydir_t direction);
|
|||||||
* @return Pointer to the entity at the position, or NULL if none.
|
* @return Pointer to the entity at the position, or NULL if none.
|
||||||
*/
|
*/
|
||||||
entity_t *entityGetAt(const worldpos_t pos);
|
entity_t *entityGetAt(const worldpos_t pos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an available entity index.
|
||||||
|
*
|
||||||
|
* @return The index of an available entity, or 0xFF if none are available.
|
||||||
|
*/
|
||||||
|
uint8_t entityGetAvailable();
|
||||||
@@ -9,11 +9,6 @@
|
|||||||
#include "rpg/world/worldpos.h"
|
#include "rpg/world/worldpos.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ENTITY_DIR_SOUTH = 0,
|
|
||||||
ENTITY_DIR_EAST = 1,
|
|
||||||
ENTITY_DIR_WEST = 2,
|
|
||||||
ENTITY_DIR_NORTH = 3,
|
|
||||||
|
|
||||||
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
|
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
|
||||||
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
|
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
|
||||||
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
|
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
|
||||||
|
|||||||
@@ -6,17 +6,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "duskdefs.h"
|
||||||
#include "rpg/entity/player.h"
|
#include "rpg/entity/player.h"
|
||||||
#include "npc.h"
|
#include "npc.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef uint8_t entitytype_t;
|
||||||
ENTITY_TYPE_NULL = 0,
|
|
||||||
|
|
||||||
ENTITY_TYPE_PLAYER,
|
|
||||||
ENTITY_TYPE_NPC,
|
|
||||||
|
|
||||||
ENTITY_TYPE_COUNT
|
|
||||||
} entitytype_t;
|
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
player_t player;
|
player_t player;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "rpgcamera.h"
|
#include "rpgcamera.h"
|
||||||
#include "rpgtextbox.h"
|
#include "rpgtextbox.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
errorret_t rpgInit(void) {
|
errorret_t rpgInit(void) {
|
||||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||||
@@ -26,16 +27,13 @@ errorret_t rpgInit(void) {
|
|||||||
rpgTextboxInit();
|
rpgTextboxInit();
|
||||||
|
|
||||||
// TEST: Create some entities.
|
// TEST: Create some entities.
|
||||||
entity_t *ent;
|
// uint8_t entIndex = entityGetAvailable();
|
||||||
ent = &ENTITIES[0];
|
// assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
||||||
entityInit(ent, ENTITY_TYPE_PLAYER);
|
// entity_t *ent = &ENTITIES[entIndex];
|
||||||
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
// entityInit(ent, ENTITY_TYPE_PLAYER);
|
||||||
RPG_CAMERA.followEntity.followEntityId = ent->id;
|
// RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
||||||
ent->position.x = 2, ent->position.y = 2;
|
// RPG_CAMERA.followEntity.followEntityId = ent->id;
|
||||||
|
// ent->position.x = 2, ent->position.y = 2;
|
||||||
ent = &ENTITIES[1];
|
|
||||||
entityInit(ent, ENTITY_TYPE_NPC);
|
|
||||||
ent->position.x = 4, ent->position.y = 0, ent->position.z = 1;
|
|
||||||
|
|
||||||
// All Good!
|
// All Good!
|
||||||
errorOk();
|
errorOk();
|
||||||
|
|||||||
@@ -10,11 +10,6 @@
|
|||||||
#include "worldpos.h"
|
#include "worldpos.h"
|
||||||
#include "display/mesh/quad.h"
|
#include "display/mesh/quad.h"
|
||||||
|
|
||||||
#define CHUNK_VERTEX_COUNT_MAX ( \
|
|
||||||
QUAD_VERTEX_COUNT * CHUNK_WIDTH * CHUNK_HEIGHT * 4 \
|
|
||||||
)
|
|
||||||
#define CHUNK_MESH_COUNT_MAX 14
|
|
||||||
|
|
||||||
typedef struct chunk_s {
|
typedef struct chunk_s {
|
||||||
chunkpos_t position;
|
chunkpos_t position;
|
||||||
tile_t tiles[CHUNK_TILE_COUNT];
|
tile_t tiles[CHUNK_TILE_COUNT];
|
||||||
@@ -22,6 +17,7 @@ typedef struct chunk_s {
|
|||||||
uint8_t meshCount;
|
uint8_t meshCount;
|
||||||
meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
|
meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
|
||||||
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
|
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
|
||||||
|
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||||
} chunk_t;
|
} chunk_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
|
||||||
map_t MAP;
|
map_t MAP;
|
||||||
|
|
||||||
errorret_t mapInit() {
|
errorret_t mapInit() {
|
||||||
memoryZero(&MAP, sizeof(map_t));
|
memoryZero(&MAP, sizeof(map_t));
|
||||||
|
|
||||||
// Init the default chunks. In future I'll probably make this based on where
|
// Init the first chunks.
|
||||||
// the player spawns in to save an initial mapSet.
|
|
||||||
chunkindex_t index = 0;
|
chunkindex_t index = 0;
|
||||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||||
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||||
@@ -126,7 +126,13 @@ void mapDispose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mapChunkUnload(chunk_t* chunk) {
|
void mapChunkUnload(chunk_t* chunk) {
|
||||||
|
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||||
|
if(chunk->entities[i] == 0xFF) break;
|
||||||
|
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||||
|
entity->type = ENTITY_TYPE_NULL;
|
||||||
|
}
|
||||||
for(uint8_t i = 0; i < chunk->meshCount; i++) {
|
for(uint8_t i = 0; i < chunk->meshCount; i++) {
|
||||||
|
if(chunk->meshes[i].vertexCount == 0) continue;
|
||||||
meshDispose(&chunk->meshes[i]);
|
meshDispose(&chunk->meshes[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,6 +141,8 @@ errorret_t mapChunkLoad(chunk_t* chunk) {
|
|||||||
char_t buffer[64];
|
char_t buffer[64];
|
||||||
|
|
||||||
chunk->meshCount = 0;
|
chunk->meshCount = 0;
|
||||||
|
memoryZero(chunk->meshes, sizeof(chunk->meshes));
|
||||||
|
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||||
|
|
||||||
snprintf(buffer, sizeof(buffer), "map/map/%d_%d_%d.dcf",
|
snprintf(buffer, sizeof(buffer), "map/map/%d_%d_%d.dcf",
|
||||||
chunk->position.x,
|
chunk->position.x,
|
||||||
@@ -179,10 +187,10 @@ tile_t mapGetTile(const worldpos_t position) {
|
|||||||
chunkpos_t chunkPos;
|
chunkpos_t chunkPos;
|
||||||
worldPosToChunkPos(&position, &chunkPos);
|
worldPosToChunkPos(&position, &chunkPos);
|
||||||
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
||||||
if(chunkIndex == -1) return TILE_NULL;
|
if(chunkIndex == -1) return TILE_SHAPE_NULL;
|
||||||
|
|
||||||
chunk_t *chunk = mapGetChunk(chunkIndex);
|
chunk_t *chunk = mapGetChunk(chunkIndex);
|
||||||
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||||
chunktileindex_t tileIndex = woprldPosToChunkTileIndex(&position);
|
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
||||||
return chunk->tiles[tileIndex];
|
return chunk->tiles[tileIndex];
|
||||||
}
|
}
|
||||||
@@ -9,24 +9,24 @@
|
|||||||
|
|
||||||
bool_t tileIsWalkable(const tile_t tile) {
|
bool_t tileIsWalkable(const tile_t tile) {
|
||||||
switch(tile) {
|
switch(tile) {
|
||||||
case TILE_WALKABLE:
|
case TILE_SHAPE_NULL:
|
||||||
case TILE_STAIRS_NORTH:
|
return false;
|
||||||
case TILE_STAIRS_SOUTH:
|
|
||||||
case TILE_STAIRS_EAST:
|
|
||||||
case TILE_STAIRS_WEST:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool_t tileIsStairs(const tile_t tile) {
|
bool_t tileIsRamp(const tile_t tile) {
|
||||||
switch(tile) {
|
switch(tile) {
|
||||||
case TILE_STAIRS_NORTH:
|
case TILE_SHAPE_RAMP_NORTH:
|
||||||
case TILE_STAIRS_SOUTH:
|
case TILE_SHAPE_RAMP_SOUTH:
|
||||||
case TILE_STAIRS_EAST:
|
case TILE_SHAPE_RAMP_EAST:
|
||||||
case TILE_STAIRS_WEST:
|
case TILE_SHAPE_RAMP_WEST:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHEAST:
|
||||||
|
case TILE_SHAPE_RAMP_NORTHWEST:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHEAST:
|
||||||
|
case TILE_SHAPE_RAMP_SOUTHWEST:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -10,15 +10,6 @@
|
|||||||
|
|
||||||
typedef uint8_t tile_t;
|
typedef uint8_t tile_t;
|
||||||
|
|
||||||
#define TILE_NULL 0
|
|
||||||
#define TILE_WALKABLE 1
|
|
||||||
#define TILE_STAIRS_SOUTH (2 + ENTITY_DIR_SOUTH)
|
|
||||||
#define TILE_STAIRS_EAST (2 + ENTITY_DIR_EAST)
|
|
||||||
#define TILE_STAIRS_WEST (2 + ENTITY_DIR_WEST)
|
|
||||||
#define TILE_STAIRS_NORTH (2 + ENTITY_DIR_NORTH)
|
|
||||||
#define TILE_TEST 6
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given tile is walkable.
|
* Returns whether or not the given tile is walkable.
|
||||||
*
|
*
|
||||||
@@ -28,9 +19,9 @@ typedef uint8_t tile_t;
|
|||||||
bool_t tileIsWalkable(const tile_t tile);
|
bool_t tileIsWalkable(const tile_t tile);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given tile is a stairs tile.
|
* Returns whether or not the given tile is a ramp tile.
|
||||||
*
|
*
|
||||||
* @param tile The tile to check.
|
* @param tile The tile to check.
|
||||||
* @return bool_t True if stairs, false if not.
|
* @return bool_t True if ramp, false if not.
|
||||||
*/
|
*/
|
||||||
bool_t tileIsStairs(const tile_t tile);
|
bool_t tileIsRamp(const tile_t tile);
|
||||||
@@ -6,39 +6,92 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "worldpos.h"
|
#include "worldpos.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b) {
|
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b) {
|
||||||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||||
}
|
}
|
||||||
|
|
||||||
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out) {
|
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out) {
|
||||||
|
assertNotNull(chunkPos, "Chunk position pointer cannot be NULL");
|
||||||
|
assertNotNull(out, "Output world position pointer cannot be NULL");
|
||||||
|
|
||||||
out->x = (worldunit_t)(chunkPos->x * CHUNK_WIDTH);
|
out->x = (worldunit_t)(chunkPos->x * CHUNK_WIDTH);
|
||||||
out->y = (worldunit_t)(chunkPos->y * CHUNK_HEIGHT);
|
out->y = (worldunit_t)(chunkPos->y * CHUNK_HEIGHT);
|
||||||
out->z = (worldunit_t)(chunkPos->z * CHUNK_DEPTH);
|
out->z = (worldunit_t)(chunkPos->z * CHUNK_DEPTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) {
|
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) {
|
||||||
|
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||||
|
assertNotNull(out, "Output chunk position pointer cannot be NULL");
|
||||||
|
|
||||||
|
if(worldPos->x < 0) {
|
||||||
|
out->x = (chunkunit_t)((worldPos->x - (CHUNK_WIDTH - 1)) / CHUNK_WIDTH);
|
||||||
|
} else {
|
||||||
out->x = (chunkunit_t)(worldPos->x / CHUNK_WIDTH);
|
out->x = (chunkunit_t)(worldPos->x / CHUNK_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(worldPos->y < 0) {
|
||||||
|
out->y = (chunkunit_t)((worldPos->y - (CHUNK_HEIGHT - 1)) / CHUNK_HEIGHT);
|
||||||
|
} else {
|
||||||
out->y = (chunkunit_t)(worldPos->y / CHUNK_HEIGHT);
|
out->y = (chunkunit_t)(worldPos->y / CHUNK_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(worldPos->z < 0) {
|
||||||
|
out->z = (chunkunit_t)((worldPos->z - (CHUNK_DEPTH - 1)) / CHUNK_DEPTH);
|
||||||
|
} else {
|
||||||
out->z = (chunkunit_t)(worldPos->z / CHUNK_DEPTH);
|
out->z = (chunkunit_t)(worldPos->z / CHUNK_DEPTH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunktileindex_t woprldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
||||||
uint8_t localX = (uint8_t)(worldPos->x % CHUNK_WIDTH);
|
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||||
uint8_t localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
|
|
||||||
uint8_t localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH);
|
|
||||||
|
|
||||||
return (chunktileindex_t)(
|
uint8_t localX, localY, localZ;
|
||||||
|
if(worldPos->x < 0) {
|
||||||
|
localX = (uint8_t)(
|
||||||
|
(CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localX = (uint8_t)(worldPos->x % CHUNK_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(worldPos->y < 0) {
|
||||||
|
localY = (uint8_t)(
|
||||||
|
(CHUNK_HEIGHT - 1) - ((-worldPos->y - 1) % CHUNK_HEIGHT)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(worldPos->z < 0) {
|
||||||
|
localZ = (uint8_t)(
|
||||||
|
(CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunktileindex_t chunkTileIndex = (chunktileindex_t)(
|
||||||
(localZ * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
(localZ * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
||||||
(localY * CHUNK_WIDTH) +
|
(localY * CHUNK_WIDTH) +
|
||||||
localX
|
localX
|
||||||
);
|
);
|
||||||
|
assertTrue(
|
||||||
|
chunkTileIndex < CHUNK_TILE_COUNT,
|
||||||
|
"Calculated chunk tile index is out of bounds"
|
||||||
|
);
|
||||||
|
return chunkTileIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
|
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
|
||||||
return (chunkindex_t)(
|
assertNotNull(pos, "Chunk position pointer cannot be NULL");
|
||||||
|
|
||||||
|
chunkindex_t chunkIndex = (chunkindex_t)(
|
||||||
(pos->z * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) +
|
(pos->z * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) +
|
||||||
(pos->y * MAP_CHUNK_WIDTH) +
|
(pos->y * MAP_CHUNK_WIDTH) +
|
||||||
pos->x
|
pos->x
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return chunkIndex;
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
#include "duskdefs.h"
|
||||||
|
|
||||||
#define CHUNK_WIDTH 16
|
|
||||||
#define CHUNK_HEIGHT 16
|
|
||||||
#define CHUNK_DEPTH 4
|
|
||||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)
|
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)
|
||||||
|
|
||||||
#define MAP_CHUNK_WIDTH 3
|
#define MAP_CHUNK_WIDTH 3
|
||||||
@@ -66,7 +64,7 @@ void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out);
|
|||||||
* @param worldPos The world position.
|
* @param worldPos The world position.
|
||||||
* @return The tile index within the chunk.
|
* @return The tile index within the chunk.
|
||||||
*/
|
*/
|
||||||
chunktileindex_t woprldPosToChunkTileIndex(const worldpos_t* worldPos);
|
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a chunk position to a world position.
|
* Converts a chunk position to a world position.
|
||||||
|
|||||||
@@ -15,10 +15,7 @@
|
|||||||
#include "display/screen.h"
|
#include "display/screen.h"
|
||||||
#include "rpg/rpgcamera.h"
|
#include "rpg/rpgcamera.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#include "duskdefs.h"
|
||||||
#define TILE_WIDTH 16.0f
|
|
||||||
#define TILE_HEIGHT 16.0f
|
|
||||||
#define TILE_DEPTH 11.36f
|
|
||||||
|
|
||||||
errorret_t sceneMapInit(scenedata_t *data) {
|
errorret_t sceneMapInit(scenedata_t *data) {
|
||||||
// Init the camera.
|
// Init the camera.
|
||||||
@@ -26,7 +23,7 @@ errorret_t sceneMapInit(scenedata_t *data) {
|
|||||||
data->sceneMap.camera.projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED;
|
data->sceneMap.camera.projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED;
|
||||||
data->sceneMap.camera.viewType = CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT;
|
data->sceneMap.camera.viewType = CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT;
|
||||||
glm_vec3_zero(data->sceneMap.camera.lookatPixelPerfect.offset);
|
glm_vec3_zero(data->sceneMap.camera.lookatPixelPerfect.offset);
|
||||||
data->sceneMap.camera.lookatPixelPerfect.offset[1] = TILE_HEIGHT;
|
data->sceneMap.camera.lookatPixelPerfect.offset[1] = RPG_CAMERA_Z_OFFSET;
|
||||||
glm_vec3_copy(
|
glm_vec3_copy(
|
||||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||||
data->sceneMap.camera.lookatPixelPerfect.target
|
data->sceneMap.camera.lookatPixelPerfect.target
|
||||||
@@ -35,8 +32,10 @@ errorret_t sceneMapInit(scenedata_t *data) {
|
|||||||
(vec3){ 0.0f, 1.0f, 0.0f },
|
(vec3){ 0.0f, 1.0f, 0.0f },
|
||||||
data->sceneMap.camera.lookatPixelPerfect.up
|
data->sceneMap.camera.lookatPixelPerfect.up
|
||||||
);
|
);
|
||||||
data->sceneMap.camera.lookatPixelPerfect.pixelsPerUnit = 1.0f;
|
data->sceneMap.camera.lookatPixelPerfect.pixelsPerUnit = (
|
||||||
data->sceneMap.camera.perspective.fov = glm_rad(90.0f);
|
RPG_CAMERA_PIXELS_PER_UNIT
|
||||||
|
);
|
||||||
|
data->sceneMap.camera.perspective.fov = glm_rad(RPG_CAMERA_FOV);
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -54,7 +53,7 @@ void sceneMapGetWorldPosition(const worldpos_t pos, vec3 outPosition) {
|
|||||||
|
|
||||||
// Handle stair tiles.
|
// Handle stair tiles.
|
||||||
tile_t tile = mapGetTile(pos);
|
tile_t tile = mapGetTile(pos);
|
||||||
if(tileIsStairs(tile)) {
|
if(tileIsRamp(tile)) {
|
||||||
outPosition[2] += TILE_DEPTH / 2.0f;
|
outPosition[2] += TILE_DEPTH / 2.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +65,9 @@ void sceneMapEntityGetPosition(const entity_t *entity, vec3 outPosition) {
|
|||||||
// Get position
|
// Get position
|
||||||
sceneMapGetWorldPosition(entity->position, outPosition);
|
sceneMapGetWorldPosition(entity->position, outPosition);
|
||||||
|
|
||||||
|
// Add a small offset so we render above the tile
|
||||||
|
outPosition[2] += 0.1f;
|
||||||
|
|
||||||
// Add animation offset(s)
|
// Add animation offset(s)
|
||||||
switch(entity->animation) {
|
switch(entity->animation) {
|
||||||
case ENTITY_ANIM_WALK: {
|
case ENTITY_ANIM_WALK: {
|
||||||
@@ -170,6 +172,7 @@ void sceneMapRenderMap() {
|
|||||||
|
|
||||||
for(uint8_t j = 0; j < chunk->meshCount; j++) {
|
for(uint8_t j = 0; j < chunk->meshCount; j++) {
|
||||||
mesh_t *mesh = &chunk->meshes[j];
|
mesh_t *mesh = &chunk->meshes[j];
|
||||||
|
if(mesh->vertexCount == 0) continue;
|
||||||
textureBind(NULL);
|
textureBind(NULL);
|
||||||
meshDraw(mesh, -1, -1);
|
meshDraw(mesh, -1, -1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ errorret_t sceneManagerInit(void) {
|
|||||||
sceneManagerRegisterScene(&SCENE_TEST);
|
sceneManagerRegisterScene(&SCENE_TEST);
|
||||||
sceneManagerRegisterScene(&SCENE_MAP);
|
sceneManagerRegisterScene(&SCENE_MAP);
|
||||||
|
|
||||||
// Initial scene
|
|
||||||
scene_t *initial = sceneManagerGetSceneByName("map");
|
|
||||||
sceneManagerSetScene(initial);
|
|
||||||
if(initial->init) errorChain(initial->init(&SCENE_MANAGER.sceneData));
|
|
||||||
initial->flags |= SCENE_FLAG_INITIALIZED;
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "scene.h"
|
#include "scene.h"
|
||||||
#include "scenedata.h"
|
#include "scenedata.h"
|
||||||
|
|
||||||
#define SCENE_MANAGER_SCENE_COUNT_MAX 32
|
#define SCENE_MANAGER_SCENE_COUNT_MAX 16
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
scene_t *current;
|
scene_t *current;
|
||||||
|
|||||||
11
src/script/CMakeLists.txt
Normal file
11
src/script/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# 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
|
||||||
|
scriptmanager.c
|
||||||
|
scriptcontext.c
|
||||||
|
)
|
||||||
14
src/script/func/scriptfunccamera.h
Normal file
14
src/script/func/scriptfunccamera.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptcontext.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
void scriptFuncCamera(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
}
|
||||||
135
src/script/func/scriptfuncentity.h
Normal file
135
src/script/func/scriptfuncentity.h
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptcontext.h"
|
||||||
|
#include "rpg/entity/entity.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
int32_t scriptFuncEntityAdd(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
|
||||||
|
assertTrue(lua_isinteger(L, 1), "Expected integer entity type");
|
||||||
|
|
||||||
|
lua_Integer entityType = luaL_checkinteger(L, 1);
|
||||||
|
assertTrue(
|
||||||
|
entityType >= ENTITY_TYPE_NULL && entityType < ENTITY_TYPE_COUNT,
|
||||||
|
"Invalid entity type passed to scriptFuncEntityAdd"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Pop entity
|
||||||
|
uint8_t available = entityGetAvailable();
|
||||||
|
if(available == 0xFF) {
|
||||||
|
lua_pushnil(L);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_t *ent = &ENTITIES[available];
|
||||||
|
entityInit(ent, (entitytype_t)entityType);
|
||||||
|
|
||||||
|
// May include X, Y and/or Z
|
||||||
|
if(lua_isinteger(L, 2)) {
|
||||||
|
lua_Integer xPos = luaL_checkinteger(L, 2);
|
||||||
|
ent->position.x = (int32_t)xPos;
|
||||||
|
}
|
||||||
|
if(lua_isinteger(L, 3)) {
|
||||||
|
lua_Integer yPos = luaL_checkinteger(L, 3);
|
||||||
|
ent->position.y = (int32_t)yPos;
|
||||||
|
}
|
||||||
|
if(lua_isinteger(L, 4)) {
|
||||||
|
lua_Integer zPos = luaL_checkinteger(L, 4);
|
||||||
|
ent->position.z = (int32_t)zPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send entity id.
|
||||||
|
lua_pushinteger(L, ent->id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t scriptFuncEntitySetX(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
|
||||||
|
assertTrue(lua_isinteger(L, 1), "Expected integer entity id");
|
||||||
|
assertTrue(lua_isinteger(L, 2), "Expected integer x position");
|
||||||
|
|
||||||
|
lua_Integer entityId = luaL_checkinteger(L, 1);
|
||||||
|
lua_Integer xPos = luaL_checkinteger(L, 2);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
entityId >= 0 && entityId < ENTITY_COUNT,
|
||||||
|
"Invalid entity id passed to scriptFuncEntitySetX"
|
||||||
|
);
|
||||||
|
|
||||||
|
entity_t *ent = &ENTITIES[entityId];
|
||||||
|
assertTrue(
|
||||||
|
ent->type != ENTITY_TYPE_NULL,
|
||||||
|
"Cannot set position of NULL entity in scriptFuncEntitySetX"
|
||||||
|
);
|
||||||
|
|
||||||
|
ent->position.x = (int32_t)xPos;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t scriptFuncEntitySetY(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
|
||||||
|
assertTrue(lua_isinteger(L, 1), "Expected integer entity id");
|
||||||
|
assertTrue(lua_isinteger(L, 2), "Expected integer y position");
|
||||||
|
|
||||||
|
lua_Integer entityId = luaL_checkinteger(L, 1);
|
||||||
|
lua_Integer yPos = luaL_checkinteger(L, 2);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
entityId >= 0 && entityId < ENTITY_COUNT,
|
||||||
|
"Invalid entity id passed to scriptFuncEntitySetY"
|
||||||
|
);
|
||||||
|
|
||||||
|
entity_t *ent = &ENTITIES[entityId];
|
||||||
|
assertTrue(
|
||||||
|
ent->type != ENTITY_TYPE_NULL,
|
||||||
|
"Cannot set position of NULL entity in scriptFuncEntitySetY"
|
||||||
|
);
|
||||||
|
|
||||||
|
ent->position.y = (int32_t)yPos;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t scriptFuncEntitySetZ(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
|
||||||
|
assertTrue(lua_isinteger(L, 1), "Expected integer entity id");
|
||||||
|
assertTrue(lua_isinteger(L, 2), "Expected integer z position");
|
||||||
|
|
||||||
|
lua_Integer entityId = luaL_checkinteger(L, 1);
|
||||||
|
lua_Integer zPos = luaL_checkinteger(L, 2);
|
||||||
|
|
||||||
|
assertTrue(
|
||||||
|
entityId >= 0 && entityId < ENTITY_COUNT,
|
||||||
|
"Invalid entity id passed to scriptFuncEntitySetZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
entity_t *ent = &ENTITIES[entityId];
|
||||||
|
assertTrue(
|
||||||
|
ent->type != ENTITY_TYPE_NULL,
|
||||||
|
"Cannot set position of NULL entity in scriptFuncEntitySetZ"
|
||||||
|
);
|
||||||
|
|
||||||
|
ent->position.z = (int32_t)zPos;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptFuncEntity(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
|
||||||
|
scriptContextRegFunc(context, "entityAdd", scriptFuncEntityAdd);
|
||||||
|
scriptContextRegFunc(context, "entitySetX", scriptFuncEntitySetX);
|
||||||
|
scriptContextRegFunc(context, "entitySetY", scriptFuncEntitySetY);
|
||||||
|
scriptContextRegFunc(context, "entitySetZ", scriptFuncEntitySetZ);
|
||||||
|
}
|
||||||
39
src/script/func/scriptfuncscene.h
Normal file
39
src/script/func/scriptfuncscene.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptcontext.h"
|
||||||
|
#include "scene/scenemanager.h"
|
||||||
|
#include "debug/debug.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "error/error.h"
|
||||||
|
|
||||||
|
int32_t scriptFuncSetScene(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
assertTrue(lua_isstring(L, 1), "First argument must be a string");
|
||||||
|
|
||||||
|
const char_t *sceneName = lua_tostring(L, 1);
|
||||||
|
scene_t *scene = sceneManagerGetSceneByName(sceneName);
|
||||||
|
assertNotNull(scene, "Scene with given name does not exist");
|
||||||
|
|
||||||
|
sceneManagerSetScene(scene);
|
||||||
|
|
||||||
|
if(scene->init) {
|
||||||
|
errorret_t err = scene->init(&SCENE_MANAGER.sceneData);
|
||||||
|
assertTrue(err.code == ERROR_OK, "Scene initialization failed");
|
||||||
|
}
|
||||||
|
scene->flags |= SCENE_FLAG_INITIALIZED;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void scriptFuncScene(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
|
||||||
|
scriptContextRegFunc(context, "setScene", scriptFuncSetScene);
|
||||||
|
}
|
||||||
80
src/script/func/scriptfuncsystem.h
Normal file
80
src/script/func/scriptfuncsystem.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "script/scriptcontext.h"
|
||||||
|
#include "debug/debug.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
|
int32_t scriptFuncPrint(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
|
||||||
|
int n = lua_gettop(L);
|
||||||
|
luaL_Buffer b;
|
||||||
|
luaL_buffinit(L, &b);
|
||||||
|
|
||||||
|
for (int i = 1; i <= n; ++i) {
|
||||||
|
size_t len;
|
||||||
|
const char *s = luaL_tolstring(L, i, &len); // converts any value to string
|
||||||
|
luaL_addlstring(&b, s, len);
|
||||||
|
lua_pop(L, 1); // pop result of luaL_tolstring
|
||||||
|
if (i < n) luaL_addlstring(&b, "\t", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_pushresult(&b);
|
||||||
|
const char *msg = lua_tostring(L, -1);
|
||||||
|
debugPrint("%s\n", msg);
|
||||||
|
return 0; // no values returned to Lua
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t scriptFuncInclude(lua_State *L) {
|
||||||
|
assertNotNull(L, "Lua state cannot be NULL");
|
||||||
|
assertTrue(lua_isstring(L, 1), "Expected string filename");
|
||||||
|
|
||||||
|
scriptcontext_t* ctx = *(scriptcontext_t**)lua_getextraspace(L);
|
||||||
|
assertNotNull(ctx, "Script context cannot be NULL");
|
||||||
|
|
||||||
|
const char_t *filename = luaL_checkstring(L, 1);
|
||||||
|
assertNotNull(filename, "Filename cannot be NULL");
|
||||||
|
assertStrLenMin(filename, 1, "Filename cannot be empty");
|
||||||
|
|
||||||
|
// Copy out filename to mutable buffer
|
||||||
|
char_t buffer[1024];
|
||||||
|
stringCopy(buffer, filename, 1024);
|
||||||
|
|
||||||
|
// Ensure it has .dsf extension
|
||||||
|
size_t len = strlen(buffer);
|
||||||
|
if(len < 4 || strcmp(&buffer[len - 4], ".dsf") != 0) {
|
||||||
|
// Append .dsf
|
||||||
|
if(len + 4 >= 1024) {
|
||||||
|
luaL_error(L, "Filename too long to append .dsf");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
stringCopy(&buffer[len], ".dsf", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the script file
|
||||||
|
errorret_t err = scriptContextExecFile(
|
||||||
|
ctx,
|
||||||
|
buffer
|
||||||
|
);
|
||||||
|
if(err.code != ERROR_OK) {
|
||||||
|
luaL_error(L, "Failed to include script file");
|
||||||
|
errorCatch(errorPrint(err));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptFuncSystem(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
|
||||||
|
scriptContextRegFunc(context, "print", scriptFuncPrint);
|
||||||
|
scriptContextRegFunc(context, "include", scriptFuncInclude);
|
||||||
|
}
|
||||||
196
src/script/scriptcontext.c
Normal file
196
src/script/scriptcontext.c
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scriptcontext.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "debug/debug.h"
|
||||||
|
|
||||||
|
#include "script/func/scriptfunccamera.h"
|
||||||
|
#include "script/func/scriptfuncentity.h"
|
||||||
|
#include "script/func/scriptfuncsystem.h"
|
||||||
|
#include "script/func/scriptfuncscene.h"
|
||||||
|
|
||||||
|
errorret_t scriptContextInit(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
|
||||||
|
memoryZero(context, sizeof(scriptcontext_t));
|
||||||
|
|
||||||
|
// Create a new Lua state for this context.
|
||||||
|
context->luaState = luaL_newstate();
|
||||||
|
if(context->luaState == NULL) {
|
||||||
|
errorThrow("Failed to init Lua state");
|
||||||
|
}
|
||||||
|
luaL_openlibs(context->luaState);
|
||||||
|
|
||||||
|
// Store context in Lua extraspace
|
||||||
|
*(scriptcontext_t**)lua_getextraspace(context->luaState) = context;
|
||||||
|
|
||||||
|
// Register variables
|
||||||
|
// scriptContextExec(context, "PLATFORM = 'DESKTOP'");
|
||||||
|
|
||||||
|
// Register functions
|
||||||
|
scriptFuncSystem(context);
|
||||||
|
scriptFuncEntity(context);
|
||||||
|
scriptFuncCamera(context);
|
||||||
|
scriptFuncScene(context);
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptContextRegFunc(
|
||||||
|
scriptcontext_t *context,
|
||||||
|
const char_t *fnName,
|
||||||
|
lua_CFunction function
|
||||||
|
) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
assertNotNull(fnName, "Function name cannot be NULL");
|
||||||
|
assertNotNull(function, "Function cannot be NULL");
|
||||||
|
|
||||||
|
lua_register(context->luaState, fnName, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptContextCallFunc(
|
||||||
|
scriptcontext_t *context,
|
||||||
|
const char_t *fnName,
|
||||||
|
const scriptvalue_t *args,
|
||||||
|
const int32_t argCount,
|
||||||
|
scriptvalue_t *retValue
|
||||||
|
) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
assertNotNull(fnName, "Function name cannot be NULL");
|
||||||
|
assertTrue(args == NULL || argCount >= 0, "Invalid arg count");
|
||||||
|
|
||||||
|
// Get func
|
||||||
|
lua_getglobal(context->luaState, fnName);
|
||||||
|
if(!lua_isfunction(context->luaState, -1)) {
|
||||||
|
errorThrow("Function '%s' not found in script context", fnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push args
|
||||||
|
for(int32_t i = 0; i < argCount; i++) {
|
||||||
|
const scriptvalue_t *arg = &args[i];
|
||||||
|
switch(arg->type) {
|
||||||
|
case SCRIPT_VALUE_TYPE_INT:
|
||||||
|
lua_pushinteger(context->luaState, arg->value.intValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCRIPT_VALUE_TYPE_FLOAT:
|
||||||
|
lua_pushnumber(context->luaState, arg->value.floatValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCRIPT_VALUE_TYPE_STRING:
|
||||||
|
lua_pushstring(context->luaState, arg->value.strValue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorThrow("Unsupported argument type %d", arg->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call func
|
||||||
|
if(lua_pcall(
|
||||||
|
context->luaState,
|
||||||
|
args ? argCount : 0,
|
||||||
|
retValue ? 1 : 0,
|
||||||
|
0
|
||||||
|
) != LUA_OK) {
|
||||||
|
const char_t *strErr = lua_tostring(context->luaState, -1);
|
||||||
|
lua_pop(context->luaState, 1);
|
||||||
|
errorThrow("Failed to call function '%s': %s", fnName, strErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was there a ret value?
|
||||||
|
if(retValue == NULL) {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ret value
|
||||||
|
switch(retValue->type) {
|
||||||
|
case SCRIPT_VALUE_TYPE_INT:
|
||||||
|
if(!lua_isinteger(context->luaState, -1)) {
|
||||||
|
errorThrow("Expected integer return value from '%s'", fnName);
|
||||||
|
}
|
||||||
|
retValue->value.intValue = (int32_t)lua_tointeger(context->luaState, -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCRIPT_VALUE_TYPE_FLOAT:
|
||||||
|
if(!lua_isnumber(context->luaState, -1)) {
|
||||||
|
errorThrow("Expected float return value from '%s'", fnName);
|
||||||
|
}
|
||||||
|
retValue->value.floatValue = (float)lua_tonumber(context->luaState, -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCRIPT_VALUE_TYPE_BOOL:
|
||||||
|
if(!lua_isboolean(context->luaState, -1)) {
|
||||||
|
errorThrow("Expected boolean return value from '%s'", fnName);
|
||||||
|
}
|
||||||
|
retValue->value.boolValue = lua_toboolean(context->luaState, -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// case SCRIPT_VALUE_TYPE_STRING:
|
||||||
|
// if(!lua_isstring(context->luaState, -1)) {
|
||||||
|
// errorThrow("Expected string return value from '%s'", fnName);
|
||||||
|
// }
|
||||||
|
// retValue->value.strValue = lua_tostring(context->luaState, -1);
|
||||||
|
// break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
errorThrow("Unsupported return value type %d", retValue->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
assertNotNull(script, "Script cannot be NULL");
|
||||||
|
|
||||||
|
if(luaL_dostring(context->luaState, script) != LUA_OK) {
|
||||||
|
const char_t *strErr = lua_tostring(context->luaState, -1);
|
||||||
|
lua_pop(context->luaState, 1);
|
||||||
|
errorThrow("Failed to execute Lua: ", strErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname) {
|
||||||
|
assertNotNull(ctx, "Script context cannot be NULL");
|
||||||
|
assertNotNull(fname, "Filename cannot be NULL");
|
||||||
|
|
||||||
|
assetscript_t script;
|
||||||
|
errorChain(assetLoad(fname, &script));
|
||||||
|
|
||||||
|
if(lua_load(
|
||||||
|
ctx->luaState, assetScriptReader, &script, fname, NULL
|
||||||
|
) != LUA_OK) {
|
||||||
|
const char_t *strErr = lua_tostring(ctx->luaState, -1);
|
||||||
|
lua_pop(ctx->luaState, 1);
|
||||||
|
errorThrow("Failed to load Lua script: %s", strErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lua_pcall(ctx->luaState, 0, LUA_MULTRET, 0) != LUA_OK) {
|
||||||
|
const char_t *strErr = lua_tostring(ctx->luaState, -1);
|
||||||
|
lua_pop(ctx->luaState, 1);
|
||||||
|
errorThrow("Failed to execute Lua script: %s", strErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorChain(assetScriptDispose(&script));
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scriptContextDispose(scriptcontext_t *context) {
|
||||||
|
assertNotNull(context, "Script context cannot be NULL");
|
||||||
|
assertNotNull(context->luaState, "Lua state is not initialized");
|
||||||
|
|
||||||
|
if(context->luaState != NULL) {
|
||||||
|
lua_close(context->luaState);
|
||||||
|
context->luaState = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/script/scriptcontext.h
Normal file
82
src/script/scriptcontext.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "scriptvalue.h"
|
||||||
|
#include <lua.h>
|
||||||
|
#include <lauxlib.h>
|
||||||
|
#include <lualib.h>
|
||||||
|
|
||||||
|
typedef struct scriptcontext_s {
|
||||||
|
lua_State *luaState;
|
||||||
|
} scriptcontext_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a script context.
|
||||||
|
*
|
||||||
|
* @param context The script context to initialize.
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptContextInit(scriptcontext_t *context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a C function within a script context.
|
||||||
|
*
|
||||||
|
* @param context The script context to use.
|
||||||
|
* @param fnName The name of the function in Lua.
|
||||||
|
* @param function The C function to register.
|
||||||
|
*/
|
||||||
|
void scriptContextRegFunc(
|
||||||
|
scriptcontext_t *context,
|
||||||
|
const char_t *fnName,
|
||||||
|
lua_CFunction function
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a Lua function within a script context.
|
||||||
|
*
|
||||||
|
* @param context The script context to use.
|
||||||
|
* @param fnName The name of the Lua function to call.
|
||||||
|
* @param args Array of args to pass to the function (or NULL for no args)
|
||||||
|
* @param argCount The number of arguments in the args array (omitable).
|
||||||
|
* @param retValue Output to store returned value (or NULL for no return value).
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
|
||||||
|
errorret_t scriptContextCallFunc(
|
||||||
|
scriptcontext_t *context,
|
||||||
|
const char_t *fnName,
|
||||||
|
const scriptvalue_t *args,
|
||||||
|
const int32_t argCount,
|
||||||
|
scriptvalue_t *retValue
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a script within a script context.
|
||||||
|
*
|
||||||
|
* @param context The script context to use.
|
||||||
|
* @param script The script to execute.
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptContextExec(scriptcontext_t *context, const char_t *script);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a script from a file within a script context.
|
||||||
|
*
|
||||||
|
* @param ctx The script context to use.
|
||||||
|
* @param fname The filename of the script to execute.
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptContextExecFile(scriptcontext_t *ctx, const char_t *fname);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of a script context.
|
||||||
|
*
|
||||||
|
* @param context The script context to dispose of.
|
||||||
|
*/
|
||||||
|
void scriptContextDispose(scriptcontext_t *context);
|
||||||
23
src/script/scriptmanager.c
Normal file
23
src/script/scriptmanager.c
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "scriptmanager.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "debug/debug.h"
|
||||||
|
#include "asset/asset.h"
|
||||||
|
|
||||||
|
scriptmanager_t SCRIPT_MANAGER;
|
||||||
|
|
||||||
|
errorret_t scriptManagerInit() {
|
||||||
|
memoryZero(&SCRIPT_MANAGER, sizeof(scriptmanager_t));
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
errorret_t scriptManagerDispose() {
|
||||||
|
errorOk();
|
||||||
|
}
|
||||||
30
src/script/scriptmanager.h
Normal file
30
src/script/scriptmanager.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "scriptcontext.h"
|
||||||
|
|
||||||
|
typedef struct scriptmanager_s {
|
||||||
|
scriptcontext_t mainContext;
|
||||||
|
} scriptmanager_t;
|
||||||
|
|
||||||
|
extern scriptmanager_t SCRIPT_MANAGER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the script manager.
|
||||||
|
*
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerInit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of the script manager.
|
||||||
|
*
|
||||||
|
* @return The error return value.
|
||||||
|
*/
|
||||||
|
errorret_t scriptManagerDispose();
|
||||||
26
src/script/scriptvalue.h
Normal file
26
src/script/scriptvalue.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
#define SCRIPT_VALUE_TYPE_NIL 0
|
||||||
|
#define SCRIPT_VALUE_TYPE_INT 1
|
||||||
|
#define SCRIPT_VALUE_TYPE_FLOAT 2
|
||||||
|
#define SCRIPT_VALUE_TYPE_STRING 3
|
||||||
|
#define SCRIPT_VALUE_TYPE_BOOL 4
|
||||||
|
|
||||||
|
typedef struct scriptvalue_s {
|
||||||
|
uint8_t type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
int32_t intValue;
|
||||||
|
float floatValue;
|
||||||
|
const char_t *strValue;
|
||||||
|
bool boolValue;
|
||||||
|
} value;
|
||||||
|
} scriptvalue_t;
|
||||||
@@ -12,3 +12,6 @@ target_sources(${DUSK_TARGET_NAME}
|
|||||||
uiframe.c
|
uiframe.c
|
||||||
uitextbox.c
|
uitextbox.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Subdirs
|
||||||
|
add_subdirectory(element)
|
||||||
9
src/ui/element/CMakeLists.txt
Normal file
9
src/ui/element/CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# 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
|
||||||
|
)
|
||||||
@@ -6,8 +6,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "dusk.h"
|
|
||||||
|
|
||||||
typedef struct cutscene_s cutscene_t;
|
typedef enum {
|
||||||
|
UI_ELEMENT_TYPE_NULL,
|
||||||
|
|
||||||
typedef cutscene_t* cutscenecutscene_t;
|
UI_ELEMENT_TYPE_TEXT,
|
||||||
|
|
||||||
|
UI_ELEMENT_TYPE_COUNT,
|
||||||
|
} uielementtype_t;
|
||||||
@@ -12,3 +12,20 @@ function(add_asset ASSET_TYPE ASSET_PATH)
|
|||||||
)
|
)
|
||||||
set(DUSK_ASSETS ${DUSK_ASSETS} CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
set(DUSK_ASSETS ${DUSK_ASSETS} CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
function(add_defs INPUT_PATH OUTPUT_NAME_RELATIVE)
|
||||||
|
set(INPUT_FULL_PATH "${CMAKE_CURRENT_LIST_DIR}/${INPUT_PATH}")
|
||||||
|
set(OUTPUT_FULL_PATH "${DUSK_GENERATED_HEADERS_DIR}/${OUTPUT_NAME_RELATIVE}")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${OUTPUT_FULL_PATH}
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DENV_FILE=${INPUT_FULL_PATH}
|
||||||
|
-DOUT_HEADER=${OUTPUT_FULL_PATH}
|
||||||
|
-P ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake
|
||||||
|
DEPENDS ${INPUT_FULL_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/envtoh.cmake
|
||||||
|
COMMENT "Generating ${OUTPUT_NAME_RELATIVE}"
|
||||||
|
)
|
||||||
|
add_custom_target(${OUTPUT_NAME_RELATIVE}_header DEPENDS ${OUTPUT_FULL_PATH})
|
||||||
|
add_dependencies(${DUSK_TARGET_NAME} ${OUTPUT_NAME_RELATIVE}_header)
|
||||||
|
endfunction()
|
||||||
@@ -1,50 +1,12 @@
|
|||||||
import os
|
import sys, os
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
|
||||||
|
|
||||||
# Check if the script is run with the correct arguments
|
# Check if the script is run with the correct arguments
|
||||||
parser = argparse.ArgumentParser(description="Generate chunk header files")
|
parser = argparse.ArgumentParser(description="Generate chunk header files")
|
||||||
parser.add_argument('--assets', required=True, help='Dir to output built assets')
|
parser.add_argument('--assets', required=True, help='Dir to output built assets')
|
||||||
parser.add_argument('--build-type', choices=['wad', 'header'], default='raw', help='Type of build to perform')
|
|
||||||
parser.add_argument('--output-file', required=True, help='Output file for built assets (required for wad build)')
|
|
||||||
parser.add_argument('--headers-dir', required=True, help='Directory to output individual asset headers (required for header build)')
|
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-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-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='+')
|
parser.add_argument('--input', required=True, help='Input assets to process', nargs='+')
|
||||||
args = parser.parse_args()
|
args = parser.parse_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)
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
from args import args
|
from assetstool.args import args
|
||||||
|
|
||||||
def getAssetRelativePath(fullPath):
|
def getAssetRelativePath(fullPath):
|
||||||
# Get the relative path to the asset
|
# Get the relative path to the asset
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
ASSET_FILE_NAME_MAX_LENGTH = 256
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import sys, os
|
|
||||||
from args import inputAssets, args
|
|
||||||
from processasset import processAsset
|
|
||||||
from processpalette import processPaletteList
|
|
||||||
from processtileset import processTilesetList
|
|
||||||
from processlanguage import processLanguageList
|
|
||||||
from assethelpers import getBuiltAssetsRelativePath
|
|
||||||
import zipfile
|
|
||||||
|
|
||||||
# Setup headers directory.
|
|
||||||
# setOutputDir(args.output)
|
|
||||||
# outputHeaders = []
|
|
||||||
|
|
||||||
# # Create output directory if it doesn't exist
|
|
||||||
# if not os.path.exists(args.output):
|
|
||||||
# os.makedirs(args.output)
|
|
||||||
|
|
||||||
files = []
|
|
||||||
|
|
||||||
for asset in inputAssets:
|
|
||||||
asset = processAsset(asset)
|
|
||||||
files.extend(asset['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()
|
|
||||||
|
|
||||||
# Finalize build
|
|
||||||
if args.build_type == 'header':
|
|
||||||
print("Error: Header build not implemented yet.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
elif args.build_type == 'wad':
|
|
||||||
# Nothing to do, already created above!
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Error: Unknown build type.")
|
|
||||||
sys.exit(1)
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import sys
|
import sys
|
||||||
# from processtileset import processTileset
|
# from processtileset import processTileset
|
||||||
from processimage import processImage
|
from assetstool.processimage import processImage
|
||||||
from processpalette import processPalette
|
from assetstool.processpalette import processPalette
|
||||||
from processtileset import processTileset
|
from assetstool.processtileset import processTileset
|
||||||
from processmap import processMap
|
from assetstool.processmap import processMap
|
||||||
from processlanguage import processLanguage
|
from assetstool.processlanguage import processLanguage
|
||||||
|
from assetstool.processscript import processScript
|
||||||
|
|
||||||
processedAssets = []
|
processedAssets = []
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ def processAsset(asset):
|
|||||||
return processMap(asset)
|
return processMap(asset)
|
||||||
elif t == 'language':
|
elif t == 'language':
|
||||||
return processLanguage(asset)
|
return processLanguage(asset)
|
||||||
|
elif t == 'script':
|
||||||
|
return processScript(asset)
|
||||||
else:
|
else:
|
||||||
print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'")
|
print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from processpalette import extractPaletteFromImage, palettes
|
from assetstool.processpalette import extractPaletteFromImage, palettes
|
||||||
from args import args
|
from assetstool.args import args
|
||||||
from assethelpers import getAssetRelativePath
|
from assetstool.assethelpers import getAssetRelativePath
|
||||||
from assetcache import assetGetCache, assetCache
|
from assetstool.assetcache import assetGetCache, assetCache
|
||||||
|
|
||||||
images = []
|
images = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from args import args
|
from assetstool.args import args
|
||||||
from assetcache import assetCache, assetGetCache
|
from assetstool.assetcache import assetCache, assetGetCache
|
||||||
from assethelpers import getAssetRelativePath
|
from assetstool.assethelpers import getAssetRelativePath
|
||||||
|
from dusk.defs import defs
|
||||||
import polib
|
import polib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
LANGUAGE_CHUNK_CHAR_COUNT = 6 * 1024 # 6 KB per chunk
|
LANGUAGE_CHUNK_CHAR_COUNT = int(defs.get('ASSET_LANG_CHUNK_CHAR_COUNT'))
|
||||||
|
|
||||||
LANGUAGE_DATA = {}
|
LANGUAGE_DATA = {}
|
||||||
LANGUAGE_KEYS = []
|
LANGUAGE_KEYS = []
|
||||||
|
|||||||
@@ -2,159 +2,96 @@ import struct
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from args import args
|
from assetstool.args import args
|
||||||
from assetcache import assetCache, assetGetCache
|
from assetstool.assetcache import assetCache, assetGetCache
|
||||||
from assethelpers import getAssetRelativePath
|
from assetstool.assethelpers import getAssetRelativePath
|
||||||
|
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_TILE_COUNT
|
||||||
|
from dusk.map import Map
|
||||||
|
from dusk.chunk import Chunk
|
||||||
|
|
||||||
CHUNK_WIDTH = 16
|
def convertModelData(modelData):
|
||||||
CHUNK_HEIGHT = 16
|
# TLDR; Model data stores things efficiently with indices, but we buffer it
|
||||||
CHUNK_DEPTH = 4
|
# out to 6 vertex quads for simplicity.
|
||||||
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH
|
outVertices = []
|
||||||
TILE_WIDTH = 16.0
|
outUVs = []
|
||||||
TILE_HEIGHT = 16.0
|
outColors = []
|
||||||
TILE_DEPTH = 11.36
|
for indice in modelData['indices']:
|
||||||
|
vertex = modelData['vertices'][indice]
|
||||||
def processTile(tileIndex, x=0, y=0, z=0, chunkX=0, chunkY=0, chunkZ=0):
|
uv = modelData['uvs'][indice]
|
||||||
vertices = []
|
color = modelData['colors'][indice]
|
||||||
indices = []
|
outVertices.append(vertex)
|
||||||
tileType = tileIndex
|
outUVs.append(uv)
|
||||||
|
outColors.append(color)
|
||||||
# Placement X, Y, Z
|
|
||||||
px = (x * TILE_WIDTH) + (chunkX * CHUNK_WIDTH * TILE_WIDTH)
|
|
||||||
py = (y * TILE_HEIGHT) + (chunkY * CHUNK_HEIGHT * TILE_HEIGHT)
|
|
||||||
pz = (z * TILE_DEPTH) + (chunkZ * CHUNK_DEPTH * TILE_DEPTH)
|
|
||||||
|
|
||||||
if tileIndex == 0:
|
|
||||||
# Tile 0, nothing
|
|
||||||
return None
|
|
||||||
|
|
||||||
elif tileIndex == 5:
|
|
||||||
# Tile 2, ramp up
|
|
||||||
color = (255,0,0)
|
|
||||||
vertices = [
|
|
||||||
{'position': (px, py, pz + TILE_DEPTH), 'color': color, 'uv': (0,0)}, # 0,0
|
|
||||||
{'position': (px + TILE_WIDTH, py, pz + TILE_DEPTH), 'color': color, 'uv': (1,0)}, # 1,0
|
|
||||||
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1
|
|
||||||
{'position': (px, py, pz + TILE_DEPTH), 'color': color, 'uv': (0,0)}, # 0,0 (repeat)
|
|
||||||
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1 (repeat)
|
|
||||||
{'position': (px, py + TILE_HEIGHT, pz), 'color': color, 'uv': (0,1)} # 0,1
|
|
||||||
]
|
|
||||||
indices = [0, 1, 2, 3, 4, 5]
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Determine color for checkerboard pattern
|
|
||||||
if tileIndex == 1:
|
|
||||||
color = (255, 255, 255)
|
|
||||||
else:
|
|
||||||
color = (0, 0, 255)
|
|
||||||
|
|
||||||
vertices = [
|
|
||||||
{'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0
|
|
||||||
{'position': (px + TILE_WIDTH, py, pz), 'color': color, 'uv': (1,0)}, # 1,0
|
|
||||||
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1
|
|
||||||
{'position': (px, py, pz), 'color': color, 'uv': (0,0)}, # 0,0 (repeat)
|
|
||||||
{'position': (px + TILE_WIDTH, py + TILE_HEIGHT, pz), 'color': color, 'uv': (1,1)}, # 1,1 (repeat)
|
|
||||||
{'position': (px, py + TILE_HEIGHT, pz), 'color': color, 'uv': (0,1)} # 0,1
|
|
||||||
]
|
|
||||||
indices = [0, 1, 2, 3, 4, 5]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'vertices': vertices,
|
'vertices': outVertices,
|
||||||
'indices': indices,
|
'uvs': outUVs,
|
||||||
'tileType': tileType
|
'colors': outColors
|
||||||
}
|
}
|
||||||
|
|
||||||
def processChunk(path):
|
def processChunk(chunk):
|
||||||
cache = assetGetCache(path)
|
cache = assetGetCache(chunk.getFilename())
|
||||||
if cache:
|
if cache:
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
# Read input file as JSON
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
inData = json.load(f)
|
|
||||||
|
|
||||||
# Filename must contain chunk coordinates as X_Y_Z
|
|
||||||
fileName = os.path.basename(path)
|
|
||||||
nameParts = os.path.splitext(fileName)[0].split('_')
|
|
||||||
if len(nameParts) != 3:
|
|
||||||
print(f"Error: Chunk filename {fileName} does not contain valid chunk coordinates.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
chunk = {
|
|
||||||
'chunkX': int(nameParts[0]),
|
|
||||||
'chunkY': int(nameParts[1]),
|
|
||||||
'chunkZ': int(nameParts[2]),
|
|
||||||
'tiles': [0] * CHUNK_TILE_COUNT,
|
|
||||||
'models': []
|
|
||||||
}
|
|
||||||
|
|
||||||
baseModel = {
|
baseModel = {
|
||||||
'vertices': [],
|
'vertices': [],
|
||||||
'indices': [],
|
'colors': [],
|
||||||
'vertexCount': 0,
|
'uvs': []
|
||||||
'indexCount': 0
|
|
||||||
}
|
}
|
||||||
|
models = [ baseModel ]
|
||||||
|
|
||||||
# Append the model to chunk.models
|
for tileIndex, tile in chunk.tiles.items():
|
||||||
chunk['models'].append(baseModel)
|
tileBase = tile.getBaseTileModel()
|
||||||
|
|
||||||
for i, tile in enumerate(inData['tiles']):
|
convertedBase = convertModelData(tileBase)
|
||||||
# Set to chunk
|
baseModel['vertices'].extend(convertedBase['vertices'])
|
||||||
|
baseModel['colors'].extend(convertedBase['colors'])
|
||||||
# Calculate x, y, z from i
|
baseModel['uvs'].extend(convertedBase['uvs'])
|
||||||
x = i % CHUNK_WIDTH
|
|
||||||
y = (i // CHUNK_WIDTH) % CHUNK_HEIGHT
|
|
||||||
z = i // (CHUNK_WIDTH * CHUNK_HEIGHT)
|
|
||||||
|
|
||||||
# Add tile 3D model
|
|
||||||
result = processTile(tile, x, y, z, chunk['chunkX'], chunk['chunkY'], chunk['chunkZ'])
|
|
||||||
if result is not None and len(result['vertices']) > 0:
|
|
||||||
base = len(baseModel['vertices'])
|
|
||||||
quad_indices = [base + idx for idx in result['indices']]
|
|
||||||
baseModel['vertices'].extend(result['vertices'])
|
|
||||||
baseModel['indices'].extend(quad_indices)
|
|
||||||
baseModel['vertexCount'] = len(baseModel['vertices'])
|
|
||||||
baseModel['indexCount'] = len(baseModel['indices'])
|
|
||||||
chunk['tiles'][i] = result['tileType']
|
|
||||||
|
|
||||||
# Generate binary buffer for efficient output
|
# Generate binary buffer for efficient output
|
||||||
buffer = bytearray()
|
buffer = bytearray()
|
||||||
buffer.extend(b'DCF')# Header
|
buffer.extend(b'DCF')# Header
|
||||||
buffer.extend(len(chunk['tiles']).to_bytes(4, 'little')) # Number of tiles
|
buffer.extend(len(chunk.tiles).to_bytes(4, 'little')) # Number of tiles
|
||||||
buffer.extend(len(chunk['models']).to_bytes(1, 'little')) # Number of models
|
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
|
# Buffer tile data as array of uint8_t
|
||||||
for tileIndex in chunk['tiles']:
|
for tileIndex, tile in chunk.tiles.items():
|
||||||
buffer.append(tileIndex.to_bytes(1, 'little')[0])
|
buffer.extend(tile.shape.to_bytes(1, 'little'))
|
||||||
|
|
||||||
# For each model
|
# # For each model
|
||||||
for model in chunk['models']:
|
for model in models:
|
||||||
# Write vertex count and index count
|
vertexCount = len(model['vertices'])
|
||||||
buffer.extend(model['vertexCount'].to_bytes(4, 'little'))
|
buffer.extend(vertexCount.to_bytes(4, 'little'))
|
||||||
|
for i in range(vertexCount):
|
||||||
|
vertex = model['vertices'][i]
|
||||||
|
uv = model['uvs'][i]
|
||||||
|
color = model['colors'][i]
|
||||||
|
|
||||||
# For each vertex
|
buffer.extend(color[0].to_bytes(1, 'little'))
|
||||||
for vertex in model['vertices']:
|
buffer.extend(color[1].to_bytes(1, 'little'))
|
||||||
# This is not tightly packed in memory.
|
buffer.extend(color[2].to_bytes(1, 'little'))
|
||||||
# R G B A U V X Y Z
|
buffer.extend(color[3].to_bytes(1, 'little'))
|
||||||
# Color is 4 bytes (RGBA)
|
|
||||||
# Rest is floats
|
buffer.extend(bytearray(struct.pack('<f', uv[0])))
|
||||||
r, g, b = vertex['color']
|
buffer.extend(bytearray(struct.pack('<f', uv[1])))
|
||||||
a = 255
|
|
||||||
buffer.extend(r.to_bytes(1, 'little'))
|
buffer.extend(bytearray(struct.pack('<f', vertex[0])))
|
||||||
buffer.extend(g.to_bytes(1, 'little'))
|
buffer.extend(bytearray(struct.pack('<f', vertex[1])))
|
||||||
buffer.extend(b.to_bytes(1, 'little'))
|
buffer.extend(bytearray(struct.pack('<f', vertex[2])))
|
||||||
buffer.extend(a.to_bytes(1, 'little'))
|
|
||||||
u, v = vertex['uv']
|
# For each entity
|
||||||
buffer.extend(bytearray(struct.pack('<f', u)))
|
for entity in chunk.entities.values():
|
||||||
buffer.extend(bytearray(struct.pack('<f', v)))
|
buffer.extend(entity.type.to_bytes(1, 'little'))
|
||||||
x, y, z = vertex['position']
|
buffer.extend(entity.localX.to_bytes(1, 'little'))
|
||||||
buffer.extend(bytearray(struct.pack('<f', x)))
|
buffer.extend(entity.localY.to_bytes(1, 'little'))
|
||||||
buffer.extend(bytearray(struct.pack('<f', y)))
|
buffer.extend(entity.localZ.to_bytes(1, 'little'))
|
||||||
buffer.extend(bytearray(struct.pack('<f', z)))
|
pass
|
||||||
|
|
||||||
# Write out map file
|
# Write out map file
|
||||||
relative = getAssetRelativePath(path)
|
relative = getAssetRelativePath(chunk.getFilename())
|
||||||
fileNameWithoutExt = os.path.splitext(os.path.basename(path))[0]
|
fileNameWithoutExt = os.path.splitext(os.path.basename(relative))[0]
|
||||||
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dcf")
|
outputFileRelative = os.path.join(os.path.dirname(relative), f"{fileNameWithoutExt}.dcf")
|
||||||
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
outputFilePath = os.path.join(args.output_assets, outputFileRelative)
|
||||||
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
os.makedirs(os.path.dirname(outputFilePath), exist_ok=True)
|
||||||
@@ -165,20 +102,42 @@ def processChunk(path):
|
|||||||
'files': [ outputFilePath ],
|
'files': [ outputFilePath ],
|
||||||
'chunk': chunk
|
'chunk': chunk
|
||||||
}
|
}
|
||||||
|
return assetCache(chunk.getFilename(), outChunk)
|
||||||
return assetCache(path, outChunk)
|
|
||||||
|
|
||||||
|
|
||||||
def processMap(asset):
|
def processMap(asset):
|
||||||
cache = assetGetCache(asset['path'])
|
cache = assetGetCache(asset['path'])
|
||||||
if cache is not None:
|
if cache is not None:
|
||||||
return cache
|
return cache
|
||||||
|
|
||||||
# Path provided should be a directory.
|
map = Map(None)
|
||||||
if not os.path.isdir(asset['path']):
|
map.load(asset['path'])
|
||||||
print(f"Error: Asset path {asset['path']} is not a directory.")
|
dir = map.getMapDirectory()
|
||||||
|
|
||||||
|
files = os.listdir(dir)
|
||||||
|
if len(files) == 0:
|
||||||
|
print(f"Error: No chunk files found in map directory {dir}.")
|
||||||
sys.exit(1)
|
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'])
|
||||||
|
|
||||||
|
outMap = {
|
||||||
|
'files': chunkFiles
|
||||||
|
}
|
||||||
|
return assetCache(asset['path'], outMap)
|
||||||
|
|
||||||
# List files
|
# List files
|
||||||
chunkFiles = []
|
chunkFiles = []
|
||||||
for fileName in os.listdir(asset['path']):
|
for fileName in os.listdir(asset['path']):
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from args import args
|
|
||||||
import sys
|
|
||||||
import datetime
|
import datetime
|
||||||
from assetcache import assetCache, assetGetCache
|
from assetstool.args import args
|
||||||
|
from assetstool.assetcache import assetCache, assetGetCache
|
||||||
|
|
||||||
palettes = []
|
palettes = []
|
||||||
|
|
||||||
|
|||||||
43
tools/assetstool/processscript.py
Normal file
43
tools/assetstool/processscript.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from assetstool.args import args
|
||||||
|
from assetstool.assetcache import assetCache, assetGetCache
|
||||||
|
from assetstool.assethelpers import getAssetRelativePath
|
||||||
|
from 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)
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
from processimage import processImage
|
|
||||||
import sys
|
import sys
|
||||||
from assethelpers import getAssetRelativePath
|
|
||||||
import os
|
import os
|
||||||
import datetime
|
import datetime
|
||||||
from args import args
|
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from assetcache import assetGetCache, assetCache
|
from assetstool.processimage import processImage
|
||||||
|
from assetstool.assethelpers import getAssetRelativePath
|
||||||
|
from assetstool.args import args
|
||||||
|
from assetstool.assetcache import assetGetCache, assetCache
|
||||||
|
|
||||||
tilesets = []
|
tilesets = []
|
||||||
|
|
||||||
|
|||||||
66
tools/assettool.py
Normal file
66
tools/assettool.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import sys, os
|
||||||
|
from assetstool.args import args
|
||||||
|
from assetstool.processasset import processAsset
|
||||||
|
from assetstool.processpalette import processPaletteList
|
||||||
|
from assetstool.processtileset import processTilesetList
|
||||||
|
from assetstool.processlanguage import processLanguageList
|
||||||
|
from assetstool.assethelpers 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()
|
||||||
158
tools/dusk/chunk.py
Normal file
158
tools/dusk/chunk.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from dusk.event import Event
|
||||||
|
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, CHUNK_VERTEX_COUNT_MAX, TILE_SHAPE_NULL
|
||||||
|
from dusk.tile import Tile
|
||||||
|
from dusk.entity import Entity
|
||||||
|
from dusk.region import Region
|
||||||
|
from editortool.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, 'getMapDirectory'):
|
||||||
|
return None
|
||||||
|
dir_path = self.map.getMapDirectory()
|
||||||
|
if dir_path is None:
|
||||||
|
return None
|
||||||
|
return f"{dir_path}/{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
|
||||||
49
tools/dusk/defs.py
Normal file
49
tools/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))
|
||||||
90
tools/dusk/entity.py
Normal file
90
tools/dusk/entity.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from dusk.defs import ENTITY_TYPE_NULL, ENTITY_TYPE_NPC, CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
|
||||||
|
from editortool.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()
|
||||||
18
tools/dusk/event.py
Normal file
18
tools/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)
|
||||||
252
tools/dusk/map.py
Normal file
252
tools/dusk/map.py
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import json
|
||||||
|
from dusk.event import Event
|
||||||
|
from PyQt5.QtWidgets import QFileDialog, QMessageBox
|
||||||
|
from PyQt5.QtCore import QTimer
|
||||||
|
import os
|
||||||
|
from dusk.chunk import Chunk
|
||||||
|
from 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):
|
||||||
|
fname = self.getMapFilename()
|
||||||
|
if not fname or not fname.endswith('.json'):
|
||||||
|
return None
|
||||||
|
return fname[:-5] # Remove '.json' extension
|
||||||
|
|
||||||
|
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
tools/dusk/region.py
Normal file
141
tools/dusk/region.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH
|
||||||
|
from editortool.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
tools/dusk/tile.py
Normal file
206
tools/dusk/tile.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
from OpenGL.GL import *
|
||||||
|
from 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
|
||||||
|
])
|
||||||
57
tools/editor.py
Executable file
57
tools/editor.py
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QApplication, QVBoxLayout, QPushButton,
|
||||||
|
QDialog
|
||||||
|
)
|
||||||
|
from OpenGL.GL import *
|
||||||
|
from OpenGL.GLU import *
|
||||||
|
from editortool.maptool import MapWindow
|
||||||
|
from editortool.langtool import LangToolWindow
|
||||||
|
from editortool.cutscenetool import CutsceneToolWindow
|
||||||
|
|
||||||
|
DEFAULT_TOOL = None
|
||||||
|
DEFAULT_TOOL = "map"
|
||||||
|
# DEFAULT_TOOL = "cutscene"
|
||||||
|
|
||||||
|
TOOLS = [
|
||||||
|
("Map Editor", "map", MapWindow),
|
||||||
|
("Language Editor", "language", LangToolWindow),
|
||||||
|
("Cutscene Editor", "cutscene", CutsceneToolWindow),
|
||||||
|
]
|
||||||
|
|
||||||
|
class EditorChoiceDialog(QDialog):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Choose Tool")
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
self.selected = None
|
||||||
|
for label, key, win_cls in TOOLS:
|
||||||
|
btn = QPushButton(label)
|
||||||
|
btn.clicked.connect(lambda checked, w=win_cls: self.choose_tool(w))
|
||||||
|
layout.addWidget(btn)
|
||||||
|
|
||||||
|
def choose_tool(self, win_cls):
|
||||||
|
self.selected = win_cls
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
def get_choice(self):
|
||||||
|
return self.selected
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
tool_map = { key: win_cls for _, key, win_cls in TOOLS }
|
||||||
|
if DEFAULT_TOOL in tool_map:
|
||||||
|
win_cls = tool_map[DEFAULT_TOOL]
|
||||||
|
else:
|
||||||
|
choice_dialog = EditorChoiceDialog()
|
||||||
|
if choice_dialog.exec_() == QDialog.Accepted:
|
||||||
|
win_cls = choice_dialog.get_choice()
|
||||||
|
else:
|
||||||
|
sys.exit(0)
|
||||||
|
win = win_cls()
|
||||||
|
win.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
58
tools/editortool/cutscene/cutsceneitemeditor.py
Normal file
58
tools/editortool/cutscene/cutsceneitemeditor.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QLabel, QSizePolicy, QComboBox, QHBoxLayout, QSpacerItem
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSignal
|
||||||
|
from .cutscenewait import CutsceneWaitEditor
|
||||||
|
from .cutscenetext import CutsceneTextEditor
|
||||||
|
|
||||||
|
EDITOR_MAP = (
|
||||||
|
( "wait", "Wait", CutsceneWaitEditor ),
|
||||||
|
( "text", "Text", CutsceneTextEditor )
|
||||||
|
)
|
||||||
|
|
||||||
|
class CutsceneItemEditor(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||||
|
self.layout.addWidget(QLabel("Item Properties:"))
|
||||||
|
|
||||||
|
rowLayout = QHBoxLayout()
|
||||||
|
rowLayout.setSpacing(8)
|
||||||
|
|
||||||
|
rowLayout.addWidget(QLabel("Name:"))
|
||||||
|
self.nameInput = QLineEdit()
|
||||||
|
rowLayout.addWidget(self.nameInput)
|
||||||
|
|
||||||
|
rowLayout.addWidget(QLabel("Type:"))
|
||||||
|
self.typeDropdown = QComboBox()
|
||||||
|
self.typeDropdown.addItems([typeName for typeKey, typeName, editorClass in EDITOR_MAP])
|
||||||
|
rowLayout.addWidget(self.typeDropdown)
|
||||||
|
self.layout.addLayout(rowLayout)
|
||||||
|
|
||||||
|
self.activeEditor = None
|
||||||
|
|
||||||
|
# Events
|
||||||
|
self.nameInput.textChanged.connect(self.onNameChanged)
|
||||||
|
self.typeDropdown.currentTextChanged.connect(self.onTypeChanged)
|
||||||
|
|
||||||
|
# First load
|
||||||
|
self.onNameChanged(self.nameInput.text())
|
||||||
|
self.onTypeChanged(self.typeDropdown.currentText())
|
||||||
|
|
||||||
|
def onNameChanged(self, nameText):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onTypeChanged(self, typeText):
|
||||||
|
typeKey = typeText.lower()
|
||||||
|
|
||||||
|
# Remove existing editor
|
||||||
|
if self.activeEditor:
|
||||||
|
self.layout.removeWidget(self.activeEditor)
|
||||||
|
self.activeEditor.deleteLater()
|
||||||
|
self.activeEditor = None
|
||||||
|
|
||||||
|
# Create new editor
|
||||||
|
for key, name, editorClass in EDITOR_MAP:
|
||||||
|
if key == typeKey:
|
||||||
|
self.activeEditor = editorClass()
|
||||||
|
self.layout.addWidget(self.activeEditor)
|
||||||
|
break
|
||||||
54
tools/editortool/cutscene/cutscenemenubar.py
Normal file
54
tools/editortool/cutscene/cutscenemenubar.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from PyQt5.QtWidgets import QMenuBar, QAction, QFileDialog, QMessageBox
|
||||||
|
from PyQt5.QtGui import QKeySequence
|
||||||
|
|
||||||
|
class CutsceneMenuBar(QMenuBar):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
fileMenu = self.addMenu("File")
|
||||||
|
|
||||||
|
self.newAction = QAction("New", self)
|
||||||
|
self.newAction.setShortcut(QKeySequence.New)
|
||||||
|
self.newAction.triggered.connect(self.newFile)
|
||||||
|
fileMenu.addAction(self.newAction)
|
||||||
|
|
||||||
|
self.openAction = QAction("Open", self)
|
||||||
|
self.openAction.setShortcut(QKeySequence.Open)
|
||||||
|
self.openAction.triggered.connect(self.openFile)
|
||||||
|
fileMenu.addAction(self.openAction)
|
||||||
|
|
||||||
|
self.saveAction = QAction("Save", self)
|
||||||
|
self.saveAction.setShortcut(QKeySequence.Save)
|
||||||
|
self.saveAction.triggered.connect(self.saveFile)
|
||||||
|
fileMenu.addAction(self.saveAction)
|
||||||
|
|
||||||
|
self.saveAsAction = QAction("Save As", self)
|
||||||
|
self.saveAsAction.setShortcut(QKeySequence.SaveAs)
|
||||||
|
self.saveAsAction.triggered.connect(self.saveFileAs)
|
||||||
|
fileMenu.addAction(self.saveAsAction)
|
||||||
|
|
||||||
|
def newFile(self):
|
||||||
|
self.parent.clearCutscene()
|
||||||
|
|
||||||
|
def openFile(self):
|
||||||
|
path, _ = QFileDialog.getOpenFileName(self.parent, "Open Cutscene File", "", "JSON Files (*.json);;All Files (*)")
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Load file contents into timeline
|
||||||
|
self.parent.currentFile = path
|
||||||
|
pass
|
||||||
|
|
||||||
|
def saveFile(self):
|
||||||
|
if not self.parent.currentFile:
|
||||||
|
self.saveFileAs()
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Save timeline to self.parent.currentFile
|
||||||
|
pass
|
||||||
|
|
||||||
|
def saveFileAs(self):
|
||||||
|
path, _ = QFileDialog.getSaveFileName(self.parent, "Save Cutscene File As", "", "JSON Files (*.json);;All Files (*)")
|
||||||
|
if path:
|
||||||
|
self.parent.currentFile = path
|
||||||
|
self.saveFile()
|
||||||
21
tools/editortool/cutscene/cutscenetext.py
Normal file
21
tools/editortool/cutscene/cutscenetext.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QTextEdit
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
class CutsceneTextEditor(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.setSpacing(0)
|
||||||
|
label = QLabel("Text:")
|
||||||
|
label.setSizePolicy(label.sizePolicy().Expanding, label.sizePolicy().Fixed)
|
||||||
|
layout.addWidget(label)
|
||||||
|
self.textInput = QTextEdit()
|
||||||
|
self.textInput.setSizePolicy(self.textInput.sizePolicy().Expanding, self.textInput.sizePolicy().Expanding)
|
||||||
|
layout.addWidget(self.textInput, stretch=1)
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
self.textInput.setPlainText(text)
|
||||||
|
|
||||||
|
def getText(self):
|
||||||
|
return self.textInput.toPlainText()
|
||||||
20
tools/editortool/cutscene/cutscenewait.py
Normal file
20
tools/editortool/cutscene/cutscenewait.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QFormLayout, QDoubleSpinBox, QLabel
|
||||||
|
|
||||||
|
class CutsceneWaitEditor(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
layout = QFormLayout(self)
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.setSpacing(0)
|
||||||
|
self.waitTimeInput = QDoubleSpinBox()
|
||||||
|
self.waitTimeInput.setMinimum(0.0)
|
||||||
|
self.waitTimeInput.setMaximum(9999.0)
|
||||||
|
self.waitTimeInput.setDecimals(2)
|
||||||
|
self.waitTimeInput.setSingleStep(0.1)
|
||||||
|
layout.addRow(QLabel("Wait Time (seconds):"), self.waitTimeInput)
|
||||||
|
|
||||||
|
def setWaitTime(self, value):
|
||||||
|
self.waitTimeInput.setValue(value)
|
||||||
|
|
||||||
|
def getWaitTime(self):
|
||||||
|
return self.waitTimeInput.value()
|
||||||
101
tools/editortool/cutscenetool.py
Normal file
101
tools/editortool/cutscenetool.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMenuBar, QAction, QFileDialog, QMessageBox
|
||||||
|
from PyQt5.QtGui import QKeySequence
|
||||||
|
from editortool.cutscene.cutsceneitemeditor import CutsceneItemEditor
|
||||||
|
from editortool.cutscene.cutscenemenubar import CutsceneMenuBar
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class CutsceneToolWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Dusk Cutscene Editor")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
self.nextItemNumber = 1 # Track next available number
|
||||||
|
self.currentFile = None
|
||||||
|
self.dirty = False # Track unsaved changes
|
||||||
|
|
||||||
|
# File menu (handled by CutsceneMenuBar)
|
||||||
|
menubar = CutsceneMenuBar(self)
|
||||||
|
self.setMenuBar(menubar)
|
||||||
|
|
||||||
|
# Main layout: horizontal split
|
||||||
|
central = QWidget()
|
||||||
|
mainLayout = QHBoxLayout(central)
|
||||||
|
self.setCentralWidget(central)
|
||||||
|
|
||||||
|
# Timeline
|
||||||
|
leftPanel = QWidget()
|
||||||
|
leftLayout = QVBoxLayout(leftPanel)
|
||||||
|
|
||||||
|
self.timelineList = QListWidget()
|
||||||
|
self.timelineList.setSelectionMode(QListWidget.SingleSelection)
|
||||||
|
leftLayout.addWidget(QLabel("Cutscene Timeline"))
|
||||||
|
leftLayout.addWidget(self.timelineList)
|
||||||
|
|
||||||
|
btnLayout = QHBoxLayout()
|
||||||
|
self.addBtn = QPushButton("Add")
|
||||||
|
self.removeBtn = QPushButton("Remove")
|
||||||
|
btnLayout.addWidget(self.addBtn)
|
||||||
|
btnLayout.addWidget(self.removeBtn)
|
||||||
|
leftLayout.addLayout(btnLayout)
|
||||||
|
mainLayout.addWidget(leftPanel, 2)
|
||||||
|
|
||||||
|
# Property editor
|
||||||
|
self.editorPanel = QWidget()
|
||||||
|
self.editorLayout = QVBoxLayout(self.editorPanel)
|
||||||
|
self.itemEditor = None # Only create when needed
|
||||||
|
mainLayout.addWidget(self.editorPanel, 3)
|
||||||
|
|
||||||
|
# Events
|
||||||
|
self.timelineList.currentItemChanged.connect(self.onItemSelected)
|
||||||
|
self.addBtn.clicked.connect(self.addCutsceneItem)
|
||||||
|
self.removeBtn.clicked.connect(self.removeCutsceneItem)
|
||||||
|
|
||||||
|
def addCutsceneItem(self):
|
||||||
|
name = f"Cutscene item {self.nextItemNumber}"
|
||||||
|
timelineItem = QListWidgetItem(name)
|
||||||
|
self.timelineList.addItem(timelineItem)
|
||||||
|
self.timelineList.setCurrentItem(timelineItem)
|
||||||
|
self.nextItemNumber += 1
|
||||||
|
self.dirty = True
|
||||||
|
|
||||||
|
def removeCutsceneItem(self):
|
||||||
|
row = self.timelineList.currentRow()
|
||||||
|
if row < 0:
|
||||||
|
return
|
||||||
|
self.timelineList.takeItem(row)
|
||||||
|
self.dirty = True
|
||||||
|
|
||||||
|
# Remove editor if nothing selected
|
||||||
|
if self.timelineList.currentItem() is None or self.itemEditor is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.editorLayout.removeWidget(self.itemEditor)
|
||||||
|
self.itemEditor.deleteLater()
|
||||||
|
self.itemEditor = None
|
||||||
|
|
||||||
|
def clearCutscene(self):
|
||||||
|
self.timelineList.clear()
|
||||||
|
self.nextItemNumber = 1
|
||||||
|
self.currentFile = None
|
||||||
|
self.dirty = False
|
||||||
|
if self.itemEditor:
|
||||||
|
self.editorLayout.removeWidget(self.itemEditor)
|
||||||
|
|
||||||
|
def onItemSelected(self, current, previous):
|
||||||
|
if current:
|
||||||
|
if not self.itemEditor:
|
||||||
|
self.itemEditor = CutsceneItemEditor()
|
||||||
|
self.editorLayout.addWidget(self.itemEditor)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.itemEditor:
|
||||||
|
return
|
||||||
|
self.editorLayout.removeWidget(self.itemEditor)
|
||||||
|
self.itemEditor.deleteLater()
|
||||||
|
self.itemEditor = None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = CutsceneToolWindow()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
202
tools/editortool/langtool.py
Normal file
202
tools/editortool/langtool.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton, QTabWidget, QFormLayout
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import polib
|
||||||
|
|
||||||
|
class LangToolWindow(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Dusk Language Editor")
|
||||||
|
self.setGeometry(100, 100, 800, 600)
|
||||||
|
self.current_file = None
|
||||||
|
self.dirty = False
|
||||||
|
self.init_menu()
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
central = QWidget()
|
||||||
|
layout = QVBoxLayout(central)
|
||||||
|
|
||||||
|
tabs = QTabWidget()
|
||||||
|
# Header Tab
|
||||||
|
header_tab = QWidget()
|
||||||
|
header_layout = QFormLayout(header_tab)
|
||||||
|
self.language_edit = QLineEdit()
|
||||||
|
self.language_edit.setMaximumWidth(220)
|
||||||
|
header_layout.addRow(QLabel("Language:"), self.language_edit)
|
||||||
|
self.plural_edit = QLineEdit()
|
||||||
|
self.plural_edit.setMaximumWidth(320)
|
||||||
|
header_layout.addRow(QLabel("Plural-Forms:"), self.plural_edit)
|
||||||
|
self.content_type_edit = QLineEdit("text/plain; charset=UTF-8")
|
||||||
|
self.content_type_edit.setMaximumWidth(320)
|
||||||
|
header_layout.addRow(QLabel("Content-Type:"), self.content_type_edit)
|
||||||
|
tabs.addTab(header_tab, "Header")
|
||||||
|
|
||||||
|
# Strings Tab
|
||||||
|
strings_tab = QWidget()
|
||||||
|
strings_layout = QVBoxLayout(strings_tab)
|
||||||
|
self.po_table = QTableWidget()
|
||||||
|
self.po_table.setColumnCount(2)
|
||||||
|
self.po_table.setHorizontalHeaderLabels(["msgid", "msgstr"])
|
||||||
|
self.po_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||||
|
self.po_table.verticalHeader().setMinimumWidth(22)
|
||||||
|
self.po_table.verticalHeader().setDefaultAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
|
strings_layout.addWidget(self.po_table)
|
||||||
|
row_btn_layout = QHBoxLayout()
|
||||||
|
add_row_btn = QPushButton("Add Row")
|
||||||
|
remove_row_btn = QPushButton("Remove Row")
|
||||||
|
row_btn_layout.addWidget(add_row_btn)
|
||||||
|
row_btn_layout.addWidget(remove_row_btn)
|
||||||
|
strings_layout.addLayout(row_btn_layout)
|
||||||
|
tabs.addTab(strings_tab, "Strings")
|
||||||
|
|
||||||
|
layout.addWidget(tabs)
|
||||||
|
|
||||||
|
add_row_btn.clicked.connect(self.add_row)
|
||||||
|
remove_row_btn.clicked.connect(self.remove_row)
|
||||||
|
self.add_row_btn = add_row_btn
|
||||||
|
self.remove_row_btn = remove_row_btn
|
||||||
|
|
||||||
|
self.setCentralWidget(central)
|
||||||
|
|
||||||
|
# Connect edits to dirty flag
|
||||||
|
self.language_edit.textChanged.connect(self.set_dirty)
|
||||||
|
self.plural_edit.textChanged.connect(self.set_dirty)
|
||||||
|
self.content_type_edit.textChanged.connect(self.set_dirty)
|
||||||
|
self.po_table.itemChanged.connect(self.set_dirty)
|
||||||
|
|
||||||
|
def set_dirty(self):
|
||||||
|
self.dirty = True
|
||||||
|
self.update_save_action()
|
||||||
|
|
||||||
|
def init_menu(self):
|
||||||
|
menubar = self.menuBar()
|
||||||
|
file_menu = menubar.addMenu("File")
|
||||||
|
|
||||||
|
new_action = QAction("New", self)
|
||||||
|
open_action = QAction("Open", self)
|
||||||
|
save_action = QAction("Save", self)
|
||||||
|
save_as_action = QAction("Save As", self)
|
||||||
|
|
||||||
|
new_action.triggered.connect(lambda: self.handle_file_action("new"))
|
||||||
|
open_action.triggered.connect(lambda: self.handle_file_action("open"))
|
||||||
|
save_action.triggered.connect(self.handle_save)
|
||||||
|
save_as_action.triggered.connect(self.handle_save_as)
|
||||||
|
|
||||||
|
file_menu.addAction(new_action)
|
||||||
|
file_menu.addAction(open_action)
|
||||||
|
file_menu.addAction(save_action)
|
||||||
|
file_menu.addAction(save_as_action)
|
||||||
|
|
||||||
|
self.save_action = save_action # Store reference for enabling/disabling
|
||||||
|
self.update_save_action()
|
||||||
|
|
||||||
|
def update_save_action(self):
|
||||||
|
self.save_action.setEnabled(self.current_file is not None)
|
||||||
|
|
||||||
|
def handle_file_action(self, action):
|
||||||
|
if self.dirty:
|
||||||
|
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before proceeding?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
|
||||||
|
if reply == QMessageBox.Cancel:
|
||||||
|
return
|
||||||
|
elif reply == QMessageBox.Yes:
|
||||||
|
self.handle_save()
|
||||||
|
if action == "new":
|
||||||
|
self.new_file()
|
||||||
|
elif action == "open":
|
||||||
|
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
|
||||||
|
file_path, _ = QFileDialog.getOpenFileName(self, "Open Language File", default_dir, "PO Files (*.po)")
|
||||||
|
if file_path:
|
||||||
|
self.open_file(file_path)
|
||||||
|
|
||||||
|
def new_file(self):
|
||||||
|
self.current_file = None
|
||||||
|
self.dirty = False
|
||||||
|
self.language_edit.setText("")
|
||||||
|
self.plural_edit.setText("")
|
||||||
|
self.po_table.setRowCount(0)
|
||||||
|
self.update_save_action()
|
||||||
|
|
||||||
|
def open_file(self, file_path):
|
||||||
|
self.current_file = file_path
|
||||||
|
self.dirty = False
|
||||||
|
self.update_save_action()
|
||||||
|
self.load_po_file(file_path)
|
||||||
|
|
||||||
|
def load_po_file(self, file_path):
|
||||||
|
po = polib.pofile(file_path)
|
||||||
|
language = po.metadata.get('Language', '')
|
||||||
|
plural = po.metadata.get('Plural-Forms', '')
|
||||||
|
content_type = po.metadata.get('Content-Type', 'text/plain; charset=UTF-8')
|
||||||
|
self.language_edit.setText(language)
|
||||||
|
self.plural_edit.setText(plural)
|
||||||
|
self.content_type_edit.setText(content_type)
|
||||||
|
self.po_table.setRowCount(len(po))
|
||||||
|
for row, entry in enumerate(po):
|
||||||
|
self.po_table.setItem(row, 0, QTableWidgetItem(entry.msgid))
|
||||||
|
self.po_table.setItem(row, 1, QTableWidgetItem(entry.msgstr))
|
||||||
|
|
||||||
|
def save_file(self, file_path):
|
||||||
|
po = polib.POFile()
|
||||||
|
po.metadata = {
|
||||||
|
'Language': self.language_edit.text(),
|
||||||
|
'Content-Type': self.content_type_edit.text(),
|
||||||
|
'Plural-Forms': self.plural_edit.text(),
|
||||||
|
}
|
||||||
|
for row in range(self.po_table.rowCount()):
|
||||||
|
msgid_item = self.po_table.item(row, 0)
|
||||||
|
msgstr_item = self.po_table.item(row, 1)
|
||||||
|
msgid = msgid_item.text() if msgid_item else ''
|
||||||
|
msgstr = msgstr_item.text() if msgstr_item else ''
|
||||||
|
if msgid or msgstr:
|
||||||
|
entry = polib.POEntry(msgid=msgid, msgstr=msgstr)
|
||||||
|
po.append(entry)
|
||||||
|
po.save(file_path)
|
||||||
|
self.dirty = False
|
||||||
|
self.update_save_action()
|
||||||
|
|
||||||
|
def handle_save(self):
|
||||||
|
if self.current_file:
|
||||||
|
self.save_file(self.current_file)
|
||||||
|
else:
|
||||||
|
self.handle_save_as()
|
||||||
|
|
||||||
|
def handle_save_as(self):
|
||||||
|
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
|
||||||
|
file_path, _ = QFileDialog.getSaveFileName(self, "Save Language File As", default_dir, "PO Files (*.po)")
|
||||||
|
if file_path:
|
||||||
|
self.current_file = file_path
|
||||||
|
self.update_save_action()
|
||||||
|
self.save_file(file_path)
|
||||||
|
|
||||||
|
def add_row(self):
|
||||||
|
row = self.po_table.rowCount()
|
||||||
|
self.po_table.insertRow(row)
|
||||||
|
self.po_table.setItem(row, 0, QTableWidgetItem(""))
|
||||||
|
self.po_table.setItem(row, 1, QTableWidgetItem(""))
|
||||||
|
self.po_table.setCurrentCell(row, 0)
|
||||||
|
self.set_dirty()
|
||||||
|
|
||||||
|
def remove_row(self):
|
||||||
|
row = self.po_table.currentRow()
|
||||||
|
if row >= 0:
|
||||||
|
self.po_table.removeRow(row)
|
||||||
|
self.set_dirty()
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
if self.dirty:
|
||||||
|
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before exiting?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
|
||||||
|
if reply == QMessageBox.Cancel:
|
||||||
|
event.ignore()
|
||||||
|
return
|
||||||
|
elif reply == QMessageBox.Yes:
|
||||||
|
self.handle_save()
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = LangToolWindow()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
55
tools/editortool/map/camera.py
Normal file
55
tools/editortool/map/camera.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import math
|
||||||
|
import time
|
||||||
|
from OpenGL.GL import *
|
||||||
|
from OpenGL.GLU import *
|
||||||
|
from dusk.defs import TILE_WIDTH, TILE_HEIGHT, TILE_DEPTH, RPG_CAMERA_PIXELS_PER_UNIT, RPG_CAMERA_Z_OFFSET, RPG_CAMERA_FOV
|
||||||
|
|
||||||
|
class Camera:
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.pixelsPerUnit = RPG_CAMERA_PIXELS_PER_UNIT
|
||||||
|
self.yOffset = RPG_CAMERA_Z_OFFSET
|
||||||
|
self.fov = RPG_CAMERA_FOV
|
||||||
|
self.scale = 8.0
|
||||||
|
self.lastTime = time.time()
|
||||||
|
self.lookAtTarget = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
def setup(self, vw, vh, duration=0.1):
|
||||||
|
now = time.time()
|
||||||
|
delta = now - self.lastTime
|
||||||
|
self.lastTime = now
|
||||||
|
# Calculate ease factor for exponential smoothing over 'duration' seconds
|
||||||
|
ease = 1 - math.exp(-delta / duration)
|
||||||
|
|
||||||
|
z = (vh / 2.0) / (
|
||||||
|
(self.pixelsPerUnit * self.scale) * math.tan(math.radians(self.fov / 2.0))
|
||||||
|
)
|
||||||
|
lookAt = [
|
||||||
|
self.parent.map.position[0] * TILE_WIDTH,
|
||||||
|
self.parent.map.position[1] * TILE_HEIGHT,
|
||||||
|
self.parent.map.position[2] * TILE_DEPTH,
|
||||||
|
]
|
||||||
|
aspectRatio = vw / vh
|
||||||
|
|
||||||
|
# Ease the lookAt target
|
||||||
|
for i in range(3):
|
||||||
|
self.lookAtTarget[i] += (lookAt[i] - self.lookAtTarget[i]) * ease
|
||||||
|
|
||||||
|
# Camera position is now based on the eased lookAtTarget
|
||||||
|
cameraPosition = (
|
||||||
|
self.lookAtTarget[0],
|
||||||
|
self.lookAtTarget[1] + self.yOffset,
|
||||||
|
self.lookAtTarget[2] + z
|
||||||
|
)
|
||||||
|
|
||||||
|
glMatrixMode(GL_PROJECTION)
|
||||||
|
glLoadIdentity()
|
||||||
|
gluPerspective(self.fov, aspectRatio, 0.1, 1000.0)
|
||||||
|
glScalef(1.0, -1.0, 1.0) # Flip the projection matrix upside down
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
|
glLoadIdentity()
|
||||||
|
gluLookAt(
|
||||||
|
cameraPosition[0], cameraPosition[1], cameraPosition[2],
|
||||||
|
self.lookAtTarget[0], self.lookAtTarget[1], self.lookAtTarget[2],
|
||||||
|
0.0, 1.0, 0.0
|
||||||
|
)
|
||||||
80
tools/editortool/map/chunkpanel.py
Normal file
80
tools/editortool/map/chunkpanel.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QGridLayout, QTreeWidget, QTreeWidgetItem, QComboBox
|
||||||
|
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, TILE_SHAPES
|
||||||
|
|
||||||
|
class ChunkPanel(QWidget):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
# Tile shape dropdown
|
||||||
|
self.tileShapeDropdown = QComboBox()
|
||||||
|
self.tileShapeDropdown.addItems(TILE_SHAPES.keys())
|
||||||
|
self.tileShapeDropdown.setToolTip("Tile Shape")
|
||||||
|
layout.addWidget(self.tileShapeDropdown)
|
||||||
|
|
||||||
|
# Add expandable tree list
|
||||||
|
self.tree = QTreeWidget()
|
||||||
|
self.tree.setHeaderLabel("Chunks")
|
||||||
|
self.tree.expandAll() # Expand by default, remove if you want collapsed
|
||||||
|
layout.addWidget(self.tree) # Removed invalid stretch factor
|
||||||
|
|
||||||
|
# Add stretch so tree expands
|
||||||
|
layout.setStretchFactor(self.tree, 1)
|
||||||
|
|
||||||
|
# Event subscriptions
|
||||||
|
self.parent.map.onMapData.sub(self.onMapData)
|
||||||
|
self.parent.map.onPositionChange.sub(self.onPositionChange)
|
||||||
|
self.tileShapeDropdown.currentTextChanged.connect(self.onTileShapeChanged)
|
||||||
|
|
||||||
|
# For each chunk
|
||||||
|
for chunk in self.parent.map.chunks.values():
|
||||||
|
# Create tree element
|
||||||
|
item = QTreeWidgetItem(self.tree, ["Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z)])
|
||||||
|
chunk.chunkPanelTree = item
|
||||||
|
chunk.chunkPanelTree.setExpanded(True)
|
||||||
|
item.setData(0, 0, chunk) # Store chunk reference
|
||||||
|
|
||||||
|
chunk.onChunkData.sub(self.onChunkData)
|
||||||
|
|
||||||
|
def onMapData(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onPositionChange(self, pos):
|
||||||
|
self.updateChunkList()
|
||||||
|
|
||||||
|
tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position)
|
||||||
|
if tile is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
key = "TILE_SHAPE_NULL"
|
||||||
|
for k, v in TILE_SHAPES.items():
|
||||||
|
if v != tile.shape:
|
||||||
|
continue
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
self.tileShapeDropdown.setCurrentText(key)
|
||||||
|
|
||||||
|
def onTileShapeChanged(self, shape_key):
|
||||||
|
tile = self.parent.map.getTileAtWorldPos(*self.parent.map.position)
|
||||||
|
|
||||||
|
if tile is None or shape_key not in TILE_SHAPES:
|
||||||
|
return
|
||||||
|
tile.setShape(TILE_SHAPES[shape_key])
|
||||||
|
|
||||||
|
def updateChunkList(self):
|
||||||
|
# Clear existing items
|
||||||
|
currentChunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position)
|
||||||
|
|
||||||
|
# Example tree items
|
||||||
|
for chunk in self.parent.map.chunks.values():
|
||||||
|
title = "Chunk ({}, {}, {})".format(chunk.x, chunk.y, chunk.z)
|
||||||
|
if chunk == currentChunk:
|
||||||
|
title += " [C]"
|
||||||
|
if chunk.isDirty():
|
||||||
|
title += " *"
|
||||||
|
item = chunk.chunkPanelTree
|
||||||
|
item.setText(0, title)
|
||||||
|
|
||||||
|
def onChunkData(self, chunk):
|
||||||
|
self.updateChunkList()
|
||||||
154
tools/editortool/map/entitypanel.py
Normal file
154
tools/editortool/map/entitypanel.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QComboBox, QHBoxLayout, QPushButton, QLineEdit, QListWidget, QListWidgetItem
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from dusk.entity import Entity
|
||||||
|
from dusk.defs import CHUNK_WIDTH, CHUNK_HEIGHT, CHUNK_DEPTH, ENTITY_TYPES, ENTITY_TYPE_NULL
|
||||||
|
|
||||||
|
class EntityPanel(QWidget):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Top panel placeholder
|
||||||
|
topWidget = QLabel("Entity Editor")
|
||||||
|
layout.addWidget(topWidget)
|
||||||
|
|
||||||
|
# Name input
|
||||||
|
nameLayout = QHBoxLayout()
|
||||||
|
nameLabel = QLabel("Name:")
|
||||||
|
self.nameInput = QLineEdit()
|
||||||
|
nameLayout.addWidget(nameLabel)
|
||||||
|
nameLayout.addWidget(self.nameInput)
|
||||||
|
layout.addLayout(nameLayout)
|
||||||
|
|
||||||
|
# Entity Type dropdown (single selection)
|
||||||
|
typeLayout = QHBoxLayout()
|
||||||
|
typeLabel = QLabel("Type:")
|
||||||
|
self.typeDropdown = QComboBox()
|
||||||
|
self.typeDropdown.addItems(ENTITY_TYPES)
|
||||||
|
typeLayout.addWidget(typeLabel)
|
||||||
|
typeLayout.addWidget(self.typeDropdown)
|
||||||
|
layout.addLayout(typeLayout)
|
||||||
|
|
||||||
|
# Entity list and buttons
|
||||||
|
self.entityList = QListWidget()
|
||||||
|
self.entityList.addItems([])
|
||||||
|
layout.addWidget(self.entityList, stretch=1)
|
||||||
|
|
||||||
|
btnLayout = QHBoxLayout()
|
||||||
|
self.btnAdd = QPushButton("Add")
|
||||||
|
self.btnRemove = QPushButton("Remove")
|
||||||
|
btnLayout.addWidget(self.btnAdd)
|
||||||
|
btnLayout.addWidget(self.btnRemove)
|
||||||
|
layout.addLayout(btnLayout)
|
||||||
|
|
||||||
|
# Events
|
||||||
|
self.btnAdd.clicked.connect(self.onAddEntity)
|
||||||
|
self.btnRemove.clicked.connect(self.onRemoveEntity)
|
||||||
|
self.parent.map.onEntityData.sub(self.onEntityData)
|
||||||
|
self.parent.map.onPositionChange.sub(self.onPositionChange)
|
||||||
|
self.entityList.itemClicked.connect(self.onEntityClicked)
|
||||||
|
self.entityList.itemDoubleClicked.connect(self.onEntityDoubleClicked)
|
||||||
|
self.typeDropdown.currentIndexChanged.connect(self.onTypeSelected)
|
||||||
|
self.nameInput.textChanged.connect(self.onNameChanged)
|
||||||
|
|
||||||
|
# Call once to populate
|
||||||
|
self.onEntityData()
|
||||||
|
self.onEntityUnselect()
|
||||||
|
|
||||||
|
def onEntityUnselect(self):
|
||||||
|
self.entityList.setCurrentItem(None)
|
||||||
|
self.nameInput.setText("")
|
||||||
|
self.typeDropdown.setCurrentIndex(ENTITY_TYPE_NULL)
|
||||||
|
|
||||||
|
def onEntitySelect(self, entity):
|
||||||
|
self.entityList.setCurrentItem(entity.item)
|
||||||
|
self.nameInput.setText(entity.name)
|
||||||
|
self.typeDropdown.setCurrentIndex(entity.type)
|
||||||
|
|
||||||
|
def onEntityDoubleClicked(self, item):
|
||||||
|
entity = item.data(Qt.UserRole)
|
||||||
|
chunk = entity.chunk
|
||||||
|
worldX = (chunk.x * CHUNK_WIDTH) + entity.localX
|
||||||
|
worldY = (chunk.y * CHUNK_HEIGHT) + entity.localY
|
||||||
|
worldZ = (chunk.z * CHUNK_DEPTH) + entity.localZ
|
||||||
|
self.parent.map.moveTo(worldX, worldY, worldZ)
|
||||||
|
|
||||||
|
def onEntityClicked(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onAddEntity(self):
|
||||||
|
chunk = self.parent.map.getChunkAtWorldPos(*self.parent.map.position)
|
||||||
|
if chunk is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
localX = (self.parent.map.position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH
|
||||||
|
localY = (self.parent.map.position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT
|
||||||
|
localZ = (self.parent.map.position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH
|
||||||
|
|
||||||
|
# Make sure there's not already an entity here
|
||||||
|
for ent in chunk.entities.values():
|
||||||
|
if ent.localX == localX and ent.localY == localY and ent.localZ == localZ:
|
||||||
|
print("Entity already exists at this location")
|
||||||
|
return
|
||||||
|
|
||||||
|
ent = chunk.addEntity(localX, localY, localZ)
|
||||||
|
|
||||||
|
def onRemoveEntity(self):
|
||||||
|
item = self.entityList.currentItem()
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
entity = item.data(Qt.UserRole)
|
||||||
|
if entity:
|
||||||
|
chunk = entity.chunk
|
||||||
|
chunk.removeEntity(entity)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onEntityData(self):
|
||||||
|
self.onEntityUnselect()
|
||||||
|
self.entityList.clear()
|
||||||
|
for chunk in self.parent.map.chunks.values():
|
||||||
|
for id, entity in chunk.entities.items():
|
||||||
|
item = QListWidgetItem(entity.name)
|
||||||
|
item.setData(Qt.UserRole, entity) # Store the entity object
|
||||||
|
entity.item = item
|
||||||
|
self.entityList.addItem(item)
|
||||||
|
|
||||||
|
# Select if there is something at current position
|
||||||
|
self.onPositionChange(self.parent.map.position)
|
||||||
|
|
||||||
|
def onPositionChange(self, position):
|
||||||
|
self.onEntityUnselect()
|
||||||
|
|
||||||
|
# Get Entity at..
|
||||||
|
chunk = self.parent.map.getChunkAtWorldPos(*position)
|
||||||
|
if chunk is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
localX = (position[0] - (chunk.x * CHUNK_WIDTH)) % CHUNK_WIDTH
|
||||||
|
localY = (position[1] - (chunk.y * CHUNK_HEIGHT)) % CHUNK_HEIGHT
|
||||||
|
localZ = (position[2] - (chunk.z * CHUNK_DEPTH)) % CHUNK_DEPTH
|
||||||
|
|
||||||
|
for ent in chunk.entities.values():
|
||||||
|
if ent.localX != localX or ent.localY != localY or ent.localZ != localZ:
|
||||||
|
continue
|
||||||
|
self.onEntitySelect(ent)
|
||||||
|
self.entityList.setCurrentItem(ent.item)
|
||||||
|
break
|
||||||
|
|
||||||
|
def onTypeSelected(self, index):
|
||||||
|
item = self.entityList.currentItem()
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
entity = item.data(Qt.UserRole)
|
||||||
|
if entity:
|
||||||
|
entity.setType(index)
|
||||||
|
|
||||||
|
def onNameChanged(self, text):
|
||||||
|
item = self.entityList.currentItem()
|
||||||
|
if item is None:
|
||||||
|
return
|
||||||
|
entity = item.data(Qt.UserRole)
|
||||||
|
if entity:
|
||||||
|
entity.setName(text)
|
||||||
41
tools/editortool/map/glwidget.py
Normal file
41
tools/editortool/map/glwidget.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from PyQt5.QtCore import QTimer
|
||||||
|
from PyQt5.QtWidgets import QOpenGLWidget
|
||||||
|
from OpenGL.GL import *
|
||||||
|
from OpenGL.GLU import *
|
||||||
|
|
||||||
|
class GLWidget(QOpenGLWidget):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.parent = parent
|
||||||
|
self.timer = QTimer(self)
|
||||||
|
self.timer.timeout.connect(self.update)
|
||||||
|
self.timer.start(16) # ~60 FPS
|
||||||
|
|
||||||
|
def initializeGL(self):
|
||||||
|
glClearColor(0.392, 0.584, 0.929, 1.0)
|
||||||
|
glEnable(GL_DEPTH_TEST)
|
||||||
|
glEnable(GL_BLEND)
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||||
|
glEnable(GL_POLYGON_OFFSET_FILL)
|
||||||
|
glPolygonOffset(1.0, 1.0)
|
||||||
|
glDisable(GL_POLYGON_OFFSET_FILL)
|
||||||
|
|
||||||
|
def resizeGL(self, w, h):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def paintGL(self):
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||||||
|
glLoadIdentity()
|
||||||
|
|
||||||
|
w = self.width()
|
||||||
|
h = self.height()
|
||||||
|
if h <= 0:
|
||||||
|
h = 1
|
||||||
|
if w <= 0:
|
||||||
|
w = 1
|
||||||
|
|
||||||
|
glViewport(0, 0, w, h)
|
||||||
|
self.parent.camera.setup(w, h)
|
||||||
|
self.parent.grid.draw()
|
||||||
|
self.parent.map.draw()
|
||||||
|
self.parent.selectBox.draw()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user