diff --git a/doc/lua_api.txt b/doc/lua_api.txt index cb8b8848f..93387ef0b 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -782,30 +782,27 @@ Schematic specifier -------------------- A schematic specifier identifies a schematic by either a filename to a Minetest Schematic file (`.mts`) or through raw data supplied through Lua, -in the form of a table. This table must specify two fields: +in the form of a table. This table specifies the following fields: -* The `size` field is a 3D vector containing the dimensions of the provided schematic. -* The `data` field is a flat table of MapNodes making up the schematic, - in the order of `[z [y [x]]]`. +* The `size` field is a 3D vector containing the dimensions of the provided schematic. (required) +* The `yslice_prob` field is a table of {ypos, prob} which sets the `ypos`th vertical slice + of the schematic to have a `prob / 256 * 100` chance of occuring. (default: 255) +* The `data` field is a flat table of MapNode tables making up the schematic, + in the order of `[z [y [x]]]`. (required) + Each MapNode table contains: + * `name`: the name of the map node to place (required) + * `prob` (alias `param1`): the probability of this node being placed (default: 255) + * `param2`: the raw param2 value of the node being placed onto the map (default: 0) + * `force_place`: boolean representing if the node should forcibly overwrite any + previous contents (default: false) -**Important**: The default value for `param1` in MapNodes here is `255`, -which represents "always place". - -In the bulk `MapNode` data, `param1`, instead of the typical light values, -instead represents the probability of that node appearing in the structure. - -When passed to `minetest.create_schematic`, probability is an integer value -ranging from `0` to `255`: - -* A probability value of `0` means that node will never appear (0% chance). -* A probability value of `255` means the node will always appear (100% chance). -* If the probability value `p` is greater than `0`, then there is a - `(p / 256 * 100)`% chance that node will appear when the schematic is +About probability values: +* A probability value of `0` or `1` means that node will never appear (0% chance). +* A probability value of `254` or `255` means the node will always appear (100% chance). +* If the probability value `p` is greater than `1`, then there is a + `(p / 256 * 100)` percent chance that node will appear when the schematic is placed on the map. -**Important note**: Node aliases cannot be used for a raw schematic provided - when registering as a decoration. - Schematic attributes -------------------- diff --git a/src/mg_schematic.cpp b/src/mg_schematic.cpp index 33f82a74c..81d849a66 100644 --- a/src/mg_schematic.cpp +++ b/src/mg_schematic.cpp @@ -133,8 +133,8 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_pla s16 y_map = p.Y; for (s16 y = 0; y != sy; y++) { - if (slice_probs[y] != MTSCHEM_PROB_ALWAYS && - myrand_range(1, 255) > slice_probs[y]) + if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) && + (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; for (s16 z = 0; z != sz; z++) { @@ -147,17 +147,20 @@ void Schematic::blitToVManip(v3s16 p, MMVManip *vm, Rotation rot, bool force_pla if (schemdata[i].getContent() == CONTENT_IGNORE) continue; - if (schemdata[i].param1 == MTSCHEM_PROB_NEVER) + u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + + if (placement_prob == MTSCHEM_PROB_NEVER) continue; - if (!force_place) { + if (!force_place && !force_place_node) { content_t c = vm->m_data[vi].getContent(); if (c != CONTENT_AIR && c != CONTENT_IGNORE) continue; } - if (schemdata[i].param1 != MTSCHEM_PROB_ALWAYS && - myrand_range(1, 255) > schemdata[i].param1) + if ((placement_prob != MTSCHEM_PROB_ALWAYS) && + (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) continue; vm->m_data[vi] = schemdata[i]; @@ -225,6 +228,7 @@ bool Schematic::deserializeFromMts(std::istream *is, content_t cignore = CONTENT_IGNORE; bool have_cignore = false; + //// Read signature u32 signature = readU32(ss); if (signature != MTSCHEM_FILE_SIGNATURE) { errorstream << "Schematic::deserializeFromMts: invalid schematic " @@ -232,6 +236,7 @@ bool Schematic::deserializeFromMts(std::istream *is, return false; } + //// Read version u16 version = readU16(ss); if (version > MTSCHEM_FILE_VER_HIGHEST_READ) { errorstream << "Schematic::deserializeFromMts: unsupported schematic " @@ -239,18 +244,21 @@ bool Schematic::deserializeFromMts(std::istream *is, return false; } + //// Read size size = readV3S16(ss); + //// Read Y-slice probability values delete []slice_probs; slice_probs = new u8[size.Y]; for (int y = 0; y != size.Y; y++) - slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS; + slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD; + //// Read node names u16 nidmapcount = readU16(ss); for (int i = 0; i != nidmapcount; i++) { std::string name = deSerializeString(ss); - // Instances of "ignore" from ver 1 are converted to air (and instances + // Instances of "ignore" from v1 are converted to air (and instances // are fixed to have MTSCHEM_PROB_NEVER later on). if (name == "ignore") { name = "air"; @@ -261,6 +269,7 @@ bool Schematic::deserializeFromMts(std::istream *is, names->push_back(name); } + //// Read node data size_t nodecount = size.X * size.Y * size.Z; delete []schemdata; @@ -269,8 +278,8 @@ bool Schematic::deserializeFromMts(std::istream *is, MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, nodecount, 2, 2, true); - // fix any probability values for nodes that were ignore - if (version == 1) { + // Fix probability values for nodes that were ignore; removed in v2 + if (version < 2) { for (size_t i = 0; i != nodecount; i++) { if (schemdata[i].param1 == 0) schemdata[i].param1 = MTSCHEM_PROB_ALWAYS; @@ -279,6 +288,14 @@ bool Schematic::deserializeFromMts(std::istream *is, } } + // Fix probability values for probability range truncation introduced in v4 + if (version < 4) { + for (s16 y = 0; y != size.Y; y++) + slice_probs[y] >>= 1; + for (size_t i = 0; i != nodecount; i++) + schemdata[i].param1 >>= 1; + } + return true; } @@ -331,9 +348,11 @@ bool Schematic::serializeToLua(std::ostream *os, ss << indent << "yslice_prob = {" << std::endl; for (u16 y = 0; y != size.Y; y++) { + u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK; + ss << indent << indent << "{" << "ypos=" << y - << ", prob=" << (u16)slice_probs[y] + << ", prob=" << (u16)probability * 2 << "}," << std::endl; } @@ -355,11 +374,18 @@ bool Schematic::serializeToLua(std::ostream *os, } for (u16 x = 0; x != size.X; x++, i++) { + u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + ss << indent << indent << "{" << "name=\"" << names[schemdata[i].getContent()] - << "\", param1=" << (u16)schemdata[i].param1 - << ", param2=" << (u16)schemdata[i].param2 - << "}," << std::endl; + << "\", prob=" << (u16)probability * 2 + << ", param2=" << (u16)schemdata[i].param2; + + if (force_place) + ss << ", force_place=true"; + + ss << "}," << std::endl; } } diff --git a/src/mg_schematic.h b/src/mg_schematic.h index 3d3e328d3..5c732648e 100644 --- a/src/mg_schematic.h +++ b/src/mg_schematic.h @@ -36,7 +36,7 @@ class IGameDef; All values are stored in big-endian byte order. [u32] signature: 'MTSM' - [u16] version: 3 + [u16] version: 4 [u16] size X [u16] size Y [u16] size Z @@ -51,7 +51,9 @@ class IGameDef; For each node in schematic: (for z, y, x) [u16] content For each node in schematic: - [u8] probability of occurance (param1) + [u8] param1 + bit 0-6: probability + bit 7: specific node force placement For each node in schematic: [u8] param2 } @@ -60,17 +62,21 @@ class IGameDef; 1 - Initial version 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always 3 - Added y-slice probabilities; this allows for variable height structures + 4 - Compressed range of node occurence prob., added per-node force placement bit */ -/////////////////// Schematic flags -#define SCHEM_CIDS_UPDATED 0x08 - +//// Schematic constants #define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' -#define MTSCHEM_FILE_VER_HIGHEST_READ 3 -#define MTSCHEM_FILE_VER_HIGHEST_WRITE 3 +#define MTSCHEM_FILE_VER_HIGHEST_READ 4 +#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 -#define MTSCHEM_PROB_NEVER 0x00 -#define MTSCHEM_PROB_ALWAYS 0xFF +#define MTSCHEM_PROB_MASK 0x7F + +#define MTSCHEM_PROB_NEVER 0x00 +#define MTSCHEM_PROB_ALWAYS 0x7F +#define MTSCHEM_PROB_ALWAYS_OLD 0xFF + +#define MTSCHEM_FORCE_PLACE 0x80 enum SchematicType { diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 157db3b7d..fc489ea95 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -331,6 +331,19 @@ bool getintfield(lua_State *L, int table, return got; } +bool getintfield(lua_State *L, int table, + const char *fieldname, u8 &result) +{ + lua_getfield(L, table, fieldname); + bool got = false; + if(lua_isnumber(L, -1)){ + result = lua_tonumber(L, -1); + got = true; + } + lua_pop(L, 1); + return got; +} + bool getintfield(lua_State *L, int table, const char *fieldname, u16 &result) { diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 1d5be6971..0847f47c9 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -53,6 +53,8 @@ size_t getstringlistfield(lua_State *L, int table, std::vector *result); bool getintfield(lua_State *L, int table, const char *fieldname, int &result); +bool getintfield(lua_State *L, int table, + const char *fieldname, u8 &result); bool getintfield(lua_State *L, int table, const char *fieldname, u16 &result); bool getintfield(lua_State *L, int table, diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index 5422447ed..3cb52eab4 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -237,36 +237,32 @@ bool read_schematic_def(lua_State *L, int index, lua_getfield(L, index, "data"); luaL_checktype(L, -1, LUA_TTABLE); - int numnodes = size.X * size.Y * size.Z; + u32 numnodes = size.X * size.Y * size.Z; schem->schemdata = new MapNode[numnodes]; - int i = 0; size_t names_base = names->size(); std::map name_id_map; - lua_pushnil(L); - while (lua_next(L, -2)) { - if (i >= numnodes) { - i++; - lua_pop(L, 1); + u32 i = 0; + for (lua_pushnil(L); lua_next(L, -2); i++, lua_pop(L, 1)) { + if (i >= numnodes) continue; - } - // same as readnode, except param1 default is MTSCHEM_PROB_CONST - lua_getfield(L, -1, "name"); - std::string name = luaL_checkstring(L, -1); - lua_pop(L, 1); + //// Read name + std::string name; + if (!getstringfield(L, -1, "name", name)) + throw LuaError("Schematic data definition with missing name field"); + //// Read param1/prob u8 param1; - lua_getfield(L, -1, "param1"); - param1 = !lua_isnil(L, -1) ? lua_tonumber(L, -1) : MTSCHEM_PROB_ALWAYS; - lua_pop(L, 1); + if (!getintfield(L, -1, "param1", param1) && + !getintfield(L, -1, "prob", param1)) + param1 = MTSCHEM_PROB_ALWAYS_OLD; - u8 param2; - lua_getfield(L, -1, "param2"); - param2 = !lua_isnil(L, -1) ? lua_tonumber(L, -1) : 0; - lua_pop(L, 1); + //// Read param2 + u8 param2 = getintfield_default(L, -1, "param2", 0); + //// Find or add new nodename-to-ID mapping std::map::iterator it = name_id_map.find(name); content_t name_index; if (it != name_id_map.end()) { @@ -277,10 +273,13 @@ bool read_schematic_def(lua_State *L, int index, names->push_back(name); } - schem->schemdata[i] = MapNode(name_index, param1, param2); + //// Perform probability/force_place fixup on param1 + param1 >>= 1; + if (getboolfield_default(L, -1, "force_place", false)) + param1 |= MTSCHEM_FORCE_PLACE; - i++; - lua_pop(L, 1); + //// Actually set the node in the schematic + schem->schemdata[i] = MapNode(name_index, param1, param2); } if (i != numnodes) { @@ -297,13 +296,13 @@ bool read_schematic_def(lua_State *L, int index, lua_getfield(L, index, "yslice_prob"); if (lua_istable(L, -1)) { - lua_pushnil(L); - while (lua_next(L, -2)) { - if (getintfield(L, -1, "ypos", i) && i >= 0 && i < size.Y) { - schem->slice_probs[i] = getintfield_default(L, -1, - "prob", MTSCHEM_PROB_ALWAYS); - } - lua_pop(L, 1); + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + u16 ypos; + if (!getintfield(L, -1, "ypos", ypos) || (ypos >= size.Y) || + !getintfield(L, -1, "prob", schem->slice_probs[ypos])) + continue; + + schem->slice_probs[ypos] >>= 1; } } diff --git a/src/unittest/test_schematic.cpp b/src/unittest/test_schematic.cpp index 24bacf6c5..df47d2bc0 100644 --- a/src/unittest/test_schematic.cpp +++ b/src/unittest/test_schematic.cpp @@ -34,8 +34,9 @@ public: void testLuaTableSerialize(INodeDefManager *ndef); void testFileSerializeDeserialize(INodeDefManager *ndef); - static const content_t test_schem_data[7 * 6 * 4]; - static const content_t test_schem_data2[3 * 3 * 3]; + static const content_t test_schem1_data[7 * 6 * 4]; + static const content_t test_schem2_data[3 * 3 * 3]; + static const u8 test_schem2_prob[3 * 3 * 3]; static const char *expected_lua_output; }; @@ -78,7 +79,7 @@ void TestSchematic::testMtsSerializeDeserialize(INodeDefManager *ndef) schem.schemdata = new MapNode[volume]; schem.slice_probs = new u8[size.Y]; for (size_t i = 0; i != volume; i++) - schem.schemdata[i] = MapNode(test_schem_data[i], MTSCHEM_PROB_ALWAYS, 0); + schem.schemdata[i] = MapNode(test_schem1_data[i], MTSCHEM_PROB_ALWAYS, 0); for (s16 y = 0; y != size.Y; y++) schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; @@ -115,7 +116,7 @@ void TestSchematic::testLuaTableSerialize(INodeDefManager *ndef) schem.schemdata = new MapNode[volume]; schem.slice_probs = new u8[size.Y]; for (size_t i = 0; i != volume; i++) - schem.schemdata[i] = MapNode(test_schem_data2[i], MTSCHEM_PROB_ALWAYS, 0); + schem.schemdata[i] = MapNode(test_schem2_data[i], test_schem2_prob[i], 0); for (s16 y = 0; y != size.Y; y++) schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS; @@ -160,8 +161,8 @@ void TestSchematic::testFileSerializeDeserialize(INodeDefManager *ndef) schem1.slice_probs[2] = 240; for (size_t i = 0; i != volume; i++) { - content_t c = content_map[test_schem_data2[i]]; - schem1.schemdata[i] = MapNode(c, MTSCHEM_PROB_ALWAYS, 0); + content_t c = content_map[test_schem2_data[i]]; + schem1.schemdata[i] = MapNode(c, test_schem2_prob[i], 0); } std::string temp_file = getTestTempFile(); @@ -174,14 +175,14 @@ void TestSchematic::testFileSerializeDeserialize(INodeDefManager *ndef) UASSERT(schem2.slice_probs[2] == 240); for (size_t i = 0; i != volume; i++) { - content_t c = content_map2[test_schem_data2[i]]; - UASSERT(schem2.schemdata[i] == MapNode(c, MTSCHEM_PROB_ALWAYS, 0)); + content_t c = content_map2[test_schem2_data[i]]; + UASSERT(schem2.schemdata[i] == MapNode(c, test_schem2_prob[i], 0)); } } // Should form a cross-shaped-thing...? -const content_t TestSchematic::test_schem_data[7 * 6 * 4] = { +const content_t TestSchematic::test_schem1_data[7 * 6 * 4] = { 3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0 3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0 3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0 @@ -211,7 +212,7 @@ const content_t TestSchematic::test_schem_data[7 * 6 * 4] = { 3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3 }; -const content_t TestSchematic::test_schem_data2[3 * 3 * 3] = { +const content_t TestSchematic::test_schem2_data[3 * 3 * 3] = { 0, 0, 0, 0, 2, 0, 0, 0, 0, @@ -225,41 +226,55 @@ const content_t TestSchematic::test_schem_data2[3 * 3 * 3] = { 0, 0, 0, }; +const u8 TestSchematic::test_schem2_prob[3 * 3 * 3] = { + 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, + + 0x00, 0xFF, 0x00, + 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, + + 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, + 0x00, 0x00, 0x00, +}; + const char *TestSchematic::expected_lua_output = "schematic = {\n" "\tsize = {x=3, y=3, z=3},\n" "\tyslice_prob = {\n" - "\t\t{ypos=0, prob=255},\n" - "\t\t{ypos=1, prob=255},\n" - "\t\t{ypos=2, prob=255},\n" + "\t\t{ypos=0, prob=254},\n" + "\t\t{ypos=1, prob=254},\n" + "\t\t{ypos=2, prob=254},\n" "\t},\n" "\tdata = {\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"default:lava_source\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"default:glass\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" - "\t\t{name=\"air\", param1=255, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"default:lava_source\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" + "\t\t{name=\"air\", prob=0, param2=0},\n" "\t},\n" "}\n";