port dungeon loot

This commit is contained in:
wsor 2021-06-27 11:59:55 -04:00
parent 7894dd0bc1
commit 37b04b933a
9 changed files with 273 additions and 5 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -4,4 +4,5 @@ dofile(modpath .. "/aliases.lua")
dofile(modpath .. "/ores.lua")
dofile(modpath .. "/biomes.lua")
dofile(modpath .. "/other.lua")
dofile(modpath .. "/abm.lua")
dofile(modpath .. "/abm.lua")
dofile(modpath .. "/dungeon/init.lua")

View File

@ -1 +1 @@
depends = fl_liquids, fl_stone, fl_ores, fl_topsoil
depends = fl_liquids, fl_stone, fl_ores, fl_topsoil, fl_storage

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B