151 lines
4.1 KiB
C
151 lines
4.1 KiB
C
/**
|
|
* 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 "util/memory.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 uint8_t notify,
|
|
const uint8_t trigger
|
|
) {
|
|
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;
|
|
area->trigger = trigger;
|
|
area->triggerCount = 0;
|
|
|
|
memorySet(area->entities, 0, sizeof(area->entities));
|
|
}
|
|
|
|
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_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 uint8_t notify,
|
|
const uint8_t trigger
|
|
) {
|
|
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, trigger);
|
|
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(!mapAreaShouldNotify(area, entity)) continue;
|
|
|
|
bool_t wasInside = area->entities[entity->id] != 0;
|
|
bool_t isInside = mapAreaIsInside(area, entity->position);
|
|
uint8_t trigger = 0;
|
|
|
|
if(isInside && !wasInside) {
|
|
area->entities[entity->id] = 1;
|
|
trigger = MAP_TRIGGER_ENTER;
|
|
} else if(isInside && wasInside) {
|
|
trigger = MAP_TRIGGER_STEP;
|
|
} else if(!isInside && wasInside) {
|
|
area->entities[entity->id] = 0;
|
|
trigger = MAP_TRIGGER_EXIT;
|
|
}
|
|
|
|
if(trigger == 0 || !(area->trigger & trigger)) continue;
|
|
area->triggerCount++;
|
|
area->callback(entity, trigger);
|
|
}
|
|
}
|
|
|
|
void mapAreaNoopCallback(entity_t *entity, const uint8_t trigger) {
|
|
}
|