From 7996e210d9675b72da0569b32ada774f1d1bb9d3 Mon Sep 17 00:00:00 2001 From: WillConker Date: Wed, 10 Jul 2024 17:59:56 +0100 Subject: [PATCH] Added woodland mansion loot, replaced /loot with /lootchest, fixed some bugs --- mods/CORE/mcl_util/init.lua | 31 +++++--- mods/CORE/vl_datapacks/init.lua | 18 +++-- mods/CORE/vl_loot/engine.lua | 1 - mods/CORE/vl_loot/init.lua | 130 +++++++++++++++++++++++++------- mods/CORE/vl_loot/mod.conf | 3 +- mods/ITEMS/mcl_chests/init.lua | 38 ++++++++-- 6 files changed, 165 insertions(+), 56 deletions(-) diff --git a/mods/CORE/mcl_util/init.lua b/mods/CORE/mcl_util/init.lua index b6fd71673..6226e9203 100644 --- a/mods/CORE/mcl_util/init.lua +++ b/mods/CORE/mcl_util/init.lua @@ -386,19 +386,26 @@ end function mcl_util.drop_items_from_meta_container(listname) return function(pos, oldnode, oldmetadata) + local inv, meta + + meta = minetest.get_meta(pos) + -- TODO: Is this check required? if oldmetadata and oldmetadata.inventory then - -- process in after_dig_node callback - local main = oldmetadata.inventory.main - if not main then return end - for _, stack in pairs(main) do - drop_item_stack(pos, stack) - end - else - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - for i = 1, inv:get_size("main") do - drop_item_stack(pos, inv:get_stack("main", i)) - end + meta:from_table(oldmetadata) + end + + inv = meta:get_inventory() + minetest.debug(inv:is_empty(listname)) + + -- Generate loot if not already done so + -- FIXME: If a player digs container, they are not recognised in loot context + vl_loot.generate_container_loot_if_exists(pos, nil, inv, listname) + -- Drop all stacks + for i = 1, inv:get_size(listname) do + drop_item_stack(pos, inv:get_stack(listname, i)) + end + -- Clear metadata if necessary + if meta then meta:from_table() end end diff --git a/mods/CORE/vl_datapacks/init.lua b/mods/CORE/vl_datapacks/init.lua index 3766b33f7..e621da8b3 100644 --- a/mods/CORE/vl_datapacks/init.lua +++ b/mods/CORE/vl_datapacks/init.lua @@ -34,15 +34,19 @@ vl_datapacks = { local function split_resource_string(resource_string) local match_start, _, namespace, path = string.find(resource_string, "([^%s]+)%:([^%s]+)") - if not match_start then - error("Invalid resource string: " .. resource_string) - end - return namespace, path + return match_start, namespace, path end -function vl_datapacks.get_resource(registry, resource_string) - local namespace, path = split_resource_string(resource_string) - return vl_datapacks.registries[registry][namespace][path] +-- Get resource, returns nil if resource does not exist +-- Can be used to check if resource exists +function vl_datapacks.get_resource(registry_name, resource_string) + local matched, namespace, path = split_resource_string(resource_string) + if not matched then return end + local registry = vl_datapacks.registries[registry_name] + if not registry then return end + local namespace_index = registry[namespace] + if not namespace_index then return end + return namespace_index[path] end for registry_name, _ in pairs(vl_datapacks.registry_specs) do diff --git a/mods/CORE/vl_loot/engine.lua b/mods/CORE/vl_loot/engine.lua index 0a9388d78..08a3053f9 100644 --- a/mods/CORE/vl_loot/engine.lua +++ b/mods/CORE/vl_loot/engine.lua @@ -126,7 +126,6 @@ local function get_pool_loot(pool_table, loot_context) end modify_stacks(pool_table.functions, loot_stacks, loot_context) - minetest.debug("Pool's loot stacks:", dump(loot_stacks)) return loot_stacks end diff --git a/mods/CORE/vl_loot/init.lua b/mods/CORE/vl_loot/init.lua index e88fa8297..9ec171da8 100644 --- a/mods/CORE/vl_loot/init.lua +++ b/mods/CORE/vl_loot/init.lua @@ -9,18 +9,52 @@ dofile(modpath .. "/predicate.lua") dofile(modpath .. "/number_provider.lua") dofile(modpath .. "/engine.lua") ---[[ -- Load resources specified in `expected_resources` from `path` --- `strict`: whether to error if resources not found -local function load_loot_tables(path, expected_resources, strict) - vl_loot.load_resources_internal(path, expected_resources, vl_loot.loot_tables, strict) -end ]] + +-- Fisher-Yates shuffle +local function shuffle(to_shuffle) + for i = #to_shuffle, 2, -1 do + local j = math.random(i) + to_shuffle[i], to_shuffle[j] = to_shuffle[j], to_shuffle[i] + end +end + +--[[ +Puts items in an inventory list into random slots. +* inv: InvRef +* listname: Inventory list name +* items: table of itemstacks to add + +- If there are fewer itemstacks than slots, random slots will be filled +- If there are too many itemstacks, a random selection will be inserted to fill the container +- If there are existing items in the inventory, they will be deleted +]] + +-- TODO: Make this deterministic +-- TODO: Currently overwrites itemstacks, could reimplement so it only inserts into empty slots +local function disperse_in_inventory(inv, listname, items) + local size = inv:get_size(listname) + + -- If there are more slots than items, should add empty itemstacks to fill container + while size > #items do + table.insert(items, ItemStack()) + end + + -- Shuffle the order of items in slots + shuffle(items) + + -- If there are too many itemstacks, decrease to inventory size + -- TODO: Is this needed or is it handled by set_list? + items = {unpack(items, 1, size)} + + -- Write the items back into the inventory + inv:set_list(listname, items) +end - --- loot_table: chest loot table --- pos: position of chest node --- opener: objectref of entity that opened chest -local function get_chest_loot(loot_table, pos, opener) +-- loot_table: container loot table +-- pos: position of container node +-- opener: objectref of entity that opened container +local function get_container_loot(loot_table, pos, opener) -- TODO: Provide better context (there are implied fields) local context = { ["this"] = opener, @@ -29,28 +63,66 @@ local function get_chest_loot(loot_table, pos, opener) return vl_loot.engine.get_loot(loot_table, context) end -minetest.register_chatcommand("loot", { +-- pos: integer node position of container +-- opener: objectref of entity that opened container +-- inventory: InvRef to put loot into +-- listname: list to put loot into in inventory +local function generate_container_loot_if_exists(pos, opener, inventory, listname) + local container_meta = minetest.get_meta(pos) + local loot_table_name = container_meta:get_string("vl_loot:loot_table") + minetest.debug("Using loot table: " .. loot_table_name) + -- Do nothing if this container is not a loot container/has already been looted + if loot_table_name == "" then return end + -- REMOVE ^^ + -- Remove loot table metadata from this container + container_meta:set_string("vl_loot:loot_table", "") + -- Generate loot to fill this container with + local loot_table = vl_datapacks.get_resource("loot_table", loot_table_name) + -- If invalid loot table, don't populate + if not loot_table then + minetest.log("warning", "Container had invalid loot table metadata (" .. loot_table_name .. ") at " .. vector.to_string(pos)) + return + end + local loot_items = get_container_loot(loot_table, pos, opener) + -- Fill inventory + disperse_in_inventory(inventory, listname, loot_items) +end + +vl_loot.generate_container_loot_if_exists = generate_container_loot_if_exists + +if minetest.global_exists("tt") then + local S = minetest.get_translator(minetest.get_current_modname()) + tt.register_snippet(function(itemstring, tool_caps, itemstack) + if not itemstack then return end + local loot_table = itemstack:get_meta():get_string("vl_loot:loot_table") + if loot_table ~= "" then + return S("Loot table: @1", loot_table), "#FFAA00" + end + end) +end + +minetest.register_chatcommand("lootchest", { + params = "", + privs = {["give"] = true}, + description = "Give yourself a loot chest with a specified loot table", func = function(name, param) - local player = minetest.get_player_by_name(name) - local pos = player:get_pos() - local loot_table = vl_datapacks.get_resource("loot_table", param) - --minetest.debug("testloot table:", dump(loot_table)) - local loot = get_chest_loot(loot_table, pos, player) - --[[ for _, stack in ipairs(loot) do - minetest.debug(stack:to_string()) - end ]] - local player_inv = player:get_inventory() - local inv_list = player_inv:get_list("main") - for i, itemstack in ipairs(inv_list) do - if itemstack:is_empty() then - inv_list[i] = table.remove(loot, 1) + if vl_datapacks.get_resource("loot_table", param) then + local itemstack = ItemStack({ + name = "mcl_chests:chest", + meta = {["vl_loot:loot_table"] = param, + } + }) + local player = minetest.get_player_by_name(name) + local leftover = player:get_inventory():add_item("main", itemstack) + if leftover:is_empty() then + return true, "Gave loot chest with table: " .. param + else + -- Could not add to inventory + return false, "No space in inventory" end + else + return false, "Loot table resource does not exist: " .. param end - player_inv:set_list("main", inv_list) - if #loot > 0 then - minetest.debug("Too much loot for inventory!") - end - return true end }) diff --git a/mods/CORE/vl_loot/mod.conf b/mods/CORE/vl_loot/mod.conf index 597d48c54..e1949c03f 100644 --- a/mods/CORE/vl_loot/mod.conf +++ b/mods/CORE/vl_loot/mod.conf @@ -1 +1,2 @@ -name=vl_loot \ No newline at end of file +name=vl_loot +optional_depends=tt \ No newline at end of file diff --git a/mods/ITEMS/mcl_chests/init.lua b/mods/ITEMS/mcl_chests/init.lua index da901da32..eb883eb0d 100644 --- a/mods/ITEMS/mcl_chests/init.lua +++ b/mods/ITEMS/mcl_chests/init.lua @@ -401,7 +401,10 @@ local function register_chest(basename, desc, longdesc, usagehelp, tt_help, tile minetest.set_node(pos, node) end, after_place_node = function(pos, placer, itemstack, pointed_thing) - minetest.get_meta(pos):set_string("name", itemstack:get_meta():get_string("name")) + local itemstack_meta = itemstack:get_meta() + local node_meta = minetest.get_meta(pos) + node_meta:set_string("name", itemstack_meta:get_string("name")) + node_meta:set_string("vl_loot:loot_table", itemstack_meta:get_string("vl_loot:loot_table")) end, }) @@ -531,10 +534,15 @@ local function register_chest(basename, desc, longdesc, usagehelp, tt_help, tile return false end end - local name = minetest.get_meta(pos):get_string("name") + local node_meta = minetest.get_meta(pos) + local name = node_meta:get_string("name") if name == "" then name = S("Chest") end + + local inventory = node_meta:get_inventory() + -- TODO: Loot is generated invidually per half of double chest + vl_loot.generate_container_loot_if_exists(pos, clicker, inventory, "main") minetest.show_formspec(clicker:get_player_name(), sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), @@ -697,13 +705,22 @@ local function register_chest(basename, desc, longdesc, usagehelp, tt_help, tile return false end - local name = minetest.get_meta(pos):get_string("name") + local node_meta = minetest.get_meta(pos) + local other_node_meta = minetest.get_meta(pos_other) + local name = node_meta:get_string("name") if name == "" then - name = minetest.get_meta(pos_other):get_string("name") + name = other_node_meta:get_string("name") end if name == "" then name = S("Large Chest") end + + -- TODO: loot is generated separately per chest half + local inventory = node_meta:get_inventory() + vl_loot.generate_container_loot_if_exists(pos, clicker, inventory, "main") + + local inventory_other = other_node_meta:get_inventory() + vl_loot.generate_container_loot_if_exists(pos_other, clicker, inventory_other, "main") minetest.show_formspec(clicker:get_player_name(), sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z), @@ -890,13 +907,22 @@ local function register_chest(basename, desc, longdesc, usagehelp, tt_help, tile return false end - local name = minetest.get_meta(pos_other):get_string("name") + local node_meta = minetest.get_meta(pos) + local other_node_meta = minetest.get_meta(pos_other) + local name = other_node_meta:get_string("name") if name == "" then - name = minetest.get_meta(pos):get_string("name") + name = node_meta:get_string("name") end if name == "" then name = S("Large Chest") end + + -- TODO: loot is generated separately per chest half + local inventory = node_meta:get_inventory() + vl_loot.generate_container_loot_if_exists(pos, clicker, inventory, "main") + + local inventory_other = other_node_meta:get_inventory() + vl_loot.generate_container_loot_if_exists(pos_other, clicker, inventory_other, "main") minetest.show_formspec(clicker:get_player_name(), sf("mcl_chests:%s_%s_%s_%s", canonical_basename, pos.x, pos.y, pos.z),