72 Commits
main ... reset

Author SHA1 Message Date
57995a0e00 Remove train station. 2024-11-24 11:18:08 -06:00
53dc496f2f Dawn progress 2024-11-07 18:28:54 -06:00
6a0ffd4a45 about to start work on downtown 2024-10-25 23:02:55 -05:00
ef84c85527 Cleaned a bit of the tile code. Built train station 2024-10-25 10:30:49 -05:00
39d74cc375 Map loading fixes and improvements 2024-10-22 08:00:48 -05:00
ddbef64f43 Started work on using tiled 2024-10-21 20:50:16 -05:00
b2a3ca4411 Example sign with conversation. 2024-10-20 15:31:04 -07:00
a74f285cb2 Memory functions 2024-10-17 09:30:36 -07:00
5fb36aad35 Moved tile interactions over. 2024-10-17 08:44:17 -07:00
258976b76c Made NPCs interactable with the conversation system. 2024-10-17 07:36:33 -07:00
7867076bbe Added language support to main menu and sign 2024-10-17 07:17:36 -07:00
3baafec9cb Adding language 2024-10-16 14:37:34 -07:00
6c062df9bb Added main menu. 2024-10-09 09:47:05 -05:00
825cc88e17 Moving some logic into separate files 2024-10-08 09:36:15 -05:00
9f4cb283c1 UI Menu more or less done perfectly. 2024-10-08 09:17:00 -05:00
86feb7e56a Mid menu coding 2024-10-07 09:49:22 -05:00
a7dc7fdcf8 Remove testmap2 2024-10-07 00:29:59 -05:00
cb3a58e456 Door and map changing 2024-10-07 00:27:20 -05:00
5334f0944e Add direction to the door 2024-10-06 23:21:28 -05:00
a1d4b0a1d7 Door 2024-10-06 23:11:45 -05:00
ecb3b9c5d1 Fixed JSON parsing throwing errors 2024-10-06 22:46:16 -05:00
bf3912bb7f Added sign 2024-10-06 22:23:26 -05:00
b5d7b7e229 Bit more cleanup 2024-10-06 21:01:05 -05:00
b7987401af Map loading basically done for now 2024-10-06 19:03:22 -05:00
ffc46c677c Added triggers to parsing. 2024-10-06 18:57:28 -05:00
7a8ca2fca1 Test loading asset data. 2024-10-06 18:52:52 -05:00
5751f7c83c Add asset loading support. 2024-10-06 18:27:54 -05:00
c69d0ec1cc Adding triggers, prepping for map loading. 2024-10-06 18:03:46 -05:00
e14445680f Add newlines to draw text 2024-10-06 17:30:04 -05:00
5256b32055 Textbox rendering back. 2024-10-06 17:16:24 -05:00
5444b0b8c7 Ensure linux terminal is still working 2024-10-06 16:43:54 -05:00
45b3cf9478 Cleaned old frame code. 2024-10-06 16:34:47 -05:00
0224ddd36b Getting around to cleaning drawing code finally. 2024-10-06 16:28:34 -05:00
40b0395552 idk what I coded 2024-10-06 01:30:45 -05:00
b7829fda5c Add tile interactions. 2024-10-05 10:29:15 -05:00
bdef59bbe1 Make map rendering centered 2024-10-05 10:17:12 -05:00
27ce6526e6 Vastly improve UI 2024-10-05 09:16:41 -05:00
704002e671 Moved TERM files around 2024-10-05 08:35:36 -05:00
cfe4cf6cad Testing mostly 2024-10-04 22:43:33 -05:00
69672cf8a6 Rendering working 2024-10-04 21:51:38 -05:00
c1345218a3 glfw 2024-10-04 16:41:01 -05:00
be27408cf4 Adding back GLFW 2024-10-04 09:37:32 -05:00
4205b919d4 Adding back GLFW 2024-10-04 09:37:24 -05:00
e14083b4b4 t 2024-10-03 22:35:22 -05:00
ad317da97e Reset 2024-10-03 20:09:10 -05:00
2ff1a159bc idk 2024-10-03 20:07:35 -05:00
c770953b2e VN Dummy 2024-09-29 00:47:12 -05:00
2cb1d745b2 Add JSON parser 2024-09-27 21:55:53 -05:00
8ce0e8f9f6 Changed more deps to use FetchContent 2024-09-25 15:11:04 -04:00
5101a856be Remove glm 2024-09-25 15:01:26 -04:00
303d0c0a6f Add camera pixel perfect. 2024-09-16 21:35:04 -05:00
e3a4368d1e Basic tile code ready. 2024-09-16 07:07:01 -05:00
7c34127900 Some more chunk work. 2024-09-16 06:50:47 -05:00
49d90b3362 Working on chunks 2024-09-15 08:41:44 -05:00
935398d45e Minigolf is pog 2024-09-15 08:06:45 -05:00
4065556d4a Switched map loops to weakptrs 2024-09-15 07:50:11 -05:00
fb8f6e3f95 Commit prog 2024-09-14 10:23:31 -05:00
b4c2ce16a0 idk if I like this structure. 2024-09-13 09:26:21 -05:00
b309478922 Trying to tune turning. 2024-09-11 10:07:23 -05:00
ad3974bde5 Testing walking. 2024-09-11 10:04:54 -05:00
e5349cc093 Created basic movement. 2024-09-11 09:21:56 -05:00
01c56477aa Basic movement example 2024-09-11 08:21:26 -05:00
916396e175 Fixed input 2024-09-10 20:17:32 -05:00
0e5b85633c Worked a bit on prefabs of all things 2024-09-10 18:26:14 -05:00
e5f3f69120 Restored UI Label support 2024-09-10 17:36:04 -05:00
ca240bc180 Moved poker code to main dawn code. 2024-09-10 08:46:57 -05:00
a3a891ddb2 Cleanup of poker code done, about to start testing. 2024-09-10 06:51:04 -05:00
5fae94c722 More refactoring 2024-09-10 00:07:15 -05:00
d5b3b6d619 Cleaning poker code pt 1. 2024-09-08 23:12:03 -05:00
b4e261d954 Move over old poker code 2024-09-08 21:54:19 -05:00
856bc306fe More cleanup complete 2024-09-08 15:30:41 -05:00
cffe7f73a2 Cleanup complete 2024-09-08 15:30:32 -05:00
181 changed files with 16404 additions and 45 deletions

View File

@ -0,0 +1,56 @@
name: build-helloworld-vita
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
env:
VITASDK: /usr/local/vitasdk
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache VITASDK
id: cache-vitasdk-restore
uses: actions/cache/restore@v3
with:
path: /usr/local/vitasdk
key: ${{ runner.os }}-vitasdk
- name: Install Vita Toolchain
if: steps.cache-vitasdk.outputs.cache-hit != 'true'
run: ./ci/install-vita-toolchain.sh
- name: Save VITASDK
id: cache-vitasdk-save
uses: actions/cache/save@v3
with:
path: /usr/local/vitasdk
key: ${{ steps.cache-vitasdk-restore.outputs.cache-primary-key }}
- name: Install Libraries
run: ./ci/install-libraries.sh
- name: Build Tools
run: ./ci/build-tools.sh
- name: Build Game
run: |
export PATH=$VITASDK/bin:$PATH
mkdir build
cd build
cmake .. -DDAWN_BUILD_TARGET=target-helloworld-vita -DCMAKE_BUILD_TYPE=Debug
make
- name: Deploying
env:
DAWN_SSH_KEY: ${{ secrets.DAWN_SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo -e "${DAWN_SSH_KEY}" > ~/.ssh/id_rsa
chmod og-rwx ~/.ssh/id_rsa
ssh-keyscan -H wish.moe >> ~/.ssh/known_hosts
ssh -t yourwishes@wish.moe "mkdir -p /home/yourwishes/Dawn/vita/debug"
scp ./build/src/dawnvita/HelloWorld.vpk yourwishes@wish.moe:/home/yourwishes/Dawn/vita/debug/

View File

@ -0,0 +1,26 @@
name: build-liminal-glfw-linux64
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Toolchain
run: ./ci/install-linux-toolchain.sh
- name: Install Libraries
run: ./ci/install-libraries.sh
- name: Build Tools
run: ./ci/build-tools.sh
- name: Build Game
run: |
mkdir build
cd build
cmake .. -DDAWN_BUILD_TARGET=target-liminial-linux64-glfw
make

2
.gitignore vendored
View File

@ -65,7 +65,7 @@ CTestTestfile.cmake
_deps
# Custom
build
/build/*
.vscode
assets/testworld/tileset.png

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/font8x8"]
path = lib/font8x8
url = https://github.com/dhepper/font8x8

View File

@ -1,4 +1,4 @@
# Copyright (c) 2022 Dominic Masters
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
@ -6,11 +6,37 @@
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(dawn STATIC
src/funcs.c
# Variable Caches
set(DAWN_CACHE_TARGET "dawn-target")
set(DAWN_TARGET_NAME "Dawn")
if(NOT DEFINED DAWN_TARGET)
set(DAWN_TARGET linux-x64-glfw CACHE INTERNAL ${DAWN_CACHE_TARGET})
# set(DAWN_TARGET linux-x64-terminal CACHE INTERNAL ${DAWN_CACHE_TARGET})
endif()
# Set Common Build Variables
set(DAWN_ROOT_DIR "${CMAKE_SOURCE_DIR}")
set(DAWN_BUILD_DIR "${CMAKE_BINARY_DIR}")
set(DAWN_SOURCES_DIR "${DAWN_ROOT_DIR}/src")
set(DAWN_TOOLS_DIR "${DAWN_ROOT_DIR}/tools")
set(DAWN_ASSETS_SOURCE_DIR "${DAWN_ROOT_DIR}/assets")
set(DAWN_ASSETS_BUILD_DIR "${DAWN_BUILD_DIR}/assets")
set(DAWN_GENERATED_DIR "${DAWN_BUILD_DIR}/generated")
set(DAWN_TEMP_DIR "${DAWN_BUILD_DIR}/temp")
# Initialize Project.
project(Dawn
VERSION 1.0.0
LANGUAGES C
)
target_include_directories(dawn PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
# Add tools
add_subdirectory(tools)
# Add Libraries
add_subdirectory(lib)
# Add Project Files
add_subdirectory(src)

View File

@ -1,3 +0,0 @@
# Dawn Project
Simple in code, complex in structure game engine for creating small, fast and
reusable games.

42
assets/en.json Normal file
View File

@ -0,0 +1,42 @@
{
"main_menu": {
"new_game": "New Game",
"load_game": "Load Game",
"options": "Options",
"exit": "Exit"
},
"tiles": {
"water": {
"interact": "A refreshing body of water."
},
"lamp": {
"interact": "An electric lamp.\nA real lightbulb idea."
},
"rail": {
"interact": "Train tracks.\n...Better not cross them."
}
},
"entities": {
"sign": {
"name": "Sign"
}
},
"maps": {
"testmap": {
"bob": "Hello, I am Bob.",
"sign": "This is a sign.",
"sign2": {
"1": "This is another sign.",
"2": "It has two lines."
}
},
"train_station": {
"stair_sign": {
"0": "Stairs slippery when wet.\n\n<- West to Town.\n-> East to lakefront."
}
}
},
"battle": {
"start": "Battle Start!"
}
}

46
assets/maps/testmap.tmx Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="8" tileheight="8" infinite="0" nextlayerid="3" nextobjectid="7">
<tileset firstgid="1" source="../tilemaps/tilemap.tsx"/>
<tileset firstgid="65" source="../tilemaps/entities.tsx"/>
<layer id="1" name="Map" width="30" height="20" locked="1">
<data encoding="csv">
1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,6,6,6,6,6,2,2,2,2,2,2,3,3,3,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,6,6,6,6,6,2,2,2,2,2,3,3,3,3,2,2,2,2,
1,1,1,1,1,2,2,2,2,2,2,2,5,5,5,5,5,2,2,2,2,2,3,3,3,2,2,2,2,2,
1,1,1,1,1,2,2,2,2,2,2,2,5,5,5,5,5,2,2,2,2,2,3,3,3,2,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,5,5,1,5,5,2,2,2,2,3,3,3,3,2,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,2,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,
1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,3,3,3,3,2,2,2,2,2,2,2,2,2,2,2,
1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,3,3,3,2,2,2,2,2,2,2,2,2,2,2,2
</data>
</layer>
<objectgroup id="2" name="Entities">
<object id="4" name="Player" type="player" gid="66" x="87.7275" y="119.497" width="8" height="8"/>
<object id="5" name="Sign" type="sign" gid="68" x="86.8498" y="55.0415" width="8" height="8">
<properties>
<property name="texts_0" value="maps.testmap.sign2.1"/>
<property name="texts_1" value="maps.testmap.sign2.2"/>
</properties>
</object>
<object id="6" name="Door" type="door" gid="69" x="111.791" y="55.7608" width="8" height="8">
<properties>
<property name="direction" type="int" value="2"/>
<property name="map" value="testmap2.map"/>
<property name="x" type="int" value="11"/>
<property name="y" type="int" value="2"/>
</properties>
</object>
</objectgroup>
</map>

17
assets/maps/testmap2.tmx Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="5" height="5" tilewidth="8" tileheight="8" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" source="../tilemaps/tilemap.tsx"/>
<tileset firstgid="67" source="../tilemaps/entities.tsx"/>
<layer id="1" name="Tile Layer 1" width="5" height="5">
<data encoding="csv">
2,2,2,2,2,
2,2,2,3,2,
2,2,3,3,2,
2,2,3,2,2,
2,3,3,2,2
</data>
</layer>
<objectgroup id="2" name="Entities">
<object id="4" name="Player" type="player" gid="68" x="6.54983" y="15.3437" width="8" height="8"/>
</objectgroup>
</map>

View File

@ -0,0 +1,14 @@
{
"automappingRulesFile": "",
"commands": [
],
"compatibilityVersion": 1100,
"extensionsPath": "extensions",
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
]
}

View File

@ -0,0 +1,114 @@
{
"Map/SizeTest": {
"height": 4300,
"width": 2
},
"activeFile": "maps/testmap.tmx",
"expandedProjectPaths": [
".",
"maps"
],
"file.lastUsedOpenFilter": "All Files (*)",
"fileStates": {
"": {
"scaleInDock": 1
},
"entities.tsx": {
"scaleInDock": 1,
"scaleInEditor": 1
},
"maps/downtown.tmx": {
"scale": 4.9072,
"selectedLayer": 0,
"viewCenter": {
"x": 39.94131072709489,
"y": 40.0432018258885
}
},
"maps/testmap.tmx": {
"expandedObjectLayers": [
2
],
"scale": 3.404166666666667,
"selectedLayer": 1,
"viewCenter": {
"x": 95.17747858017135,
"y": 86.80538555691552
}
},
"maps/testmap2.tmx": {
"expandedObjectLayers": [
2
],
"scale": 5.0383,
"selectedLayer": 1,
"viewCenter": {
"x": 19.84796459123116,
"y": 19.947204414187325
}
},
"maps/train_station.tmx": {
"expandedObjectLayers": [
2
],
"scale": 2.7603,
"selectedLayer": 1,
"viewCenter": {
"x": 160.48980183313407,
"y": 120.4579212404449
}
},
"testmap.tmx": {
"scale": 3.23,
"selectedLayer": 1,
"viewCenter": {
"x": 122.60061919504646,
"y": 56.965944272445824
}
},
"testmap2.tmx": {
"scale": 3.281458333333333,
"selectedLayer": 0,
"viewCenter": {
"x": 119.91619579709226,
"y": 80.14729223541363
}
},
"tilemap.tsx": {
"scaleInDock": 1,
"scaleInEditor": 4
},
"tilemaps/entities.tsx": {
"scaleInDock": 2,
"scaleInEditor": 9.7785
},
"tilemaps/tilemap.tsx": {
"scaleInDock": 2
}
},
"last.externalTilesetPath": "/home/yourwishes/htdocs/Dawn/assets",
"last.imagePath": "/home/yourwishes/htdocs/Dawn/assets",
"map.height": 10,
"map.lastUsedFormat": "tmx",
"map.tileHeight": 8,
"map.tileWidth": 8,
"map.width": 10,
"openFiles": [
"maps/testmap.tmx"
],
"project": "tiled_project.tiled-project",
"property.type": "string",
"recentFiles": [
"maps/testmap.tmx",
"maps/downtown.tmx",
"maps/testmap2.tmx",
"maps/train_station.tmx",
"tilemaps/entities.tsx"
],
"tileset.lastUsedFilter": "All Files (*)",
"tileset.lastUsedFormat": "tsx",
"tileset.tileSize": {
"height": 8,
"width": 8
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.0" name="entities" tilewidth="8" tileheight="8" tilecount="64" columns="8">
<image source="entities.png" width="64" height="64"/>
<tile id="1" type="player"/>
<tile id="2" type="npc"/>
<tile id="3" type="sign"/>
<tile id="4" type="door"/>
</tileset>

BIN
assets/tilemaps/tilemap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.11.0" name="tilemap" tilewidth="8" tileheight="8" tilecount="64" columns="8">
<image source="tilemap.png" width="64" height="64"/>
</tileset>

234
gentileset.py Normal file
View File

@ -0,0 +1,234 @@
# font is a 2D array of 8x8 pixel characters where each bit is a pixel on the
# row.
font8x8_basic = [
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0000 (nul)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0001
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0002
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0003
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0004
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0005
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0006
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0007
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0008
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0009
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000A
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000B
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000C
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000D
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000E
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+000F
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0010
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0011
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0012
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0013
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0014
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0015
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0016
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0017
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0018
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0019
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001A
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001B
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001C
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001D
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001E
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+001F
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0020 (space)
[0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00], # U+0021 (!)
[0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0022 (")
[0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00], # U+0023 (#)
[0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00], # U+0024 ($)
[0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00], # U+0025 (%)
[0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00], # U+0026 (&)
[0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0027 (')
[0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00], # U+0028 (()
[0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00], # U+0029 ())
[0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00], # U+002A (*)
[0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00], # U+002B (+)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06], # U+002C (,)
[0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00], # U+002D (-)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00], # U+002E (.)
[0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00], # U+002F (/)
[0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00], # U+0030 (0)
[0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00], # U+0031 (1)
[0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00], # U+0032 (2)
[0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00], # U+0033 (3)
[0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00], # U+0034 (4)
[0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00], # U+0035 (5)
[0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00], # U+0036 (6)
[0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00], # U+0037 (7)
[0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00], # U+0038 (8)
[0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00], # U+0039 (9)
[0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00], # U+003A (:)
[0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06], # U+003B (;)
[0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00], # U+003C (<)
[0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00], # U+003D (=)
[0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00], # U+003E (>)
[0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00], # U+003F (?)
[0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00], # U+0040 (@)
[0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00], # U+0041 (A)
[0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00], # U+0042 (B)
[0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00], # U+0043 (C)
[0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00], # U+0044 (D)
[0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00], # U+0045 (E)
[0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00], # U+0046 (F)
[0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00], # U+0047 (G)
[0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00], # U+0048 (H)
[0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], # U+0049 (I)
[0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00], # U+004A (J)
[0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00], # U+004B (K)
[0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00], # U+004C (L)
[0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00], # U+004D (M)
[0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00], # U+004E (N)
[0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00], # U+004F (O)
[0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00], # U+0050 (P)
[0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00], # U+0051 (Q)
[0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00], # U+0052 (R)
[0x1E, 0x33, 0x07, 0x1E, 0x38, 0x33, 0x1E, 0x00], # U+0053 (S)
[0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x00], # U+0054 (T)
[0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00], # U+0055 (U)
[0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], # U+0056 (V)
[0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00], # U+0057 (W)
[0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00], # U+0058 (X)
[0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x0C, 0x00], # U+0059 (Y)
[0x3F, 0x33, 0x19, 0x0C, 0x26, 0x33, 0x3F, 0x00], # U+005A (Z)
[0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], # U+005B ([)
[0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x00], # U+005C (\)
[0x1E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1E, 0x00], # U+005D (])
[0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00], # U+005E (^)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF], # U+005F (_)
[0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00], # U+0060 (`)
[0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00], # U+0061 (a)
[0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00], # U+0062 (b)
[0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00], # U+0063 (c)
[0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00], # U+0064 (d)
[0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00], # U+0065 (e)
[0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00], # U+0066 (f)
[0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1E], # U+0067 (g)
[0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00], # U+0068 (h)
[0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], # U+0069 (i)
[0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E], # U+006A (j)
[0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00], # U+006B (k)
[0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], # U+006C (l)
[0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00], # U+006D (m)
[0x00, 0x00, 0x1B, 0x37, 0x33, 0x33, 0x33, 0x00], # U+006E (n)
[0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00], # U+006F (o)
[0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F], # U+0070 (p)
[0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78], # U+0071 (q)
[0x00, 0x00, 0x1B, 0x36, 0x36, 0x06, 0x0F, 0x00], # U+0072 (r)
[0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00], # U+0073 (s)
[0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00], # U+0074 (t)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00], # U+0075 (u)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], # U+0076 (v)
[0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00], # U+0077 (w)
[0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00], # U+0078 (x)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1E], # U+0079 (y)
[0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00], # U+007A (z)
[0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00], # U+007B ({)
[0x0C, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x0C, 0x00], # U+007C (|)
[0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00], # U+007D (})
[0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], # U+007E (~)
]
# COLOR4F(0.0f, 0.0f, 0.0f, 1.0f), # Black
# COLOR4F(1.0f, 1.0f, 1.0f, 1.0f), # White
# COLOR4F(0.8f, 0.0f, 0.0f, 1.0f), # Red
# COLOR4F(0.0f, 0.5f, 0.0f, 1.0f), # Green
# COLOR4F(0.0f, 0.0f, 0.8f, 1.0f), # Blue
# COLOR4F(0.8f, 0.8f, 0.0f, 1.0f), # Yellow
# COLOR4F(0.8f, 0.0f, 0.8f, 1.0f), # Magenta
# COLOR4F(0.0f, 0.8f, 0.8f, 1.0f), # Cyan
# COLOR4F(0.5f, 0.5f, 0.5f, 1.0f), # Gray
# COLOR4F(0.5f, 0.25f, 0.0f, 1.0f) # Brown
# Tilemap
COLORS = {
"BLACK": [ 0, 0, 0 ],
"WHITE": [ 255, 255, 255 ],
"RED": [ 204, 0, 0 ],
"GREEN": [ 0, 128, 0 ],
"BLUE": [ 0, 0, 204 ],
"YELLOW": [ 204, 204, 0 ],
"MAGENTA": [ 204, 0, 204 ],
"CYAN": [ 0, 204, 204 ],
"GRAY": [ 128, 128, 128 ],
"BROWN": [ 128, 64, 0 ]
}
TILES = {
# NAME: [ id, symbol, color ]
"NULL": [ 0, 'N', COLORS["RED"] ],
"GRASS": [ 1, '#', COLORS["GREEN"] ],
"WATER": [ 2, '\\', COLORS["BLUE"] ],
# "DOOR": [ 3, 'D', COLORS["BROWN"] ],
"BUILDING_WALL": [ 4, '-', COLORS["GRAY"] ],
"ROOF": [ 5, '/', COLORS["BROWN"] ],
"WALKABLE_NULL": [ 6, ' ', COLORS["BLACK"] ],
"COBBLESTONE": [ 7, '#', COLORS["GRAY"] ],
"STAIRS": [ 8, '=', COLORS["GRAY"] ],
"RAILING": [ 9, 'n', COLORS["BROWN"] ],
"COLUMN": [ 10, 'I', COLORS["WHITE"] ],
"RAIL_TRACK": [ 11, '|', COLORS["GRAY"] ],
"RAIL_SLEEPER": [ 12, '-', COLORS["BROWN"] ],
"CARPET": [ 13, '#', COLORS["RED"] ],
"LAMP": [ 14, 'i', COLORS["YELLOW"] ],
}
ENTITIES = {
"NULL": [ 0, 'N', COLORS["RED"] ],
"PLAYER": [ 1, 'v', COLORS["RED"] ],
"NPC": [ 2, 'V', COLORS["RED"] ],
"SIGN": [ 3, '+', COLORS["YELLOW"] ],
"DOOR": [ 4, 'D', COLORS["BROWN"] ],
}
# Create 64 x 64 RGB pixel buffer.
import png
WIDTH = 64
HEIGHT = 64
def genTileset(filename, tileset, background):
# Create 64x64 pixel buffer with black
PIXEL_BUFFER = []
for y in range(HEIGHT):
row = []
for x in range(WIDTH):
row.append(background)
PIXEL_BUFFER.append(row)
# For each TILES
for tile in tileset:
# Get the tile data
tile_data = tileset[tile]
tile_id = tile_data[0]
tile_char = tile_data[1]
tile_color = tile_data[2]
# Get char pixels
char_pixels = font8x8_basic[ord(tile_char)]
tile_color_rgba = [ tile_color[0], tile_color[1], tile_color[2], 255 ]
# Write char pixels to output buffer, using tile_id to determine pixels with
# each row holding 8 characters of 8 pixels
for y in range(8):
row = char_pixels[y]
for x in range(8):
if row & (1 << x):
PIXEL_BUFFER[(tile_id // 8) * 8 + y][(tile_id % 8) * 8 + x] = tile_color_rgba
# Convert from [ [ [ r, g, b ], [ r, g, b] ], [ [ r, g, b ], [ r, g, b ] ] ] to
# [ [R, G, B, A, R, G, B, A] ] (Flatten level 2 and add alpha channel)
FLATTENED_PIXEL_BUFFER = [ [ 0 for i in range(WIDTH * 4) ] for j in range(HEIGHT) ]
for y in range(HEIGHT):
for x in range(WIDTH):
FLATTENED_PIXEL_BUFFER[y][x * 4 + 0] = PIXEL_BUFFER[y][x][0]
FLATTENED_PIXEL_BUFFER[y][x * 4 + 1] = PIXEL_BUFFER[y][x][1]
FLATTENED_PIXEL_BUFFER[y][x * 4 + 2] = PIXEL_BUFFER[y][x][2]
FLATTENED_PIXEL_BUFFER[y][x * 4 + 3] = PIXEL_BUFFER[y][x][3]
# Now write PIXEL_BUFFER to png
png.from_array(FLATTENED_PIXEL_BUFFER, 'RGBA').save(filename)
genTileset("./assets/tilemaps/tilemap.png", TILES, [ 0, 0, 0, 255 ])
genTileset("./assets/tilemaps/entities.png", ENTITIES, [ 0, 0, 0, 100 ])

31
lib/CMakeLists.txt Normal file
View File

@ -0,0 +1,31 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
include(FetchContent)
add_library(font8x8 INTERFACE)
target_include_directories(font8x8 INTERFACE font8x8)
add_subdirectory(glad)
# GLFW
FetchContent_Declare(glfw URL https://github.com/glfw/glfw/releases/download/3.4/glfw-3.4.zip)
FetchContent_MakeAvailable(glfw)
# GLM
FetchContent_Declare(
cglm
GIT_REPOSITORY https://github.com/recp/cglm
GIT_TAG 1796cc5ce298235b615dc7a4750b8c3ba56a05dd
)
FetchContent_MakeAvailable(cglm)
#LibArchive
FetchContent_Declare(
libarchive
GIT_REPOSITORY https://github.com/libarchive/libarchive
GIT_TAG v3.7.6
)
FetchContent_MakeAvailable(libarchive)

1
lib/font8x8 Submodule

Submodule lib/font8x8 added at 8e279d2d86

12
lib/glad/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.11)
project(glad)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_library(${PROJECT_NAME}
src/glad.c
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
)

View File

@ -0,0 +1,290 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef _WIN64
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

4068
lib/glad/include/glad/glad.h Normal file

File diff suppressed because it is too large Load Diff

2522
lib/glad/src/glad.c Normal file

File diff suppressed because it is too large Load Diff

22
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,22 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Build Project
add_executable(${DAWN_TARGET_NAME})
# Add base
add_subdirectory(dawn)
# Compile entries
if(DAWN_TARGET STREQUAL "linux-x64-terminal")
add_subdirectory(dawnlinux)
add_subdirectory(dawntermlinux)
elseif(DAWN_TARGET STREQUAL "linux-x64-glfw")
add_subdirectory(dawnlinux)
add_subdirectory(dawnglfw)
add_subdirectory(dawnopengl)
else()
message(FATAL_ERROR "Unknown target: ${DAWN_TARGET}")
endif()

39
src/dawn/CMakeLists.txt Normal file
View File

@ -0,0 +1,39 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Libraries
target_link_libraries(${DAWN_TARGET_NAME}
PUBLIC
archive_static
)
# Includes
target_include_directories(${DAWN_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
# Subdirs
add_subdirectory(assert)
add_subdirectory(asset)
add_subdirectory(display)
add_subdirectory(game)
add_subdirectory(rpg)
add_subdirectory(ui)
add_subdirectory(locale)
add_subdirectory(util)
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
input.c
)
# Assets
tool_map(testmap maps/testmap.tmx)
tool_map(testmap2 maps/testmap2.tmx)
tool_copy(en en.json)
add_dependencies(${DAWN_TARGET_NAME} dawnassets)

View File

@ -0,0 +1,12 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
assert.c
)

37
src/dawn/assert/assert.c Normal file
View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2022 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert.h"
#include <stdarg.h>
void assertTrueImplement(
const char *file,
const int32_t line,
const char *func,
const bool_t result,
const char *message,
...
) {
if(result) return;
// Print file info.
fprintf(
stderr,
"Assert failed in %s:%i :: %s\n",
file,
line,
func
);
// Print message.
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
fprintf(stderr, "\n");
abort();
}

117
src/dawn/assert/assert.h Normal file
View File

@ -0,0 +1,117 @@
/**
* Copyright (c) 2022 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Asserts that a given statement must evaluate to true or the assertion fails
* and the game will close semi-gracefully.
*
* @param file String filename of the file that has the assertion.
* @param line Integer line number within the file that the assertion is on.
* @param func Called function that has the assertion.
* @param result The statement that must equate to true.
* @param message Message (sprintf format) to send to the stdout.
* @param ... Varargs of the sprintf arguments.
*/
void assertTrueImplement(
const char *file,
const int32_t line,
const char *func,
const bool_t result,
const char *message,
...
);
/**
* Asserts that a statement must be true in order for the assertion to not cause
* an error. Basically this is a throw statement in disguise.
*
* @param statement Statement of the assertion.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertTrue(...) assertTrueImplement( \
__FILE__, __LINE__, __func__, __VA_ARGS__ \
)
/**
* Asserts that a statement must be false in order for the assertion to not
* cause an error.
*
* @param statement Statement of the assertion.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertFalse(x, ...) assertTrue(!(x), __VA_ARGS__)
/**
* Asserts that a specified piece of code should be entirely unreachable.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertUnreachable(...) assertTrue(false, __VA_ARGS__)
/**
* Asserts that a given pointer is not null.
* @param x Pointer to check.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertNotNull(x, ...) assertTrue(x != NULL, __VA_ARGS__)
/**
* Asserts that a given pointer is null.
* @param x Pointer to check.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertNull(x, ...) assertTrue(x == NULL, __VA_ARGS__)
/**
* Asserts that a given value has a specific flag turned off.
*
* @param value Value to check.
* @param flag Flag to check for.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertFlagOff(value, flag, ...) assertTrue( \
(value & flag) == 0, __VA_ARGS__ \
)
/**
* Asserts that a given value has a specific flag turned on.
*
* @param value Value to check.
* @param flag Flag to check for.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertFlagOn(value, flag, ...) assertTrue( \
(value & flag) == flag, __VA_ARGS__ \
)
/**
* Asserts that the given string is not null, and has a length that is less
* the maximum buffer size, including the NULL terminator.
*
* @param str String to check.
* @param bufferSize Maximum buffer size.
* @param message Message (sprintf format) to send to the logger.
* @param args Optional TParam args for the sprintf message to accept.
*/
#define assertStringValid(str, bufferSize, ...) assertTrue( \
((str != NULL) && ((strlen(str)+1) < bufferSize)), __VA_ARGS__ \
)
/**
* Asserts that the current code is deprecated and should not be used anymore.
* @deprecated
*/
#define assertDeprecated(...) assertUnreachable(__VA_ARGS__)

View File

@ -0,0 +1,14 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https:#opensource.org/licenses/MIT
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
asset.c
assetarchive.c
assetjson.c
assetmap.c
assetlanguage.c
)

159
src/dawn/asset/asset.c Normal file
View File

@ -0,0 +1,159 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "asset.h"
#include "assetarchive.h"
#include "assert/assert.h"
#include "util/math.h"
void assetInit() {
// TODO: Works on Windows? path sep probs wrong.
// const char_t *assetFilename = "dawn.tar";
// char_t *assetPath = malloc(sizeof(char_t) * (
// strlen(SYSTEM.executableDirectory) + strlen(assetFilename) + 1
// ));
// sprintf(assetPath, "%s/%s", SYSTEM.executableDirectory, assetFilename);
char_t *assetPath = "/home/yourwishes/htdocs/Dawn/build/dawn.tar";
ASSET_FILE = fopen(assetPath, "rb");
// free(assetPath);
assertNotNull(ASSET_FILE, "assetInit: Failed to open asset file!");
ASSET_ARCHIVE = NULL;
ASSET_ENTRY = NULL;
}
size_t assetReadUntil(uint8_t *buffer, const char_t c, const size_t maxLength) {
if(buffer == NULL) {
assertTrue(
maxLength == -1, "If no buffer is provided, maxLength must be -1."
);
uint8_t tBuffer[1];
size_t read = 0;
while(assetRead(tBuffer, 1) == 1 && (char_t)tBuffer[0] != c) read++;
return read;
} else {
size_t read = 0;
while(read < maxLength) {
// TODO: Read more than 1 char at a time.
read += assetRead(buffer + read, 1);
if((char_t)buffer[read-1] == c) return read - 1;
}
return -1;
}
}
void assetOpen(const char_t *path) {
assertNull(ASSET_ARCHIVE, "assetOpenFile: Archive is not NULL!");
assertNull(ASSET_ENTRY, "assetOpenFile: Entry is not NULL!");
assertStringValid(path, 1024, "assetOpenFile: Path is not valid!");
// Store path
strcpy(ASSET_PATH_CURRENT, path);
// Prepare data
ASSET_ARCHIVE = archive_read_new();
assertNotNull(ASSET_ARCHIVE, "assetOpenFile: Failed to create archive!");
// Set up the reader
// archive_read_support_filter_bzip2(ASSET_ARCHIVE);
archive_read_support_format_tar(ASSET_ARCHIVE);
// Open reader
archive_read_set_open_callback(ASSET_ARCHIVE, &assetArchiveOpen);
archive_read_set_read_callback(ASSET_ARCHIVE, &assetArchiveRead);
archive_read_set_seek_callback(ASSET_ARCHIVE, &assetArchiveSeek);
archive_read_set_close_callback(ASSET_ARCHIVE, &assetArchiveOpen);
archive_read_set_callback_data(ASSET_ARCHIVE, ASSET_ARCHIVE_BUFFER);
int32_t ret = archive_read_open1(ASSET_ARCHIVE);
assertTrue(ret == ARCHIVE_OK, "assetOpenFile: Failed to open archive!");
// Iterate over each file.
while(archive_read_next_header(ASSET_ARCHIVE, &ASSET_ENTRY) == ARCHIVE_OK) {
const char_t *headerFile = (char_t*)archive_entry_pathname(ASSET_ENTRY);
if(strcmp(headerFile, ASSET_PATH_CURRENT) == 0) return;
int32_t ret = archive_read_data_skip(ASSET_ARCHIVE);
assertTrue(ret == ARCHIVE_OK, "assetOpenFile: Failed to skip data!");
}
assertUnreachable("assetOpenFile: Failed to find file!");
}
size_t assetGetSize() {
assertNotNull(ASSET_ARCHIVE, "assetGetSize: Archive is NULL!");
assertNotNull(ASSET_ENTRY, "assetGetSize: Entry is NULL!");
assertTrue(
archive_entry_size_is_set(ASSET_ENTRY),
"assetGetSize: Entry size is not set!"
);
size_t n = archive_entry_size(ASSET_ENTRY);
// Remnant when get size was doing some incorrect stuff.
// char_t path[2048];
// sprintf(
// path, "/home/yourwishes/htdocs/dusk/build/assets/%s", ASSET_PATH_CURRENT
// );
// FILE *temp = fopen(path, "rb");
// assertNotNull(temp, "assetGetSize: Failed to open temp file!");
// fseek(temp, 0, SEEK_END);
// size_t size = ftell(temp);
// assertTrue(size == n, "assetGetSize: Size is not equal!");
// fclose(temp);
return n;
}
size_t assetRead(uint8_t *buffer, size_t bufferSize) {
assertNotNull(ASSET_ARCHIVE, "assetRead: Archive is NULL!");
assertNotNull(ASSET_ENTRY, "assetRead: Entry is NULL!");
assertNotNull(buffer, "assetRead: Buffer is NULL!");
assertTrue(bufferSize > 0, "assetRead: Buffer size must be greater than 0!");
ssize_t read = archive_read_data(ASSET_ARCHIVE, buffer, bufferSize);
if(read == ARCHIVE_FATAL) {
assertUnreachable(archive_error_string(ASSET_ARCHIVE));
}
assertTrue(read != ARCHIVE_RETRY, "assetRead: Failed to read data (RETRY)!");
assertTrue(read != ARCHIVE_WARN, "assetRead: Failed to read data (WARN)!");
return read;
}
void assetSkip(const size_t length) {
assertNotNull(ASSET_ARCHIVE, "assetSkip: Archive is NULL!");
assertNotNull(ASSET_ENTRY, "assetSkip: Entry is NULL!");
assertTrue(length > 0, "assetSkip: Length must be greater than 0!");
// Asset archive does not support skipping, so we have to read and discard.
uint8_t buffer[1024];
size_t remaining = length;
do {
size_t toRead = mathMin(remaining, 1024);
size_t read = assetRead(buffer, toRead);
assertTrue(read == toRead, "assetSkip: Failed to skip data! (overskip?)");
remaining -= read;
} while(remaining > 0);
}
void assetClose() {
assertNotNull(ASSET_ARCHIVE, "assetClose: Archive is NULL!");
assertNotNull(ASSET_ENTRY, "assetClose: Entry is NULL!");
int32_t ret = archive_read_free(ASSET_ARCHIVE);
assertTrue(ret == ARCHIVE_OK, "assetClose: Failed to close archive!");
ASSET_ARCHIVE = NULL;
ASSET_ENTRY = NULL;
}
void assetDispose() {
assertNull(ASSET_ARCHIVE, "assetDestroy: Archive is not NULL!");
assertNull(ASSET_ENTRY, "assetDestroy: Entry is not NULL!");
int32_t result = fclose(ASSET_FILE);
assertTrue(result == 0, "assetDestroy: Failed to close asset file!");
}

73
src/dawn/asset/asset.h Normal file
View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2023 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Initializes the asset manager.
*/
void assetInit();
/**
* Opens an asset by its filename (within the asset archive). Asset paths should
* always use the unix forward slash '/' as a path separator.
*
* @param path The path to the asset within the archive.
*/
void assetOpen(const char_t *path);
/**
* Returns the size of the asset.
*
* @return The size of the asset.
*/
size_t assetGetSize();
/**
* Reads the asset into the buffer.
*
* @param buffer The buffer to read the asset into.
* @param bufferSize The size of the buffer.
* @return The amount of data read.
*/
size_t assetRead(uint8_t *buffer, size_t bufferSize);
/**
* Reads ahead in the buffer until either the end of the buffer, or the
* specified character is found. Return value will be -1 if the character was
* not found.
*
* Buffer can be NULL if you just want to skip ahead.
*
* Returned value will be either the amount of data read into the buffer, which
* excludes the extra 1 character that was read from the asset. If the character
* was not found, -1 will be returned.
*
* @param buffer Buffer to read into.
* @param c Character to read until.
* @param maxLength Maximum length to read.
* @return -1 if the character was not found, otherwise the amount of data read.
*/
size_t assetReadUntil(uint8_t *buffer, const char_t c, const size_t maxLength);
/**
* Skips ahead in the buffer by the specified length.
*
* @param length The length to skip ahead by.
*/
void assetSkip(const size_t length);
/**
* Closes the asset.
*/
void assetClose();
/**
* Destroys and cleans up the asset manager.
*/
void assetDispose();

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2023 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "asset/assetarchive.h"
#include "assert/assert.h"
#include "util/math.h"
FILE *ASSET_FILE;
struct archive *ASSET_ARCHIVE;
struct archive_entry *ASSET_ENTRY;
uint8_t ASSET_ARCHIVE_BUFFER[ASSET_BUFFER_SIZE];
char_t ASSET_PATH_CURRENT[ASSET_PATH_MAX];
ssize_t assetArchiveRead(
struct archive *archive,
void *data,
const void **buffer
) {
assertNotNull(archive, "assetArchiveRead: Archive is NULL!");
assertNotNull(data, "assetArchiveRead: Data is NULL!");
assertNotNull(buffer, "assetArchiveRead: Buffer is NULL!");
*buffer = data;
size_t read = fread(data, 1, ASSET_BUFFER_SIZE, ASSET_FILE);
if(ferror(ASSET_FILE)) return ARCHIVE_FATAL;
return read;
}
int64_t assetArchiveSeek(
struct archive *archive,
void *data,
int64_t offset,
int32_t whence
) {
assertNotNull(archive, "assetArchiveSeek: Archive is NULL!");
assertNotNull(data, "assetArchiveSeek: Data is NULL!");
assertTrue(offset > 0, "assetArchiveSeek: Offset must be greater than 0!");
int32_t ret = fseek(ASSET_FILE, offset, whence);
assertTrue(ret == 0, "assetArchiveSeek: Failed to seek!");
return ftell(ASSET_FILE);
}
int32_t assetArchiveOpen(struct archive *a, void *data) {
int32_t ret = fseek(ASSET_FILE, 0, SEEK_SET);
assertTrue(ret == 0, "assetArchiveOpen: Failed to seek to start of file!");
return ARCHIVE_OK;
}
int32_t assetArchiveClose(struct archive *a, void *data) {
return assetArchiveOpen(a, data);
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2023 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/asset.h"
#include <archive.h>
#include <archive_entry.h>
#define ASSET_BUFFER_SIZE 32768
#define ASSET_PATH_MAX 1024
extern FILE *ASSET_FILE;
extern struct archive *ASSET_ARCHIVE;
extern struct archive_entry *ASSET_ENTRY;
extern uint8_t ASSET_ARCHIVE_BUFFER[ASSET_BUFFER_SIZE];
extern char_t ASSET_PATH_CURRENT[ASSET_PATH_MAX];
/**
* Internal read method provided to libarchive api.
*
* @param archive The archive to read from.
* @param data The data to read into.
* @param buffer The buffer to read from.
* @return The amount of data read.
*/
ssize_t assetArchiveRead(
struct archive *archive,
void *data,
const void **buffer
);
/**
* Internal seek method provided to libarchive api.
*
* @param archive The archive to seek in.
* @param data The data to seek in.
* @param offset Offset bytes to seek.
* @param whence Relative to whence to seek.
* @return The new position.
*/
int64_t assetArchiveSeek(
struct archive *archive,
void *data,
int64_t offset,
int32_t whence
);
/**
* Internal open method provided to libarchive api.
*
* @param archive The archive to open.
* @param data The data to open.
* @return The result of the open.
*/
int32_t assetArchiveOpen(struct archive *a, void *data);
/**
* Internal close method provided to libarchive api.
*
* @param archive The archive to close.
* @param data The data to close.
* @return The result of the close.
*/
int32_t assetArchiveClose(struct archive *a, void *data);

560
src/dawn/asset/assetjson.c Normal file
View File

@ -0,0 +1,560 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetjson.h"
#include "assert/assert.h"
#include "util/memory.h"
size_t assetJsonParse(const char_t *json, assetjson_t **out) {
size_t offset = assetJsonParseSub(json, out);
// We only expect whitespace or EOF here
char_t c;
size_t len = strlen(json);
while(offset <= len) {
c = json[offset];
if(c == '\0') break;
if(c == ' ' || c == '\t' || c == '\n' || c == '\r') {
offset++;
continue;
}
assertUnreachable("Unexpected character found after JSON data.");
}
assertTrue(c == '\0', "Unexpected character found after JSON data.");
assertTrue(offset == len, "Unexpected character found after JSON data.");
return offset;
}
size_t assetJsonParseSub(
const char_t *json,
assetjson_t **out
) {
size_t offset = 0;
char_t c;
// Skip whitespace
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// Read first character
c = json[offset];
switch(c) {
case '{':
offset += assetJsonParseAsObject(json + offset, out);
break;
case '[':
offset += assetJsonParseAsArray(json + offset, out);
break;
case '"':
offset += assetJsonParseAsString(json + offset, out);
break;
case '-':
case '0' ... '9':
case '.':
offset += assetJsonParseAsNumber(json + offset, out);
break;
case 't':
case 'f':
offset += assetJsonParseAsBoolean(json + offset, out);
break;
case 'n':
offset += assetJsonParseAsNull(json + offset, out);
break;
default:
assertUnreachable("Invalid JSON data type found.");
break;
}
return offset;
}
size_t assetJsonParseAsNull(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = (assetjson_t*)memoryAllocate(sizeof(assetjson_t));
// Read "null"
assertTrue(json[0] == 'n', "Expected NULL data type. (n0)");
assertTrue(json[1] == 'u', "Expected NULL data type. (u0)");
assertTrue(json[2] == 'l', "Expected NULL data type. (l0)");
assertTrue(json[3] == 'l', "Expected NULL data type. (l1)");
obj->type = ASSET_JSON_DATA_TYPE_NULL;
*out = obj;
return 4;
}
size_t assetJsonParseAsBoolean(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = (assetjson_t*)memoryAllocate(sizeof(assetjson_t));
obj->type = ASSET_JSON_DATA_TYPE_BOOLEAN;
*out = obj;
if(json[0] == 't') {
// Read "true"
assertTrue(json[0] == 't', "Expected TRUE data type. (t0)");
assertTrue(json[1] == 'r', "Expected TRUE data type. (r0)");
assertTrue(json[2] == 'u', "Expected TRUE data type. (u0)");
assertTrue(json[3] == 'e', "Expected TRUE data type. (e0)");
obj->boolean = true;
return 4;
} else {
// Read "false"
assertTrue(json[0] == 'f', "Expected FALSE data type. (f0)");
assertTrue(json[1] == 'a', "Expected FALSE data type. (a0)");
assertTrue(json[2] == 'l', "Expected FALSE data type. (l0)");
assertTrue(json[3] == 's', "Expected FALSE data type. (s0)");
assertTrue(json[4] == 'e', "Expected FALSE data type. (e0)");
obj->boolean = false;
return 5;
}
}
size_t assetJsonParseAsString(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = memoryAllocate(sizeof(assetjson_t));
obj->type = ASSET_JSON_DATA_TYPE_STRING;
// For each char
size_t offset = 1;// Skip opening quote
size_t outOffset = 0;
char c;
bool_t inEscape = false;
size_t bufferSize = 2;
char_t *string = (char_t*)memoryAllocate(bufferSize * sizeof(char_t));
while(true) {
c = json[offset];
if(c == '\0') assertUnreachable("Unexpected end of string.");
if(inEscape) {
inEscape = false;
switch(c) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'u':
assertUnreachable("Unicode escape sequences are not supported.");
break;
default:
break;
}
if(outOffset >= bufferSize) {
bufferSize *= 2;
string = memoryReallocate(string, bufferSize * sizeof(char_t));
}
string[outOffset] = c;
offset++;
outOffset++;
continue;
}
if(c == '\\') {
inEscape = true;
offset++;
continue;
}
if(c == '"') break;
if(outOffset >= bufferSize) {
bufferSize *= 2;
string = memoryReallocate(string, bufferSize * sizeof(char_t));
}
string[outOffset] = c;
offset++;
outOffset++;
}
string[outOffset] = '\0';
outOffset++;
*out = obj;
obj->string = string;
return offset + 1;// For closing string quote
}
size_t assetJsonParseAsObject(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = memoryAllocate(sizeof(assetjson_t));
obj->type = ASSET_JSON_DATA_TYPE_OBJECT;
size_t bufferSize = 2;
char_t **keys = memoryAllocate(bufferSize * sizeof(char_t*));
assetjson_t **values = memoryAllocate(bufferSize * sizeof(assetjson_t*));
size_t length = 0;
// Skip whitespace
size_t offset = 1;// Skip opening bracket
char_t c;
while(true) {
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// Only expect opening string or closing brace
if(c == '}') break;
assertTrue(c == '"', "Expected opening string for JSON object key.");
char_t *bufferKey;
// Skip "
offset++;
offset += assetJsonReadString(json + offset, &bufferKey);
// Skip whitespace
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// Only expect colon
assertTrue(c == ':', "Expected colon after JSON object key.");
offset++;
// Skip whitespace
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// Parse sub
assetjson_t *value;
offset += assetJsonParseSub(json + offset, &value);
// Need to resize?
if(length >= bufferSize) {
bufferSize *= 2;
keys = memoryReallocate(keys, bufferSize * sizeof(char_t*));
values = memoryReallocate(values, bufferSize * sizeof(assetjson_t*));
}
keys[length] = bufferKey;
values[length] = value;
length++;
// Skip whitespace
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// Expect either comma or closing bracket
assertTrue(
c == ',' || c == '}',
"Expected comma or closing bracket after JSON object value."
);
if(c == '}') break;
offset++;
}
obj->object.keys = keys;
obj->object.values = values;
obj->object.length = length;
*out = obj;
return offset + 1;// Skip closing bracket
}
size_t assetJsonParseAsArray(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = (assetjson_t*)memoryAllocate(sizeof(assetjson_t));
obj->type = ASSET_JSON_DATA_TYPE_ARRAY;
size_t offset = 1;// Skip opening bracket
char_t c;
// Create array
size_t arraySize = 2;
obj->array.value = (assetjson_t**)memoryAllocate(
arraySize * sizeof(assetjson_t*)
);
obj->array.length = 0;
// Until closing bracket
while(true) {
c = json[offset];
if(c == '\0') assertUnreachable("Unexpected end of JSON array.");
if(c == ']') break;
// Skip whitespace ONLY
if(c == ' ' || c == '\t' || c == '\n' || c == '\r') {
offset++;
continue;
}
// Need to expand?
if(obj->array.length >= arraySize) {
arraySize *= 2;
obj->array.value = memoryReallocate(
obj->array.value, arraySize * sizeof(assetjson_t*)
);
}
// Parse sub
offset += assetJsonParseSub(
json + offset,
&obj->array.value[obj->array.length++]
);
// Skip whitespace ONLY
while((c = json[offset]) == ' ' || c == '\t' || c == '\n' || c == '\r') {
if(c == '\0') assertUnreachable("Unexpected end of JSON data.");
offset++;
}
// If comma, continue
if(c == ',') {
offset++;
continue;
}
// If closing bracket, break
if(c == ']') break;
// Else, error
assertUnreachable("Unexpected character found in JSON array.");
}
// End of array
*out = obj;
return offset + 1;// Skip closing bracket
}
size_t assetJsonParseAsNumber(
const char_t *json,
assetjson_t **out
) {
assetjson_t *obj = (assetjson_t*)memoryAllocate(sizeof(assetjson_t));
obj->type = ASSET_JSON_DATA_TYPE_NUMBER;
// For each char
size_t offset = 0;
size_t outOffset = 0;
char_t c;
size_t bufferSize = 2;
char_t *buffer = (char_t*)memoryAllocate(bufferSize * sizeof(char_t));
bool_t hasDecimal = false;
bool_t hasNumber = false;
// Read number
while(true) {
c = json[offset];
if(c == '\0') assertUnreachable("Unexpected end of number.");
if(c == '-') {
// only accepted on first input
assertTrue(outOffset == 0, "Unexpected - after first digit");
} else if(c == '.') {
// only accepted once
assertTrue(!hasDecimal, "Unexpected . after first decimal");
hasDecimal = true;
if(!hasNumber) {
// If no number before decimal, add a 0
if(outOffset >= bufferSize) {
bufferSize *= 2;
buffer = memoryReallocate(buffer, bufferSize * sizeof(char_t));
}
buffer[outOffset] = '0';
outOffset++;
}
} else if(c >= '0' && c <= '9') {
hasNumber = true;
} else {
break;
}
// Need to expand?
if(outOffset >= bufferSize) {
bufferSize *= 2;
buffer = memoryReallocate(buffer, bufferSize * sizeof(char_t));
}
buffer[outOffset] = c;
offset++;
outOffset++;
}
// Seal the buffer, parse and cleanup
buffer[outOffset] = '\0';
obj->number = strtod(buffer, NULL);
memoryFree(buffer);
*out = obj;
return offset;
}
size_t assetJsonReadString(
const char_t *json,
char_t **buffer
) {
size_t offset = 0;
size_t outOffset = 0;
char_t c;
size_t bufferSize = 32;
char_t *string = (char_t*)memoryAllocate(sizeof(char_t) * bufferSize);
bool_t inEscape = false;
// For each char
while(true) {
c = json[offset];
if(c == '\0') assertUnreachable("Unexpected end of string.");
if(inEscape) {
inEscape = false;
switch(c) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'u':
assertUnreachable("Unicode escape sequences are not supported.");
break;
default:
break;
}
if(outOffset >= bufferSize) {
bufferSize *= 2;
string = memoryReallocate(string, bufferSize);
}
string[outOffset] = c;
offset++;
outOffset++;
continue;
}
if(c == '\\') {
inEscape = true;
offset++;
continue;
}
if(c == '"') break;
if(outOffset >= bufferSize) {
bufferSize *= 2;
string = memoryReallocate(string, bufferSize);
}
string[outOffset] = c;
offset++;
outOffset++;
}
string[outOffset] = '\0';
outOffset++;
*buffer = string;
return offset + 1;
}
assetjson_t * assetJsonGetObjectValue(assetjson_t *json, const char_t *key) {
assertTrue(json->type == ASSET_JSON_DATA_TYPE_OBJECT, "Expected JSON object.");
for(size_t i = 0; i < json->object.length; i++) {
if(strcmp(json->object.keys[i], key) == 0) {
return json->object.values[i];
}
}
return NULL;
}
void assetJsonDispose(assetjson_t *json) {
switch(json->type) {
case ASSET_JSON_DATA_TYPE_OBJECT:
for(size_t i = 0; i < json->object.length; i++) {
memoryFree(json->object.keys[i]);
assetJsonDispose(json->object.values[i]);
}
memoryFree(json->object.keys);
memoryFree(json->object.values);
break;
case ASSET_JSON_DATA_TYPE_ARRAY:
for(size_t i = 0; i < json->array.length; i++) {
assetJsonDispose(json->array.value[i]);
}
memoryFree(json->array.value);
break;
case ASSET_JSON_DATA_TYPE_STRING:
memoryFree(json->string);
break;
case ASSET_JSON_DATA_TYPE_NUMBER:
break;
case ASSET_JSON_DATA_TYPE_BOOLEAN:
break;
case ASSET_JSON_DATA_TYPE_NULL:
break;
}
memoryFree(json);
}

140
src/dawn/asset/assetjson.h Normal file
View File

@ -0,0 +1,140 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef enum {
ASSET_JSON_DATA_TYPE_OBJECT,
ASSET_JSON_DATA_TYPE_ARRAY,
ASSET_JSON_DATA_TYPE_STRING,
ASSET_JSON_DATA_TYPE_NUMBER,
ASSET_JSON_DATA_TYPE_BOOLEAN,
ASSET_JSON_DATA_TYPE_NULL
} assetjsondatatype_t;
typedef struct _assetjson_t assetjson_t;
typedef struct _assetjson_t {
assetjsondatatype_t type;
union {
struct {
char_t **keys;
assetjson_t **values;
size_t length;
} object;
struct {
assetjson_t **value;
size_t length;
} array;
double_t number;
char_t *string;
bool_t boolean;
};
} assetjson_t;
/**
* Parses a JSON string.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParse(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a sub-object.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseSub(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a NULL value.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsNull(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a boolean value.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsBoolean(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as an object.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsObject(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as an array.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsArray(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a string.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsString(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a number.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonParseAsNumber(const char_t *json, assetjson_t **out);
/**
* Parses a JSON string as a string.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
size_t assetJsonReadString(const char_t *json, char_t **out);
/**
* Parses a JSON string as a number.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
assetjson_t *assetJsonGetObjectValue(assetjson_t *object, const char_t *key);
/**
* Parses a JSON string as a number.
*
* @param json JSON string to parse.
* @param out Pointer to store the parsed JSON object.
* @return The number of characters parsed.
*/
void assetJsonDispose(assetjson_t *json);

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetlanguage.h"
#include "asset/asset.h"
#include "assert/assert.h"
#include "locale/language.h"
#include "util/memory.h"
void assetLanguageObjectLoad(
const char_t *key,
assetjson_t *json
) {
char_t tKey[LANGUAGE_STRING_KEY_LENGTH_MAX];
size_t keyLength = strlen(key);
strcpy(tKey, key);
if(keyLength > 0) {
tKey[keyLength] = '.';
keyLength++;
}
for(int32_t i = 0; i < json->object.length; i++) {
const char_t *subKey = json->object.keys[i];
assetjson_t *value = json->object.values[i];
strcpy(tKey + keyLength, subKey);
if(json->object.values[i]->type == ASSET_JSON_DATA_TYPE_OBJECT) {
assetLanguageObjectLoad(tKey, value);
continue;
}
if(json->object.values[i]->type == ASSET_JSON_DATA_TYPE_STRING) {
strcpy(LANGUAGE.keys[LANGUAGE.count], tKey);
strcpy(LANGUAGE.strings[LANGUAGE.count], value->string);
LANGUAGE.count++;
continue;
}
assertUnreachable("Language value is not a string or object!");
}
}
void assetLanguageLoad(const char_t *path) {
assertNotNull(path, "Path cannot be NULL.");
assetOpen(path);
size_t length = assetGetSize();
char_t *buffer = memoryAllocate(sizeof(char_t) * (length + 1));
buffer[length] = '\0';
size_t read = assetRead((uint8_t*)buffer, length);
assertTrue(read == length, "Failed to read language file!");
assetClose();
assetjson_t *json;
read = assetJsonParse(buffer, &json);
memoryFree(buffer);
assertTrue(
json->type == ASSET_JSON_DATA_TYPE_OBJECT,
"Language file is not an object!"
);
languageInit();
assetLanguageObjectLoad("", json);
assetJsonDispose(json);
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "asset/assetjson.h"
/**
* Loads a JSON object from the asset file.
*
* @param key The key to load.
* @param json The value to load into.
*/
void assetLanguageObjectLoad(
const char_t *key,
assetjson_t *json
);
/**
* Loads the language file from the specified path.
*
* @param path Path to the language file.
*/
void assetLanguageLoad(const char_t *path);

236
src/dawn/asset/assetmap.c Normal file
View File

@ -0,0 +1,236 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetmap.h"
#include "asset/asset.h"
#include "assert/assert.h"
#include "util/memory.h"
void assetMapLoadEntity(
assetjson_t *jEnt,
map_t *map
) {
int32_t x = (int32_t)assetJsonGetObjectValue(jEnt, "x")->number;
int32_t y = (int32_t)assetJsonGetObjectValue(jEnt, "y")->number;
entitytype_t type = (entitytype_t)assetJsonGetObjectValue(
jEnt, "type"
)->number;
entity_t *ent = mapEntityAdd(map);
entityInit(ent, type, map);
entityPositionSet(ent, x, y);
assetjson_t *val;
switch(type) {
case ENTITY_TYPE_NPC:
val = assetJsonGetObjectValue(jEnt, "name");
if(val != NULL) {
assertTrue(
val->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: NPC name is not a string!"
);
npcNameSet(&ent->npc, val->string);
}
val = assetJsonGetObjectValue(jEnt, "text");
if(val != NULL) {
assertTrue(
val->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: NPC text is not a string!"
);
npcTextSet(&ent->npc, val->string);
}
break;
case ENTITY_TYPE_SIGN:
val = assetJsonGetObjectValue(jEnt, "text");
if(val != NULL) {
assertTrue(
val->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: Sign text is not a string!"
);
signTextAppend(&ent->sign, val->string);
}
val = assetJsonGetObjectValue(jEnt, "texts");
if(val != NULL) {
assertTrue(
val->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Sign texts is not an array!"
);
assertTrue(
val->array.length <= SIGN_TEXT_COUNT_MAX,
"assetMapLoad: Too many sign texts!"
);
for(int32_t i = 0; i < val->array.length; i++) {
assetjson_t *subVal = val->array.value[i];
assertTrue(
subVal->type == ASSET_JSON_DATA_TYPE_STRING,
"assetMapLoad: Sign text is not a string!"
);
signTextAppend(&ent->sign, subVal->string);
}
}
break;
case ENTITY_TYPE_DOOR:
val = assetJsonGetObjectValue(jEnt, "door");
if(val != NULL) {
assetjson_t *door = assetJsonGetObjectValue(val, "x");
x = (int32_t)door->number;
assertTrue(
door->type == ASSET_JSON_DATA_TYPE_NUMBER,
"assetMapLoad: Door x is not a number!"
);
door = assetJsonGetObjectValue(val, "y");
y = (int32_t)door->number;
assertTrue(
door->type == ASSET_JSON_DATA_TYPE_NUMBER,
"assetMapLoad: Door y is not a number!"
);
door = assetJsonGetObjectValue(val, "direction");
entitydirection_t direction = ENTITY_DIRECTION_SOUTH;
if(door != NULL) {
direction = (entitydirection_t)(
assetJsonGetObjectValue(val, "direction")->number
);
}
door = assetJsonGetObjectValue(val, "map");
maplist_t list;
if(door == NULL) {
list = map->list;
} else if(door->type == ASSET_JSON_DATA_TYPE_STRING) {
list = mapListGet(door->string);
} else if(door->type == ASSET_JSON_DATA_TYPE_NUMBER) {
list = (maplist_t)door->number;
} else {
assertUnreachable("assetMapLoad: Door map not string or number!");
}
doorDestinationSet(&ent->door, x, y, direction, list);
}
break;
default:
break;
}
}
void assetMapLoad(
const char_t *path,
map_t *map
) {
assertNotNull(map, "assetMapLoad: Map is NULL!");
// Read in the string data.
assetOpen(path);
size_t length = assetGetSize();
char_t *buffer = memoryAllocate(sizeof(char_t) * (length + 1));
size_t read = assetRead((uint8_t*)buffer, length);
buffer[length] = '\0';
assertTrue(read == length, "assetMapLoad: Failed to read map data!");
assetClose();
// Begin parsing JSON data.
assetjson_t *json;
read = assetJsonParse(buffer, &json);
memoryFree(buffer);
assertTrue(
json->type == ASSET_JSON_DATA_TYPE_OBJECT,
"assetMapLoad: Map data is not an object!"
);
int32_t width = (int32_t)assetJsonGetObjectValue(json, "width")->number;
int32_t height = (int32_t)assetJsonGetObjectValue(json, "height")->number;
assetjson_t *layers = assetJsonGetObjectValue(json, "layers");
assertTrue(
layers->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Layers is not an array!"
);
int32_t layerCount = layers->array.length;
assertTrue(layerCount == MAP_LAYERS_MAX, "assetMapLoad: No layers found!");
mapInit(map, mapListGet(path), width, height, layerCount);
// Load tile data.
for(int32_t i = 0; i < layerCount; i++) {
assetjson_t *layer = layers->array.value[i];
assertTrue(
layer->type == ASSET_JSON_DATA_TYPE_OBJECT,
"assetMapLoad: Layer is not an object!"
);
assetjson_t *tiles = assetJsonGetObjectValue(layer, "tiles");
assertTrue(
tiles->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Tiles is not an array!"
);
assertTrue(
tiles->array.length == width * height,
"assetMapLoad: Tile count does not match map size!"
);
for(int32_t j = 0; j < width * height; j++) {
map->tiles[j] = (tile_t)tiles->array.value[j]->number;
}
}
// Load entity data
assetjson_t *entities = assetJsonGetObjectValue(json, "entities");
if(entities != NULL) {
assertTrue(
entities->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Entities is not an array!"
);
for(int32_t i = 0; i < entities->array.length; i++) {
assetjson_t *jEnt = entities->array.value[i];
assertTrue(
jEnt->type == ASSET_JSON_DATA_TYPE_OBJECT,
"assetMapLoad: Entity is not an object!"
);
assetMapLoadEntity(jEnt, map);
}
}
// Load trigger data
assetjson_t *triggers = assetJsonGetObjectValue(json, "triggers");
if(triggers != NULL) {
assertTrue(
triggers->type == ASSET_JSON_DATA_TYPE_ARRAY,
"assetMapLoad: Triggers is not an array!"
);
for(int32_t i = 0; i < triggers->array.length; i++) {
assetjson_t *jTrig = triggers->array.value[i];
assertTrue(
jTrig->type == ASSET_JSON_DATA_TYPE_OBJECT,
"assetMapLoad: Trigger is not an object!"
);
int32_t x = (int32_t)assetJsonGetObjectValue(jTrig, "x")->number;
int32_t y = (int32_t)assetJsonGetObjectValue(jTrig, "y")->number;
int32_t width = (int32_t)assetJsonGetObjectValue(jTrig, "width")->number;
int32_t height = (int32_t)assetJsonGetObjectValue(
jTrig, "height"
)->number;
triggertype_t type = (triggertype_t)assetJsonGetObjectValue(
jTrig, "type"
)->number;
trigger_t *trigger = mapTriggerAdd(map);
triggerInit(trigger, type, x, y, width, height);
}
}
assetJsonDispose(json);
}

32
src/dawn/asset/assetmap.h Normal file
View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/world/map.h"
#include "asset/assetjson.h"
/**
* Loads an entity from the specified JSON object.
*
* @param jEnt JSON object to load the entity from.
* @param map Map to load the entity into.
*/
void assetMapLoadEntity(
assetjson_t *jEnt,
map_t *map
);
/**
* Loads a map from the specified path.
*
* @param path Path to the map file.
* @param map Map to load the data into.
*/
void assetMapLoad(
const char_t *path,
map_t *map
);

22
src/dawn/dawn.h Normal file
View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <float.h>
#include <errno.h>
typedef bool bool_t;
typedef char char_t;
typedef unsigned char uchar_t;
typedef uint_fast32_t flag_t;

View File

@ -0,0 +1,15 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
add_subdirectory(draw)
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
color.c
frame.c
symbol.c
)

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "animationitem.h"
#define ANIMATION_ITEM_COUNT_MAX 64
typedef struct _animation_t {
float_t time;
bool_t loop;
float_t duration;
animationitem_t items[ANIMATION_ITEM_COUNT_MAX];
uint8_t itemCount;
} animation_t;
/**
* Initializes an animation.
* @param anim The animation to initialize.
*/
void animationInit(animation_t *anim);
/**
* Updates an animation.
* @param anim The animation to update.
* @param delta The time since the last update.
*/
void animationUpdate(animation_t *anim, const float_t delta);
/**
* Returns the total duration of the animation.
*
* @param anim The animation to get the duration of.
* @return The total duration of the animation.
*/
float_t animationDurationGet(const animation_t *anim);

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef struct _animation_t animation_t;
typedef struct _animationitem_t {
float_t time;
float_t duration;
void *user;
void (*start)(animation_t *anim, animationitem_t *item);
void (*update)(animation_t *anim,animationitem_t *item,const float_t rDelta);
} animationitem_t;

26
src/dawn/display/color.c Normal file
View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2023 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "color.h"
#include "assert/assert.h"
const color4f_t COLOR4F_COLOR_MAP[COLOR_COUNT] = {
COLOR4F_BLACK,
COLOR4F_WHITE,
COLOR4F(0.8f, 0.0f, 0.0f, 1.0f),
COLOR4F(0.0f, 0.5f, 0.0f, 1.0f),
COLOR4F(0.0f, 0.0f, 0.8f, 1.0f),
COLOR4F(0.8f, 0.8f, 0.0f, 1.0f),
COLOR4F(0.8f, 0.0f, 0.8f, 1.0f),
COLOR4F(0.0f, 0.8f, 0.8f, 1.0f),
COLOR4F(0.5f, 0.5f, 0.5f, 1.0f),
COLOR4F(0.5f, 0.25f, 0.0f, 1.0f)
};
void color4fCopy(const color4f_t src, color4f_t dest) {
memcpy(dest, src, sizeof(color4f_t));
}

81
src/dawn/display/color.h Normal file
View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef float_t color3f_t[3];
typedef float_t color4f_t[4];
// Simple colors, used by the frame system.
#define COLOR_BLACK 0x00
#define COLOR_WHITE 0x01
#define COLOR_RED 0x02
#define COLOR_GREEN 0x03
#define COLOR_BLUE 0x04
#define COLOR_YELLOW 0x05
#define COLOR_MAGENTA 0x06
#define COLOR_CYAN 0x07
#define COLOR_GRAY 0x08
#define COLOR_BROWN 0x09
#define COLOR_COUNT COLOR_BROWN+1
#define COLOR3F(r,g,b) ((color3f_t){ r, g, b })
#define COLOR3F_RED COLOR3F(1, 0, 0)
#define COLOR3F_GREEN COLOR3F(0, 1, 0)
#define COLOR3F_BLUE COLOR3F(0, 0, 1)
#define COLOR3F_BLACK COLOR3F(0, 0, 0)
#define COLOR3F_WHITE COLOR3F(1, 1, 1)
#define COLOR3F_MAGENTA COLOR3F(1, 0, 1)
#define COLOR3F_YELLOW COLOR3F(1, 1, 0)
#define COLOR3F_CYAN COLOR3F(0, 1, 1)
#define COLOR3F_GRAY COLOR3F(0.5f, 0.5f, 0.5f)
#define COLOR3F_DARKGRAY COLOR3F(0.25f, 0.25f, 0.25f)
#define COLOR3F_LIGHTGRAY COLOR3F(0.75f, 0.75f, 0.75f)
#define COLOR3F_ORANGE COLOR3F(1, 0.5f, 0)
#define COLOR3F_PURPLE COLOR3F(0.5f, 0, 1)
#define COLOR3F_PINK COLOR3F(1, 0, 0.5f)
#define COLOR3F_BROWN COLOR3F(0.5f, 0.25f, 0)
#define COLOR3F_GOLD COLOR3F(1, 0.75f, 0)
#define COLOR3F_SILVER COLOR3F(0.75f, 0.75f, 0.75f)
#define COLOR3F_BRONZE COLOR3F(0.75f, 0.5f, 0.25f)
#define COLOR3F_CORNFLOWERBLUE COLOR3F(0.4f, 0.6f, 0.9f)
#define COLOR4F(r,g,b,a) ((color4f_t){ r, g, b, a })
#define COLOR4F_RED COLOR4F(1, 0, 0, 1)
#define COLOR4F_GREEN COLOR4F(0, 1, 0, 1)
#define COLOR4F_BLUE COLOR4F(0, 0, 1, 1)
#define COLOR4F_BLACK COLOR4F(0, 0, 0, 1)
#define COLOR4F_WHITE COLOR4F(1, 1, 1, 1)
#define COLOR4F_MAGENTA COLOR4F(1, 0, 1, 1)
#define COLOR4F_YELLOW COLOR4F(1, 1, 0, 1)
#define COLOR4F_CYAN COLOR4F(0, 1, 1, 1)
#define COLOR4F_GRAY COLOR4F(0.5f, 0.5f, 0.5f, 1)
#define COLOR4F_DARKGRAY COLOR4F(0.25f, 0.25f, 0.25f, 1)
#define COLOR4F_LIGHTGRAY COLOR4F(0.75f, 0.75f, 0.75f, 1)
#define COLOR4F_ORANGE COLOR4F(1, 0.5f, 0, 1)
#define COLOR4F_PURPLE COLOR4F(0.5f, 0, 1, 1)
#define COLOR4F_PINK COLOR4F(1, 0, 0.5f, 1)
#define COLOR4F_BROWN COLOR4F(0.5f, 0.25f, 0, 1)
#define COLOR4F_GOLD COLOR4F(1, 0.75f, 0, 1)
#define COLOR4F_SILVER COLOR4F(0.75f, 0.75f, 0.75f, 1)
#define COLOR4F_BRONZE COLOR4F(0.75f, 0.5f, 0.25f, 1)
#define COLOR4F_CORNFLOWERBLUE COLOR4F(0.4f, 0.6f, 0.9f, 1)
#define COLOR4F_TRANSPARENT_BLACK COLOR4F(0, 0, 0, 0)
#define COLOR4F_TRANSPARENT_WHITE COLOR4F(1, 1, 1, 0)
#define COLOR4F_TRANSPARENT COLOR4F_TRANSPARENT_BLACK
extern const color4f_t COLOR4F_COLOR_MAP[COLOR_COUNT];
/**
* Copies a color.
*
* @param src Source color.
* @param dest Destination color.
*/
void color4fCopy(const color4f_t src, color4f_t dest);

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Initializes the display subsystem.
*/
void displayInit();
/**
* Updates the display subsystem, this is basically asking the display renderer
* to perform a render.
*/
void displayUpdate();
/**
* Cleans up the display subsystem.
*/
void displayDispose();

View File

@ -0,0 +1,18 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
drawbattle.c
drawstatemainmenu.c
drawstateoverworld.c
drawmap.c
drawtext.c
drawshape.c
drawui.c
)

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawbattle.h"
#include "display/draw/drawshape.h"
#include "display/draw/drawui.h"
void drawBattle() {
drawClear(' ', COLOR_WHITE);
// Draw UI
drawUITextbox();
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/**
* Draws the battle state.
*/
void drawBattle();

View File

@ -0,0 +1,91 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawmap.h"
#include "assert/assert.h"
#include "display/symbol.h"
void drawMap(
const map_t *map,
const uint16_t cameraX,
const uint16_t cameraY,
const uint16_t drawX,
const uint16_t drawY,
const uint16_t drawWidth,
const uint16_t drawHeight
) {
assertNotNull(map, "Map cannot be NULL.");
assertTrue(drawX < FRAME_WIDTH, "Map is too far right.");
assertTrue(drawY < FRAME_HEIGHT, "Map is too far down.");
assertTrue(drawWidth > 0, "Map is too narrow.");
assertTrue(drawHeight > 0, "Map is too short.");
assertTrue(drawX + drawWidth <= FRAME_WIDTH, "Map is too wide.");
assertTrue(drawY + drawHeight <= FRAME_HEIGHT, "Map is too tall.");
entity_t *ent;
tile_t tile;
uint32_t i;
char_t *bufferDest;
uint8_t *colorDest;
// If the size of the map's smaller than the frame, center it, otherwise use
// the cameraPosition as the center point.
int16_t offsetX = 0, offsetY = 0;
if(map->width < drawWidth) {
offsetX = (drawWidth - map->width) / 2;
} else {
// Clamp to map bounds
if(cameraX < drawWidth / 2) {
offsetX = 0;
} else if(cameraX >= map->width - (drawWidth / 2)) {
offsetX = -(map->width - drawWidth);
} else {
offsetX = -(cameraX - (drawWidth / 2));
}
}
if(map->height < drawHeight) {
offsetY = (drawHeight - map->height) / 2;
} else {
// Clamp to map bounds
if(cameraY < drawHeight / 2) {
offsetY = 0;
} else if(cameraY >= map->height - (drawHeight / 2)) {
offsetY = -(map->height - drawHeight);
} else {
offsetY = -(cameraY - (drawHeight / 2));
}
}
// Draw the map
i = 0;
for(uint16_t y = 0; y < drawHeight; y++) {
bufferDest = &FRAME_BUFFER[(drawY + y) * FRAME_WIDTH + drawX];
colorDest = &FRAME_COLOR[(drawY + y) * FRAME_WIDTH + drawX];
for(uint16_t x = 0; x < drawWidth; x++) {
if(x < offsetX || y < offsetY || x >= map->width + offsetX || y >= map->height + offsetY) {
colorDest[x] = COLOR_BLACK;
bufferDest[x] = ' ';
continue;
}
// Entity?
ent = mapEntityGetByPosition(map, x - offsetX, y - offsetY);
if(ent != NULL) {
colorDest[x] = symbolGetColorByEntity(ent);
bufferDest[x] = symbolGetCharByEntity(ent);
continue;
}
// Tile?
tile = mapTileGetByPosition(map, x - offsetX, y - offsetY, 0);
colorDest[x] = tileColorGet(tile);
bufferDest[x] = tileSymbolGet(tile);
}
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
#include "rpg/world/map.h"
/**
* Draws a map to a region of the screen.
*
* @param map Map to draw.
* @param cameraX X position of the camera.
* @param cameraY Y position of the camera.
* @param drawX X position to draw the map.
* @param drawY Y position to draw the map.
* @param drawWidth Width of the map to draw.
* @param drawHeight Height of the map to draw.
*/
void drawMap(
const map_t *map,
const uint16_t cameraX,
const uint16_t cameraY,
const uint16_t drawX,
const uint16_t drawY,
const uint16_t drawWidth,
const uint16_t drawHeight
);

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawshape.h"
#include "assert/assert.h"
#include "util/memory.h"
void drawClear(
const char_t c,
const uint8_t color
) {
memorySet(FRAME_BUFFER, c, FRAME_HEIGHT * FRAME_WIDTH);
memorySet(FRAME_COLOR, color, FRAME_HEIGHT * FRAME_WIDTH);
}
void drawBox(
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const char_t c,
const uint8_t color
) {
assertTrue(x < FRAME_WIDTH, "Box is too far right.");
assertTrue(y < FRAME_HEIGHT, "Box is too far down.");
if(width == 0 || height == 0) return;
assertTrue(x + width <= FRAME_WIDTH, "Box is too wide.");
assertTrue(y + height <= FRAME_HEIGHT, "Box is too tall.");
if(width == FRAME_WIDTH) {
memorySet(&FRAME_BUFFER[y * FRAME_WIDTH + x], c, height * width);
memorySet(&FRAME_COLOR[y * FRAME_WIDTH + x], color, height * width);
return;
}
for(uint16_t iy = 0; iy < height; iy++) {
memorySet(&FRAME_BUFFER[(y + iy) * FRAME_WIDTH + x], c, width);
memorySet(&FRAME_COLOR[(y + iy) * FRAME_WIDTH + x], color, width);
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
/**
* Clears the frame buffer with a character and color.
*
* @param c Character to clear the frame buffer with.
* @param color Color to clear the frame buffer with.
*/
void drawClear(
const char_t c,
const uint8_t color
);
/**
* Draws a box to the frame buffer.
*
* @param x The x position to draw the box.
* @param y The y position to draw the box.
* @param width The width of the box.
* @param height The height of the box.
* @param c The character to draw the box with.
* @param color The color to draw the box.
*/
void drawBox(
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const char_t c,
const uint8_t color
);

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawstatemainmenu.h"
#include "display/draw/drawtext.h"
#include "display/draw/drawui.h"
#include "ui/mainmenu.h"
void drawStateMainMenu() {
// Draw the logo
drawText("Dawn", 4, 0, 0, COLOR_WHITE);
// Draw the menu
uint16_t width = menuGetLongestStringLength(&MAIN_MENU) + 3;
uint16_t height = MAIN_MENU.rows + 2;
drawUIMenuBox(
&MAIN_MENU,
(FRAME_WIDTH - width) / 2, FRAME_HEIGHT - height - 4,
width, height,
COLOR_WHITE, COLOR_WHITE, COLOR_WHITE
);
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
/**
* Draws the main menu state.
*/
void drawStateMainMenu();

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawstateoverworld.h"
#include "ui/textbox.h"
#include "rpg/world/map.h"
#include "game/game.h"
#include "display/draw/drawmap.h"
#include "display/draw/drawui.h"
void drawStateOverworld() {
map_t *map = GAME.currentMap;
if(map == NULL) return;
// Draw the map, based on player position
entity_t *player = mapEntityGetByType(map, ENTITY_TYPE_PLAYER);
uint16_t cameraPositionX, cameraPositionY;
if(player == NULL) {
cameraPositionX = 0;
cameraPositionY = 0;
} else {
cameraPositionX = player->x;
cameraPositionY = player->y;
}
drawMap(
GAME.currentMap,
cameraPositionX, cameraPositionY,
0, 0,
FRAME_WIDTH, FRAME_HEIGHT
);
// Draw UI
drawUITextbox();
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
/**
* Draws the overworld state.
*/
void drawStateOverworld();

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawtext.h"
#include "assert/assert.h"
#include "util/math.h"
void drawText(
const char_t *text,
const size_t len,
const uint16_t x,
const uint16_t y,
const uint8_t color
) {
if(len == 0) return;
size_t rLen = mathMin(len, strlen(text));
if(rLen == 0) return;
assertTrue(x < FRAME_WIDTH, "Text is too far right.");
assertTrue(y < FRAME_HEIGHT, "Text is too low.");
char_t c;
uint16_t xPos = x;
uint16_t yPos = y * FRAME_WIDTH;
size_t i = 0;
do {
c = text[i++];
if(c == '\n') {
yPos += FRAME_WIDTH;
xPos = x;
continue;
}
assertTrue(xPos < FRAME_WIDTH, "Text overflows right.");
FRAME_BUFFER[yPos + xPos] = c;
FRAME_COLOR[yPos + xPos] = color;
xPos++;
} while(i < rLen);
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
/**
* Draws some text to the frame buffer.
*
* @param text The text to draw.
* @param len The length of the text to draw.
* @param x The x position to draw the text.
* @param y The y position to draw the text.
* @param color The color to draw the text.
*/
void drawText(
const char_t *text,
const size_t len,
const uint16_t x,
const uint16_t y,
const uint8_t color
);

View File

@ -0,0 +1,229 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "drawui.h"
#include "drawshape.h"
#include "assert/assert.h"
#include "game/time.h"
#include "display/draw/drawtext.h"
#include "ui/textbox.h"
#include "ui/testmenu.h"
#include "util/math.h"
#include "util/memory.h"
void drawUIBox(
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t color,
const bool_t fill
) {
assertTrue(x < FRAME_WIDTH, "Box is too far right.");
assertTrue(y < FRAME_HEIGHT, "Box is too far down.");
assertTrue(width > 2, "Box is too narrow.");
assertTrue(height > 2, "Box is too short.");
assertTrue(x + width <= FRAME_WIDTH, "Box is too wide.");
assertTrue(y + height <= FRAME_HEIGHT, "Box is too tall.");
// Draw the top and bottom borders
for(uint16_t i = 0; i < width; i++) {
FRAME_BUFFER[y * FRAME_WIDTH + x + i] = '-';
FRAME_BUFFER[(y + height - 1) * FRAME_WIDTH + x + i] = '-';
FRAME_COLOR[y * FRAME_WIDTH + x + i] = color;
FRAME_COLOR[(y + height - 1) * FRAME_WIDTH + x + i] = color;
}
// Draw the left and right borders
for(uint16_t i = 0; i < height; i++) {
FRAME_BUFFER[(y + i) * FRAME_WIDTH + x] = '|';
FRAME_BUFFER[(y + i) * FRAME_WIDTH + x + width - 1] = '|';
FRAME_COLOR[(y + i) * FRAME_WIDTH + x] = color;
FRAME_COLOR[(y + i) * FRAME_WIDTH + x + width - 1] = color;
}
// Draw the corners
FRAME_BUFFER[y * FRAME_WIDTH + x] = '+';
FRAME_BUFFER[y * FRAME_WIDTH + x + width - 1] = '+';
FRAME_BUFFER[(y + height - 1) * FRAME_WIDTH + x] = '+';
FRAME_BUFFER[(y + height - 1) * FRAME_WIDTH + x + width - 1] = '+';
FRAME_COLOR[y * FRAME_WIDTH + x] = color;
FRAME_COLOR[y * FRAME_WIDTH + x + width - 1] = color;
FRAME_COLOR[(y + height - 1) * FRAME_WIDTH + x] = color;
FRAME_COLOR[(y + height - 1) * FRAME_WIDTH + x + width - 1] = color;
// Fill the inside
if(!fill) return;
drawBox(x + 1, y + 1, width - 2, height - 2, ' ', COLOR_BLACK);
}
void drawUIMenu(
menu_t *menu,
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t cursorColor,
const uint8_t textColor
) {
assertTrue(menu, "Menu cannot be NULL.");
assertTrue(menu->rows > 0, "Rows must be greater than 0.");
assertTrue(menu->columns > 0, "Columns must be greater than 0.");
assertTrue(width*2 > menu->columns, "Width must be greater than columns.");
uint16_t colWidth;
uint16_t startingCol;
uint16_t startingRow;
uint16_t rowCount;
uint16_t colCount;
char_t *str;
size_t i;
// Determine the width of each column. This must include space for the cursor
// and the text.
if(menu->columns == 1 || rowCount == 0) {
colWidth = width;
colCount = 1;
} else {
size_t longestString = 0;
i = 0;
while(true) {
str = menu->strings[i++];
if(str == NULL) break;
size_t len = strlen(str);
if(len > longestString) longestString = len;
// If we overrun the size of the box, we can stop looking.
if((len+1) > width) {
longestString = width;
break;
}
}
// How many columns can we fit?
uint16_t possibleColumnsAtThisWidth = width / (longestString + 1);
if(possibleColumnsAtThisWidth < menu->columns) {
// We need to take a subset of the columns
colWidth = width / possibleColumnsAtThisWidth;
colCount = possibleColumnsAtThisWidth;
} else {
// We can fit all columns in
colWidth = width / menu->columns;
colCount = menu->columns;
}
assertTrue(longestString != 0, "Longest string has length 0?");
}
// Determine count of rows to render
if(rowCount == 1) {
rowCount = 1;
} else {
rowCount = mathMin(rowCount, height);
}
// Where are we rendering from?
startingCol = menu->renderOffsetX;
if(menu->x >= startingCol + colCount) {
startingCol = (menu->x + 1) - colCount;
} else if(menu->x < startingCol) {
startingCol = menu->x;
}
startingRow = menu->renderOffsetY;
if(menu->y >= startingRow + rowCount) {
startingRow = (menu->y + 1) - rowCount;
} else if(menu->y < startingRow) {
startingRow = menu->y;
}
// Update back to the menu
menu->renderOffsetX = startingCol;
menu->renderOffsetY = startingRow;
for(uint16_t col = startingCol; col < startingCol+colCount; col++) {
for(uint16_t row = startingRow; row < startingRow+rowCount; row++) {
uint16_t index = (row * menu->columns) + col;
if(menu->strings[index] == NULL) continue;
drawText(
menu->strings[index],
colWidth,
x + (colWidth * (col - startingCol)) + 1,
y + (row - startingRow),
textColor
);
}
}
// Draw the cursor
i = (
x + ((menu->x - startingCol) * colWidth)
) + (
(y + (menu->y - startingRow)) * FRAME_WIDTH
);
FRAME_BUFFER[i] = '>';
FRAME_COLOR[i] = cursorColor;
}
void drawUIMenuBox(
menu_t *menu,
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t cursorColor,
const uint8_t textColor,
const uint8_t boxColor
) {
drawUIBox(x, y, width, height, boxColor, true);
drawUIMenu(
menu,
x + 1, y + 1,
width - 2, height - 2,
cursorColor, textColor
);
}
void drawUITextbox() {
if(!textboxIsOpen()) return;
// Border
drawUIBox(
0, FRAME_HEIGHT - DRAW_UI_TEXTBOX_HEIGHT,
DRAW_UI_TEXTBOX_WIDTH, DRAW_UI_TEXTBOX_HEIGHT,
COLOR_MAGENTA, true
);
// Title
drawText(
TEXTBOX.title,
-1,
2, FRAME_HEIGHT - DRAW_UI_TEXTBOX_HEIGHT,
COLOR_WHITE
);
// Text
drawText(
TEXTBOX.text,
TEXTBOX.textIndex,
1, FRAME_HEIGHT - DRAW_UI_TEXTBOX_HEIGHT + 1,
COLOR_WHITE
);
// Blinking cursor
memorySet(&FRAME_BUFFER[DRAW_UI_TEXTBOX_CURSOR_POS], ' ', 3);
memorySet(&FRAME_COLOR[DRAW_UI_TEXTBOX_CURSOR_POS], COLOR_WHITE, 3);
if(TEXTBOX.textIndex < TEXTBOX.textLength) return;
int32_t blink = (int32_t)(TIME.time * DRAW_UI_TEXTBOX_BLINKS_PER_SECOND) % 2;
FRAME_BUFFER[DRAW_UI_TEXTBOX_CURSOR_POS + 1] = blink ? '>' : ' ';
FRAME_BUFFER[DRAW_UI_TEXTBOX_CURSOR_POS + 2] = blink ? ' ' : '>';
}

View File

@ -0,0 +1,83 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/frame.h"
#include "ui/menu.h"
#define DRAW_UI_TEXTBOX_WIDTH FRAME_WIDTH
#define DRAW_UI_TEXTBOX_HEIGHT 8
#define DRAW_UI_TEXTBOX_CURSOR_POS ((FRAME_HEIGHT * FRAME_WIDTH) - 4)
#define DRAW_UI_TEXTBOX_BLINKS_PER_SECOND 2
/**
* Draws a UI box to the frame buffer.
*
* @param x The x position to draw the border.
* @param y The y position to draw the border.
* @param width The width of the border.
* @param height The height of the border.
* @param color The color to draw the border.
* @param fill Whether or not to fill the inside of the border.
*/
void drawUIBox(
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t color,
const bool_t fill
);
/**
* Draws a UI menu to the frame buffer.
*
* @param menu The menu to draw.
* @param x The x position to draw the menu.
* @param y The y position to draw the menu.
* @param width The width of the menu.
* @param height The height of the menu.
* @param cursorColor The color of the cursor.
* @param textColor The color of the text.
*/
void drawUIMenu(
menu_t *menu,
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t cursorColor,
const uint8_t textColor
);
/**
* Draws a UI menu box to the frame buffer.
*
* @param menu The menu to draw.
* @param x The x position to draw the menu.
* @param y The y position to draw the menu.
* @param width The width of the menu.
* @param height The height of the menu.
* @param cursorColor The color of the cursor.
* @param textColor The color of the text.
* @param boxColor The color of the box.
*/
void drawUIMenuBox(
menu_t *menu,
const uint16_t x,
const uint16_t y,
const uint16_t width,
const uint16_t height,
const uint8_t cursorColor,
const uint8_t textColor,
const uint8_t boxColor
);
/**
* Draws the UI textbox to the frame buffer.
*/
void drawUITextbox();

54
src/dawn/display/frame.c Normal file
View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "frame.h"
#include "game/game.h"
#include "display/draw/drawshape.h"
#include "display/draw/drawtext.h"
#include "display/draw/drawstateoverworld.h"
#include "display/draw/drawstatemainmenu.h"
#include "display/draw/drawbattle.h"
char_t FRAME_BUFFER[FRAME_HEIGHT * FRAME_WIDTH];
uint8_t FRAME_COLOR[FRAME_HEIGHT * FRAME_WIDTH];
void frameInit() {
drawClear(' ', COLOR_BLACK);
}
void frameUpdate() {
switch(GAME.state) {
case GAME_STATE_PAUSED:
const char_t *str = "PAUSED";
size_t len = strlen(str);
drawText(
str,
-1,
(FRAME_WIDTH - len) / 2,
(FRAME_HEIGHT - 1) / 2,
COLOR_WHITE
);
break;
case GAME_STATE_OVERWORLD:
drawStateOverworld();
break;
case GAME_STATE_MAIN_MENU:
drawStateMainMenu();
break;
case GAME_STATE_BATTLE:
drawBattle();
break;
default:
printf("Rendering unknown game state: %d\n", GAME.state);
break;
}
}

33
src/dawn/display/frame.h Normal file
View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "display/color.h"
#define FRAME_CHAR_SIZE 8
#define FRAME_PIXEL_WIDTH 320
#define FRAME_PIXEL_HEIGHT 240
#define FRAME_WIDTH FRAME_PIXEL_WIDTH/FRAME_CHAR_SIZE
#define FRAME_HEIGHT FRAME_PIXEL_HEIGHT/FRAME_CHAR_SIZE
extern char_t FRAME_BUFFER[FRAME_HEIGHT * FRAME_WIDTH];
extern uint8_t FRAME_COLOR[FRAME_HEIGHT * FRAME_WIDTH];
/**
* Initializes the frame buffer.
*/
void frameInit();
/**
* Updates the terminal frame.
*/
void frameUpdate();
/**
* Clears the frame buffer.
*/
void frameClear();

55
src/dawn/display/symbol.c Normal file
View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert/assert.h"
#include "rpg/entity/entitydirection.h"
#include "symbol.h"
#include "game/time.h"
char_t symbolGetCharByEntity(const entity_t *ent) {
assertNotNull(ent, "Entity cannot be NULL.");
switch(ent->type) {
case ENTITY_TYPE_SIGN:
return '+';
case ENTITY_TYPE_DOOR:
return 'D';
default:
break;
}
switch(ent->direction) {
case ENTITY_DIRECTION_EAST: return '>';
case ENTITY_DIRECTION_WEST: return '<';
case ENTITY_DIRECTION_NORTH: return '^';
case ENTITY_DIRECTION_SOUTH: return 'v';
default:
assertUnreachable("Invalid entity direction.");
}
}
uint8_t symbolGetColorByEntity(const entity_t *ent) {
assertNotNull(ent, "Entity cannot be NULL.");
switch(ent->type) {
case ENTITY_TYPE_PLAYER:
return COLOR_RED;
case ENTITY_TYPE_NPC:
return COLOR_MAGENTA;
case ENTITY_TYPE_SIGN:
return COLOR_YELLOW;
case ENTITY_TYPE_DOOR:
return COLOR_BROWN;
default:
assertUnreachable("Invalid entity type.");
}
}

26
src/dawn/display/symbol.h Normal file
View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/entity/entity.h"
#include "display/color.h"
/**
* Returns the symbol for the given entity.
*
* @param ent Entity to get the symbol for.
* @return The symbol for the entity.
*/
char_t symbolGetCharByEntity(const entity_t *ent);
/**
* Returns the color for the given entity.
*
* @param ent Entity to get the color for.
* @return The color for the entity.
*/
uint8_t symbolGetColorByEntity(const entity_t *ent);

View File

@ -0,0 +1,14 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
add_subdirectory(state)
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
time.c
game.c
)

94
src/dawn/game/game.c Normal file
View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "game/game.h"
#include "game/time.h"
#include "input.h"
#include "display/display.h"
#include "asset/asset.h"
#include "ui/textbox.h"
#include "ui/testmenu.h"
#include "ui/mainmenu.h"
#include "game/state/mainmenu.h"
#include "game/state/mapchange.h"
#include "game/state/overworld.h"
#include "game/state/battle.h"
#include "rpg/conversation/conversation.h"
#include "asset/assetlanguage.h"
#include "util/memory.h"
game_t GAME;
void gameInit() {
memoryInit();
memorySet(&GAME, 0, sizeof(game_t));
timeInit();
inputInit();
displayInit();
assetInit();
assetLanguageLoad("en.json");
textboxInit();
testMenuInit();
mainMenuInit();
conversationInit();
GAME.state = GAME_STATE_INITIAL;
}
gameupdateresult_t gameUpdate(const float_t delta) {
timeUpdate(delta);
inputUpdate();
switch(GAME.state) {
case GAME_STATE_INITIAL:
GAME.state = GAME_STATE_MAIN_MENU;
break;
case GAME_STATE_OVERWORLD:
gameStateOverworldUpdate();
break;
case GAME_STATE_PAUSED:
if(inputWasPressed(INPUT_BIND_PAUSE)) GAME.state = GAME_STATE_OVERWORLD;
break;
case GAME_STATE_MAP_CHANGE:
gameStateMapChangeUpdate();
break;
case GAME_STATE_MAIN_MENU:
gameStateMainMenuUpdate();
break;
case GAME_STATE_BATTLE:
gameStateBattleUpdate();
break;
default:
printf("Updating unknown state %d\n", GAME.state);
}
if(inputWasPressed(INPUT_BIND_FORCE_QUIT)) GAME.shouldExit = true;
// Perform render.
displayUpdate();
if(GAME.shouldExit) return GAME_UPDATE_RESULT_EXIT;
return GAME_UPDATE_RESULT_CONTINUE;
}
void gameDispose() {
assetDispose();
displayDispose();
memoryTestZero();
}

50
src/dawn/game/game.h Normal file
View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/world/map.h"
typedef enum {
GAME_UPDATE_RESULT_CONTINUE = 0,
GAME_UPDATE_RESULT_EXIT = 1
} gameupdateresult_t;
typedef enum {
GAME_STATE_INITIAL = 0,
GAME_STATE_OVERWORLD = 1,
GAME_STATE_PAUSED = 2,
GAME_STATE_MAP_CHANGE = 3,
GAME_STATE_MAIN_MENU = 4,
GAME_STATE_BATTLE = 5
} gamestate_t;
typedef struct {
map_t *currentMap;
gamestate_t state;
bool_t shouldExit;
maplist_t mapNext;
} game_t;
extern game_t GAME;
/**
* Initializes the game state.
*/
void gameInit();
/**
* Updates the game state.
*
* @param delta Time since last update.
* @return Game update result, 0 for continue, 1 for exit, else for failure.
*/
gameupdateresult_t gameUpdate(const float_t delta);
/**
* Cleans up the game state.
*/
void gameDispose();

View File

@ -0,0 +1,15 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
battle.c
mainmenu.c
mapchange.c
overworld.c
)

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "battle.h"
#include "game/game.h"
#include "ui/textbox.h"
#include "rpg/battle/battle.h"
void gameStateBattleUpdate() {
battleUpdate();
textboxUpdate();
}

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
/**
* Updates the battle state.
*/
void gameStateBattleUpdate();

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "mainmenu.h"
#include "ui/mainmenu.h"
void gameStateMainMenuUpdate() {
// Update the main menu
mainMenuUpdate();
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Updates the main menu state.
*/
void gameStateMainMenuUpdate();

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "mapchange.h"
#include "asset/assetmap.h"
#include "game/game.h"
#include "util/memory.h"
void gameStateMapChangeUpdate() {
// First try and find the player object
entity_t *player = NULL;
player_t oldPlayerData;
entitydirection_t dir = ENTITY_DIRECTION_SOUTH;
if(GAME.currentMap != NULL) {
player = mapEntityGetByType(GAME.currentMap, ENTITY_TYPE_PLAYER);
dir = player->direction;
oldPlayerData = player->player;
}
assetMapLoad(MAP_LIST_PATHS[GAME.mapNext], &MAP_MAIN);
GAME.state = GAME_STATE_OVERWORLD;
GAME.currentMap = &MAP_MAIN;
// Do not reference player since its invalid, just check if it DID exist
if(GAME.currentMap != NULL && player != NULL) {
player = mapEntityGetByType(GAME.currentMap, ENTITY_TYPE_PLAYER);
if(player == NULL) return;
player->player = oldPlayerData;
player->direction = dir;
}
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Updates the map change game state.
*/
void gameStateMapChangeUpdate();

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "overworld.h"
#include "game/game.h"
#include "input.h"
#include "ui/textbox.h"
#include "ui/testmenu.h"
#include "rpg/conversation/conversation.h"
void gameStateOverworldUpdate() {
textboxUpdate();
testMenuUpdate();
conversationUpdate();
if(GAME.currentMap) mapUpdate(GAME.currentMap);
if(inputWasPressed(INPUT_BIND_PAUSE)) GAME.state = GAME_STATE_PAUSED;
}

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
/**
* Updates the overworld game state.
*/
void gameStateOverworldUpdate();

23
src/dawn/game/time.c Normal file
View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "game/time.h"
#include "assert/assert.h"
dawntime_t TIME;
void timeInit() {
TIME.delta = 0.0f;
TIME.time = 0.0f;
}
void timeUpdate(const float_t delta) {
assertTrue(delta > 0, "time delta must be greater than 0.");
TIME.delta = delta;
TIME.time += delta;
}

28
src/dawn/game/time.h Normal file
View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef struct {
float_t delta;
float_t time;
} dawntime_t;
extern dawntime_t TIME;
/**
* Initializes the time system.
*/
void timeInit();
/**
* Updates the time system.
*
* @param delta Time since last update.
*/
void timeUpdate(const float_t delta);

41
src/dawn/input.c Normal file
View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "input.h"
#include "util/memory.h"
input_t INPUT;
void inputInit() {
memorySet(&INPUT, 0, sizeof(input_t));
}
void inputUpdate() {
// Update the previous state
memoryCopy(
INPUT.previousState,
INPUT.currentState,
sizeof(INPUT.previousState)
);
// Get the new state
for(inputbind_t i = 0; i < INPUT_BIND_COUNT; i++) {
INPUT.currentState[i] = inputGetState(i);
}
}
inputstate_t inputIsDown(const inputbind_t bind) {
return INPUT.currentState[bind];
}
inputstate_t inputWasPressed(const inputbind_t bind) {
return INPUT.currentState[bind] && !INPUT.previousState[bind];
}
inputstate_t inputWasReleased(const inputbind_t bind) {
return !INPUT.currentState[bind] && INPUT.previousState[bind];
}

68
src/dawn/input.h Normal file
View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2023 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef uint8_t inputbind_t;
typedef bool_t inputstate_t;
#define INPUT_BIND_UP 0x00
#define INPUT_BIND_DOWN 0x01
#define INPUT_BIND_LEFT 0x02
#define INPUT_BIND_RIGHT 0x03
#define INPUT_BIND_ACCEPT 0x04
#define INPUT_BIND_CANCEL 0x05
#define INPUT_BIND_PAUSE 0x06
#define INPUT_BIND_FORCE_QUIT 0x07
#define INPUT_BIND_COUNT (INPUT_BIND_FORCE_QUIT + 1)
typedef struct {
inputstate_t currentState[INPUT_BIND_COUNT];
inputstate_t previousState[INPUT_BIND_COUNT];
} input_t;
extern input_t INPUT;
/**
* Initializes the input system.
*/
void inputInit();
/**
* Updates the input system.
*/
void inputUpdate();
/**
* Returns whether or not the given input is down.
*
* @param bind The input to check.
*/
inputstate_t inputIsDown(const inputbind_t bind);
/**
* Returns whether or not the given input was pressed this update tick.
*
* @param bind The input to check.
*/
inputstate_t inputWasPressed(const inputbind_t bind);
/**
* Returns whether or not the given input was released this update tick.
*
* @param bind The input to check.
*/
inputstate_t inputWasReleased(const inputbind_t bind);
/**
* Returns the current state of the given input. Overridden per platform.
*
* @param bind The input to check.
*/
inputstate_t inputGetState(const inputbind_t bind);

View File

@ -0,0 +1,12 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
language.c
)

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "language.h"
#include "assert/assert.h"
#include "util/memory.h"
language_t LANGUAGE;
void languageInit() {
memorySet(&LANGUAGE, 0, sizeof(language_t));
}
const char_t * languageGetPointer(const char_t *key) {
assertNotNull(key, "Key cannot be NULL.");
language_t *lang = &LANGUAGE;
lang->count;
int32_t i = 0;
while(i < LANGUAGE.count) {
if(strcmp(key, LANGUAGE.keys[i]) != 0) {
i++;
continue;
}
return LANGUAGE.strings[i];
}
return NULL;
}
int32_t languageGet(char_t *buffer, const char_t *key) {
const char_t *str = languageGetPointer(key);
if(str == NULL) return -1;
if(buffer == NULL) return strlen(str);
strcpy(buffer, str);
return strlen(str);
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
#define LANGUAGE_STRING_COUNT_MAX 128
#define LANGUAGE_STRING_KEY_LENGTH_MAX 64
#define LANGUAGE_STRING_LENGTH_MAX 1024
typedef struct {
const char_t keys[LANGUAGE_STRING_COUNT_MAX][LANGUAGE_STRING_KEY_LENGTH_MAX];
const char_t strings[LANGUAGE_STRING_COUNT_MAX][LANGUAGE_STRING_LENGTH_MAX];
int32_t count;
} language_t;
extern language_t LANGUAGE;
/**
* Initializes the language system.
*/
void languageInit();
/**
* Gets the pointer to the language string for the given key. Pointer should not
* be modified or freed.
*
* @param key The key to get the string for.
* @return The pointer to the string.
*/
const char_t * languageGetPointer(const char_t *key);
/**
* Returns the language string for the given key.
*
* @param buffer The buffer to write the string to, or NULL to get length.
* @param key The key to get the string for.
* @return The length of the string.
*/
int32_t languageGet(char_t *buffer, const char_t *key);

View File

@ -0,0 +1,12 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
add_subdirectory(battle)
add_subdirectory(conversation)
add_subdirectory(entity)
add_subdirectory(world)
# Sources

View File

@ -0,0 +1,12 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
battle.c
)

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assert/assert.h"
#include "battle.h"
#include "util/memory.h"
#include "game/game.h"
#include "ui/textbox.h"
battle_t BATTLE;
void battleInit() {
memorySet(&BATTLE, 0, sizeof(battle_t));
}
void battleStart() {
GAME.state = GAME_STATE_BATTLE;
BATTLE.state = BATTLE_STATE_INITIAL;
textboxSetText(NULL, "battle.start");
}
void battleUpdate() {
switch(BATTLE.state) {
case BATTLE_STATE_INITIAL:
if(textboxIsOpen()) return;
break;
default:
assertUnreachable("Unknown battle state.");
break;
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dawn.h"
typedef enum {
BATTLE_STATE_INITIAL
} battlestate_t;
typedef struct {
} battlefighter_t;
typedef struct {
battlestate_t state;
} battle_t;
extern battle_t BATTLE;
/**
* Initializes the battle system.
*/
void battleInit();
/**
* Starts a battle.
*/
void battleStart(
);
/**
* Updates the battle system.
*/
void battleUpdate();

View File

@ -0,0 +1,14 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
conversation.c
conversationinteractentity.c
conversationinteracttile.c
)

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "conversation.h"
#include "assert/assert.h"
#include "util/memory.h"
conversation_t CONVERSATION;
void conversationInit() {
memorySet(&CONVERSATION, 0, sizeof(conversation_t));
}
void conversationSet(
const conversationcallback_t init,
const conversationcallback_t update,
const conversationdata_t data
) {
CONVERSATION.init = init;
CONVERSATION.update = update;
CONVERSATION.index = CONVERSATION_NOT_INITIALIZED;
CONVERSATION.data = data;
}
void conversationUpdate() {
if(CONVERSATION.init == NULL || CONVERSATION.update == NULL) return;
uint16_t ret;
bool_t recheck = true;
// Init or update.
while(recheck) {
switch(CONVERSATION.index) {
case CONVERSATION_NOT_INITIALIZED:
CONVERSATION.index = 0;
ret = CONVERSATION.init(
&CONVERSATION, CONVERSATION.index
);
break;
default:
ret = CONVERSATION.update(&CONVERSATION, CONVERSATION.index);
break;
}
// Check ret value and update conversation.
switch(ret) {
case CONVERSATION_NEXT:
CONVERSATION.index++;
CONVERSATION.index = CONVERSATION.init(
&CONVERSATION, CONVERSATION.index
);
break;
case CONVERSATION_CONTINUE:
recheck = false;
break;
case CONVERSATION_RESTART:
CONVERSATION.index = 0;
CONVERSATION.init(&CONVERSATION, CONVERSATION.index);
break;
case CONVERSATION_INVALID:
assertUnreachable("Invalid converstaion retval");
recheck = false;
break;
case CONVERSATION_DONE:
CONVERSATION.init = NULL;
CONVERSATION.update = NULL;
recheck = false;
break;
default:
CONVERSATION.index = ret;
break;
}
}
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "rpg/entity/entity.h"
#include "rpg/world/tile.h"
#define CONVERSATION_NEXT ((uint16_t)-1)
#define CONVERSATION_CONTINUE ((uint16_t)-2)
#define CONVERSATION_NOT_INITIALIZED ((uint16_t)-3)
#define CONVERSATION_RESTART CONVERSATION_NOT_INITIALIZED
#define CONVERSATION_DONE ((uint16_t)-4)
#define CONVERSATION_INVALID ((uint16_t)-5)
typedef struct _conversation_t conversation_t;
typedef uint16_t (*conversationcallback_t) (
conversation_t *convo, const uint16_t
);
typedef union {
struct {
entity_t *entity;
} entityInteract;
struct {
tile_t tile;
} tileInteract;
} conversationdata_t;
typedef struct _conversation_t {
uint16_t index;
conversationcallback_t init;
conversationcallback_t update;
conversationdata_t data;
} conversation_t;
extern conversation_t CONVERSATION;
/**
* Initializes the conversation object.
*/
void conversationInit();
/**
* Sets the conversation object.
*
* @param init The initialization callback.
* @param update The update callback.
* @param data The user data.
*/
void conversationSet(
const conversationcallback_t init,
const conversationcallback_t update,
const conversationdata_t data
);
/**
* Update a given conversation.
*/
void conversationUpdate();

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "conversationinteractentity.h"
#include "assert/assert.h"
#include "rpg/entity/entity.h"
#include "locale/language.h"
#include "ui/textbox.h"
uint16_t conversationInteractEntityInit(
conversation_t *convo,
const uint16_t i
) {
assertNotNull(convo, "Conversation is NULL!");
assertNotNull(convo->data.entityInteract.entity, "Conversation user is NULL!");
entity_t *e = (entity_t*)convo->data.entityInteract.entity;
switch(e->type) {
case ENTITY_TYPE_SIGN:
textboxSetText("entities.sign.name", e->sign.texts[i]);
return i;
case ENTITY_TYPE_NPC:
textboxSetText(e->npc.text.name, e->npc.text.text);
return i;
default:
assertUnreachable("Invalid entity type for conversation.");
return CONVERSATION_INVALID;
}
}
uint16_t conversationInteractEntityUpdate(
conversation_t *convo,
const uint16_t i
) {
assertNotNull(convo, "Conversation is NULL!");
assertNotNull(convo->data.entityInteract.entity, "Conversation user is NULL!");
entity_t *e = (entity_t*)convo->data.entityInteract.entity;
switch(e->type) {
case ENTITY_TYPE_SIGN:
if(textboxIsOpen()) return CONVERSATION_CONTINUE;
if(i == SIGN_TEXT_COUNT_MAX-1) return CONVERSATION_DONE;
if(e->sign.texts[i+1][0] == '\0') return CONVERSATION_DONE;
return CONVERSATION_NEXT;
default:
switch(i) {
case 0:
return textboxIsOpen() ? CONVERSATION_CONTINUE : CONVERSATION_DONE;
default:
return CONVERSATION_INVALID;
}
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "conversation.h"
/**
* Initializes the conversation object for an entity interaction.
*
* @param convo The conversation object to initialize.
* @param i The index to initialize the conversation at.
* @return The next index to run.
*/
uint16_t conversationInteractEntityInit(
conversation_t *convo, const uint16_t i
);
/**
* Updates the conversation object for an entity interaction.
*
* @param convo The conversation object to update.
* @param i The index to update the conversation at.
* @return The next index to run.
*/
uint16_t conversationInteractEntityUpdate(
conversation_t *convo, const uint16_t i
);

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "conversationinteractentity.h"
#include "assert/assert.h"
#include "rpg/entity/entity.h"
#include "locale/language.h"
#include "ui/textbox.h"
uint16_t conversationInteractTileInit(
conversation_t *convo,
const uint16_t i
) {
assertNotNull(convo, "Conversation is NULL!");
switch(convo->data.tileInteract.tile) {
case TILE_WATER:
textboxSetText(NULL, "tiles.water.interact");
break;
case TILE_LAMP:
textboxSetText(NULL, "tiles.lamp.interact");
break;
case TILE_RAIL_SLEEPER:
case TILE_RAIL_TRACK:
textboxSetText(NULL, "tiles.rail.interact");
break;
default:
assertUnreachable("Invalid tile interaction!");
return CONVERSATION_INVALID;
}
return 0;
}
uint16_t conversationInteractTileUpdate(
conversation_t *convo,
const uint16_t i
) {
switch(i) {
case 0:
return textboxIsOpen() ? CONVERSATION_CONTINUE : CONVERSATION_DONE;
default:
return CONVERSATION_INVALID;
}
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "conversation.h"
/**
* Initializes the conversation object for a tile interaction.
*
* @param convo The conversation object to initialize.
* @param i The index to initialize the conversation at.
* @return The next index to run.
*/
uint16_t conversationInteractTileInit(
conversation_t *convo, const uint16_t i
);
/**
* Updates the conversation object for a tile interaction.
*
* @param convo The conversation object to update.
* @param i The index to update the conversation at.
* @return The next index to run.
*/
uint16_t conversationInteractTileUpdate(
conversation_t *convo, const uint16_t i
);

View File

@ -0,0 +1,18 @@
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
entity.c
entitydirection.c
player.c
sign.c
interact.c
npc.c
door.c
)

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "door.h"
#include "assert/assert.h"
void doorInit(door_t *door) {
assertNotNull(door, "Door cannot be NULL.");
}
void doorDestinationSet(
door_t *door,
const int32_t x,
const int32_t y,
const entitydirection_t direction,
const maplist_t map
) {
assertNotNull(door, "Door cannot be NULL.");
door->x = x;
door->y = y;
door->direction = direction;
door->map = map;
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "entitydirection.h"
#include "rpg/world/maplist.h"
typedef struct {
int32_t x;
int32_t y;
entitydirection_t direction;
maplist_t map;
} door_t;
/**
* Initializes a door.
*
* @param door Door to initialize.
*/
void doorInit(door_t *door);
/**
* Sets the destination of a door.
*
* @param door Door to set the destination of.
* @param x X coordinate of the destination.
* @param y Y coordinate of the destination.
* @param direction Direction the player should face when they arrive.
* @param map Map the player should arrive in.
*/
void doorDestinationSet(
door_t *door,
const int32_t x,
const int32_t y,
const entitydirection_t direction,
const maplist_t map
);

View File

@ -0,0 +1,133 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entity.h"
#include "entitydirection.h"
#include "rpg/world/map.h"
#include "assert/assert.h"
#include "game/time.h"
#include "ui/textbox.h"
#include "util/memory.h"
void entityInit(
entity_t *entity,
const entitytype_t type,
map_t *map
) {
assertNotNull(entity, "Entity cannot be NULL.");
assertNotNull(map, "Map cannot be NULL.");
memorySet(entity, 0, sizeof(entity_t));
entity->type = type;
entity->map = map;
switch(type) {
case ENTITY_TYPE_NULL:
assertUnreachable("Should not be initializing a NULL entity.");
break;
case ENTITY_TYPE_NPC:
npcInit(&entity->npc);
break;
case ENTITY_TYPE_SIGN:
signInit(&entity->sign);
break;
case ENTITY_TYPE_DOOR:
doorInit(&entity->door);
break;
case ENTITY_TYPE_PLAYER:
playerInit(entity);
break;
}
}
void entityUpdate(entity_t *entity) {
assertNotNull(entity, "Entity cannot be NULL.");
switch(entity->state) {
case ENTITY_STATE_IDLE:
switch(entity->type) {
case ENTITY_TYPE_NULL:
assertUnreachable("Should not be updating a NULL entity.");
break;
case ENTITY_TYPE_PLAYER:
playerUpdate(entity);
break;
}
break;
case ENTITY_STATE_WALKING:
entity->walk.time -= TIME.delta;
if(entity->walk.time > 0.0f) return;
entity->state = ENTITY_STATE_IDLE;
break;
case ENTITY_STATE_TALKING:
if(!textboxIsOpen()) entity->state = ENTITY_STATE_IDLE;
break;
default:
assertUnreachable("Unknown entity state.");
}
}
void entityWalk(entity_t *entity, const entitydirection_t dir) {
assertNotNull(entity, "Entity cannot be NULL.");
uint16_t newX = entity->x, newY = entity->y;
entityDirectionOffsetAdd(dir, &newX, &newY);
// Update direction, this happens regardless of if the player can move
entity->direction = dir;
// Walking OOB?
if(newX >= entity->map->width || newY >= entity->map->height) {
return;
}
// Get tile at position
tile_t tileAtPosition = mapTileGetByPosition(
entity->map,
newX,
newY,
entity->layer
);
if(tileIsSolid(tileAtPosition)) return;
// Get entity at position
entity_t *entityAtPosition = mapEntityGetByPosition(
entity->map,
newX,
newY
);
if(entityAtPosition != NULL) return;
// Commit to move
entity->x = newX;
entity->y = newY;
entity->state = ENTITY_STATE_WALKING;
entity->walk.time = 0.1f;
// Check for triggers
trigger_t *trigger = mapTriggerGetByPosition(
entity->map,
entity->x,
entity->y
);
if(trigger != NULL) triggerOnStep(trigger, entity);
}
void entityPositionSet(entity_t *entity, const uint16_t x, const uint16_t y) {
assertNotNull(entity, "Entity cannot be NULL.");
entity->x = x;
entity->y = y;
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "player.h"
#include "sign.h"
#include "entitydirection.h"
#include "npc.h"
#include "door.h"
typedef struct _map_t map_t;
typedef enum {
ENTITY_TYPE_NULL = 0,
ENTITY_TYPE_PLAYER = 1,
ENTITY_TYPE_NPC = 2,
ENTITY_TYPE_SIGN = 3,
ENTITY_TYPE_DOOR = 4
} entitytype_t;
typedef enum {
ENTITY_STATE_IDLE = 0,
ENTITY_STATE_WALKING = 1,
ENTITY_STATE_TALKING = 2
} entitystate_t;
typedef struct {
float_t time;
} entitywalkstate_t;
typedef struct _entity_t {
map_t *map;
entitytype_t type;
uint16_t x;
uint16_t y;
uint8_t layer;
entitydirection_t direction;
entitystate_t state;
// State
union {
entitywalkstate_t walk;
};
// Type data
union {
player_t player;
npc_t npc;
sign_t sign;
door_t door;
};
} entity_t;
/**
* Initializes an entity.
*
* @param entity Entity to initialize.
* @param type Type of entity to initialize.
*/
void entityInit(
entity_t *entity,
const entitytype_t type,
map_t *map
);
/**
* Updates an entity.
*
* @param entity Entity to update.
*/
void entityUpdate(entity_t *entity);
/**
* Moves an entity.
*
* @param entity Entity to move.
* @param dir Direction to move.
*/
void entityWalk(entity_t *entity, const entitydirection_t dir);
/**
* Sets the position of an entity. Performs an immediate move.
*
* @param entity Entity to set position of.
* @param x X position to set.
* @param y Y position to set.
*/
void entityPositionSet(entity_t *entity, const uint16_t x, const uint16_t y);

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2024 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "entitydirection.h"
#include "assert/assert.h"
void entityDirectionOffsetGet(
const entitydirection_t dir,
uint16_t *x,
uint16_t *y
) {
switch(dir) {
case ENTITY_DIRECTION_SOUTH:
*x = 0;
*y = 1;
break;
case ENTITY_DIRECTION_NORTH:
*x = 0;
*y = -1;
break;
case ENTITY_DIRECTION_WEST:
*x = -1;
*y = 0;
break;
case ENTITY_DIRECTION_EAST:
*x = 1;
*y = 0;
break;
default:
assertUnreachable("Invalid entity direction.");
}
}
void entityDirectionOffsetAdd(
const entitydirection_t dir,
uint16_t *x,
uint16_t *y
) {
switch(dir) {
case ENTITY_DIRECTION_SOUTH:
*y += 1;
break;
case ENTITY_DIRECTION_NORTH:
*y -= 1;
break;
case ENTITY_DIRECTION_WEST:
*x -= 1;
break;
case ENTITY_DIRECTION_EAST:
*x += 1;
break;
default:
assertUnreachable("Invalid entity direction.");
}
}
entitydirection_t entityDirectionLookAt(
const uint16_t srcX, const uint16_t srcY,
const uint16_t trgX, const uint16_t trgY
) {
if(srcX < trgX) return ENTITY_DIRECTION_EAST;
if(srcX > trgX) return ENTITY_DIRECTION_WEST;
if(srcY < trgY) return ENTITY_DIRECTION_SOUTH;
if(srcY > trgY) return ENTITY_DIRECTION_NORTH;
return ENTITY_DIRECTION_SOUTH;
}

Some files were not shown because too many files have changed in this diff Show More