Files
dusk/src/dusk/rpg/overworld/maparea.c
T

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) {
}