Add inventory.
This commit is contained in:
17
archive/rpg/CMakeLists.txt
Normal file
17
archive/rpg/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
rpg.c
|
||||
rpgcamera.c
|
||||
rpgtextbox.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(cutscene)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(overworld)
|
||||
14
archive/rpg/cutscene/CMakeLists.txt
Normal file
14
archive/rpg/cutscene/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutscenesystem.c
|
||||
cutscenemode.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(item)
|
||||
14
archive/rpg/cutscene/cutscene.h
Normal file
14
archive/rpg/cutscene/cutscene.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/item/cutsceneitem.h"
|
||||
|
||||
typedef struct cutscene_s {
|
||||
const cutsceneitem_t *items;
|
||||
uint8_t itemCount;
|
||||
} cutscene_t;
|
||||
19
archive/rpg/cutscene/cutscenemode.c
Normal file
19
archive/rpg/cutscene/cutscenemode.c
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
bool_t cutsceneModeIsInputAllowed() {
|
||||
switch(CUTSCENE_SYSTEM.mode) {
|
||||
case CUTSCENE_MODE_FULL_FREEZE:
|
||||
case CUTSCENE_MODE_INPUT_FREEZE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
26
archive/rpg/cutscene/cutscenemode.h
Normal file
26
archive/rpg/cutscene/cutscenemode.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
CUTSCENE_MODE_NONE,
|
||||
CUTSCENE_MODE_FULL_FREEZE,
|
||||
CUTSCENE_MODE_INPUT_FREEZE,
|
||||
CUTSCENE_MODE_GAMEPLAY
|
||||
} cutscenemode_t;
|
||||
|
||||
// Default mode for all cutscenes.
|
||||
#define CUTSCENE_MODE_INITIAL CUTSCENE_MODE_INPUT_FREEZE
|
||||
|
||||
/**
|
||||
* Check if input is allowed in the current cutscene mode.
|
||||
*
|
||||
* @return true if input is allowed, false otherwise.
|
||||
*/
|
||||
bool_t cutsceneModeIsInputAllowed();
|
||||
56
archive/rpg/cutscene/cutscenesystem.c
Normal file
56
archive/rpg/cutscene/cutscenesystem.c
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "cutscenesystem.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
void cutsceneSystemInit() {
|
||||
memoryZero(&CUTSCENE_SYSTEM, sizeof(cutscenesystem_t));
|
||||
}
|
||||
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene) {
|
||||
CUTSCENE_SYSTEM.scene = cutscene;
|
||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_INITIAL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;// Set to 0xFF so start wraps.
|
||||
cutsceneSystemNext();
|
||||
}
|
||||
|
||||
void cutsceneSystemUpdate() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||
|
||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||
cutsceneItemUpdate(item, &CUTSCENE_SYSTEM.data);
|
||||
}
|
||||
|
||||
void cutsceneSystemNext() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return;
|
||||
|
||||
CUTSCENE_SYSTEM.currentItem++;
|
||||
|
||||
// End of the cutscene?
|
||||
if(
|
||||
CUTSCENE_SYSTEM.currentItem >= CUTSCENE_SYSTEM.scene->itemCount
|
||||
) {
|
||||
CUTSCENE_SYSTEM.scene = NULL;
|
||||
CUTSCENE_SYSTEM.currentItem = 0xFF;
|
||||
CUTSCENE_SYSTEM.mode = CUTSCENE_MODE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Start item.
|
||||
const cutsceneitem_t *item = cutsceneSystemGetCurrentItem();
|
||||
memset(&CUTSCENE_SYSTEM.data, 0, sizeof(CUTSCENE_SYSTEM.data));
|
||||
cutsceneItemStart(item, &CUTSCENE_SYSTEM.data);
|
||||
}
|
||||
|
||||
const cutsceneitem_t * cutsceneSystemGetCurrentItem() {
|
||||
if(CUTSCENE_SYSTEM.scene == NULL) return NULL;
|
||||
|
||||
return &CUTSCENE_SYSTEM.scene->items[CUTSCENE_SYSTEM.currentItem];
|
||||
}
|
||||
50
archive/rpg/cutscene/cutscenesystem.h
Normal file
50
archive/rpg/cutscene/cutscenesystem.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscene.h"
|
||||
#include "cutscenemode.h"
|
||||
|
||||
typedef struct {
|
||||
const cutscene_t *scene;
|
||||
uint8_t currentItem;
|
||||
|
||||
// Data (used by the current item).
|
||||
cutsceneitemdata_t data;
|
||||
cutscenemode_t mode;
|
||||
} cutscenesystem_t;
|
||||
|
||||
extern cutscenesystem_t CUTSCENE_SYSTEM;
|
||||
|
||||
/**
|
||||
* Initialize the cutscene system.
|
||||
*/
|
||||
void cutsceneSystemInit();
|
||||
|
||||
/**
|
||||
* Start a cutscene.
|
||||
*
|
||||
* @param cutscene Pointer to the cutscene to start.
|
||||
*/
|
||||
void cutsceneSystemStartCutscene(const cutscene_t *cutscene);
|
||||
|
||||
/**
|
||||
* Advance to the next item in the cutscene.
|
||||
*/
|
||||
void cutsceneSystemNext();
|
||||
|
||||
/**
|
||||
* Update the cutscene system for one frame.
|
||||
*/
|
||||
void cutsceneSystemUpdate();
|
||||
|
||||
/**
|
||||
* Get the current cutscene item.
|
||||
*
|
||||
* @return Pointer to the current cutscene item.
|
||||
*/
|
||||
const cutsceneitem_t * cutsceneSystemGetCurrentItem();
|
||||
10
archive/rpg/cutscene/item/CMakeLists.txt
Executable file
10
archive/rpg/cutscene/item/CMakeLists.txt
Executable file
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
cutsceneitem.c
|
||||
)
|
||||
11
archive/rpg/cutscene/item/cutscenecallback.h
Normal file
11
archive/rpg/cutscene/item/cutscenecallback.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef void (*cutscenecallback_t)(void);
|
||||
54
archive/rpg/cutscene/item/cutsceneitem.c
Normal file
54
archive/rpg/cutscene/item/cutsceneitem.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "input/input.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
switch(item->type) {
|
||||
case CUTSCENE_ITEM_TYPE_TEXT: {
|
||||
rpgTextboxShow(
|
||||
item->text.position,
|
||||
item->text.text
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||
data->wait = item->wait;
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CALLBACK:
|
||||
if(item->callback != NULL) item->callback();
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_CUTSCENE:
|
||||
if(item->cutscene != NULL) cutsceneSystemStartCutscene(item->cutscene);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) {
|
||||
switch(item->type) {
|
||||
case CUTSCENE_ITEM_TYPE_TEXT:
|
||||
if(rpgTextboxIsVisible()) return;
|
||||
cutsceneSystemNext();
|
||||
break;
|
||||
|
||||
case CUTSCENE_ITEM_TYPE_WAIT:
|
||||
data->wait -= TIME.delta;
|
||||
if(data->wait <= 0) cutsceneSystemNext();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
53
archive/rpg/cutscene/item/cutsceneitem.h
Normal file
53
archive/rpg/cutscene/item/cutsceneitem.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "cutscenewait.h"
|
||||
#include "cutscenecallback.h"
|
||||
#include "cutscenetext.h"
|
||||
|
||||
typedef struct cutscene_s cutscene_t;
|
||||
|
||||
typedef enum {
|
||||
CUTSCENE_ITEM_TYPE_NULL,
|
||||
CUTSCENE_ITEM_TYPE_TEXT,
|
||||
CUTSCENE_ITEM_TYPE_CALLBACK,
|
||||
CUTSCENE_ITEM_TYPE_WAIT,
|
||||
CUTSCENE_ITEM_TYPE_CUTSCENE
|
||||
} cutsceneitemtype_t;
|
||||
|
||||
typedef struct cutsceneitem_s {
|
||||
cutsceneitemtype_t type;
|
||||
|
||||
// Arguments/Data that will be used when this item is invoked.
|
||||
union {
|
||||
cutscenetext_t text;
|
||||
cutscenecallback_t callback;
|
||||
cutscenewait_t wait;
|
||||
const cutscene_t *cutscene;
|
||||
};
|
||||
} cutsceneitem_t;
|
||||
|
||||
typedef union {
|
||||
cutscenewaitdata_t wait;
|
||||
} cutsceneitemdata_t;
|
||||
|
||||
/**
|
||||
* Start the given cutscene item.
|
||||
*
|
||||
* @param item The cutscene item to start.
|
||||
* @param data The cutscene item data storage.
|
||||
*/
|
||||
void cutsceneItemStart(const cutsceneitem_t *item, cutsceneitemdata_t *data);
|
||||
|
||||
/**
|
||||
* Tick the given cutscene item (one frame).
|
||||
*
|
||||
* @param item The cutscene item to tick.
|
||||
* @param data The cutscene item data storage.
|
||||
*/
|
||||
void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data);
|
||||
14
archive/rpg/cutscene/item/cutscenetext.h
Normal file
14
archive/rpg/cutscene/item/cutscenetext.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/rpgtextbox.h"
|
||||
|
||||
typedef struct {
|
||||
char_t text[RPG_TEXTBOX_MAX_CHARS];
|
||||
rpgtextboxpos_t position;
|
||||
} cutscenetext_t;
|
||||
12
archive/rpg/cutscene/item/cutscenewait.h
Normal file
12
archive/rpg/cutscene/item/cutscenewait.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef float_t cutscenewait_t;
|
||||
typedef float_t cutscenewaitdata_t;
|
||||
30
archive/rpg/cutscene/scene/testcutscene.h
Executable file
30
archive/rpg/cutscene/scene/testcutscene.h
Executable file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
|
||||
static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = {
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } },
|
||||
};
|
||||
|
||||
static const cutscene_t TEST_CUTSCENE_ONE = {
|
||||
.items = TEST_CUTSCENE_ONE_ITEMS,
|
||||
.itemCount = sizeof(TEST_CUTSCENE_ONE_ITEMS) / sizeof(cutsceneitem_t)
|
||||
};
|
||||
|
||||
static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = {
|
||||
{ .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f },
|
||||
{ .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE },
|
||||
};
|
||||
|
||||
static const cutscene_t TEST_CUTSCENE = {
|
||||
.items = TEST_CUTSCENE_TWO_ITEMS,
|
||||
.itemCount = sizeof(TEST_CUTSCENE_TWO_ITEMS) / sizeof(cutsceneitem_t)
|
||||
};
|
||||
14
archive/rpg/entity/CMakeLists.txt
Normal file
14
archive/rpg/entity/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entity.c
|
||||
entityanim.c
|
||||
npc.c
|
||||
player.c
|
||||
entitydir.c
|
||||
)
|
||||
200
archive/rpg/entity/entity.c
Normal file
200
archive/rpg/entity/entity.c
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "util/math.h"
|
||||
#include "rpg/cutscene/cutscenemode.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
|
||||
entity_t ENTITIES[ENTITY_COUNT];
|
||||
|
||||
void entityInit(entity_t *entity, const entitytype_t type) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertTrue(type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
|
||||
assertTrue(
|
||||
entity >= ENTITIES && entity < ENTITIES + ENTITY_COUNT,
|
||||
"Entity pointer is out of bounds"
|
||||
);
|
||||
|
||||
memoryZero(entity, sizeof(entity_t));
|
||||
entity->id = (uint8_t)(entity - ENTITIES);
|
||||
entity->type = type;
|
||||
|
||||
if(ENTITY_CALLBACKS[type].init != NULL) ENTITY_CALLBACKS[type].init(entity);
|
||||
}
|
||||
|
||||
void entityUpdate(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type");
|
||||
assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type");
|
||||
|
||||
// What state is the entity in?
|
||||
if(entity->animation != ENTITY_ANIM_IDLE) {
|
||||
// Entity is mid animation, tick it (down).
|
||||
entity->animTime -= TIME.delta;
|
||||
if(entity->animTime <= 0) {
|
||||
entity->animation = ENTITY_ANIM_IDLE;
|
||||
entity->animTime = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Movement code.
|
||||
if(
|
||||
cutsceneModeIsInputAllowed() &&
|
||||
ENTITY_CALLBACKS[entity->type].movement != NULL
|
||||
) {
|
||||
ENTITY_CALLBACKS[entity->type].movement(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void entityTurn(entity_t *entity, const entitydir_t direction) {
|
||||
entity->direction = direction;
|
||||
entity->animation = ENTITY_ANIM_TURN;
|
||||
entity->animTime = ENTITY_ANIM_TURN_DURATION;
|
||||
}
|
||||
|
||||
void entityWalk(entity_t *entity, const entitydir_t direction) {
|
||||
// TODO: Animation, delay, etc.
|
||||
entity->direction = direction;
|
||||
|
||||
// Where are we moving?
|
||||
worldpos_t newPos = entity->position;
|
||||
{
|
||||
worldunits_t relX, relY;
|
||||
entityDirGetRelative(direction, &relX, &relY);
|
||||
newPos.x += relX;
|
||||
newPos.y += relY;
|
||||
}
|
||||
|
||||
// Get tile under foot
|
||||
tile_t tileCurrent = mapGetTile(entity->position);
|
||||
tile_t tileNew = mapGetTile(newPos);
|
||||
bool_t fall = false;
|
||||
bool_t raise = false;
|
||||
|
||||
// Are we walking up a ramp?
|
||||
if(
|
||||
tileIsRamp(tileCurrent) &&
|
||||
(
|
||||
// Can only walk UP the direction the ramp faces.
|
||||
(direction+TILE_SHAPE_RAMP_SOUTH) == tileCurrent ||
|
||||
// If diagonal ramp, can go up one of two ways only.
|
||||
(
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHEAST &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_SOUTHWEST &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHEAST &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
tileCurrent == TILE_SHAPE_RAMP_NORTHWEST &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||
)
|
||||
)
|
||||
// Must be able to walk up.
|
||||
)
|
||||
) {
|
||||
tileNew = TILE_SHAPE_NULL;// Force check for ramp above.
|
||||
worldpos_t abovePos = newPos;
|
||||
abovePos.z += 1;
|
||||
tile_t tileAbove = mapGetTile(abovePos);
|
||||
|
||||
if(tileAbove != TILE_SHAPE_NULL && tileIsWalkable(tileAbove)) {
|
||||
// We can go up the ramp.
|
||||
raise = true;
|
||||
}
|
||||
} else if(tileNew == TILE_SHAPE_NULL && newPos.z > 0) {
|
||||
// Falling down?
|
||||
worldpos_t belowPos = newPos;
|
||||
belowPos.z -= 1;
|
||||
tile_t tileBelow = mapGetTile(belowPos);
|
||||
if(
|
||||
tileBelow != TILE_SHAPE_NULL &&
|
||||
tileIsRamp(tileBelow) &&
|
||||
(
|
||||
// This handles regular cardinal ramps
|
||||
(entityDirGetOpposite(direction)+TILE_SHAPE_RAMP_SOUTH) == tileBelow ||
|
||||
// This handles diagonal ramps
|
||||
(
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHEAST &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_SOUTHWEST &&
|
||||
(direction == ENTITY_DIR_NORTH || direction == ENTITY_DIR_EAST)
|
||||
) ||
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHEAST &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_WEST)
|
||||
) ||
|
||||
(
|
||||
tileBelow == TILE_SHAPE_RAMP_NORTHWEST &&
|
||||
(direction == ENTITY_DIR_SOUTH || direction == ENTITY_DIR_EAST)
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
// We will fall to this tile.
|
||||
fall = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Can we walk here?
|
||||
if(!raise && !fall && !tileIsWalkable(tileNew)) return;// Blocked
|
||||
|
||||
// Entity in way?
|
||||
entity_t *other = ENTITIES;
|
||||
do {
|
||||
if(other == entity) continue;
|
||||
if(other->type == ENTITY_TYPE_NULL) continue;
|
||||
if(!worldPosIsEqual(other->position, newPos)) continue;
|
||||
return;// Blocked
|
||||
} while(++other, other < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
entity->lastPosition = entity->position;
|
||||
entity->position = newPos;
|
||||
entity->animation = ENTITY_ANIM_WALK;
|
||||
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
||||
|
||||
if(raise) {
|
||||
entity->position.z += 1;
|
||||
} else if(fall) {
|
||||
entity->position.z -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
entity_t * entityGetAt(const worldpos_t position) {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
if(!worldPosIsEqual(ent->position, position)) continue;
|
||||
return ent;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t entityGetAvailable() {
|
||||
entity_t *ent = ENTITIES;
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) return ent - ENTITIES;
|
||||
} while(++ent, ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
return 0xFF;
|
||||
}
|
||||
77
archive/rpg/entity/entity.h
Normal file
77
archive/rpg/entity/entity.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entitydir.h"
|
||||
#include "entityanim.h"
|
||||
#include "entitytype.h"
|
||||
#include "npc.h"
|
||||
|
||||
typedef struct map_s map_t;
|
||||
|
||||
typedef struct entity_s {
|
||||
uint8_t id;
|
||||
entitytype_t type;
|
||||
entitytypedata_t data;
|
||||
|
||||
// Movement
|
||||
entitydir_t direction;
|
||||
worldpos_t position;
|
||||
worldpos_t lastPosition;
|
||||
|
||||
entityanim_t animation;
|
||||
float_t animTime;
|
||||
} entity_t;
|
||||
|
||||
extern entity_t ENTITIES[ENTITY_COUNT];
|
||||
|
||||
/**
|
||||
* Initializes an entity structure.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
* @param type The type of the entity.
|
||||
*/
|
||||
void entityInit(entity_t *entity, const entitytype_t type);
|
||||
|
||||
/**
|
||||
* Updates an entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void entityUpdate(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Turn an entity to face a new direction.
|
||||
*
|
||||
* @param entity Pointer to the entity to turn.
|
||||
* @param direction The direction to face.
|
||||
*/
|
||||
void entityTurn(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Make an entity walk in a direction.
|
||||
*
|
||||
* @param entity Pointer to the entity to make walk.
|
||||
* @param direction The direction to walk in.
|
||||
*/
|
||||
void entityWalk(entity_t *entity, const entitydir_t direction);
|
||||
|
||||
/**
|
||||
* Gets the entity at a specific world position.
|
||||
*
|
||||
* @param map Pointer to the map to check.
|
||||
* @param pos The world position to check.
|
||||
* @return Pointer to the entity at the position, or NULL if none.
|
||||
*/
|
||||
entity_t *entityGetAt(const worldpos_t pos);
|
||||
|
||||
/**
|
||||
* Gets an available entity index.
|
||||
*
|
||||
* @return The index of an available entity, or 0xFF if none are available.
|
||||
*/
|
||||
uint8_t entityGetAvailable();
|
||||
9
archive/rpg/entity/entityanim.c
Normal file
9
archive/rpg/entity/entityanim.c
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityanim.h"
|
||||
|
||||
18
archive/rpg/entity/entityanim.h
Normal file
18
archive/rpg/entity/entityanim.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define ENTITY_ANIM_TURN_DURATION 0.06f
|
||||
#define ENTITY_ANIM_WALK_DURATION 0.1f
|
||||
|
||||
typedef enum {
|
||||
ENTITY_ANIM_IDLE,
|
||||
ENTITY_ANIM_TURN,
|
||||
ENTITY_ANIM_WALK,
|
||||
} entityanim_t;
|
||||
51
archive/rpg/entity/entitydir.c
Normal file
51
archive/rpg/entity/entitydir.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitydir.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
entitydir_t entityDirGetOpposite(const entitydir_t dir) {
|
||||
switch(dir) {
|
||||
case ENTITY_DIR_NORTH: return ENTITY_DIR_SOUTH;
|
||||
case ENTITY_DIR_SOUTH: return ENTITY_DIR_NORTH;
|
||||
case ENTITY_DIR_EAST: return ENTITY_DIR_WEST;
|
||||
case ENTITY_DIR_WEST: return ENTITY_DIR_EAST;
|
||||
default: return dir;
|
||||
}
|
||||
}
|
||||
|
||||
void entityDirGetRelative(
|
||||
const entitydir_t from,
|
||||
worldunits_t *outX,
|
||||
worldunits_t *outY
|
||||
) {
|
||||
assertValidEntityDir(from, "Invalid direction provided");
|
||||
assertNotNull(outX, "Output X pointer cannot be NULL");
|
||||
assertNotNull(outY, "Output Y pointer cannot be NULL");
|
||||
|
||||
switch(from) {
|
||||
case ENTITY_DIR_NORTH:
|
||||
*outX = 0;
|
||||
*outY = -1;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_EAST:
|
||||
*outX = 1;
|
||||
*outY = 0;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_SOUTH:
|
||||
*outX = 0;
|
||||
*outY = 1;
|
||||
break;
|
||||
|
||||
case ENTITY_DIR_WEST:
|
||||
*outX = -1;
|
||||
*outY = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
50
archive/rpg/entity/entitydir.h
Normal file
50
archive/rpg/entity/entitydir.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_DIR_UP = ENTITY_DIR_NORTH,
|
||||
ENTITY_DIR_DOWN = ENTITY_DIR_SOUTH,
|
||||
ENTITY_DIR_LEFT = ENTITY_DIR_WEST,
|
||||
ENTITY_DIR_RIGHT = ENTITY_DIR_EAST,
|
||||
} entitydir_t;
|
||||
|
||||
/**
|
||||
* Gets the opposite direction of a given direction.
|
||||
*
|
||||
* @param dir The direction to get the opposite of.
|
||||
* @return entitydir_t The opposite direction.
|
||||
*/
|
||||
entitydir_t entityDirGetOpposite(const entitydir_t dir);
|
||||
|
||||
/**
|
||||
* Asserts a given direction is valid.
|
||||
*
|
||||
* @param dir The direction to validate.
|
||||
* @param msg The message to display if the assertion fails.
|
||||
*/
|
||||
#define assertValidEntityDir(dir, msg) \
|
||||
assertTrue( \
|
||||
(dir) == ENTITY_DIR_NORTH || \
|
||||
(dir) == ENTITY_DIR_EAST || \
|
||||
(dir) == ENTITY_DIR_SOUTH || \
|
||||
(dir) == ENTITY_DIR_WEST, \
|
||||
msg \
|
||||
)
|
||||
|
||||
/**
|
||||
* Gets the relative x and y offsets for a given direction.
|
||||
*
|
||||
* @param dir The direction to get offsets for.
|
||||
* @param relX Pointer to store the relative x offset.
|
||||
* @param relY Pointer to store the relative y offset.
|
||||
*/
|
||||
void entityDirGetRelative(
|
||||
const entitydir_t dir, worldunits_t *relX, worldunits_t *relY
|
||||
);
|
||||
55
archive/rpg/entity/entitytype.h
Normal file
55
archive/rpg/entity/entitytype.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "duskdefs.h"
|
||||
#include "rpg/entity/player.h"
|
||||
#include "npc.h"
|
||||
|
||||
typedef uint8_t entitytype_t;
|
||||
|
||||
typedef union {
|
||||
player_t player;
|
||||
npc_t npc;
|
||||
} entitytypedata_t;
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* Initialization callback for the entity type.
|
||||
* @param entity Pointer to the entity to initialize.
|
||||
*/
|
||||
void (*init)(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Movement callback for the entity type.
|
||||
* @param entity Pointer to the entity to move.
|
||||
*/
|
||||
void (*movement)(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Interaction callback for the entity type.
|
||||
* @param player Pointer to the player entity.
|
||||
* @param entity Pointer to the entity to interact with.
|
||||
* @return True if the entity handled the interaction, false otherwise.
|
||||
*/
|
||||
bool_t (*interact)(entity_t *player, entity_t *entity);
|
||||
} entitycallback_t;
|
||||
|
||||
static const entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = {
|
||||
[ENTITY_TYPE_NULL] = { NULL },
|
||||
|
||||
[ENTITY_TYPE_PLAYER] = {
|
||||
.init = playerInit,
|
||||
.movement = playerInput
|
||||
},
|
||||
|
||||
[ENTITY_TYPE_NPC] = {
|
||||
.init = npcInit,
|
||||
.movement = npcMovement,
|
||||
.interact = npcInteract
|
||||
}
|
||||
};
|
||||
31
archive/rpg/entity/npc.c
Normal file
31
archive/rpg/entity/npc.c
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
#include "rpg/cutscene/scene/testcutscene.h"
|
||||
#include "rpg/rpgtextbox.h"
|
||||
|
||||
void npcInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
}
|
||||
|
||||
void npcMovement(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
}
|
||||
|
||||
bool_t npcInteract(entity_t *player, entity_t *npc) {
|
||||
assertNotNull(player, "Player entity pointer cannot be NULL");
|
||||
assertNotNull(npc, "NPC entity pointer cannot be NULL");
|
||||
|
||||
cutsceneSystemStartCutscene(&TEST_CUTSCENE);
|
||||
|
||||
// rpgTextboxShow(RPG_TEXTBOX_POS_BOTTOM, "Hello World!");
|
||||
|
||||
return false;
|
||||
};
|
||||
37
archive/rpg/entity/npc.h
Normal file
37
archive/rpg/entity/npc.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} npc_t;
|
||||
|
||||
/**
|
||||
* Initializes an NPC entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
*/
|
||||
void npcInit(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Updates an NPC entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to update.
|
||||
*/
|
||||
void npcMovement(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Handles interaction with an NPC entity.
|
||||
*
|
||||
* @param player Pointer to the player entity.
|
||||
* @param npc Pointer to the NPC entity.
|
||||
*/
|
||||
bool_t npcInteract(entity_t *player, entity_t *npc);
|
||||
54
archive/rpg/entity/player.c
Normal file
54
archive/rpg/entity/player.c
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity.h"
|
||||
#include "assert/assert.h"
|
||||
#include "rpg/rpgcamera.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void playerInit(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
}
|
||||
|
||||
void playerInput(entity_t *entity) {
|
||||
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||
|
||||
// Turn
|
||||
const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP;
|
||||
do {
|
||||
if(!inputIsDown(dirMap->action)) continue;
|
||||
if(entity->direction == dirMap->direction) continue;
|
||||
return entityTurn(entity, dirMap->direction);
|
||||
} while((++dirMap)->action != 0xFF);
|
||||
|
||||
// Walk
|
||||
dirMap = PLAYER_INPUT_DIR_MAP;
|
||||
do {
|
||||
if(!inputIsDown(dirMap->action)) continue;
|
||||
if(entity->direction != dirMap->direction) continue;
|
||||
return entityWalk(entity, dirMap->direction);
|
||||
} while((++dirMap)->action != 0xFF);
|
||||
|
||||
// Interaction
|
||||
if(inputPressed(INPUT_ACTION_ACCEPT)) {
|
||||
worldunit_t x, y, z;
|
||||
{
|
||||
worldunits_t relX, relY;
|
||||
entityDirGetRelative(entity->direction, &relX, &relY);
|
||||
x = entity->position.x + relX;
|
||||
y = entity->position.y + relY;
|
||||
z = entity->position.z;
|
||||
}
|
||||
|
||||
entity_t *interact = entityGetAt((worldpos_t){ x, y, z });
|
||||
if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) {
|
||||
if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
43
archive/rpg/entity/player.h
Normal file
43
archive/rpg/entity/player.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "input/input.h"
|
||||
|
||||
typedef struct entity_s entity_t;
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} player_t;
|
||||
|
||||
typedef struct {
|
||||
inputaction_t action;
|
||||
entitydir_t direction;
|
||||
} playerinputdirmap_t;
|
||||
|
||||
static const playerinputdirmap_t PLAYER_INPUT_DIR_MAP[] = {
|
||||
{ INPUT_ACTION_UP, ENTITY_DIR_NORTH },
|
||||
{ INPUT_ACTION_DOWN, ENTITY_DIR_SOUTH },
|
||||
{ INPUT_ACTION_LEFT, ENTITY_DIR_WEST },
|
||||
{ INPUT_ACTION_RIGHT, ENTITY_DIR_EAST },
|
||||
|
||||
{ 0xFF, 0xFF }
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a player entity.
|
||||
*
|
||||
* @param entity Pointer to the entity structure to initialize.
|
||||
*/
|
||||
void playerInit(entity_t *entity);
|
||||
|
||||
/**
|
||||
* Handles movement logic for the player entity.
|
||||
*
|
||||
* @param entity Pointer to the player entity structure.
|
||||
*/
|
||||
void playerInput(entity_t *entity);
|
||||
13
archive/rpg/item/inventory.h
Normal file
13
archive/rpg/item/inventory.h
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef struct {
|
||||
void *nothing;
|
||||
} inventory_t;
|
||||
13
archive/rpg/overworld/CMakeLists.txt
Normal file
13
archive/rpg/overworld/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
chunk.c
|
||||
map.c
|
||||
worldpos.c
|
||||
tile.c
|
||||
)
|
||||
20
archive/rpg/overworld/chunk.c
Normal file
20
archive/rpg/overworld/chunk.c
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "chunk.h"
|
||||
|
||||
uint32_t chunkGetTileIndex(const chunkpos_t position) {
|
||||
return (
|
||||
(position.z * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
||||
(position.y * CHUNK_WIDTH) +
|
||||
position.x
|
||||
);
|
||||
}
|
||||
|
||||
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b) {
|
||||
return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
|
||||
}
|
||||
38
archive/rpg/overworld/chunk.h
Normal file
38
archive/rpg/overworld/chunk.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/tile.h"
|
||||
#include "worldpos.h"
|
||||
#include "display/mesh/quad.h"
|
||||
|
||||
typedef struct chunk_s {
|
||||
chunkpos_t position;
|
||||
tile_t tiles[CHUNK_TILE_COUNT];
|
||||
|
||||
uint8_t meshCount;
|
||||
meshvertex_t vertices[CHUNK_VERTEX_COUNT_MAX];
|
||||
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
|
||||
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||
} chunk_t;
|
||||
|
||||
/**
|
||||
* Gets the tile index for a tile position within a chunk.
|
||||
*
|
||||
* @param position The position within the chunk.
|
||||
* @return The tile index within the chunk.
|
||||
*/
|
||||
uint32_t chunkGetTileIndex(const chunkpos_t position);
|
||||
|
||||
/**
|
||||
* Checks if two chunk positions are equal.
|
||||
*
|
||||
* @param a The first chunk position.
|
||||
* @param b The second chunk position.
|
||||
* @return true if equal, false otherwise.
|
||||
*/
|
||||
bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b);
|
||||
269
archive/rpg/overworld/map.c
Normal file
269
archive/rpg/overworld/map.c
Normal file
@@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "map.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/asset.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "util/string.h"
|
||||
#include "script/scriptcontext.h"
|
||||
|
||||
map_t MAP;
|
||||
|
||||
errorret_t mapInit() {
|
||||
memoryZero(&MAP, sizeof(map_t));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
bool_t mapIsLoaded() {
|
||||
return MAP.filePath[0] != '\0';
|
||||
}
|
||||
|
||||
errorret_t mapLoad(const char_t *path, const chunkpos_t position) {
|
||||
assertStrLenMin(path, 1, "Map file path cannot be empty");
|
||||
assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long");
|
||||
|
||||
if(stringCompare(MAP.filePath, path) == 0) {
|
||||
// Same map, no need to reload
|
||||
errorOk();
|
||||
}
|
||||
|
||||
chunkindex_t i;
|
||||
|
||||
// Unload all loaded chunks
|
||||
if(mapIsLoaded()) {
|
||||
for(i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
mapChunkUnload(&MAP.chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the map file path
|
||||
stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX);
|
||||
|
||||
// Determine directory path (it is dirname)
|
||||
stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX);
|
||||
char_t *last = stringFindLastChar(MAP.dirPath, '/');
|
||||
if(last == NULL) errorThrow("Invalid map file path");
|
||||
|
||||
// Store filename, sans extension
|
||||
stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX);
|
||||
*last = '\0'; // Terminate to get directory path
|
||||
|
||||
last = stringFindLastChar(MAP.fileName, '.');
|
||||
if(last == NULL) errorThrow("Map file name has no extension");
|
||||
*last = '\0'; // Terminate to remove extension
|
||||
|
||||
// Reset map position
|
||||
MAP.chunkPosition = position;
|
||||
|
||||
// Perform "initial load"
|
||||
i = 0;
|
||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||
for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) {
|
||||
for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) {
|
||||
chunk_t *chunk = &MAP.chunks[i];
|
||||
chunk->position.x = x + position.x;
|
||||
chunk->position.y = y + position.y;
|
||||
chunk->position.z = z + position.z;
|
||||
MAP.chunkOrder[i] = chunk;
|
||||
errorChain(mapChunkLoad(chunk));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute map script.
|
||||
char_t scriptPath[MAP_FILE_PATH_MAX + 16];
|
||||
stringFormat(
|
||||
scriptPath, sizeof(scriptPath), "%s/%s.dsf",
|
||||
MAP.dirPath, MAP.fileName
|
||||
);
|
||||
if(assetFileExists(scriptPath)) {
|
||||
scriptcontext_t ctx;
|
||||
errorChain(scriptContextInit(&ctx));
|
||||
errorChain(scriptContextExecFile(&ctx, scriptPath));
|
||||
scriptContextDispose(&ctx);
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
|
||||
const chunkpos_t curPos = MAP.chunkPosition;
|
||||
if(chunkPositionIsEqual(curPos, newPos)) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Determine which chunks remain loaded
|
||||
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
|
||||
chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0};
|
||||
|
||||
uint32_t remainingCount = 0;
|
||||
uint32_t freedCount = 0;
|
||||
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
// Will this chunk remain loaded?
|
||||
chunk_t *chunk = &MAP.chunks[i];
|
||||
if(
|
||||
chunk->position.x >= newPos.x &&
|
||||
chunk->position.x < newPos.x + MAP_CHUNK_WIDTH &&
|
||||
|
||||
chunk->position.y >= newPos.y &&
|
||||
chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT &&
|
||||
|
||||
chunk->position.z >= newPos.z &&
|
||||
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
|
||||
) {
|
||||
// Stays loaded
|
||||
chunksRemaining[remainingCount++] = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not remaining loaded
|
||||
chunksFreed[freedCount++] = i;
|
||||
}
|
||||
|
||||
// Unload the freed chunks
|
||||
for(chunkindex_t i = 0; i < freedCount; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[chunksFreed[i]];
|
||||
mapChunkUnload(chunk);
|
||||
}
|
||||
|
||||
// This can probably be optimized later, for now we check each chunk and see
|
||||
// if it needs loading or not, and update the chunk order
|
||||
chunkindex_t orderIndex = 0;
|
||||
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
|
||||
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
|
||||
for(chunkunit_t xOff = 0; xOff < MAP_CHUNK_WIDTH; xOff++) {
|
||||
const chunkpos_t newChunkPos = {
|
||||
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
|
||||
};
|
||||
|
||||
// Is this chunk already loaded (was not unloaded earlier)?
|
||||
chunkindex_t chunkIndex = -1;
|
||||
for(chunkindex_t i = 0; i < remainingCount; i++) {
|
||||
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
|
||||
if(!chunkPositionIsEqual(chunk->position, newChunkPos)) continue;
|
||||
chunkIndex = chunksRemaining[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// Need to load this chunk
|
||||
if(chunkIndex == -1) {
|
||||
// Find a freed chunk to reuse
|
||||
chunkIndex = chunksFreed[--freedCount];
|
||||
chunk_t *chunk = &MAP.chunks[chunkIndex];
|
||||
chunk->position = newChunkPos;
|
||||
errorChain(mapChunkLoad(chunk));
|
||||
}
|
||||
|
||||
MAP.chunkOrder[orderIndex++] = &MAP.chunks[chunkIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update map position
|
||||
MAP.chunkPosition = newPos;
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void mapUpdate() {
|
||||
|
||||
}
|
||||
|
||||
void mapDispose() {
|
||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||
mapChunkUnload(&MAP.chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void mapChunkUnload(chunk_t* chunk) {
|
||||
for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) {
|
||||
if(chunk->entities[i] == 0xFF) break;
|
||||
entity_t *entity = &ENTITIES[chunk->entities[i]];
|
||||
entity->type = ENTITY_TYPE_NULL;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < chunk->meshCount; i++) {
|
||||
if(chunk->meshes[i].vertexCount == 0) continue;
|
||||
meshDispose(&chunk->meshes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
errorret_t mapChunkLoad(chunk_t* chunk) {
|
||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||
|
||||
char_t buffer[64];
|
||||
|
||||
// TODO: Can probably move this to asset load logic?
|
||||
chunk->meshCount = 0;
|
||||
memoryZero(chunk->meshes, sizeof(chunk->meshes));
|
||||
memorySet(chunk->entities, 0xFF, sizeof(chunk->entities));
|
||||
|
||||
// Get chunk filepath.
|
||||
snprintf(buffer, sizeof(buffer), "%s/chunks/%d_%d_%d.dcf",
|
||||
MAP.dirPath,
|
||||
chunk->position.x,
|
||||
chunk->position.y,
|
||||
chunk->position.z
|
||||
);
|
||||
|
||||
// Chunk available?
|
||||
if(!assetFileExists(buffer)) {
|
||||
memoryZero(chunk->tiles, sizeof(chunk->tiles));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Load.
|
||||
errorChain(assetLoad(buffer, chunk));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) {
|
||||
if(!mapIsLoaded()) return -1;
|
||||
|
||||
chunkpos_t relPos = {
|
||||
position.x - MAP.chunkPosition.x,
|
||||
position.y - MAP.chunkPosition.y,
|
||||
position.z - MAP.chunkPosition.z
|
||||
};
|
||||
|
||||
if(
|
||||
relPos.x < 0 || relPos.y < 0 || relPos.z < 0 ||
|
||||
relPos.x >= MAP_CHUNK_WIDTH ||
|
||||
relPos.y >= MAP_CHUNK_HEIGHT ||
|
||||
relPos.z >= MAP_CHUNK_DEPTH
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return chunkPosToIndex(&relPos);
|
||||
}
|
||||
|
||||
chunk_t* mapGetChunk(const uint8_t index) {
|
||||
if(index >= MAP_CHUNK_COUNT) return NULL;
|
||||
if(!mapIsLoaded()) return NULL;
|
||||
return MAP.chunkOrder[index];
|
||||
}
|
||||
|
||||
tile_t mapGetTile(const worldpos_t position) {
|
||||
if(!mapIsLoaded()) return TILE_SHAPE_NULL;
|
||||
|
||||
chunkpos_t chunkPos;
|
||||
worldPosToChunkPos(&position, &chunkPos);
|
||||
chunkindex_t chunkIndex = mapGetChunkIndexAt(chunkPos);
|
||||
if(chunkIndex == -1) return TILE_SHAPE_NULL;
|
||||
|
||||
chunk_t *chunk = mapGetChunk(chunkIndex);
|
||||
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||
chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position);
|
||||
return chunk->tiles[tileIndex];
|
||||
}
|
||||
106
archive/rpg/overworld/map.h
Normal file
106
archive/rpg/overworld/map.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/chunk.h"
|
||||
|
||||
#define MAP_FILE_PATH_MAX 128
|
||||
|
||||
typedef struct map_s {
|
||||
char_t filePath[MAP_FILE_PATH_MAX];
|
||||
char_t dirPath[MAP_FILE_PATH_MAX];
|
||||
char_t fileName[MAP_FILE_PATH_MAX];
|
||||
|
||||
chunk_t chunks[MAP_CHUNK_COUNT];
|
||||
chunk_t *chunkOrder[MAP_CHUNK_COUNT];
|
||||
chunkpos_t chunkPosition;
|
||||
} map_t;
|
||||
|
||||
extern map_t MAP;
|
||||
|
||||
/**
|
||||
* Initializes the map.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapInit();
|
||||
|
||||
/**
|
||||
* Checks if a map is loaded.
|
||||
*
|
||||
* @return true if a map is loaded, false otherwise.
|
||||
*/
|
||||
bool_t mapIsLoaded();
|
||||
|
||||
/**
|
||||
* Loads a map from the given file path.
|
||||
*
|
||||
* @param path The file path.
|
||||
* @param position The initial chunk position.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapLoad(
|
||||
const char_t *path,
|
||||
const chunkpos_t position
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates the map.
|
||||
*/
|
||||
void mapUpdate();
|
||||
|
||||
/**
|
||||
* Disposes of the map.
|
||||
*/
|
||||
void mapDispose();
|
||||
|
||||
/**
|
||||
* Sets the map position and updates chunks accordingly.
|
||||
*
|
||||
* @param newPos The new chunk position.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapPositionSet(const chunkpos_t newPos);
|
||||
|
||||
/**
|
||||
* Unloads a chunk.
|
||||
*
|
||||
* @param chunk The chunk to unload.
|
||||
*/
|
||||
void mapChunkUnload(chunk_t* chunk);
|
||||
|
||||
/**
|
||||
* Loads a chunk.
|
||||
*
|
||||
* @param chunk The chunk to load.
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t mapChunkLoad(chunk_t* chunk);
|
||||
|
||||
/**
|
||||
* Gets the index of a chunk, within the world, at the given position.
|
||||
*
|
||||
* @param position The chunk position.
|
||||
* @return The index of the chunk, or -1 if out of bounds.
|
||||
*/
|
||||
chunkindex_t mapGetChunkIndexAt(const chunkpos_t position);
|
||||
|
||||
/**
|
||||
* Gets a chunk by its index.
|
||||
*
|
||||
* @param chunkIndex The index of the chunk.
|
||||
* @return A pointer to the chunk.
|
||||
*/
|
||||
chunk_t * mapGetChunk(const uint8_t chunkIndex);
|
||||
|
||||
/**
|
||||
* Gets the tile at the given world position.
|
||||
*
|
||||
* @param position The world position.
|
||||
* @return The tile at that position, or TILE_NULL if the chunk is unloaded.
|
||||
*/
|
||||
tile_t mapGetTile(const worldpos_t position);
|
||||
35
archive/rpg/overworld/tile.c
Normal file
35
archive/rpg/overworld/tile.c
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "tile.h"
|
||||
|
||||
bool_t tileIsWalkable(const tile_t tile) {
|
||||
switch(tile) {
|
||||
case TILE_SHAPE_NULL:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool_t tileIsRamp(const tile_t tile) {
|
||||
switch(tile) {
|
||||
case TILE_SHAPE_RAMP_NORTH:
|
||||
case TILE_SHAPE_RAMP_SOUTH:
|
||||
case TILE_SHAPE_RAMP_EAST:
|
||||
case TILE_SHAPE_RAMP_WEST:
|
||||
case TILE_SHAPE_RAMP_NORTHEAST:
|
||||
case TILE_SHAPE_RAMP_NORTHWEST:
|
||||
case TILE_SHAPE_RAMP_SOUTHEAST:
|
||||
case TILE_SHAPE_RAMP_SOUTHWEST:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
27
archive/rpg/overworld/tile.h
Normal file
27
archive/rpg/overworld/tile.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/entity/entitydir.h"
|
||||
|
||||
typedef uint8_t tile_t;
|
||||
|
||||
/**
|
||||
* Returns whether or not the given tile is walkable.
|
||||
*
|
||||
* @param tile The tile to check.
|
||||
* @return bool_t True if walkable, false if not.
|
||||
*/
|
||||
bool_t tileIsWalkable(const tile_t tile);
|
||||
|
||||
/**
|
||||
* Returns whether or not the given tile is a ramp tile.
|
||||
*
|
||||
* @param tile The tile to check.
|
||||
* @return bool_t True if ramp, false if not.
|
||||
*/
|
||||
bool_t tileIsRamp(const tile_t tile);
|
||||
97
archive/rpg/overworld/worldpos.c
Normal file
97
archive/rpg/overworld/worldpos.c
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "worldpos.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b) {
|
||||
return a.x == b.x && a.y == b.y && a.z == b.z;
|
||||
}
|
||||
|
||||
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out) {
|
||||
assertNotNull(chunkPos, "Chunk position pointer cannot be NULL");
|
||||
assertNotNull(out, "Output world position pointer cannot be NULL");
|
||||
|
||||
out->x = (worldunit_t)(chunkPos->x * CHUNK_WIDTH);
|
||||
out->y = (worldunit_t)(chunkPos->y * CHUNK_HEIGHT);
|
||||
out->z = (worldunit_t)(chunkPos->z * CHUNK_DEPTH);
|
||||
}
|
||||
|
||||
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out) {
|
||||
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||
assertNotNull(out, "Output chunk position pointer cannot be NULL");
|
||||
|
||||
if(worldPos->x < 0) {
|
||||
out->x = (chunkunit_t)((worldPos->x - (CHUNK_WIDTH - 1)) / CHUNK_WIDTH);
|
||||
} else {
|
||||
out->x = (chunkunit_t)(worldPos->x / CHUNK_WIDTH);
|
||||
}
|
||||
|
||||
if(worldPos->y < 0) {
|
||||
out->y = (chunkunit_t)((worldPos->y - (CHUNK_HEIGHT - 1)) / CHUNK_HEIGHT);
|
||||
} else {
|
||||
out->y = (chunkunit_t)(worldPos->y / CHUNK_HEIGHT);
|
||||
}
|
||||
|
||||
if(worldPos->z < 0) {
|
||||
out->z = (chunkunit_t)((worldPos->z - (CHUNK_DEPTH - 1)) / CHUNK_DEPTH);
|
||||
} else {
|
||||
out->z = (chunkunit_t)(worldPos->z / CHUNK_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) {
|
||||
assertNotNull(worldPos, "World position pointer cannot be NULL");
|
||||
|
||||
uint8_t localX, localY, localZ;
|
||||
if(worldPos->x < 0) {
|
||||
localX = (uint8_t)(
|
||||
(CHUNK_WIDTH - 1) - ((-worldPos->x - 1) % CHUNK_WIDTH)
|
||||
);
|
||||
} else {
|
||||
localX = (uint8_t)(worldPos->x % CHUNK_WIDTH);
|
||||
}
|
||||
|
||||
if(worldPos->y < 0) {
|
||||
localY = (uint8_t)(
|
||||
(CHUNK_HEIGHT - 1) - ((-worldPos->y - 1) % CHUNK_HEIGHT)
|
||||
);
|
||||
} else {
|
||||
localY = (uint8_t)(worldPos->y % CHUNK_HEIGHT);
|
||||
}
|
||||
|
||||
if(worldPos->z < 0) {
|
||||
localZ = (uint8_t)(
|
||||
(CHUNK_DEPTH - 1) - ((-worldPos->z - 1) % CHUNK_DEPTH)
|
||||
);
|
||||
} else {
|
||||
localZ = (uint8_t)(worldPos->z % CHUNK_DEPTH);
|
||||
}
|
||||
|
||||
chunktileindex_t chunkTileIndex = (chunktileindex_t)(
|
||||
(localZ * CHUNK_WIDTH * CHUNK_HEIGHT) +
|
||||
(localY * CHUNK_WIDTH) +
|
||||
localX
|
||||
);
|
||||
assertTrue(
|
||||
chunkTileIndex < CHUNK_TILE_COUNT,
|
||||
"Calculated chunk tile index is out of bounds"
|
||||
);
|
||||
return chunkTileIndex;
|
||||
}
|
||||
|
||||
chunkindex_t chunkPosToIndex(const chunkpos_t* pos) {
|
||||
assertNotNull(pos, "Chunk position pointer cannot be NULL");
|
||||
|
||||
chunkindex_t chunkIndex = (chunkindex_t)(
|
||||
(pos->z * MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT) +
|
||||
(pos->y * MAP_CHUNK_WIDTH) +
|
||||
pos->x
|
||||
);
|
||||
|
||||
return chunkIndex;
|
||||
}
|
||||
75
archive/rpg/overworld/worldpos.h
Normal file
75
archive/rpg/overworld/worldpos.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include "duskdefs.h"
|
||||
|
||||
#define CHUNK_TILE_COUNT (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH)
|
||||
|
||||
#define MAP_CHUNK_WIDTH 3
|
||||
#define MAP_CHUNK_HEIGHT 3
|
||||
#define MAP_CHUNK_DEPTH 3
|
||||
#define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH)
|
||||
|
||||
typedef int16_t worldunit_t;
|
||||
typedef int16_t chunkunit_t;
|
||||
typedef int16_t chunkindex_t;
|
||||
typedef uint32_t chunktileindex_t;
|
||||
|
||||
typedef int32_t worldunits_t;
|
||||
typedef int32_t chunkunits_t;
|
||||
|
||||
typedef struct worldpos_s {
|
||||
worldunit_t x, y, z;
|
||||
} worldpos_t;
|
||||
|
||||
typedef struct chunkpos_t {
|
||||
chunkunit_t x, y, z;
|
||||
} chunkpos_t;
|
||||
|
||||
/**
|
||||
* Compares two world positions for equality.
|
||||
*
|
||||
* @param a The first world position.
|
||||
* @param b The second world position.
|
||||
* @return true if equal, false otherwise.
|
||||
*/
|
||||
bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b);
|
||||
|
||||
/**
|
||||
* Converts a world position to a chunk position.
|
||||
*
|
||||
* @param worldPos The world position.
|
||||
* @param out The output chunk position.
|
||||
*/
|
||||
void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out);
|
||||
|
||||
/**
|
||||
* Converts a chunk position to a world position.
|
||||
*
|
||||
* @param worldPos The world position.
|
||||
* @param out The output chunk position.
|
||||
*/
|
||||
void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out);
|
||||
|
||||
/**
|
||||
* Converts a position in world-space to an index inside a chunk that the tile
|
||||
* resides in.
|
||||
*
|
||||
* @param worldPos The world position.
|
||||
* @return The tile index within the chunk.
|
||||
*/
|
||||
chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos);
|
||||
|
||||
/**
|
||||
* Converts a chunk position to a world position.
|
||||
*
|
||||
* @param worldPos The world position.
|
||||
* @param out The output chunk position.
|
||||
*/
|
||||
chunkindex_t chunkPosToIndex(const chunkpos_t* pos);
|
||||
66
archive/rpg/rpg.c
Normal file
66
archive/rpg/rpg.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpg.h"
|
||||
#include "entity/entity.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "rpg/cutscene/cutscenesystem.h"
|
||||
#include "time/time.h"
|
||||
#include "rpgcamera.h"
|
||||
#include "rpgtextbox.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
errorret_t rpgInit(void) {
|
||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||
|
||||
// Init cutscene subsystem
|
||||
cutsceneSystemInit();
|
||||
|
||||
errorChain(mapInit());
|
||||
|
||||
rpgCameraInit();
|
||||
rpgTextboxInit();
|
||||
|
||||
// TEST: Create some entities.
|
||||
// uint8_t entIndex = entityGetAvailable();
|
||||
// assertTrue(entIndex != 0xFF, "No available entity slots!.");
|
||||
// entity_t *ent = &ENTITIES[entIndex];
|
||||
// entityInit(ent, ENTITY_TYPE_PLAYER);
|
||||
// RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
|
||||
// RPG_CAMERA.followEntity.followEntityId = ent->id;
|
||||
// ent->position.x = 2, ent->position.y = 2;
|
||||
|
||||
// All Good!
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t rpgUpdate(void) {
|
||||
#if TIME_FIXED == 0
|
||||
if(TIME.dynamicUpdate) {
|
||||
errorOk();
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO: Do not update if the scene is not the map scene?
|
||||
mapUpdate();
|
||||
|
||||
// Update overworld ents.
|
||||
entity_t *ent = &ENTITIES[0];
|
||||
do {
|
||||
if(ent->type == ENTITY_TYPE_NULL) continue;
|
||||
entityUpdate(ent);
|
||||
} while(++ent < &ENTITIES[ENTITY_COUNT]);
|
||||
|
||||
cutsceneSystemUpdate();
|
||||
errorChain(rpgCameraUpdate());
|
||||
errorOk();
|
||||
}
|
||||
|
||||
void rpgDispose(void) {
|
||||
mapDispose();
|
||||
}
|
||||
32
archive/rpg/rpg.h
Normal file
32
archive/rpg/rpg.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
typedef struct {
|
||||
int32_t nothing;
|
||||
} rpg_t;
|
||||
|
||||
/**
|
||||
* Initialize the RPG subsystem.
|
||||
*
|
||||
* @return An error code and state.
|
||||
*/
|
||||
errorret_t rpgInit(void);
|
||||
|
||||
/**
|
||||
* Update the RPG subsystem.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t rpgUpdate(void);
|
||||
|
||||
/**
|
||||
* Dispose of the RPG subsystem.
|
||||
*/
|
||||
void rpgDispose(void);
|
||||
52
archive/rpg/rpgcamera.c
Normal file
52
archive/rpg/rpgcamera.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpgcamera.h"
|
||||
#include "util/memory.h"
|
||||
#include "rpg/entity/entity.h"
|
||||
#include "rpg/overworld/map.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
rpgcamera_t RPG_CAMERA;
|
||||
|
||||
void rpgCameraInit(void) {
|
||||
memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t));
|
||||
}
|
||||
|
||||
errorret_t rpgCameraUpdate(void) {
|
||||
if(!mapIsLoaded()) errorOk();
|
||||
|
||||
chunkpos_t chunkPos;
|
||||
|
||||
switch(RPG_CAMERA.mode) {
|
||||
case RPG_CAMERA_MODE_FREE:
|
||||
worldPosToChunkPos(&RPG_CAMERA.free, &chunkPos);
|
||||
break;
|
||||
|
||||
case RPG_CAMERA_MODE_FOLLOW_ENTITY: {
|
||||
entity_t *entity = &ENTITIES[RPG_CAMERA.followEntity.followEntityId];
|
||||
if(entity->type == ENTITY_TYPE_NULL) {
|
||||
errorOk();
|
||||
}
|
||||
|
||||
// Update map position to match camera. By default map wants to know the
|
||||
// top left but we want to set the center, so we need to sub half map size
|
||||
worldPosToChunkPos(&entity->position, &chunkPos);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
assertUnreachable("Invalid RPG camera mode");
|
||||
}
|
||||
|
||||
errorChain(mapPositionSet((chunkpos_t){
|
||||
.x = chunkPos.x - (MAP_CHUNK_WIDTH / 2),
|
||||
.y = chunkPos.y - (MAP_CHUNK_HEIGHT / 2),
|
||||
.z = chunkPos.z - (MAP_CHUNK_DEPTH / 2)
|
||||
}));
|
||||
errorOk();
|
||||
}
|
||||
40
archive/rpg/rpgcamera.h
Normal file
40
archive/rpg/rpgcamera.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "rpg/overworld/worldpos.h"
|
||||
#include "error/error.h"
|
||||
|
||||
typedef enum {
|
||||
RPG_CAMERA_MODE_FREE,
|
||||
RPG_CAMERA_MODE_FOLLOW_ENTITY,
|
||||
} rpgcameramode_t;
|
||||
|
||||
typedef struct {
|
||||
rpgcameramode_t mode;
|
||||
|
||||
union {
|
||||
worldpos_t free;
|
||||
struct {
|
||||
uint8_t followEntityId;
|
||||
} followEntity;
|
||||
};
|
||||
} rpgcamera_t;
|
||||
|
||||
extern rpgcamera_t RPG_CAMERA;
|
||||
|
||||
/**
|
||||
* Initializes the RPG camera.
|
||||
*/
|
||||
void rpgCameraInit(void);
|
||||
|
||||
/**
|
||||
* Updates the RPG camera.
|
||||
*
|
||||
* @return An error code.
|
||||
*/
|
||||
errorret_t rpgCameraUpdate(void);
|
||||
39
archive/rpg/rpgtextbox.c
Normal file
39
archive/rpg/rpgtextbox.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "rpgtextbox.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
rpgtextbox_t RPG_TEXTBOX;
|
||||
|
||||
void rpgTextboxInit() {
|
||||
memoryZero(&RPG_TEXTBOX, sizeof(rpgtextbox_t));
|
||||
}
|
||||
|
||||
void rpgTextboxShow(
|
||||
const rpgtextboxpos_t position,
|
||||
const char_t *text
|
||||
) {
|
||||
RPG_TEXTBOX.position = position;
|
||||
RPG_TEXTBOX.visible = true;
|
||||
|
||||
stringCopy(
|
||||
RPG_TEXTBOX.text,
|
||||
text,
|
||||
RPG_TEXTBOX_MAX_CHARS
|
||||
);
|
||||
}
|
||||
|
||||
void rpgTextboxHide() {
|
||||
RPG_TEXTBOX.visible = false;
|
||||
}
|
||||
|
||||
bool_t rpgTextboxIsVisible() {
|
||||
return RPG_TEXTBOX.visible;
|
||||
}
|
||||
52
archive/rpg/rpgtextbox.h
Normal file
52
archive/rpg/rpgtextbox.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define RPG_TEXTBOX_MAX_CHARS 256
|
||||
|
||||
typedef enum {
|
||||
RPG_TEXTBOX_POS_TOP,
|
||||
RPG_TEXTBOX_POS_BOTTOM,
|
||||
} rpgtextboxpos_t;
|
||||
|
||||
typedef struct {
|
||||
rpgtextboxpos_t position;
|
||||
bool_t visible;
|
||||
char_t text[RPG_TEXTBOX_MAX_CHARS];
|
||||
} rpgtextbox_t;
|
||||
|
||||
extern rpgtextbox_t RPG_TEXTBOX;
|
||||
|
||||
/**
|
||||
* Initializes the RPG textbox.
|
||||
*/
|
||||
void rpgTextboxInit();
|
||||
|
||||
/**
|
||||
* Shows the RPG textbox at a specified position.
|
||||
*
|
||||
* @param position The position to show the textbox at.
|
||||
* @param text The text to display in the textbox (copied).
|
||||
*/
|
||||
void rpgTextboxShow(
|
||||
const rpgtextboxpos_t position,
|
||||
const char_t *text
|
||||
);
|
||||
|
||||
/**
|
||||
* Hides the RPG textbox.
|
||||
*/
|
||||
void rpgTextboxHide();
|
||||
|
||||
/**
|
||||
* Checks if the RPG textbox is currently visible.
|
||||
*
|
||||
* @return true if the textbox is visible, false otherwise.
|
||||
*/
|
||||
bool_t rpgTextboxIsVisible();
|
||||
Reference in New Issue
Block a user