RMA: new module

master
Martin Gerhardy 2021-11-24 19:30:55 +01:00
parent 8eefcf837f
commit 94128691a4
20 changed files with 929 additions and 4 deletions

BIN
data/rma/maps/house1_0.qb Normal file

Binary file not shown.

BIN
data/rma/maps/house1_1.qb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -46,4 +46,5 @@ add_subdirectory(animation)
add_subdirectory(frontend)
add_subdirectory(voxelrender)
add_subdirectory(voxelworldrender)
add_subdirectory(rma)
add_subdirectory(testcore)

View File

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

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

122
src/modules/rma/MetaMap.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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