diff --git a/minetest.conf b/minetest.conf index 3949f9c..19d0431 100644 --- a/minetest.conf +++ b/minetest.conf @@ -1,3 +1,5 @@ time_speed = 0 static_spawnpoint = 93, 4.6, 24 item_entity_ttl = -1 +water_level=-31000 +chunksize = 5 diff --git a/mods/tutorial/init.lua b/mods/tutorial/init.lua index 315ef8b..809959e 100644 --- a/mods/tutorial/init.lua +++ b/mods/tutorial/init.lua @@ -129,7 +129,7 @@ tutorial.gold = 13 tutorial.diamonds = 12 - + tutorial.texts = {} tutorial.texts.intro = [[Welcome! This tutorial will teach you the most crucial basics of Minetest. @@ -239,7 +239,7 @@ the block it is attached to, it will drop as an item which you can collect.]] tutorial.texts.disable_jump = [[These nasty blocks on the floor prevent you from jumping when you stand on them.]] -tutorial.texts.runover = +tutorial.texts.runover = [[This abyss behind this sign is so small that you can even walk over it, as long as you don't stop midway. But you can jump over it anyways, just to be, safe.]] @@ -359,7 +359,7 @@ Movement in a liquid is slightly different than on solid ground: At the bottom of the pool lies a gold ingot. Try to get it!]] -tutorial.texts.dive = +tutorial.texts.dive = [=[To get to the other side, you have to dive here. Don't worry, the tunnel is not long. But don't stay too long in the water, or else you take damage. At the bottom of the pool lies a gold ingot. Try to get it! @@ -563,7 +563,7 @@ two inventories, on the upper part the chest inventory and on the lower part the inventory. Exchanging items works exactly the same as in the inventory menu.]] -tutorial.texts.build = +tutorial.texts.build = [[Another important task in Minetest is building blocks. "Building" here refers to the task of placing one block in your possession onto another block in the world. @@ -777,7 +777,7 @@ How did you do that? Anyways, you've got teleported back to the starting location. Whatever you did, be more careful next time.]] -tutorial.texts.first_gold = +tutorial.texts.first_gold = [[You have collected your first gold ingot. Those will help you to keep track in this tutorial. There are 14 gold ingots in this tutorial. @@ -1155,6 +1155,11 @@ minetest.register_on_joinplayer(function(player) tutorial.convert_newlines(minetest.formspec_escape(S(tutorial.texts.intro))).. "]".. "button_exit[4.5,5.5;3,1;close;"..minetest.formspec_escape(S("Close")).."]" + + if tutorial.first_spawn then + player:setpos(tutorial.first_spawn.pos) + player:set_look_yaw(tutorial.first_spawn.yaw) + end end tutorial.state.first_join = false tutorial.save_state() @@ -1311,3 +1316,6 @@ function tutorial.extract_texts() end io.close(file) end + +-- Load map generation and map data management functions +dofile(minetest.get_modpath("tutorial").."/mapgen.lua") diff --git a/mods/tutorial/mapdata/entities b/mods/tutorial/mapdata/entities new file mode 100644 index 0000000..5b9b697 Binary files /dev/null and b/mods/tutorial/mapdata/entities differ diff --git a/mods/tutorial/mapdata/sector_1.meta b/mods/tutorial/mapdata/sector_1.meta new file mode 100644 index 0000000..ef0fc2d Binary files /dev/null and b/mods/tutorial/mapdata/sector_1.meta differ diff --git a/mods/tutorial/mapdata/sector_1.mts b/mods/tutorial/mapdata/sector_1.mts new file mode 100644 index 0000000..68b25fe Binary files /dev/null and b/mods/tutorial/mapdata/sector_1.mts differ diff --git a/mods/tutorial/mapdata/sector_10.mts b/mods/tutorial/mapdata/sector_10.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_10.mts differ diff --git a/mods/tutorial/mapdata/sector_11.mts b/mods/tutorial/mapdata/sector_11.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_11.mts differ diff --git a/mods/tutorial/mapdata/sector_12.mts b/mods/tutorial/mapdata/sector_12.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_12.mts differ diff --git a/mods/tutorial/mapdata/sector_13.mts b/mods/tutorial/mapdata/sector_13.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_13.mts differ diff --git a/mods/tutorial/mapdata/sector_14.mts b/mods/tutorial/mapdata/sector_14.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_14.mts differ diff --git a/mods/tutorial/mapdata/sector_15.mts b/mods/tutorial/mapdata/sector_15.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_15.mts differ diff --git a/mods/tutorial/mapdata/sector_16.mts b/mods/tutorial/mapdata/sector_16.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_16.mts differ diff --git a/mods/tutorial/mapdata/sector_17.mts b/mods/tutorial/mapdata/sector_17.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_17.mts differ diff --git a/mods/tutorial/mapdata/sector_18.mts b/mods/tutorial/mapdata/sector_18.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_18.mts differ diff --git a/mods/tutorial/mapdata/sector_19.mts b/mods/tutorial/mapdata/sector_19.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_19.mts differ diff --git a/mods/tutorial/mapdata/sector_2.meta b/mods/tutorial/mapdata/sector_2.meta new file mode 100644 index 0000000..933d294 Binary files /dev/null and b/mods/tutorial/mapdata/sector_2.meta differ diff --git a/mods/tutorial/mapdata/sector_2.mts b/mods/tutorial/mapdata/sector_2.mts new file mode 100644 index 0000000..a87f7d2 Binary files /dev/null and b/mods/tutorial/mapdata/sector_2.mts differ diff --git a/mods/tutorial/mapdata/sector_20.mts b/mods/tutorial/mapdata/sector_20.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_20.mts differ diff --git a/mods/tutorial/mapdata/sector_21.mts b/mods/tutorial/mapdata/sector_21.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_21.mts differ diff --git a/mods/tutorial/mapdata/sector_22.mts b/mods/tutorial/mapdata/sector_22.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_22.mts differ diff --git a/mods/tutorial/mapdata/sector_23.mts b/mods/tutorial/mapdata/sector_23.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_23.mts differ diff --git a/mods/tutorial/mapdata/sector_24.mts b/mods/tutorial/mapdata/sector_24.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_24.mts differ diff --git a/mods/tutorial/mapdata/sector_3.mts b/mods/tutorial/mapdata/sector_3.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_3.mts differ diff --git a/mods/tutorial/mapdata/sector_4.mts b/mods/tutorial/mapdata/sector_4.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_4.mts differ diff --git a/mods/tutorial/mapdata/sector_5.mts b/mods/tutorial/mapdata/sector_5.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_5.mts differ diff --git a/mods/tutorial/mapdata/sector_6.mts b/mods/tutorial/mapdata/sector_6.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_6.mts differ diff --git a/mods/tutorial/mapdata/sector_7.meta b/mods/tutorial/mapdata/sector_7.meta new file mode 100644 index 0000000..9780b64 Binary files /dev/null and b/mods/tutorial/mapdata/sector_7.meta differ diff --git a/mods/tutorial/mapdata/sector_7.mts b/mods/tutorial/mapdata/sector_7.mts new file mode 100644 index 0000000..0e4b11e Binary files /dev/null and b/mods/tutorial/mapdata/sector_7.mts differ diff --git a/mods/tutorial/mapdata/sector_8.meta b/mods/tutorial/mapdata/sector_8.meta new file mode 100644 index 0000000..3a7768b Binary files /dev/null and b/mods/tutorial/mapdata/sector_8.meta differ diff --git a/mods/tutorial/mapdata/sector_8.mts b/mods/tutorial/mapdata/sector_8.mts new file mode 100644 index 0000000..e5cef72 Binary files /dev/null and b/mods/tutorial/mapdata/sector_8.mts differ diff --git a/mods/tutorial/mapdata/sector_9.mts b/mods/tutorial/mapdata/sector_9.mts new file mode 100644 index 0000000..ac7265c Binary files /dev/null and b/mods/tutorial/mapdata/sector_9.mts differ diff --git a/mods/tutorial/mapgen.lua b/mods/tutorial/mapgen.lua new file mode 100644 index 0000000..9bf39f8 --- /dev/null +++ b/mods/tutorial/mapgen.lua @@ -0,0 +1,448 @@ + + +-- Directory where the map data will be stored +tutorial.map_directory = minetest.get_modpath("tutorial").."/mapdata/" + +-- entity management functions + +function save_entities() + local entities = {} + local count = 0; + + for id,entity in pairs(minetest.luaentities) do + local entry = { + pos = entity.object:getpos(), + name = entity.name, + staticdata = entity.object:get_luaentity().get_staticdata(entity.object) + } + if entry.name == "__builtin:item" then + entry.itemstring = entity.itemstring + end + table.insert(entities, entry) + count = count+1 + minetest.log("action", "[tutorial] entity FOUND to be saved: "..(entry.itemstring or entry.name).." at " ..entry.pos.x..","..entry.pos.y..","..entry.pos.z) + end + + -- Because the entities can easily be unloaded, we won't override the + -- entities save file. Instead, we will try to deduce as best as we can to try + -- to include as well the already saved entities without creating duplicates. + local saved_entities = get_saved_entities() + + for k,ent in pairs(saved_entities) do + local already_added=false + + for k,e in pairs(entities) do + if math.abs(ent.pos.x-e.pos.x) + math.abs(ent.pos.y - e.pos.y) + math.abs(ent.pos.z -e.pos.z) < 1 then + already_added=true + break + end + end + if not already_added then + table.insert(entities,ent) + count = count + 1 + minetest.log("action", "[tutorial] entity to CONTINUE saved: "..(ent.itemstring or ent.name).." at " ..ent.pos.x..","..ent.pos.y..","..ent.pos.z) + end + end + + local str = minetest.serialize(entities) + + local filename = tutorial.map_directory .. "entities" + local file, err = io.open(filename, "wb") + if err ~= nil then + error("Couldn't write to \"" .. filename .. "\"") + end + file:write(minetest.compress(str)) + file:flush() + file:close() + minetest.log("action","[tutorial] " .. filename .. ": " .. count .. " entities saved") + return count +end + +function get_saved_entities() + local filename = tutorial.map_directory .. "entities" + local f, err = io.open(filename, "rb") + if not f then + minetest.log("action", "[tutorial] Could not open file '" .. filename .. "': " .. err) + return {} + end + local entities = minetest.deserialize(minetest.decompress(f:read("*a"))) + f:close() + return entities +end + +function load_entities() + local entities = get_saved_entities() + + local count = 0 + for k,entity in pairs(entities) do + if entity.name == "__builtin:item" then + minetest.add_item(entity.pos, entity.itemstring) + else + local luaentity = minetest.add_entity(entity.pos, entity.name) + luaentity.on_activate(luaentity, entity.staticdata) + end + count = count + 1 + end + minetest.log("action", "[tutorial] " .. count .. " entities loaded") +end + +function load_entities_area(minp, maxp) + + if not tutorial.entities_cache then + tutorial.entities_cache = get_saved_entities() + end + + local count = 0 + for k,entity in pairs(tutorial.entities_cache) do + + -- Only load it if not out of the generating range + if not ((maxp.x < entity.pos.x) or (minp.x > entity.pos.x) + or (maxp.y < entity.pos.y) or (minp.y > entity.pos.y) + or (maxp.z < entity.pos.z) or (minp.z > entity.pos.z)) + then + if entity.name == "__builtin:item" then + minetest.add_item(entity.pos, entity.itemstring) + else + local luaentity = minetest.add_entity(entity.pos, entity.name) + luaentity.on_activate(luaentity, entity.staticdata) + end + count = count + 1 + end + end + minetest.log("action", "[tutorial] " .. count .. " entities loaded") +end + +--- + +-- Sectors of the map to save/load +-- Each element of the array will contain the coordinate where the sector starts +-- along with a "l" property indicating its length in each direction. +tutorial.map_sector = {} + +-- Array with the minimum and the maximum positions of the cube that contains the +-- entire Tutorial World, it's best if the start matches the start of a mapchunk +tutorial.limits = { + { x = -32, y = -32, z = -32 }, + { x = 224, y = 48, z = 144 }, +} + +-- size of the sectors to form divisions of the map. +-- This needs to be a multiple of 16, since it will also determine the +-- chunksize +tutorial.sector_size = 80 + +-- perform the divisions using the given sector size within the limits provided +for x = tutorial.limits[1].x, tutorial.limits[2].x, tutorial.sector_size do + for y = tutorial.limits[1].y, tutorial.limits[2].y, tutorial.sector_size do + for z = tutorial.limits[1].z, tutorial.limits[2].z, tutorial.sector_size do + table.insert(tutorial.map_sector, {x=x,y=y,z=z,l=(tutorial.sector_size - 1)}) + end + end +end +--]] + +function save_schematic() + local success = true + for k,sector in pairs(tutorial.map_sector) do + local filename = tutorial.map_directory .. "sector_"..k + local minp = sector + local maxp = { + x = sector.x + sector.l, + y = sector.y + sector.l, + z = sector.z + sector.l + } + if not save_region(minp, maxp, nil, filename) then + minetest.log("error", "[tutorial] error loading Tutorial World sector " .. minetest.pos_to_string(sector)) + success = false + end + end + return success +end + +function load_schematic() + local success = true + for k,sector in pairs(tutorial.map_sector) do + local filename = tutorial.map_directory .. "sector_"..k + minetest.log("action", "loading sector " .. minetest.pos_to_string(sector)) + sector.maxp = vector.add(sector, {x=sector.l, y=sector.l, z=sector.l}) + + -- Load the area above the schematic to guarantee we have blue sky above + -- and prevent lighting glitches + --minetest.emerge_area(vector.add(sector, {x=0, y=sector.l, z=0}), vector.add(sector.maxp, {x=0,y=32,z=0})) + + local vmanip = VoxelManip(sector, sector.maxp) + if not load_region(sector, filename, vmanip, nil, nil, true) then + minetest.log("error", "[tutorial] error loading Tutorial World sector " .. minetest.pos_to_string(sector)) + success = false + end + vmanip:calc_lighting() + vmanip:write_to_map() + vmanip:update_map() + end + return success +end + + +-- Saves schematic in the Minetest Schematic (and metadata) to disk. +-- Takes the same arguments as minetest.create_schematic +-- @param minp Lowest position (in all 3 coordinates) of the area to save +-- @param maxp Highest position (in all 3 coordinates) of the area to save +-- @param probability_list = {{pos={x=,y=,z=},prob=}, ...} list of probabilities for the nodes to be loaded (if nil, always load) +-- @param filename (without externsion) with the path to save the shcematic and metadata to +-- @param slice_prob_list = {{ypos=,prob=}, ...} list of probabilities for the slices to be loaded (if nil, always load) +-- @return The number of nodes with metadata. +function save_region(minp, maxp, probability_list, filename, slice_prob_list) + + local success = minetest.create_schematic(minp, maxp, probability_list, filename .. ".mts", slice_prob_list) + if not success then + minetest.log("error", "[tutorial] problem creating schematic on ".. minetest.pos_to_string(minp) .. ": " .. filename) + return false + end + + local manip = minetest.get_voxel_manip() + manip:read_from_map(minp, maxp) + local pos = {x=minp.x, y=0, z=0} + local count = 0 + local nodes = {} + local get_node, get_meta = minetest.get_node, minetest.get_meta + while pos.x <= maxp.x do + pos.y = minp.y + while pos.y <= maxp.y do + pos.z = minp.z + while pos.z <= maxp.z do + local node = get_node(pos) + if node.name ~= "air" and node.name ~= "ignore" then + local meta = get_meta(pos):to_table() + + local meta_empty = true + -- Convert metadata item stacks to item strings + for name, inventory in pairs(meta.inventory) do + for index, stack in ipairs(inventory) do + meta_empty = false + inventory[index] = stack.to_string and stack:to_string() or stack + end + end + if meta.fields and next(meta.fields) ~= nil then + meta_empty = false + end + + if not meta_empty then + count = count + 1 + nodes[count] = { + x = pos.x - minp.x, + y = pos.y - minp.y, + z = pos.z - minp.z, + meta = meta, + } + end + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + if count > 0 then + + local result = { + size = { + x = maxp.x - minp.x, + y = maxp.y - minp.y, + z = maxp.z - minp.z, + }, + nodes = nodes, + } + + -- Serialize entries + result = minetest.serialize(result) + + local file, err = io.open(filename..".meta", "wb") + if err ~= nil then + error("Couldn't write to \"" .. filename .. "\"") + end + file:write(minetest.compress(result)) + file:flush() + file:close() + minetest.log("action", "[tutorial] schematic + metadata saved: " .. filename) + else + minetest.log("action", "[tutorial] schematic (no metadata) saved: " .. filename) + end + return success, count +end + + + +-- Places the schematic specified in the given position. +-- @param minp Lowest position (in all 3 coordinates) of the area to load +-- @param filename without extension, but with path of the file to load +-- @param vmanip voxelmanip object to use to place the schematic in +-- @param rotation can be 0, 90, 180, 270, or "random". +-- @param replacements = {["old_name"] = "convert_to", ...} +-- @param force_placement is a boolean indicating whether nodes other than air and ignore are replaced by the schematic +-- @return boolean indicating success or failure +function load_region(minp, filename, vmanip, rotation, replacements, force_placement) + + if rotation == "random" then + rotation = {nil, 90, 180, 270} + rotation = rotation[math.random(1,4)] + end + + local success + if vmanip and minetest.place_schematic_on_vmanip then + success = minetest.place_schematic_on_vmanip(vmanip, minp, filename .. ".mts", tostring(rotation), replacements, force_placement) + else + success = minetest.place_schematic(minp, filename .. ".mts", tostring(rotation), replacements, force_placement) + end + + if success == false then + minetest.log("action", "[tutorial] schematic partionally loaded on ".. minetest.pos_to_string(minp)) + elseif not success then + minetest.log("error", "[tutorial] problem placing schematic on ".. minetest.pos_to_string(minp) .. ": " .. filename) + return nil + end + + local f, err = io.open(filename..".meta", "rb") + if not f then + minetest.log("action", "[tutorial] schematic loaded on ".. minetest.pos_to_string(minp)) + return true + end + local data = minetest.deserialize(minetest.decompress(f:read("*a"))) + f:close() + if not data then return end + + local get_meta = minetest.get_meta + + if not rotation or rotation == 0 then + for i, entry in ipairs(data.nodes) do + entry.x, entry.y, entry.z = minp.x + entry.x, minp.y + entry.y, minp.z + entry.z + if entry.meta then get_meta(entry):from_table(entry.meta) end + end + else + local maxp_x, maxp_z = minp.x + data.size.x, minp.z + data.size.z + if rotation == 90 then + for i, entry in ipairs(data.nodes) do + entry.x, entry.y, entry.z = minp.x + entry.z, minp.y + entry.y, maxp_z - entry.x + if entry.meta then get_meta(entry):from_table(entry.meta) end + end + elseif rotation == 180 then + for i, entry in ipairs(data.nodes) do + entry.x, entry.y, entry.z = maxp_x - entry.x, minp.y + entry.y, maxp_z - entry.z + if entry.meta then get_meta(entry):from_table(entry.meta) end + end + elseif rotation == 270 then + for i, entry in ipairs(data.nodes) do + entry.x, entry.y, entry.z = maxp_x - entry.z, minp.y + entry.y, minp.z + entry.x + if entry.meta then get_meta(entry):from_table(entry.meta) end + end + else + minetest.log("error", "[tutorial] unsupported rotation angle: " .. (rotation or "nil")) + return false + end + end + minetest.log("action", "[tutorial] schematic + metadata loaded on ".. minetest.pos_to_string(minp)) + return true +end + + +------ Commands + +minetest.register_privilege("tutorialmap", "Can use commands to manage the tutorial map") +minetest.register_chatcommand("treset", { + params = "", + description = "Resets the tutorial map", + privs = {tutorialmap=true}, + func = function(name, param) + if load_schematic() then + minetest.chat_send_player(name, "Tutorial World schematic loaded") + else + minetest.chat_send_player(name, "An error occurred while loading Tutorial World schematic") + end + + -- TODO: right now there's no clear way we can properly remove all entities + --remove_entities() + --load_entities() + end, +}) + +minetest.register_chatcommand("tsave", { + params = "", + description = "Saves the tutorial map", + privs = {tutorialmap=true}, + func = function(name, param) + if save_schematic() then + minetest.chat_send_player(name, "Tutorial World schematic saved") + else + minetest.chat_send_player(name, "An error occurred while saving Tutorial World schematic") + end + end, +}) + +minetest.register_chatcommand("tsave_entities", { + params = "", + description = "Saves the tutorial map", + privs = {tutorialmap=true}, + func = function(name, param) + for k,s in pairs(tutorial.map_sector) do + minetest.forceload_block(s) + end + local count = save_entities() + minetest.chat_send_player(name, count .. " entities saved") + end, +}) + +------ Map Generation + +tutorial.state = tutorial.state or {} +tutorial.state.loaded = tutorial.state.loaded or {} +minetest.register_on_generated(function(minp, maxp, seed) + local state_changed = false + local vm, emin, emax = minetest.get_mapgen_object("voxelmanip") + + for k,sector in pairs(tutorial.map_sector) do + if not tutorial.state.loaded[k] then + + if sector.maxp == nil then + sector.maxp = { + x = sector.x + sector.l, + y = sector.y + sector.l, + z = sector.z + sector.l, + } + end + + -- Only load it if not out of the generating range + if not ((maxp.x < sector.x) or (minp.x > sector.maxp.x) + or (maxp.y < sector.y) or (minp.y > sector.maxp.y) + or (maxp.z < sector.z) or (minp.z > sector.maxp.z)) + then + + local filename = tutorial.map_directory .. "sector_" .. k + local loaded = load_region(sector, filename, vm) + if loaded then + -- Load entities in the area as well, and mark it as loaded + load_entities_area(sector, sector.maxp) + tutorial.state.loaded[k] = true + end + state_changed = true + end + end + end + + if(state_changed) then + vm:calc_lighting(nil, nil, false) + vm:write_to_map() + tutorial.save_state() + -- Update the lgihting of the sector below as well + minp.y = minp.y - tutorial.sector_size + local vm = minetest.get_voxel_manip(minp, maxp) + vm:calc_lighting(nil, nil, false) + vm:write_to_map() + vm:update_map() + end +end) + +minetest.register_on_mapgen_init(function(mgparams) + minetest.set_mapgen_params({mgname="singlenode", water_level=-31000, chunksize=(tutorial.sector_size/16)}) +end) + +-- coordinates for the first time the player spawns +tutorial.first_spawn = { pos={x=42,y=0.5,z=28}, yaw=(math.pi * 0.5) }