Add camera shake

This commit is contained in:
2026-07-11 11:02:09 -05:00
parent fbaa54145e
commit ca02ee0352
15 changed files with 335 additions and 185 deletions
+3 -3
View File
@@ -57,14 +57,14 @@ msgstr "Objetos"
msgid "ui.game_menu.settings" msgid "ui.game_menu.settings"
msgstr "Configuración" msgstr "Configuración"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.potion.name" msgid "item.potion.name"
msgstr "Poción" msgstr "Poción"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.potato.name" msgid "item.potato.name"
msgstr "Papa" msgstr "Papa"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.apple.name" msgid "item.apple.name"
msgstr "Manzana" msgstr "Manzana"
+3 -3
View File
@@ -57,14 +57,14 @@ msgstr "アイテム"
msgid "ui.game_menu.settings" msgid "ui.game_menu.settings"
msgstr "設定" msgstr "設定"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.potion.name" msgid "item.potion.name"
msgstr "ポーション" msgstr "ポーション"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.potato.name" msgid "item.potato.name"
msgstr "ジャガイモ" msgstr "ジャガイモ"
#: src/dusk/rpg/item/item.csv #: src/dusk/rpg/item/item.json
msgid "item.apple.name" msgid "item.apple.name"
msgstr "リンゴ" msgstr "リンゴ"
+1
View File
@@ -56,6 +56,7 @@ target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
DUSK_DISPLAY_HEIGHT=272 DUSK_DISPLAY_HEIGHT=272
DUSK_THREAD_PTHREAD DUSK_THREAD_PTHREAD
DUSK_TIME_DYNAMIC DUSK_TIME_DYNAMIC
DUSK_DISPLAY_OVERSCAN=6
) )
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+8
View File
@@ -154,6 +154,14 @@ typedef struct cutscene_s {
} \ } \
} }
// AMOUNT ranges 0 (no shake) to 4 (three tiles): 1 is half a tile, 2 is
// a full tile, 3 is two tiles, and 4 is three tiles.
#define CUTSCENE_SHAKE(AMOUNT, DURATION) \
{ \
.type = CUTSCENE_ITEM_TYPE_SHAKE, \
.shake = { .amount = AMOUNT, .duration = DURATION } \
}
#define CUTSCENE_SET_PAUSE(FLAGS) \ #define CUTSCENE_SET_PAUSE(FLAGS) \
{ .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) } { .type = CUTSCENE_ITEM_TYPE_SET_PAUSE, .setPause = (FLAGS) }
+138 -152
View File
@@ -7,160 +7,146 @@
#include "rpg/cutscene/cutscenesystem.h" #include "rpg/cutscene/cutscenesystem.h"
cutsceneitemcallbacks_t CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT] = {
[CUTSCENE_ITEM_TYPE_NULL] = { 0 },
[CUTSCENE_ITEM_TYPE_TEXT] = {
.init = cutsceneTextStart,
.update = cutsceneTextUpdate
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI] = {
.init = cutsceneTextMiniStart,
.update = cutsceneTextMiniUpdate
},
[CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE] = {
.init = cutsceneTextMiniHideStart,
.update = cutsceneTextMiniHideUpdate
},
[CUTSCENE_ITEM_TYPE_CALLBACK] = {
.init = cutsceneCallbackStart,
.update = cutsceneCallbackUpdate
},
[CUTSCENE_ITEM_TYPE_WAIT] = {
.init = cutsceneWaitStart,
.update = cutsceneWaitUpdate
},
[CUTSCENE_ITEM_TYPE_CUTSCENE] = {
.init = cutsceneCutsceneStart,
.update = cutsceneCutsceneUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT] = {
.init = cutsceneEntityTeleportStart,
.update = cutsceneEntityTeleportUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO] = {
.init = cutsceneEntityWalkToStart,
.update = cutsceneEntityWalkToUpdate
},
[CUTSCENE_ITEM_TYPE_FADE] = {
.init = cutsceneFadeStart,
.update = cutsceneFadeUpdate
},
[CUTSCENE_ITEM_TYPE_SET_PAUSE] = {
.init = cutsceneSetPauseStart,
.update = cutsceneSetPauseUpdate
},
[CUTSCENE_ITEM_TYPE_CONCURRENT] = {
.init = cutsceneConcurrentStart,
.update = cutsceneConcurrentUpdate
},
[CUTSCENE_ITEM_TYPE_ITEM_GIVE] = {
.init = cutsceneItemGiveStart,
.update = cutsceneItemGiveUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_REMOVE] = {
.init = cutsceneEntityRemoveStart,
.update = cutsceneEntityRemoveUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_ADD] = {
.init = cutsceneEntityAddStart,
.update = cutsceneEntityAddUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_TURN] = {
.init = cutsceneEntityTurnStart,
.update = cutsceneEntityTurnUpdate
},
[CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY] = {
.init = cutsceneEntityWalkToEntityStart,
.update = cutsceneEntityWalkToEntityUpdate
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_ADD] = {
.init = cutsceneMapAreaAddStart,
.update = cutsceneMapAreaAddUpdate
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE] = {
.init = cutsceneMapAreaRemoveStart,
.update = cutsceneMapAreaRemoveUpdate
},
[CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT] = {
.init = cutsceneMapAreaWaitStart,
.update = cutsceneMapAreaWaitUpdate
},
[CUTSCENE_ITEM_TYPE_START_BATTLE] = {
.init = cutsceneStartBattleStart,
.update = cutsceneStartBattleUpdate
},
[CUTSCENE_ITEM_TYPE_EMOJI] = {
.init = cutsceneEmojiStart,
.update = cutsceneEmojiUpdate
},
[CUTSCENE_ITEM_TYPE_SHAKE] = {
.init = cutsceneShakeStart,
.update = cutsceneShakeUpdate
}
};
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) { void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
switch(item->type) { cutsceneiteminitcallback_t *init = CUTSCENE_ITEM_CALLBACKS[item->type].init;
case CUTSCENE_ITEM_TYPE_TEXT: if(init != NULL) init(item, data);
cutsceneTextStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_TEXT_MINI:
cutsceneTextMiniStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE:
cutsceneTextMiniHideStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_CALLBACK:
cutsceneCallbackStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_WAIT:
cutsceneWaitStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_CUTSCENE:
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT:
cutsceneEntityTeleportStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
cutsceneEntityWalkToStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_FADE:
cutsceneFadeStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
cutsceneSetPauseStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_CONCURRENT:
cutsceneConcurrentStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ITEM_GIVE:
cutsceneItemGiveStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
cutsceneEntityRemoveStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
cutsceneEntityAddStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
cutsceneEntityTurnStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
cutsceneEntityWalkToEntityStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
cutsceneMapAreaAddStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
cutsceneMapAreaRemoveStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
cutsceneMapAreaWaitStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_START_BATTLE:
cutsceneStartBattleStart(item, data);
break;
case CUTSCENE_ITEM_TYPE_EMOJI:
cutsceneEmojiStart(item, data);
break;
default:
break;
}
} }
bool_t cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { bool_t cutsceneItemUpdate(
switch(item->type) { const cutsceneitem_t *item,
case CUTSCENE_ITEM_TYPE_TEXT: cutsceneitemdata_t *data
return cutsceneTextUpdate(item, data); ) {
cutsceneitemupdatecallback_t *update =
CUTSCENE_ITEM_CALLBACKS[item->type].update;
if(update == NULL) return false;
case CUTSCENE_ITEM_TYPE_TEXT_MINI: return update(item, data);
return cutsceneTextMiniUpdate(item, data); }
case CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE: void cutsceneCutsceneStart(
return cutsceneTextMiniHideUpdate(item, data); const cutsceneitem_t *item,
cutsceneitemdata_t *data
case CUTSCENE_ITEM_TYPE_CALLBACK: ) {
return cutsceneCallbackUpdate(item, data); if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
}
case CUTSCENE_ITEM_TYPE_WAIT:
return cutsceneWaitUpdate(item, data); bool_t cutsceneCutsceneUpdate(
const cutsceneitem_t *item,
case CUTSCENE_ITEM_TYPE_ENTITY_TELEPORT: cutsceneitemdata_t *data
return cutsceneEntityTeleportUpdate(item, data); ) {
return false;
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO:
return cutsceneEntityWalkToUpdate(item, data);
case CUTSCENE_ITEM_TYPE_FADE:
return cutsceneFadeUpdate(item, data);
case CUTSCENE_ITEM_TYPE_SET_PAUSE:
return cutsceneSetPauseUpdate(item, data);
case CUTSCENE_ITEM_TYPE_CONCURRENT:
return cutsceneConcurrentUpdate(item, data);
case CUTSCENE_ITEM_TYPE_ITEM_GIVE:
return cutsceneItemGiveUpdate(item, data);
case CUTSCENE_ITEM_TYPE_ENTITY_REMOVE:
return cutsceneEntityRemoveUpdate(item, data);
case CUTSCENE_ITEM_TYPE_ENTITY_ADD:
return cutsceneEntityAddUpdate(item, data);
case CUTSCENE_ITEM_TYPE_ENTITY_TURN:
return cutsceneEntityTurnUpdate(item, data);
case CUTSCENE_ITEM_TYPE_ENTITY_WALK_TO_ENTITY:
return cutsceneEntityWalkToEntityUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_ADD:
return cutsceneMapAreaAddUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE:
return cutsceneMapAreaRemoveUpdate(item, data);
case CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT:
return cutsceneMapAreaWaitUpdate(item, data);
case CUTSCENE_ITEM_TYPE_START_BATTLE:
return cutsceneStartBattleUpdate(item, data);
case CUTSCENE_ITEM_TYPE_EMOJI:
return cutsceneEmojiUpdate(item, data);
default:
return false;
}
} }
+51 -1
View File
@@ -21,6 +21,7 @@
#include "ui/cutscenetextminihide.h" #include "ui/cutscenetextminihide.h"
#include "ui/cutscenefade.h" #include "ui/cutscenefade.h"
#include "ui/cutsceneemoji.h" #include "ui/cutsceneemoji.h"
#include "ui/cutsceneshake.h"
#include "item/cutsceneitemgive.h" #include "item/cutsceneitemgive.h"
#include "maparea/cutscenemapareaadd.h" #include "maparea/cutscenemapareaadd.h"
#include "maparea/cutscenemaparearemove.h" #include "maparea/cutscenemaparearemove.h"
@@ -31,6 +32,7 @@ typedef struct cutscene_s cutscene_t;
typedef enum { typedef enum {
CUTSCENE_ITEM_TYPE_NULL, CUTSCENE_ITEM_TYPE_NULL,
CUTSCENE_ITEM_TYPE_TEXT, CUTSCENE_ITEM_TYPE_TEXT,
CUTSCENE_ITEM_TYPE_TEXT_MINI, CUTSCENE_ITEM_TYPE_TEXT_MINI,
CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE, CUTSCENE_ITEM_TYPE_TEXT_MINI_HIDE,
@@ -51,7 +53,10 @@ typedef enum {
CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE, CUTSCENE_ITEM_TYPE_MAP_AREA_REMOVE,
CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT, CUTSCENE_ITEM_TYPE_MAP_AREA_WAIT,
CUTSCENE_ITEM_TYPE_START_BATTLE, CUTSCENE_ITEM_TYPE_START_BATTLE,
CUTSCENE_ITEM_TYPE_EMOJI CUTSCENE_ITEM_TYPE_EMOJI,
CUTSCENE_ITEM_TYPE_SHAKE,
CUTSCENE_ITEM_TYPE_COUNT
} cutsceneitemtype_t; } cutsceneitemtype_t;
struct cutsceneitem_s { struct cutsceneitem_s {
@@ -79,6 +84,7 @@ struct cutsceneitem_s {
cutscenemapareawait_t mapAreaWait; cutscenemapareawait_t mapAreaWait;
cutscenestartbattle_t startBattle; cutscenestartbattle_t startBattle;
cutsceneemoji_t emoji; cutsceneemoji_t emoji;
cutsceneshake_t shake;
}; };
}; };
@@ -89,6 +95,24 @@ typedef union cutsceneitemdata_u {
cutscenemapareawaitdata_t mapAreaWait; cutscenemapareawaitdata_t mapAreaWait;
} cutsceneitemdata_t; } cutsceneitemdata_t;
typedef void (cutsceneiteminitcallback_t)(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
typedef bool_t (cutsceneitemupdatecallback_t)(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
typedef struct {
cutsceneiteminitcallback_t *init;
cutsceneitemupdatecallback_t *update;
} cutsceneitemcallbacks_t;
extern cutsceneitemcallbacks_t
CUTSCENE_ITEM_CALLBACKS[CUTSCENE_ITEM_TYPE_COUNT];
/** /**
* Start the given cutscene item. * Start the given cutscene item.
* *
@@ -111,3 +135,29 @@ bool_t cutsceneItemUpdate(
const cutsceneitem_t *item, const cutsceneitem_t *item,
cutsceneitemdata_t *data cutsceneitemdata_t *data
); );
/**
* Starts a nested-cutscene item, handing control over to the
* referenced cutscene.
*
* @param item The cutscene item.
* @param data Runtime data storage.
*/
void cutsceneCutsceneStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
/**
* Updates a nested-cutscene item. By the time this would run, control
* has already moved on to the referenced cutscene, so this always
* reports incomplete.
*
* @param item The cutscene item.
* @param data Runtime data storage.
* @returns false always.
*/
bool_t cutsceneCutsceneUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
@@ -10,4 +10,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
cutscenetextminihide.c cutscenetextminihide.c
cutscenefade.c cutscenefade.c
cutsceneemoji.c cutsceneemoji.c
cutsceneshake.c
) )
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "rpg/cutscene/item/cutsceneitem.h"
#include "rpg/rpgcamera.h"
void cutsceneShakeStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
rpgCameraShake(item->shake.amount, item->shake.duration);
}
bool_t cutsceneShakeUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
) {
return true;
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct cutsceneitem_s cutsceneitem_t;
typedef union cutsceneitemdata_u cutsceneitemdata_t;
typedef struct {
uint8_t amount;
float_t duration;
} cutsceneshake_t;
/**
* Starts a camera shake item (kicks off the shake on the RPG camera).
*
* @param item The cutscene item.
* @param data Runtime data storage.
*/
void cutsceneShakeStart(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
/**
* Updates a camera shake item. The shake itself runs asynchronously on
* the RPG camera, so this always completes immediately.
*
* @param item The cutscene item.
* @param data Runtime data storage.
* @returns true always.
*/
bool_t cutsceneShakeUpdate(
const cutsceneitem_t *item,
cutsceneitemdata_t *data
);
+3 -3
View File
@@ -14,9 +14,9 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Item Definitions # Item Definitions
dusk_run_python( dusk_run_python(
dusk_item_csv_defs dusk_item_json_defs
tools.item tools.item
--csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv --json ${CMAKE_CURRENT_SOURCE_DIR}/item.json
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/itemdef.h --output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/itemdef.h
) )
add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_csv_defs) add_dependencies(${DUSK_LIBRARY_TARGET_NAME} dusk_item_json_defs)
-4
View File
@@ -1,4 +0,0 @@
id,type,weight,name
POTION,MEDICINE,1.0,potion
POTATO,FOOD,0.5,potato
APPLE,FOOD,0.3,apple
1 id type weight name
2 POTION MEDICINE 1.0 potion
3 POTATO FOOD 0.5 potato
4 APPLE FOOD 0.3 apple
+5
View File
@@ -0,0 +1,5 @@
[
{ "id": "POTION", "type": "MEDICINE", "weight": 1.0, "name": "potion" },
{ "id": "POTATO", "type": "FOOD", "weight": 0.5, "name": "potato" },
{ "id": "APPLE", "type": "FOOD", "weight": 0.3, "name": "apple" }
]
+24
View File
@@ -7,12 +7,18 @@
#include "rpgcamera.h" #include "rpgcamera.h"
#include "util/memory.h" #include "util/memory.h"
#include "util/random.h"
#include "rpg/entity/entity.h" #include "rpg/entity/entity.h"
#include "rpg/overworld/map.h" #include "rpg/overworld/map.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "time/time.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
static const float_t RPG_CAMERA_SHAKE_AMOUNTS[] = {
0.0f, 0.5f, 1.0f, 2.0f, 3.0f
};
rpgcamera_t RPG_CAMERA; rpgcamera_t RPG_CAMERA;
void rpgCameraInit(void) { void rpgCameraInit(void) {
@@ -20,6 +26,13 @@ void rpgCameraInit(void) {
RPG_CAMERA.projectionDirty = true; RPG_CAMERA.projectionDirty = true;
} }
void rpgCameraShake(uint8_t amount, float_t duration) {
assertTrue(amount <= 4, "Camera shake amount must be between 0 and 4");
RPG_CAMERA.shakeAmount = RPG_CAMERA_SHAKE_AMOUNTS[amount];
RPG_CAMERA.shakeDuration = duration;
RPG_CAMERA.shakeTime = 0.0f;
}
void rpgCameraGetPosition(vec3 out) { void rpgCameraGetPosition(vec3 out) {
switch(RPG_CAMERA.mode) { switch(RPG_CAMERA.mode) {
case RPG_CAMERA_MODE_FREE: case RPG_CAMERA_MODE_FREE:
@@ -67,6 +80,13 @@ void rpgCameraUpdateEye(void) {
rpgCameraGetPosition(target); rpgCameraGetPosition(target);
glm_vec3_add(target, (vec3){ 0.5f, 0.5f, 0.5f }, target); glm_vec3_add(target, (vec3){ 0.5f, 0.5f, 0.5f }, target);
if(RPG_CAMERA.shakeTime < RPG_CAMERA.shakeDuration) {
float_t t = 1.0f - (RPG_CAMERA.shakeTime / RPG_CAMERA.shakeDuration);
float_t magnitude = RPG_CAMERA.shakeAmount * t;
target[0] += randomFloat(-magnitude, magnitude);
target[2] += randomFloat(-magnitude, magnitude);
}
glm_lookat( glm_lookat(
(vec3){ target[0], target[1] + offset, target[2] + z }, (vec3){ target[0], target[1] + offset, target[2] + z },
target, target,
@@ -90,6 +110,10 @@ void rpgCameraToScreen(vec3 worldPos, vec2 out) {
} }
errorret_t rpgCameraUpdate(void) { errorret_t rpgCameraUpdate(void) {
if(RPG_CAMERA.shakeTime < RPG_CAMERA.shakeDuration) {
RPG_CAMERA.shakeTime += TIME.delta;
}
if(!mapIsLoaded()) errorOk(); if(!mapIsLoaded()) errorOk();
vec3 pos; vec3 pos;
+15
View File
@@ -30,6 +30,10 @@ typedef struct {
mat4 eye; mat4 eye;
mat4 projection; mat4 projection;
bool_t projectionDirty; bool_t projectionDirty;
float_t shakeAmount;
float_t shakeDuration;
float_t shakeTime;
} rpgcamera_t; } rpgcamera_t;
extern rpgcamera_t RPG_CAMERA; extern rpgcamera_t RPG_CAMERA;
@@ -69,6 +73,17 @@ void rpgCameraUpdateProjection(void);
*/ */
void rpgCameraUpdateEye(void); void rpgCameraUpdateEye(void);
/**
* Shakes the RPG camera, randomly offsetting its position by a
* decreasing amount over the given duration.
*
* @param amount Shake strength from 0 (no shake) to 4 (three tiles).
* 1 is half a tile, 2 is a full tile, 3 is two tiles, and 4 is three
* tiles.
* @param duration How long the shake lasts, in seconds.
*/
void rpgCameraShake(uint8_t amount, float_t duration);
/** /**
* Converts a world-space position to screen-space pixel coordinates, * Converts a world-space position to screen-space pixel coordinates,
* using the camera's current eye and projection matrices. * using the camera's current eye and projection matrices.
+19 -19
View File
@@ -1,9 +1,9 @@
import argparse import argparse
import csv import json
import os import os
parser = argparse.ArgumentParser(description="Item CSV to .h defines") parser = argparse.ArgumentParser(description="Item JSON to .h defines")
parser.add_argument("--csv", required=True, help="Path to item CSV file") parser.add_argument("--json", required=True, help="Path to item JSON file")
parser.add_argument("--output", required=True, help="Path to output .h file") parser.add_argument("--output", required=True, help="Path to output .h file")
args = parser.parse_args() args = parser.parse_args()
@@ -13,26 +13,26 @@ def type_enum(name):
def id_enum(name): def id_enum(name):
return "ITEM_ID_" + name.upper() return "ITEM_ID_" + name.upper()
# Load CSV # Load JSON
item_ids = [] item_ids = []
item_types = [] item_types = []
rows = {} rows = {}
with open(args.csv, newline="", encoding="utf-8") as f: with open(args.json, encoding="utf-8") as f:
reader = csv.DictReader(f) entries = json.load(f)
if (
"id" not in reader.fieldnames or if not all(
"type" not in reader.fieldnames or "id" in row and "type" in row and "name" in row for row in entries
"name" not in reader.fieldnames ):
): raise ValueError("Each item must have 'id', 'type', and 'name' fields")
raise ValueError("CSV must have 'id', 'type', and 'name' columns")
for row in reader: for row in entries:
item_id, item_type = row["id"], row["type"] item_id, item_type = row["id"], row["type"]
if item_id not in item_ids: if item_id not in item_ids:
item_ids.append(item_id) item_ids.append(item_id)
if item_type not in item_types: if item_type not in item_types:
item_types.append(item_type) item_types.append(item_type)
rows[item_id] = row rows[item_id] = row
# Assign enum values: types and IDs each start from 1 with NULL = 0. # Assign enum values: types and IDs each start from 1 with NULL = 0.
type_values = {} type_values = {}