269 lines
7.1 KiB
C
269 lines
7.1 KiB
C
/**
|
|
* 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];
|
|
} |