diff --git a/.luacheckrc b/.luacheckrc index 1340f50..331188c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -4,16 +4,18 @@ allow_defined_top = true exclude_files = {".luacheckrc"} globals = { - "minetest", + "minetest", "dungeon_loot" } read_globals = { string = {fields = {"split"}}, table = {fields = {"copy", "getn"}}, + --luac + "math", "table", + -- Builtin - "vector", "ItemStack", - "dump", "DIR_DELIM", "VoxelArea", "Settings", + "vector", "ItemStack", "dump", "DIR_DELIM", "VoxelArea", "Settings", "PcgRandom", --mod produced "fl_dyes", "fl_hand", "fl_tools", "fl_workshop", "mobkit", diff --git a/mods/fl_mapgen/dungeon/init.lua b/mods/fl_mapgen/dungeon/init.lua new file mode 100644 index 0000000..b52b850 --- /dev/null +++ b/mods/fl_mapgen/dungeon/init.lua @@ -0,0 +1,9 @@ +--sfan5 MIT +dungeon_loot = {} + +dungeon_loot.CHESTS_MIN = 0 -- not necessarily in a single dungeon +dungeon_loot.CHESTS_MAX = 2 +dungeon_loot.STACKS_PER_CHEST_MAX = 8 + +dofile(minetest.get_modpath("fl_mapgen") .. "/dungeon/loot.lua") +dofile(minetest.get_modpath("fl_mapgen") .. "/dungeon/mapgen.lua") \ No newline at end of file diff --git a/mods/fl_mapgen/dungeon/loot.lua b/mods/fl_mapgen/dungeon/loot.lua new file mode 100644 index 0000000..9a709b9 --- /dev/null +++ b/mods/fl_mapgen/dungeon/loot.lua @@ -0,0 +1,71 @@ +--sfan5 MIT +-- Loot from the `default` mod is registered here, +-- with the rest being registered in the respective mods + +dungeon_loot.registered_loot = { + --[[ + -- various items + {name = "default:stick", chance = 0.6, count = {3, 6}}, + {name = "default:flint", chance = 0.4, count = {1, 3}}, + + -- farming / consumable + {name = "default:apple", chance = 0.4, count = {1, 4}}, + {name = "default:cactus", chance = 0.4, count = {1, 4}, + types = {"sandstone", "desert"}}, + + -- minerals + {name = "default:coal_lump", chance = 0.9, count = {1, 12}}, + {name = "default:gold_ingot", chance = 0.5}, + {name = "default:steel_ingot", chance = 0.4, count = {1, 6}}, + {name = "default:mese_crystal", chance = 0.1, count = {2, 3}}, + + -- tools + {name = "default:sword_wood", chance = 0.6}, + {name = "default:pick_stone", chance = 0.3}, + {name = "default:axe_diamond", chance = 0.05}, + + -- natural materials + {name = "default:sand", chance = 0.8, count = {4, 32}, y = {-64, 32768}, + types = {"normal"}}, + {name = "default:desert_sand", chance = 0.8, count = {4, 32}, y = {-64, 32768}, + types = {"sandstone"}}, + {name = "default:desert_cobble", chance = 0.8, count = {4, 32}, + types = {"desert"}}, + {name = "default:snow", chance = 0.8, count = {8, 64}, y = {-64, 32768}, + types = {"ice"}}, + {name = "default:dirt", chance = 0.6, count = {2, 16}, y = {-64, 32768}, + types = {"normal", "sandstone", "desert"}}, + {name = "default:obsidian", chance = 0.25, count = {1, 3}, y = {-32768, -512}}, + {name = "default:mese", chance = 0.15, y = {-32768, -512}}, + --]] +} + +function dungeon_loot.register(t) + if t.name ~= nil then + t = {t} -- single entry + end + for _, loot in ipairs(t) do + table.insert(dungeon_loot.registered_loot, loot) + end +end + +function dungeon_loot._internal_get_loot(pos_y, dungeontype) + -- filter by y pos and type + local ret = {} + for _, l in ipairs(dungeon_loot.registered_loot) do + if l.y == nil or (pos_y >= l.y[1] and pos_y <= l.y[2]) then + if l.types == nil or table.indexof(l.types, dungeontype) ~= -1 then + table.insert(ret, l) + end + end + end + return ret +end + +minetest.register_on_mods_loaded(function() + for _, item in pairs(minetest.registered_items) do + if item._dungeon_loot then + dungeon_loot.register(item._dungeon_loot) + end + end +end) \ No newline at end of file diff --git a/mods/fl_mapgen/dungeon/mapgen.lua b/mods/fl_mapgen/dungeon/mapgen.lua new file mode 100644 index 0000000..92b0d83 --- /dev/null +++ b/mods/fl_mapgen/dungeon/mapgen.lua @@ -0,0 +1,173 @@ +--sfan5 MIT +minetest.set_gen_notify({dungeon = true, temple = true}) + +local function noise3d_integer(noise, pos) + return math.abs(math.floor(noise:get_3d(pos) * 0x7fffffff)) +end + +local function random_sample(rand, list, count) + local ret = {} + for n = 1, count do + local idx = rand:next(1, #list) + table.insert(ret, list[idx]) + table.remove(list, idx) + end + return ret +end + +local function find_walls(cpos) + local is_wall = function(node) + return node.name ~= "air" and node.name ~= "ignore" + end + + local dirs = {{x=1, z=0}, {x=-1, z=0}, {x=0, z=1}, {x=0, z=-1}} + local get_node = minetest.get_node + + local ret = {} + local mindist = {x=0, z=0} + local min = function(a, b) return a ~= 0 and math.min(a, b) or b end + for _, dir in ipairs(dirs) do + for i = 1, 9 do -- 9 = max room size / 2 + local pos = vector.add(cpos, {x=dir.x*i, y=0, z=dir.z*i}) + + -- continue in that direction until we find a wall-like node + local node = get_node(pos) + if is_wall(node) then + local front_below = vector.subtract(pos, {x=dir.x, y=1, z=dir.z}) + local above = vector.add(pos, {x=0, y=1, z=0}) + + -- check that it: + --- is at least 2 nodes high (not a staircase) + --- has a floor + if is_wall(get_node(front_below)) and is_wall(get_node(above)) then + table.insert(ret, {pos = pos, facing = {x=-dir.x, y=0, z=-dir.z}}) + if dir.z == 0 then + mindist.x = min(mindist.x, i-1) + else + mindist.z = min(mindist.z, i-1) + end + end + -- abort even if it wasn't a wall cause something is in the way + break + end + end + end + + --need to customize this to registered_biomes + local biome = minetest.get_biome_data(cpos) + biome = biome and minetest.get_biome_name(biome.biome) or "" + local type = "normal" + if biome:find("desert") == 1 then + type = "desert" + elseif biome:find("sandstone_desert") == 1 then + type = "sandstone" + elseif biome:find("icesheet") == 1 then + type = "ice" + end + + return { + walls = ret, + size = {x=mindist.x*2, z=mindist.z*2}, + type = type, + } +end + +local function populate_chest(pos, rand, dungeontype) + --minetest.chat_send_all("chest placed at " .. minetest.pos_to_string(pos) .. " [" .. dungeontype .. "]") + --minetest.add_node(vector.add(pos, {x=0, y=1, z=0}), {name="default:torch", param2=1}) + + local item_list = dungeon_loot._internal_get_loot(pos.y, dungeontype) + -- take random (partial) sample of all possible items + local sample_n = math.min(#item_list, dungeon_loot.STACKS_PER_CHEST_MAX) + item_list = random_sample(rand, item_list, sample_n) + + -- apply chances / randomized amounts and collect resulting items + local items = {} + for _, loot in ipairs(item_list) do + if rand:next(0, 1000) / 1000 <= loot.chance then + local itemdef = minetest.registered_items[loot.name] + local amount = 1 + if loot.count ~= nil then + amount = rand:next(loot.count[1], loot.count[2]) + end + + if not itemdef then + minetest.log("warning", "Registered loot item " .. loot.name .. " does not exist") + elseif itemdef.tool_capabilities then + for n = 1, amount do + local wear = rand:next(0.20 * 65535, 0.75 * 65535) -- 20% to 75% wear + table.insert(items, ItemStack({name = loot.name, wear = wear})) + end + elseif itemdef.stack_max == 1 then + -- not stackable, add separately + for n = 1, amount do + table.insert(items, loot.name) + end + else + table.insert(items, ItemStack({name = loot.name, count = amount})) + end + end + end + + -- place items at random places in chest + local inv = minetest.get_meta(pos):get_inventory() + local listsz = inv:get_size("main") + assert(listsz >= #items) + for _, item in ipairs(items) do + local index = rand:next(1, listsz) + if inv:get_stack("main", index):is_empty() then + inv:set_stack("main", index, item) + else + inv:add_item("main", item) -- space occupied, just put it anywhere + end + end +end + + +minetest.register_on_generated(function(minp, maxp, blockseed) + local gennotify = minetest.get_mapgen_object("gennotify") + local poslist = gennotify["dungeon"] or {} + for _, entry in ipairs(gennotify["temple"] or {}) do + table.insert(poslist, entry) + end + if #poslist == 0 then return end + + local noise = minetest.get_perlin(10115, 4, 0.5, 1) + local rand = PcgRandom(noise3d_integer(noise, poslist[1])) + + local candidates = {} + -- process at most 8 rooms to keep runtime of this predictable + local num_process = math.min(#poslist, 8) + for i = 1, num_process do + local room = find_walls(poslist[i]) + -- skip small rooms and everything that doesn't at least have 3 walls + if math.min(room.size.x, room.size.z) >= 4 and #room.walls >= 3 then + table.insert(candidates, room) + end + end + + local num_chests = rand:next(dungeon_loot.CHESTS_MIN, dungeon_loot.CHESTS_MAX) + num_chests = math.min(#candidates, num_chests) + local rooms = random_sample(rand, candidates, num_chests) + + for _, room in ipairs(rooms) do + -- choose place somewhere in front of any of the walls + local wall = room.walls[rand:next(1, #room.walls)] + local v, vi -- vector / axis that runs alongside the wall + if wall.facing.x ~= 0 then + v, vi = {x=0, y=0, z=1}, "z" + else + v, vi = {x=1, y=0, z=0}, "x" + end + local chestpos = vector.add(wall.pos, wall.facing) + local off = rand:next(-room.size[vi]/2 + 1, room.size[vi]/2 - 1) + chestpos = vector.add(chestpos, vector.multiply(v, off)) + + if minetest.get_node(chestpos).name == "air" then + -- make it face inwards to the room + local facedir = minetest.dir_to_facedir(vector.multiply(wall.facing, -1)) + minetest.add_node(chestpos, {name = "fl_storage:wood_chest", param2 = facedir}) + populate_chest(chestpos, PcgRandom(noise3d_integer(noise, chestpos)), room.type) + end + end +end) \ No newline at end of file diff --git a/mods/fl_mapgen/init.lua b/mods/fl_mapgen/init.lua index a281035..c3b15b2 100644 --- a/mods/fl_mapgen/init.lua +++ b/mods/fl_mapgen/init.lua @@ -4,4 +4,5 @@ dofile(modpath .. "/aliases.lua") dofile(modpath .. "/ores.lua") dofile(modpath .. "/biomes.lua") dofile(modpath .. "/other.lua") -dofile(modpath .. "/abm.lua") \ No newline at end of file +dofile(modpath .. "/abm.lua") +dofile(modpath .. "/dungeon/init.lua") \ No newline at end of file diff --git a/mods/fl_mapgen/mod.conf b/mods/fl_mapgen/mod.conf index 17e4536..c7d8eaf 100644 --- a/mods/fl_mapgen/mod.conf +++ b/mods/fl_mapgen/mod.conf @@ -1 +1 @@ -depends = fl_liquids, fl_stone, fl_ores, fl_topsoil \ No newline at end of file +depends = fl_liquids, fl_stone, fl_ores, fl_topsoil, fl_storage \ No newline at end of file diff --git a/mods/fl_topsoil/topsoil.lua b/mods/fl_topsoil/topsoil.lua index d0308b7..0d821bf 100644 --- a/mods/fl_topsoil/topsoil.lua +++ b/mods/fl_topsoil/topsoil.lua @@ -1,6 +1,7 @@ minetest.register_node("fl_topsoil:dirt", { description = "dirt", tiles = {"farlands_dirt.png"}, + _dungeon_loot = {name = "fl_topsoil:dirt", chance = 0.6, count = {2, 16}, y = {-64, 32768}}, groups = {oddly_breakable_by_hand = 3}, }) diff --git a/mods/fl_trees/nodes.lua b/mods/fl_trees/nodes.lua index ab6b146..8db9795 100644 --- a/mods/fl_trees/nodes.lua +++ b/mods/fl_trees/nodes.lua @@ -1,3 +1,9 @@ +minetest.register_craftitem("fl_trees:stick", { + description = "stick", + inventory_image = "farlands_stick.png", + _dungeon_loot = {name = "fl_trees:stick", chance = 0.6, count = {3, 6}}, +}) + local function tree_nodes(name, tgroup, lgroup, pgroup) local tgp = tgroup or {oddly_breakable_by_hand = 3, wood_related = 1, tree = 1, trunk = 1} local lgp = lgroup or {oddly_breakable_by_hand = 3, wood_related = 1, tree = 1, leaf = 1} @@ -54,6 +60,11 @@ local function tree_nodes(name, tgroup, lgroup, pgroup) output = "fl_trees:" .. name .. "_plank 4", recipe = {{"fl_trees:" .. name .. "_trunk",}} }) + + minetest.register_craft({ + output = "fl_trees:stick 4", + recipe = {{"fl_trees:" .. name .. "_plank"}}, + }) end tree_nodes("acacia") diff --git a/mods/fl_trees/textures/farlands_stick.png b/mods/fl_trees/textures/farlands_stick.png new file mode 100644 index 0000000..6958efa Binary files /dev/null and b/mods/fl_trees/textures/farlands_stick.png differ