mirror of
https://git.minetest.land/VoxeLibre/VoxeLibre.git
synced 2024-11-23 02:51:06 +01:00
159 lines
5.1 KiB
Lua
159 lines
5.1 KiB
Lua
mcl_loot = {}
|
|
|
|
--[[
|
|
Select a number of itemstacks out of a pool of treasure definitions randomly.
|
|
|
|
Parameters:
|
|
* loot_definitions: Probabilities and information about the loot to select. Syntax:
|
|
|
|
{
|
|
stacks_min = 1, -- Minimum number of item stacks to get. Default: 1
|
|
stacks_max = 3, -- Maximum number of item stacks to get. Default: 1
|
|
items = { -- Table of possible loot items. This function selects between stacks_min and stacks_max of these.
|
|
{
|
|
weight = 5, -- Likelihood of this item being selected (see below). Optional (default: 1)
|
|
|
|
itemstack = ItemStack("example:item1"), -- Itemstack to select
|
|
-- OR
|
|
itemstring = "example:item1", -- Which item to select
|
|
amount_min = 1, -- Minimum size of itemstack. Must not be larger than 6553. Optional (default: 1)
|
|
amount_max = 10, -- Maximum size of item stack. Must not be larger than item definition's stack_max or 6553. Optional (default: 1)
|
|
wear_min = 1, -- Minimum wear value. Must be at least 1. Optional (default: no wear)
|
|
wear_max = 1, -- Maxiumum wear value. Must be at least 1. Optional (default: no wear)
|
|
},
|
|
{ -- more tables like above, one table per item stack }
|
|
}
|
|
}
|
|
* pr: PseudoRandom object used for the randomness
|
|
|
|
How weight works: The probability of a single item stack being selected is weight/total_weight, with
|
|
total_weight being the sum of all weight values in the items table. If you leave out the weight for
|
|
all items, the likelihood of each item being selected is equal.
|
|
|
|
Returns: Table of itemstrings
|
|
]]
|
|
function mcl_loot.get_loot(loot_definitions, pr)
|
|
local items = {}
|
|
|
|
local total_weight = 0
|
|
for i=1, #loot_definitions.items do
|
|
total_weight = total_weight + (loot_definitions.items[i].weight or 1)
|
|
end
|
|
|
|
local stacks_min = loot_definitions.stacks_min
|
|
local stacks_max = loot_definitions.stacks_max
|
|
if not stacks_min then stacks_min = 1 end
|
|
if not stacks_max then stacks_max = 1 end
|
|
local stacks = pr:next(loot_definitions.stacks_min, loot_definitions.stacks_max)
|
|
for s=1, stacks do
|
|
local r = pr:next(1, total_weight)
|
|
|
|
local accumulated_weight = 0
|
|
local item
|
|
for i=1, #loot_definitions.items do
|
|
accumulated_weight = accumulated_weight + (loot_definitions.items[i].weight or 1)
|
|
if accumulated_weight >= r then
|
|
item = loot_definitions.items[i]
|
|
break
|
|
end
|
|
end
|
|
if item then
|
|
local itemstring = item.itemstring
|
|
local itemstack = item.itemstack
|
|
if itemstring then
|
|
if item.amount_min and item.amount_max then
|
|
itemstring = itemstring .. " " .. pr:next(item.amount_min, item.amount_max)
|
|
end
|
|
if item.wear_min and item.wear_max then
|
|
-- Sadly, PseudoRandom only allows very narrow ranges, so we set wear in steps of 10
|
|
local wear_min = math.floor(item.wear_min / 10)
|
|
local wear_max = math.floor(item.wear_max / 10)
|
|
local wear = pr:next(wear_min, wear_max) * 10
|
|
|
|
if not item.amount_min and not item.amount_max then
|
|
itemstring = itemstring .. " 1"
|
|
end
|
|
|
|
itemstring = itemstring .. " " .. tostring(wear)
|
|
end
|
|
table.insert(items, itemstring)
|
|
elseif itemstack then
|
|
table.insert(items, itemstack)
|
|
else
|
|
minetest.log("error", "[mcl_loot] INTERNAL ERROR! Failed to select random loot item!")
|
|
end
|
|
end
|
|
end
|
|
|
|
return items
|
|
end
|
|
|
|
--[[
|
|
Repeat mcl_loot.get_loot multiple times for various loot_definitions.
|
|
Useful for filling chests.
|
|
|
|
* multi_loot_definitions: Table of loot_definitions (see mcl_loot.get_loot)
|
|
* pr: PseudoRandom object used for the randomness
|
|
|
|
Returns: Table of itemstrings ]]
|
|
function mcl_loot.get_multi_loot(multi_loot_definitions, pr)
|
|
local items = {}
|
|
for m=1, #multi_loot_definitions do
|
|
local group = mcl_loot.get_loot(multi_loot_definitions[m], pr)
|
|
for g=1, #group do
|
|
table.insert(items, group[g])
|
|
end
|
|
end
|
|
return items
|
|
end
|
|
|
|
--[[
|
|
Returns a table of length `max_slot` and all natural numbers between 1 and `max_slot`
|
|
in a random order.
|
|
]]
|
|
local function get_random_slots(max_slot, pr)
|
|
local slots = {}
|
|
for s=1, max_slot do
|
|
slots[s] = s
|
|
end
|
|
local slots_out = {}
|
|
while #slots > 0 do
|
|
local r = pr and pr:next(1, #slots) or math.random(1, #slots)
|
|
table.insert(slots_out, slots[r])
|
|
table.remove(slots, r)
|
|
end
|
|
return slots_out
|
|
end
|
|
|
|
--[[
|
|
Puts items in an inventory list into random slots.
|
|
* inv: InvRef
|
|
* listname: Inventory list name
|
|
* items: table of items to add
|
|
|
|
Items will be added from start of the table to end.
|
|
If the inventory already has occupied slots, or is
|
|
too small, placement of some items might fail.
|
|
]]
|
|
function mcl_loot.fill_inventory(inv, listname, items, pr)
|
|
local size = inv:get_size(listname)
|
|
local slots = get_random_slots(size, pr)
|
|
local leftovers = {}
|
|
-- 1st pass: Add items into random slots
|
|
for i=1, math.min(#items, size) do
|
|
local item = items[i]
|
|
local slot = slots[i]
|
|
local old_item = inv:get_stack(listname, slot)
|
|
local leftover = old_item:add_item(item)
|
|
inv:set_stack(listname, slot, old_item)
|
|
if not leftover:is_empty() then
|
|
table.insert(leftovers, item)
|
|
end
|
|
end
|
|
-- 2nd pass: If some items couldn't be added in first pass,
|
|
-- try again in a non-random fashion
|
|
for l=1, math.min(#leftovers, size) do
|
|
inv:add_item(listname, leftovers[l])
|
|
end
|
|
-- If there are still items left, tough luck!
|
|
end
|