RMA: new module
parent
8eefcf837f
commit
94128691a4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,13 @@
|
|||
# Server
|
||||
|
||||
## Setup
|
||||
|
||||
[Setup](Setup.md) the server first.
|
||||
|
||||
## GameDesign
|
||||
|
||||
[GameDesign](GameDesign.md).
|
||||
|
||||
## Architecture
|
||||
|
||||
TODO: document server architecture, world, map, ai-zone, chunk management, user handling
|
|
@ -27,16 +27,14 @@ nav:
|
|||
- VoxEdit:
|
||||
- voxedit/Index.md
|
||||
- voxedit/LUAScript.md
|
||||
- Client/Server:
|
||||
- server/Index.md
|
||||
- server/Setup.md
|
||||
- Games:
|
||||
- OpenWorld: openworld/Index.md
|
||||
- Tools:
|
||||
- VoxConvert: voxconvert/Index.md
|
||||
- Thumbnailer: thumbnailer/Index.md
|
||||
- MapView: mapview/Index.md
|
||||
- Development:
|
||||
- Visual Tests: VisualTests.md
|
||||
- Game Design: GameDesign.md
|
||||
- Shader integration: ShaderTool.md
|
||||
- Compute Shader integration: ComputeShaderTool.md
|
||||
- Persistence: Persistence.md
|
||||
|
|
|
@ -46,4 +46,5 @@ add_subdirectory(animation)
|
|||
add_subdirectory(frontend)
|
||||
add_subdirectory(voxelrender)
|
||||
add_subdirectory(voxelworldrender)
|
||||
add_subdirectory(rma)
|
||||
add_subdirectory(testcore)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
set(SRCS
|
||||
MapBuilder.h MapBuilder.cpp
|
||||
MetaMap.h MetaMap.cpp
|
||||
LUAMetaMap.h LUAMetaMap.cpp
|
||||
)
|
||||
|
||||
set(FILES
|
||||
rma/maps/house1_0.qb
|
||||
rma/maps/house1_1.qb
|
||||
rma/maps/street_h_1_0.qb
|
||||
rma/maps/street_v_1_0.qb
|
||||
rma/maps/street_cross_1_0.qb
|
||||
)
|
||||
|
||||
set(LUA_SRCS
|
||||
tiles.lua
|
||||
)
|
||||
|
||||
set(LIB rma)
|
||||
engine_add_module(TARGET ${LIB} SRCS ${SRCS} LUA_SRCS ${LUA_SRCS} ${SRCS_SHADERS} FILES ${FILES} DEPENDENCIES math voxelutil commonlua voxelformat)
|
||||
|
||||
set(TEST_SRCS
|
||||
tests/LUAMetaMapTest.cpp
|
||||
tests/MapBuilderTest.cpp
|
||||
)
|
||||
|
||||
gtest_suite_sources(tests ${TEST_SRCS})
|
||||
gtest_suite_deps(tests ${LIB} test-app rma)
|
||||
|
||||
gtest_suite_begin(tests-${LIB} TEMPLATE ${ROOT_DIR}/src/modules/core/tests/main.cpp.in)
|
||||
gtest_suite_sources(tests-${LIB} ${TEST_SRCS})
|
||||
gtest_suite_deps(tests-${LIB} ${LIB} test-app rma)
|
||||
gtest_suite_end(tests-${LIB})
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "LUAMetaMap.h"
|
||||
#include "MetaMap.h"
|
||||
#include "commonlua/LUAFunctions.h"
|
||||
|
||||
template <> struct clua_meta<rma::MetaMap> {
|
||||
static char const *name() {
|
||||
return "__meta_metamap";
|
||||
}
|
||||
};
|
||||
|
||||
rma::MetaMap *luametamap_tometamap(lua_State *s, int idx) {
|
||||
rma::MetaMap **metaMap = clua_get<rma::MetaMap*>(s, idx);
|
||||
if (metaMap == nullptr) {
|
||||
clua_error(s, "Unable to find meta map");
|
||||
return nullptr;
|
||||
}
|
||||
return *metaMap;
|
||||
}
|
||||
|
||||
static int luametamap_tostring(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
lua_pushfstring(l, "metamap[%s]", metaMap->name().c_str());
|
||||
Log::error("name: %s", metaMap->name().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luametamap_name(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
lua_pushstring(l, metaMap->name().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int luametamap_setimage(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
metaMap->image = lua_tostring(l, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_settitle(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
metaMap->title = lua_tostring(l, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_setdescription(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
metaMap->description = lua_tostring(l, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_setmodel(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
metaMap->model = lua_tostring(l, 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_setsize(lua_State *l) {
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
metaMap->width = lua_tointeger(l, 2);
|
||||
metaMap->height = lua_tointeger(l, 3);
|
||||
|
||||
if (metaMap->width < 0 || metaMap->width >= RMA_MAP_TILE_VOXEL_SIZE) {
|
||||
clua_error(l, "Invalid width given [0-%d]", RMA_MAP_TILE_VOXEL_SIZE);
|
||||
}
|
||||
if (metaMap->height < 0 || metaMap->height >= RMA_MAP_TILE_VOXEL_SIZE) {
|
||||
clua_error(l, "Invalid height given [0-%d]", RMA_MAP_TILE_VOXEL_SIZE);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_settiles(lua_State *l) {
|
||||
const int argc = lua_gettop(l);
|
||||
clua_assert_argc(l, argc == 2);
|
||||
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
if (!lua_istable(l, 2)) {
|
||||
clua_typerror(l, 2, "table");
|
||||
}
|
||||
|
||||
lua_pushnil(l);
|
||||
while (lua_next(l, 2) != 0) {
|
||||
const char *tileId = luaL_checkstring(l, -2);
|
||||
if (tileId == nullptr || tileId[0] == '\0') {
|
||||
clua_error(l, "Empty tile id given in definition of metamap %s", metaMap->name().c_str());
|
||||
}
|
||||
|
||||
rma::Tile tile;
|
||||
// Get the number of entries
|
||||
const int len = (int)lua_rawlen(l, -1);
|
||||
if (len != lengthof(tile.tiles3x3)) {
|
||||
clua_error(l, "Expected to find a 3x3 matrix as value");
|
||||
}
|
||||
|
||||
for (int index = 0; index < lengthof(tile.tiles3x3); ++index) {
|
||||
// Push our target index to the stack. (lua starts at index 1!)
|
||||
lua_pushinteger(l, index + 1);
|
||||
// Get the table data at this index
|
||||
lua_gettable(l, -2);
|
||||
// Get it's value.
|
||||
const char *val = luaL_checkstring(l, -1);
|
||||
if (val == nullptr || val[0] == '\0') {
|
||||
val = "0";
|
||||
}
|
||||
tile.tiles3x3[index] = val;
|
||||
// Pop it off again.
|
||||
lua_pop(l, 1);
|
||||
}
|
||||
metaMap->tiles.put(tileId, tile);
|
||||
|
||||
lua_pop(l, 1); // remove value, keep key for lua_next
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_addfixedtile(lua_State *l) {
|
||||
const int argc = lua_gettop(l);
|
||||
clua_assert_argc(l, argc == 4);
|
||||
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
rma::FixedTile fixedTile;
|
||||
fixedTile.tileName = lua_tostring(l, 2);
|
||||
fixedTile.x = (int)luaL_optinteger(l, 3, 1);
|
||||
fixedTile.z = (int)luaL_optinteger(l, 4, 100);
|
||||
|
||||
if (fixedTile.x < 0 || fixedTile.z < 0 || fixedTile.x >= metaMap->width || fixedTile.z >= metaMap->height) {
|
||||
clua_error(l, "Given fixed tile '%s' at %d:%d is out the map range 0:0-%d:%d", fixedTile.tileName.c_str(),
|
||||
fixedTile.x, fixedTile.z, metaMap->width - 1, metaMap->height - 1);
|
||||
}
|
||||
|
||||
metaMap->fixedTiles.push_back(fixedTile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int luametamap_addtileconfig(lua_State *l) {
|
||||
const int argc = lua_gettop(l);
|
||||
clua_assert_argc(l, argc == 3);
|
||||
|
||||
rma::MetaMap *metaMap = luametamap_tometamap(l, 1);
|
||||
const char *tile = lua_tostring(l, 2);
|
||||
rma::TileConfig tileConfig;
|
||||
tileConfig.maximum = (int)luaL_optinteger(l, 3, 10);
|
||||
metaMap->tileConfigs.put(tile, tileConfig);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int luametamap_pushmetamap(lua_State *s, rma::MetaMap *b) {
|
||||
return clua_push(s, b);
|
||||
}
|
||||
|
||||
void luametamap_setup(lua_State *s, const core::DynamicArray<luaL_Reg> &extensions) {
|
||||
core::DynamicArray<luaL_Reg> metaMapFuncs;
|
||||
metaMapFuncs.push_back({"name", luametamap_name});
|
||||
metaMapFuncs.push_back({"setImage", luametamap_setimage});
|
||||
metaMapFuncs.push_back({"addTileConfig", luametamap_addtileconfig});
|
||||
metaMapFuncs.push_back({"addFixedTile", luametamap_addfixedtile});
|
||||
metaMapFuncs.push_back({"setSize", luametamap_setsize});
|
||||
metaMapFuncs.push_back({"setTiles", luametamap_settiles});
|
||||
metaMapFuncs.push_back({"setModel", luametamap_setmodel});
|
||||
metaMapFuncs.push_back({"setTitle", luametamap_settitle});
|
||||
metaMapFuncs.push_back({"setDescription", luametamap_setdescription});
|
||||
metaMapFuncs.push_back({"__tostring", luametamap_tostring});
|
||||
for (luaL_Reg f : extensions) {
|
||||
metaMapFuncs.push_back(f);
|
||||
}
|
||||
metaMapFuncs.push_back({nullptr, nullptr});
|
||||
clua_registerfuncs(s, metaMapFuncs.data(), clua_meta<rma::MetaMap>::name());
|
||||
clua_mathregister(s);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "commonlua/LUA.h"
|
||||
#include "core/collection/DynamicArray.h"
|
||||
|
||||
namespace rma {
|
||||
class MetaMap;
|
||||
}
|
||||
|
||||
extern int luametamap_pushmetamap(lua_State *s, rma::MetaMap *b);
|
||||
extern void luametamap_setup(lua_State *s, const core::DynamicArray<luaL_Reg> &extensions);
|
||||
extern rma::MetaMap *luametamap_tometamap(lua_State *s, int idx);
|
|
@ -0,0 +1,263 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "MapBuilder.h"
|
||||
#include "MetaMap.h"
|
||||
#include "core/Log.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "core/collection/Array2DView.h"
|
||||
#include "core/collection/DynamicArray.h"
|
||||
#include "math/Random.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "voxelutil/VoxelUtil.h"
|
||||
|
||||
namespace rma {
|
||||
|
||||
struct PlacedTile {
|
||||
core::String tileName;
|
||||
Tile tile;
|
||||
};
|
||||
|
||||
using MapTileArray = core::DynamicArray<PlacedTile>;
|
||||
using MapTileView = core::Array2DView<PlacedTile>;
|
||||
using MapTileCountArray = core::StringMap<int>;
|
||||
|
||||
static bool checkTile(const MetaMap *metaMap, const MapTileView &view, int x, int z, const Tile &tile,
|
||||
Direction direction) {
|
||||
int nx; // coordinate for the already placed tile
|
||||
int nz; // coordinate for the already placed tile
|
||||
switch (direction) {
|
||||
case Direction::Left:
|
||||
nx = x - 1;
|
||||
nz = z;
|
||||
break;
|
||||
case Direction::Up:
|
||||
nx = x;
|
||||
nz = z - 1;
|
||||
break;
|
||||
case Direction::Right:
|
||||
nx = x + 1;
|
||||
nz = z;
|
||||
break;
|
||||
case Direction::Down:
|
||||
nx = x;
|
||||
nz = z + 1;
|
||||
break;
|
||||
}
|
||||
// border values don't have constraints outside the map
|
||||
if (nx < 0 || nx >= metaMap->width || nz < 0 || nz >= metaMap->height) {
|
||||
return true;
|
||||
}
|
||||
const PlacedTile &pt = view.get(nx, nz);
|
||||
if (pt.tileName.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int placedIdx;
|
||||
int idx;
|
||||
Tile::oppositeIndices(direction, idx, placedIdx);
|
||||
|
||||
const uint64_t mask = tile.convertTileIdToMask(idx);
|
||||
const uint64_t placedMask = pt.tile.convertTileIdToMask(placedIdx);
|
||||
return mask & placedMask;
|
||||
}
|
||||
|
||||
static bool checkTileFits(const MetaMap *metaMap, const MapTileView &view, int x, int z, const Tile &tile) {
|
||||
if (!checkTile(metaMap, view, x, z, tile, Direction::Left)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkTile(metaMap, view, x, z, tile, Direction::Up)) {
|
||||
return false;
|
||||
}
|
||||
// this is just for fixed placed tiles - otherwise we would just look backwards
|
||||
if (!checkTile(metaMap, view, x, z, tile, Direction::Right)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkTile(metaMap, view, x, z, tile, Direction::Down)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fillFixedTiles(const MetaMap *&metaMap, MapTileCountArray &cnt, MapTileView &view) {
|
||||
Log::debug("Fill fixed tiles: %i", (int)metaMap->fixedTiles.size());
|
||||
for (const FixedTile &fixedTile : metaMap->fixedTiles) {
|
||||
const auto it = metaMap->tiles.find(fixedTile.tileName);
|
||||
if (it == metaMap->tiles.end()) {
|
||||
Log::error("Failed to find tile %s", fixedTile.tileName.c_str());
|
||||
continue;
|
||||
}
|
||||
const int x = fixedTile.x;
|
||||
const int z = fixedTile.z;
|
||||
if (!view.get(x, z).tileName.empty()) {
|
||||
Log::warn("Fixed tile can't get placed - the field is already occupied");
|
||||
continue;
|
||||
}
|
||||
const PlacedTile placedTile{fixedTile.tileName, it->value};
|
||||
view.set(x, z, placedTile);
|
||||
int count = 0;
|
||||
cnt.get(fixedTile.tileName, count);
|
||||
cnt.put(fixedTile.tileName, ++count);
|
||||
Log::debug("Place fixed tile %s at %i:%i", fixedTile.tileName.c_str(), x, z);
|
||||
}
|
||||
}
|
||||
|
||||
static bool tryToPlaceTile(int x, int z, const core::String &tileId, const Tile &tile, const MetaMap *&metaMap,
|
||||
MapTileCountArray &cnt, MapTileView &view) {
|
||||
int count = 0;
|
||||
cnt.get(tileId, count);
|
||||
|
||||
const auto it = metaMap->tileConfigs.find(tileId);
|
||||
if (it != metaMap->tileConfigs.end()) {
|
||||
const TileConfig &cfg = it->value;
|
||||
if (count >= cfg.maximum) {
|
||||
Log::debug("max count for %s reached", tileId.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (checkTileFits(metaMap, view, x, z, tile)) {
|
||||
const PlacedTile placedTile{it->key, tile};
|
||||
view.set(x, z, placedTile);
|
||||
cnt.put(tileId, ++count);
|
||||
Log::debug("tile fits %s at %i:%i", tileId.c_str(), x, z);
|
||||
return true;
|
||||
}
|
||||
Log::debug("tile doesn't fit %s at %i:%i", tileId.c_str(), x, z);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void fillSuitableTiles(math::Random &rnd, const MetaMap *&metaMap, MapTileCountArray &cnt, MapTileView &view) {
|
||||
const int w = metaMap->width;
|
||||
for (int i = 0; i < (int)view.size(); ++i) {
|
||||
const int x = i % w;
|
||||
const int z = i / w;
|
||||
if (!view.get(x, z).tileName.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &e : metaMap->tiles) {
|
||||
const float r = rnd.randomf();
|
||||
if (r >= 0.8f) {
|
||||
continue;
|
||||
}
|
||||
if (tryToPlaceTile(x, z, e->key, e->value, metaMap, cnt, view)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fillGaps(const MetaMap *&metaMap, MapTileCountArray &cnt, MapTileView &view) {
|
||||
const int w = metaMap->width;
|
||||
for (int i = 0; i < (int)view.size(); ++i) {
|
||||
const int x = i % w;
|
||||
const int z = i / w;
|
||||
if (!view.get(x, z).tileName.empty()) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &e : metaMap->tiles) {
|
||||
if (tryToPlaceTile(x, z, e->key, e->value, metaMap, cnt, view)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isCompleted(const MetaMap *&metaMap, MapTileCountArray &cnt, MapTileView &view) {
|
||||
const int w = metaMap->width;
|
||||
for (int i = 0; i < (int)view.size(); ++i) {
|
||||
const int x = i % w;
|
||||
const int z = i / w;
|
||||
if (view.get(x, z).tileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static core::String getPathForTileName(const core::String &tileName, int level) {
|
||||
return core::string::format("maps/%s_%i", tileName.c_str(), level);
|
||||
}
|
||||
|
||||
static void createVolumes(LevelVolumes &volumes, const MapTileView &view,
|
||||
const voxelformat::VolumeCachePtr &volumeCache) {
|
||||
const int w = view.width();
|
||||
const int h = view.height();
|
||||
for (int level = 0; level < (int)volumes.size(); ++level) {
|
||||
const int minsY = RMA_MAP_LEVEL_VOXEL_HEIGHT * level;
|
||||
const int maxsX = w * RMA_MAP_TILE_VOXEL_SIZE - 1;
|
||||
const int maxsZ = maxsX;
|
||||
const int maxsY = minsY + RMA_MAP_LEVEL_VOXEL_HEIGHT - 1;
|
||||
const voxel::Region region(0, minsY, 0, maxsX, maxsY, maxsZ);
|
||||
voxel::RawVolume *finalVolume = new voxel::RawVolume(region);
|
||||
for (int x = 0; x < w; ++x) {
|
||||
for (int z = 0; z < h; ++z) {
|
||||
const PlacedTile &placedTile = view.get(x, z);
|
||||
if (placedTile.tileName.empty()) {
|
||||
Log::warn("Failed to place a tile at %i:%i", x, z);
|
||||
continue;
|
||||
}
|
||||
const core::String &tilePath = getPathForTileName(placedTile.tileName, level);
|
||||
const voxel::RawVolume *v = volumeCache->loadVolume(tilePath);
|
||||
if (v == nullptr) {
|
||||
if (level == 0) {
|
||||
Log::warn("Could not find map tile for %s", tilePath.c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const int tminsx = x * RMA_MAP_TILE_VOXEL_SIZE;
|
||||
const int tminsy = level * RMA_MAP_LEVEL_VOXEL_HEIGHT;
|
||||
const int tminsz = z * RMA_MAP_TILE_VOXEL_SIZE;
|
||||
const int tmaxsx = tminsx + RMA_MAP_TILE_VOXEL_SIZE;
|
||||
const int tmaxsy = tminsy + RMA_MAP_LEVEL_VOXEL_HEIGHT;
|
||||
const int tmaxsz = tminsz + RMA_MAP_TILE_VOXEL_SIZE;
|
||||
const voxel::Region targetRegion(tminsx, tminsy, tminsz, tmaxsx, tmaxsy, tmaxsz);
|
||||
voxelutil::copyIntoRegion(*v, *finalVolume, targetRegion);
|
||||
}
|
||||
}
|
||||
volumes[level] = finalVolume;
|
||||
}
|
||||
}
|
||||
|
||||
LevelVolumes buildMap(const MetaMap *metaMap, const voxelformat::VolumeCachePtr &volumeCache, unsigned int seed) {
|
||||
core_assert(!metaMap->tiles.empty());
|
||||
const int w = metaMap->width;
|
||||
const int h = metaMap->height;
|
||||
|
||||
LevelVolumes levels;
|
||||
levels.fill(nullptr);
|
||||
|
||||
math::Random rnd(seed);
|
||||
|
||||
// assemble the map
|
||||
const int maxRuns = 4;
|
||||
for (int i = 0; i < maxRuns; ++i) {
|
||||
MapTileArray map(w * h);
|
||||
MapTileView view(map.data(), w, h);
|
||||
MapTileCountArray cnt;
|
||||
fillFixedTiles(metaMap, cnt, view);
|
||||
fillSuitableTiles(rnd, metaMap, cnt, view);
|
||||
fillGaps(metaMap, cnt, view);
|
||||
if (isCompleted(metaMap, cnt, view)) {
|
||||
for (int i = 0; i < (int)view.size(); ++i) {
|
||||
const int x = i % w;
|
||||
const int y = i / w;
|
||||
const PlacedTile &placedTile = view.get(x, y);
|
||||
Log::debug("%i:%i => %s", x, y, placedTile.tileName.c_str());
|
||||
}
|
||||
|
||||
// create the map levels
|
||||
createVolumes(levels, view, volumeCache);
|
||||
break;
|
||||
}
|
||||
Log::warn("Failed to assemble map with run %i/%i", i, maxRuns);
|
||||
}
|
||||
|
||||
// TODO: improve this and replace the dummy algo
|
||||
// https://ijdykeman.github.io/ml/2017/10/12/wang-tile-procedural-generation.html
|
||||
// https://en.wikipedia.org/wiki/Wang_tile
|
||||
// https://nothings.org/gamedev/herringbone/herringbone_src.html
|
||||
return levels;
|
||||
}
|
||||
|
||||
} // namespace rma
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MetaMap.h"
|
||||
#include "voxelformat/VolumeCache.h"
|
||||
|
||||
namespace rma {
|
||||
|
||||
LevelVolumes buildMap(const MetaMap *metaMap, const voxelformat::VolumeCachePtr &volumeCache, unsigned int seed = 0u);
|
||||
|
||||
} // namespace rma
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "MetaMap.h"
|
||||
#include "LUAMetaMap.h"
|
||||
|
||||
namespace rma {
|
||||
|
||||
uint64_t Tile::convertTileIdToMask(int idx) const {
|
||||
uint64_t mask = 0l;
|
||||
for (char chr : tiles3x3[idx]) {
|
||||
if (chr == '+') {
|
||||
mask |= RMA_SOLID;
|
||||
} else if (chr == '0') {
|
||||
mask |= RMA_EVERYTHING_FITS;
|
||||
} else if (chr >= 'a' && chr <= 'z') {
|
||||
mask |= 1UL << ((chr - 'a') + 1);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
void Tile::oppositeIndices(Direction dir, int &side1, int &side2) {
|
||||
switch (dir) {
|
||||
case Direction::Left:
|
||||
side1 = 3;
|
||||
side2 = 5;
|
||||
break;
|
||||
case Direction::Right:
|
||||
side1 = 5;
|
||||
side2 = 3;
|
||||
break;
|
||||
case Direction::Up:
|
||||
side1 = 1;
|
||||
side2 = 7;
|
||||
break;
|
||||
case Direction::Down:
|
||||
side1 = 7;
|
||||
side2 = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
MetaMap::MetaMap(const core::String &name) : _name(name), model(name) {
|
||||
}
|
||||
|
||||
bool MetaMap::load(const core::String &luaString) {
|
||||
lua::LUA lua;
|
||||
luametamap_setup(lua, luaExtensions());
|
||||
if (!lua.load(luaString)) {
|
||||
Log::error("Failed to load lua script for map %s: %s", _name.c_str(), lua.error().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_getglobal(lua, "init");
|
||||
if (lua_isnil(lua, -1)) {
|
||||
Log::error("Function init(map) wasn't found");
|
||||
return false;
|
||||
}
|
||||
|
||||
luametamap_pushmetamap(lua, this);
|
||||
const int ret = lua_pcall(lua, 1, 0, 0);
|
||||
if (ret != LUA_OK) {
|
||||
Log::error("%s", lua_tostring(lua, -1));
|
||||
return false;
|
||||
}
|
||||
Log::debug("map %s loaded", _name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace rma
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/SharedPtr.h"
|
||||
#include "core/String.h"
|
||||
#include "core/collection/DynamicArray.h"
|
||||
#include "core/collection/StringMap.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "commonlua/LUA.h"
|
||||
|
||||
namespace lua {
|
||||
class LUA;
|
||||
}
|
||||
|
||||
namespace rma {
|
||||
|
||||
#define RMA_MAX_MAP_LEVEL 4
|
||||
using LevelVolumes = core::Array<voxel::RawVolume *, RMA_MAX_MAP_LEVEL>;
|
||||
|
||||
// x and z dimensions
|
||||
#define RMA_MAP_TILE_VOXEL_SIZE 64
|
||||
// y dimension
|
||||
#define RMA_MAP_LEVEL_VOXEL_HEIGHT 19
|
||||
|
||||
#define RMA_SOLID (1UL)
|
||||
#define RMA_IS_PLACED(x) ((x)&RMA_SOLID)
|
||||
#define RMA_EVERYTHING_FITS (~RMA_SOLID)
|
||||
|
||||
enum class Direction { Left, Up, Right, Down };
|
||||
|
||||
struct Tile {
|
||||
// this is a map of surrounding definitions of other tiles
|
||||
// and the center that holds the own tile definition
|
||||
core::String tiles3x3[9];
|
||||
|
||||
uint64_t convertTileIdToMask(int idx) const;
|
||||
|
||||
inline uint64_t ownMask() const {
|
||||
return convertTileIdToMask(4);
|
||||
}
|
||||
|
||||
inline uint64_t leftMask() const {
|
||||
return convertTileIdToMask(3);
|
||||
}
|
||||
inline uint64_t rightMask() const {
|
||||
return convertTileIdToMask(5);
|
||||
}
|
||||
|
||||
inline uint64_t upMask() const {
|
||||
return convertTileIdToMask(1);
|
||||
}
|
||||
inline uint64_t downMask() const {
|
||||
return convertTileIdToMask(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Direction indices into the 3x3 matrix
|
||||
*
|
||||
* @code
|
||||
* 0 1 2
|
||||
* 3 4 5
|
||||
* 6 7 8
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] dir @c Directionto walk into
|
||||
* @param[out] side1 The side of your own tile into the given direction
|
||||
* @param[out] side2 The side of the neighbour tile for the given direction
|
||||
*/
|
||||
static void oppositeIndices(Direction dir, int &side1, int &side2);
|
||||
};
|
||||
|
||||
struct TileConfig {
|
||||
int maximum;
|
||||
};
|
||||
|
||||
struct FixedTile {
|
||||
core::String tileName;
|
||||
int x;
|
||||
int z;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is a description of a liveless map object. The tower positions in the final map are - next to other
|
||||
* things - defined here.
|
||||
*/
|
||||
class MetaMap {
|
||||
private:
|
||||
const core::String _name; //!< filename without extension
|
||||
|
||||
protected:
|
||||
virtual core::DynamicArray<luaL_Reg> luaExtensions() {
|
||||
return core::DynamicArray<luaL_Reg>{};
|
||||
}
|
||||
public:
|
||||
MetaMap(const core::String &name);
|
||||
|
||||
bool load(const core::String &luaString);
|
||||
const core::String &name() const;
|
||||
|
||||
core::String model; //!< model inside the maps folder - extension is optional
|
||||
core::String title;
|
||||
core::String image; //!< image inside the maps folder - extension is optional
|
||||
core::String description;
|
||||
|
||||
// generator stuff
|
||||
int width = 3;
|
||||
int height = 3;
|
||||
core::StringMap<Tile> tiles;
|
||||
core::StringMap<TileConfig> tileConfigs;
|
||||
core::DynamicArray<FixedTile> fixedTiles;
|
||||
};
|
||||
|
||||
inline const core::String &MetaMap::name() const {
|
||||
return _name;
|
||||
}
|
||||
|
||||
typedef core::SharedPtr<MetaMap> MetaMapPtr;
|
||||
|
||||
} // namespace rma
|
|
@ -0,0 +1,31 @@
|
|||
local tiles = {}
|
||||
|
||||
-- a defines arbitrary tiles
|
||||
-- h is a horizontal street
|
||||
-- v is a vertical street
|
||||
-- x is a street cross
|
||||
|
||||
tiles.city = {
|
||||
['house1'] = {
|
||||
"0", "ha", "0",
|
||||
"va", "+a", "va",
|
||||
"0", "ha", "0"
|
||||
},
|
||||
['street_h_1'] = {
|
||||
"0", "a", "0",
|
||||
"hx", "+h", "hx",
|
||||
"0", "a", "0"
|
||||
},
|
||||
['street_v_1'] = {
|
||||
"0", "vx", "0",
|
||||
"a", "+v", "a",
|
||||
"0", "vx", "0"
|
||||
},
|
||||
['street_cross_1'] = {
|
||||
"0", "vx", "0",
|
||||
"hx", "+x", "hx",
|
||||
"0", "vx", "0"
|
||||
}
|
||||
}
|
||||
|
||||
return tiles
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
#include "rma/MetaMap.h"
|
||||
#include "app/tests/AbstractTest.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace rma {
|
||||
|
||||
class LUAMetaMapTest : public app::AbstractTest {};
|
||||
|
||||
TEST_F(LUAMetaMapTest, testLoadMaps) {
|
||||
MetaMap metaMap("test");
|
||||
ASSERT_TRUE(metaMap.load(R"(
|
||||
local tiles_city = {
|
||||
['house1'] = {
|
||||
'0', '0', '0',
|
||||
'0', '+a', '0',
|
||||
'0', 'h', '0'
|
||||
},
|
||||
['street_h_1'] = {
|
||||
'0', '0', '0',
|
||||
'h', '+h', 'h',
|
||||
'0', '0', '0'
|
||||
},
|
||||
['street_v_1'] = {
|
||||
'0', 'vx', '0',
|
||||
'0', '+v', '0',
|
||||
'0', 'vx', '0'
|
||||
},
|
||||
['street_cross_1'] = {
|
||||
'0', 'v', '0',
|
||||
'h', '+x', 'h',
|
||||
'0', 'v', '0'
|
||||
}
|
||||
}
|
||||
|
||||
function init(map)
|
||||
map:setTiles(tiles_city)
|
||||
map:setSize(6, 6)
|
||||
map:addTileConfig('street_h_1', 10)
|
||||
map:addTileConfig('street_cross', 1)
|
||||
map:addFixedTile('street_h_1', 1, 4)
|
||||
--map:addFixedTile('street_h_1', -10, -10)
|
||||
map:setDescription('Small test map to implement the game. Not much that can be seen here yet - but more will follow')
|
||||
map:setTitle('Small test map')
|
||||
map:setImage('city')
|
||||
end
|
||||
)"));
|
||||
EXPECT_STREQ("test", metaMap.name().c_str());
|
||||
EXPECT_EQ(4, (int)metaMap.tiles.size());
|
||||
EXPECT_EQ(2, (int)metaMap.tileConfigs.size());
|
||||
|
||||
Tile street_h_1;
|
||||
EXPECT_TRUE(metaMap.tiles.get("street_h_1", street_h_1));
|
||||
|
||||
TileConfig street_h_1_cfg;
|
||||
EXPECT_TRUE(metaMap.tileConfigs.get("street_h_1", street_h_1_cfg));
|
||||
EXPECT_EQ(10, street_h_1_cfg.maximum);
|
||||
|
||||
ASSERT_EQ(1, (int)metaMap.fixedTiles.size());
|
||||
EXPECT_STREQ("street_h_1", metaMap.fixedTiles[0].tileName.c_str());
|
||||
EXPECT_EQ(1, metaMap.fixedTiles[0].x);
|
||||
EXPECT_EQ(4, metaMap.fixedTiles[0].z);
|
||||
}
|
||||
|
||||
} // namespace towerdefense
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @file
|
||||
*/
|
||||
|
||||
#include "rma/MapBuilder.h"
|
||||
#include "app/App.h"
|
||||
#include "app/tests/AbstractTest.h"
|
||||
#include "io/File.h"
|
||||
#include "io/Filesystem.h"
|
||||
#include "rma/MetaMap.h"
|
||||
#include "voxel/MaterialColor.h"
|
||||
#include "voxel/RawVolume.h"
|
||||
#include "voxelformat/VolumeCache.h"
|
||||
#include "voxelformat/VolumeFormat.h"
|
||||
#include "voxelutil/VolumeMerger.h"
|
||||
#include "voxelutil/VolumeVisitor.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace rma {
|
||||
|
||||
static const Tile street_v_1{"0", "vx", "0", "a", "+v", "a", "0", "vx", "0"};
|
||||
static const Tile street_h_1{"0", "a", "0", "hx", "+h", "hx", "0", "a", "0"};
|
||||
static const Tile street_cross_1{"0", "vx", "0", "hx", "+x", "hx", "0", "vx", "0"};
|
||||
static const Tile house1{"0", "ha", "0", "va", "+a", "va", "0", "ha", "0"};
|
||||
|
||||
class MapBuilderTest : public app::AbstractTest {
|
||||
protected:
|
||||
voxelformat::VolumeCachePtr _volumeCache;
|
||||
|
||||
bool onInitApp() override {
|
||||
voxel::initDefaultMaterialColors();
|
||||
_volumeCache = std::make_shared<voxelformat::VolumeCache>();
|
||||
if (!_volumeCache->init()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void onCleanupApp() override {
|
||||
_volumeCache->shutdown();
|
||||
}
|
||||
|
||||
bool save(const LevelVolumes &volumes) const {
|
||||
voxel::VoxelVolumes v;
|
||||
v.resize(volumes.size());
|
||||
int j = 0;
|
||||
for (int i = 0; i < (int)volumes.size(); ++i) {
|
||||
if (volumes[i] != nullptr) {
|
||||
v[j++] = volumes[i];
|
||||
}
|
||||
}
|
||||
v.resize(j);
|
||||
const io::FilePtr &file = _testApp->filesystem()->open("mapbuildertest.qb", io::FileMode::SysWrite);
|
||||
return voxelformat::saveVolumeFormat(file, v);
|
||||
}
|
||||
|
||||
void cleanup(const LevelVolumes &volumes) {
|
||||
for (voxel::RawVolume *v : volumes) {
|
||||
delete v;
|
||||
}
|
||||
}
|
||||
|
||||
bool empty(const rma::LevelVolumes &volumes) const {
|
||||
bool empty = true;
|
||||
for (voxel::RawVolume *v : volumes) {
|
||||
if (v == nullptr) {
|
||||
return true;
|
||||
}
|
||||
voxelutil::visitVolume(*v, [&empty](int, int, int, const voxel::Voxel &voxel) {
|
||||
if (!voxel::isAir(voxel.getMaterial())) {
|
||||
empty = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
return empty;
|
||||
}
|
||||
|
||||
MetaMap createMetaMap() const {
|
||||
MetaMap metaMap("test");
|
||||
metaMap.width = 3;
|
||||
metaMap.height = 3;
|
||||
metaMap.tiles.put("house1", house1);
|
||||
metaMap.tiles.put("street_h_1", street_h_1);
|
||||
metaMap.tiles.put("street_v_1", street_v_1);
|
||||
metaMap.tiles.put("street_cross_1", street_cross_1);
|
||||
metaMap.tileConfigs.put("house1", TileConfig{4});
|
||||
metaMap.tileConfigs.put("street_h_1", TileConfig{10});
|
||||
metaMap.tileConfigs.put("street_v_1", TileConfig{10});
|
||||
metaMap.tileConfigs.put("street_cross_1", TileConfig{1});
|
||||
metaMap.fixedTiles.push_back(FixedTile{"street_cross_1", 1, 1});
|
||||
return metaMap;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(MapBuilderTest, testConvertTileIdToMask) {
|
||||
EXPECT_TRUE(street_cross_1.convertTileIdToMask(0) == RMA_EVERYTHING_FITS);
|
||||
EXPECT_TRUE(street_cross_1.convertTileIdToMask(2) == RMA_EVERYTHING_FITS);
|
||||
EXPECT_TRUE(street_cross_1.convertTileIdToMask(6) == RMA_EVERYTHING_FITS);
|
||||
EXPECT_TRUE(street_cross_1.convertTileIdToMask(8) == RMA_EVERYTHING_FITS);
|
||||
EXPECT_TRUE(street_cross_1.convertTileIdToMask(4) & RMA_SOLID);
|
||||
}
|
||||
|
||||
TEST_F(MapBuilderTest, testStreetFitsLeftCross) {
|
||||
int streetRight, crossLeft;
|
||||
Tile::oppositeIndices(Direction::Left, crossLeft, streetRight);
|
||||
const uint64_t crossMask = street_cross_1.convertTileIdToMask(crossLeft);
|
||||
const uint64_t streetMask = street_h_1.convertTileIdToMask(streetRight);
|
||||
EXPECT_TRUE(crossMask & streetMask) << "crossMask: " << crossMask << ", streeMask: " << streetMask;
|
||||
}
|
||||
|
||||
TEST_F(MapBuilderTest, testStreetDoesNotFitLeftCross) {
|
||||
int streetRight, crossLeft;
|
||||
Tile::oppositeIndices(Direction::Left, crossLeft, streetRight);
|
||||
const uint64_t crossMask = street_cross_1.convertTileIdToMask(crossLeft);
|
||||
const uint64_t streetMask = street_v_1.convertTileIdToMask(streetRight);
|
||||
EXPECT_FALSE(crossMask & streetMask) << "crossMask: " << crossMask << ", streeMask: " << streetMask;
|
||||
}
|
||||
|
||||
TEST_F(MapBuilderTest, testBuildMap) {
|
||||
const MetaMap &metaMap = createMetaMap();
|
||||
const LevelVolumes &volumes = buildMap(&metaMap, _volumeCache);
|
||||
EXPECT_FALSE(empty(volumes));
|
||||
EXPECT_TRUE(save(volumes));
|
||||
cleanup(volumes);
|
||||
}
|
||||
|
||||
} // namespace rma
|
Loading…
Reference in New Issue