Added woodland mansion loot, replaced /loot with /lootchest, fixed some bugs

This commit is contained in:
WillConker 2024-07-10 17:59:56 +01:00
parent 4867904ab1
commit 7996e210d9
6 changed files with 165 additions and 56 deletions

View File

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

View File

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

View File

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

View File

@ -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 = "<loot table resource location>",
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
})

View File

@ -1 +1,2 @@
name=vl_loot
name=vl_loot
optional_depends=tt

View File

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