Area basics
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
#include "time/time.h"
|
#include "time/time.h"
|
||||||
#include "util/math.h"
|
#include "util/math.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
#include "rpg/overworld/tile.h"
|
#include "rpg/overworld/tile.h"
|
||||||
|
|
||||||
@@ -224,6 +225,7 @@ void entityWalk(entity_t *entity, const entitydir_t direction) {
|
|||||||
entity->animation = ENTITY_ANIM_WALK;
|
entity->animation = ENTITY_ANIM_WALK;
|
||||||
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking
|
||||||
entityUpdateChunk(entity);
|
entityUpdateChunk(entity);
|
||||||
|
mapAreaCheckEntity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void entityRun(entity_t *entity, const entitydir_t direction) {
|
void entityRun(entity_t *entity, const entitydir_t direction) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
chunk.c
|
chunk.c
|
||||||
map.c
|
map.c
|
||||||
|
maparea.c
|
||||||
worldpos.c
|
worldpos.c
|
||||||
tile.c
|
tile.c
|
||||||
tileshape.c
|
tileshape.c
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "maparea.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "rpg/overworld/map.h"
|
||||||
|
|
||||||
|
maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX];
|
||||||
|
|
||||||
|
void mapAreaInit(
|
||||||
|
maparea_t *area,
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const mapareanotify_t notify
|
||||||
|
) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(callback, "Map area callback cannot be NULL");
|
||||||
|
|
||||||
|
area->min.x = mathMin(min.x, max.x);
|
||||||
|
area->min.y = mathMin(min.y, max.y);
|
||||||
|
area->min.z = mathMin(min.z, max.z);
|
||||||
|
|
||||||
|
area->max.x = mathMax(min.x, max.x);
|
||||||
|
area->max.y = mathMax(min.y, max.y);
|
||||||
|
area->max.z = mathMax(min.z, max.z);
|
||||||
|
|
||||||
|
area->callback = callback;
|
||||||
|
area->notify = notify;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
|
||||||
|
return (
|
||||||
|
position.x >= area->min.x && position.x <= area->max.x &&
|
||||||
|
position.y >= area->min.y && position.y <= area->max.y &&
|
||||||
|
position.z >= area->min.z && position.z <= area->max.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaIsChunkOverlappingOrInside(
|
||||||
|
const maparea_t *area,
|
||||||
|
const chunk_t *chunk
|
||||||
|
) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(chunk, "Chunk pointer cannot be NULL");
|
||||||
|
|
||||||
|
worldpos_t chunkMin, chunkMax;
|
||||||
|
chunkPosToWorldPos(&chunk->position, &chunkMin);
|
||||||
|
chunkMax.x = chunkMin.x + CHUNK_WIDTH - 1;
|
||||||
|
chunkMax.y = chunkMin.y + CHUNK_HEIGHT - 1;
|
||||||
|
chunkMax.z = chunkMin.z + CHUNK_DEPTH - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
chunkMin.x <= area->max.x && area->min.x <= chunkMax.x &&
|
||||||
|
chunkMin.y <= area->max.y && area->min.y <= chunkMax.y &&
|
||||||
|
chunkMin.z <= area->max.z && area->min.z <= chunkMax.z
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaCanUnload(const maparea_t *area) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
|
||||||
|
for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) {
|
||||||
|
if(mapAreaIsChunkOverlappingOrInside(area, &MAP.chunks[i])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity) {
|
||||||
|
assertNotNull(area, "Map area pointer cannot be NULL");
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
switch(entity->type) {
|
||||||
|
case ENTITY_TYPE_PLAYER:
|
||||||
|
return (area->notify & MAP_AREA_NOTIFY_PLAYER) != 0;
|
||||||
|
|
||||||
|
case ENTITY_TYPE_NPC:
|
||||||
|
return (area->notify & MAP_AREA_NOTIFY_NPC) != 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t mapAreaAdd(
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const mapareanotify_t notify
|
||||||
|
) {
|
||||||
|
for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) {
|
||||||
|
if(MAP_AREAS[i].callback != NULL) continue;
|
||||||
|
mapAreaInit(&MAP_AREAS[i], min, max, callback, notify);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertUnreachable("No available map area slots");
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapAreaRemove(const uint8_t id) {
|
||||||
|
assertTrue(id < MAP_AREA_COUNT_MAX, "Map area ID is out of range");
|
||||||
|
MAP_AREAS[id].callback = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mapAreaCheckEntity(entity_t *entity) {
|
||||||
|
assertNotNull(entity, "Entity pointer cannot be NULL");
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < MAP_AREA_COUNT_MAX; i++) {
|
||||||
|
maparea_t *area = &MAP_AREAS[i];
|
||||||
|
if(area->callback == NULL) continue;
|
||||||
|
if(!mapAreaIsInside(area, entity->position)) continue;
|
||||||
|
if(!mapAreaShouldNotify(area, entity)) continue;
|
||||||
|
area->callback(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "worldpos.h"
|
||||||
|
|
||||||
|
typedef struct chunk_s chunk_t;
|
||||||
|
typedef struct entity_s entity_t;
|
||||||
|
typedef struct maparea_s maparea_t;
|
||||||
|
|
||||||
|
#define MAP_AREA_COUNT_MAX 32
|
||||||
|
|
||||||
|
typedef uint8_t mapareanotify_t;
|
||||||
|
|
||||||
|
#define MAP_AREA_NOTIFY_PLAYER ((mapareanotify_t)(1 << 0))
|
||||||
|
#define MAP_AREA_NOTIFY_NPC ((mapareanotify_t)(1 << 1))
|
||||||
|
#define MAP_AREA_NOTIFY_ALL ((mapareanotify_t)( \
|
||||||
|
MAP_AREA_NOTIFY_PLAYER | MAP_AREA_NOTIFY_NPC \
|
||||||
|
))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked for a map area.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity associated with the callback.
|
||||||
|
*/
|
||||||
|
typedef void (*mapareacallback_t)(entity_t *entity);
|
||||||
|
|
||||||
|
typedef struct maparea_s {
|
||||||
|
worldpos_t min;
|
||||||
|
worldpos_t max;
|
||||||
|
mapareacallback_t callback;
|
||||||
|
mapareanotify_t notify;
|
||||||
|
} maparea_t;
|
||||||
|
|
||||||
|
extern maparea_t MAP_AREAS[MAP_AREA_COUNT_MAX];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a map area with the given bounds, callback and notify flags.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to initialize.
|
||||||
|
* @param min The minimum world position of the area.
|
||||||
|
* @param max The maximum world position of the area.
|
||||||
|
* @param callback The callback to invoke for this area. Must not be NULL.
|
||||||
|
* @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types
|
||||||
|
* should trigger the callback.
|
||||||
|
*/
|
||||||
|
void mapAreaInit(
|
||||||
|
maparea_t *area,
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const mapareanotify_t notify
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a world position falls within a map area's bounds
|
||||||
|
* (inclusive on all axes).
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param position The world position to check.
|
||||||
|
* @returns true if the position is inside the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaIsInside(const maparea_t *area, const worldpos_t position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a chunk's full footprint overlaps a map area's bounds,
|
||||||
|
* rather than just testing the chunk's origin corner.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param chunk Pointer to the chunk to check.
|
||||||
|
* @returns true if the chunk overlaps the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaIsChunkOverlappingOrInside(
|
||||||
|
const maparea_t *area,
|
||||||
|
const chunk_t *chunk
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a map area is safe to unload, i.e. no entity currently
|
||||||
|
* occupies a position within its bounds.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @returns true if no entity is inside the area.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaCanUnload(const maparea_t *area);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether an entity's type is included in a map area's notify flags.
|
||||||
|
*
|
||||||
|
* @param area Pointer to the map area to check.
|
||||||
|
* @param entity Pointer to the entity to check.
|
||||||
|
* @returns true if the entity's type should trigger this area's callback.
|
||||||
|
*/
|
||||||
|
bool_t mapAreaShouldNotify(const maparea_t *area, const entity_t *entity);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a map area to the global MAP_AREAS list, in the first free slot.
|
||||||
|
* A slot is considered free if its callback is NULL.
|
||||||
|
*
|
||||||
|
* @param min The minimum world position of the area.
|
||||||
|
* @param max The maximum world position of the area.
|
||||||
|
* @param callback The callback to invoke for this area. Must not be NULL.
|
||||||
|
* @param notify Bitwise MAP_AREA_NOTIFY_* flags for which entity types
|
||||||
|
* should trigger the callback.
|
||||||
|
* @returns The ID of the newly added map area.
|
||||||
|
*/
|
||||||
|
uint8_t mapAreaAdd(
|
||||||
|
const worldpos_t min,
|
||||||
|
const worldpos_t max,
|
||||||
|
const mapareacallback_t callback,
|
||||||
|
const mapareanotify_t notify
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a map area from the global MAP_AREAS list, freeing its slot by
|
||||||
|
* clearing its callback to NULL.
|
||||||
|
*
|
||||||
|
* @param id The ID of the map area to remove.
|
||||||
|
*/
|
||||||
|
void mapAreaRemove(const uint8_t id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks every active map area against an entity's current position,
|
||||||
|
* invoking each matching area's callback if the entity is inside it and
|
||||||
|
* its type is included in that area's notify flags.
|
||||||
|
*
|
||||||
|
* @param entity Pointer to the entity to check.
|
||||||
|
*/
|
||||||
|
void mapAreaCheckEntity(entity_t *entity);
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "entity/entity.h"
|
#include "entity/entity.h"
|
||||||
#include "rpg/entity/npc/npcpath.h"
|
#include "rpg/entity/npc/npcpath.h"
|
||||||
#include "rpg/overworld/map.h"
|
#include "rpg/overworld/map.h"
|
||||||
|
#include "rpg/overworld/maparea.h"
|
||||||
#include "rpg/cutscene/cutscenesystem.h"
|
#include "rpg/cutscene/cutscenesystem.h"
|
||||||
#include "rpg/cutscene/scene/testcutscene.h"
|
#include "rpg/cutscene/scene/testcutscene.h"
|
||||||
#include "rpg/item/backpack.h"
|
#include "rpg/item/backpack.h"
|
||||||
@@ -17,9 +18,15 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
|
#include "console/console.h"
|
||||||
|
|
||||||
|
void rpgTestAreaCallback(entity_t *entity) {
|
||||||
|
consolePrint("rpgTestAreaCallback");
|
||||||
|
}
|
||||||
|
|
||||||
errorret_t rpgInit(void) {
|
errorret_t rpgInit(void) {
|
||||||
memoryZero(ENTITIES, sizeof(ENTITIES));
|
memoryZero(ENTITIES, sizeof(ENTITIES));
|
||||||
|
memoryZero(MAP_AREAS, sizeof(MAP_AREAS));
|
||||||
|
|
||||||
backpackInit();
|
backpackInit();
|
||||||
cutsceneSystemInit();
|
cutsceneSystemInit();
|
||||||
@@ -41,6 +48,15 @@ errorret_t rpgInit(void) {
|
|||||||
|
|
||||||
mapSpawnEntity(3, (worldpos_t){ 8, 8, 1 });
|
mapSpawnEntity(3, (worldpos_t){ 8, 8, 1 });
|
||||||
|
|
||||||
|
// TEST: Create a test map area.
|
||||||
|
uint8_t areaIndex = mapAreaAdd(
|
||||||
|
(worldpos_t){ 11, 3, 0 },
|
||||||
|
(worldpos_t){ 16, 9, 10 },
|
||||||
|
rpgTestAreaCallback,
|
||||||
|
MAP_AREA_NOTIFY_ALL
|
||||||
|
);
|
||||||
|
assertTrue(areaIndex != 0xFF, "No available map area slots!.");
|
||||||
|
|
||||||
// All Good!
|
// All Good!
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user