forked from VoxeLibre/VoxeLibre
Added datapacks and loot table functionality
This commit is contained in:
parent
026ea5940c
commit
ed1bb20e24
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"type": "chest",
|
||||||
|
"functions": [],
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"conditions": [],
|
||||||
|
"functions": [],
|
||||||
|
"rolls": {
|
||||||
|
"type": "uniform",
|
||||||
|
"min": 2,
|
||||||
|
"max": 4
|
||||||
|
},
|
||||||
|
"bonus_rolls": 1,
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"conditions": [],
|
||||||
|
"type": "item",
|
||||||
|
"weight": 1,
|
||||||
|
"quality": 0,
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"conditions": [],
|
||||||
|
"function": "set_count",
|
||||||
|
"count": {
|
||||||
|
"type": "binomial",
|
||||||
|
"n": 20,
|
||||||
|
"p": 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "mcl_core:diamond"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||||
|
|
||||||
|
local default_datapack_path = modpath .. "/../../../datapacks/vanilla"
|
||||||
|
|
||||||
|
|
||||||
|
vl_datapacks = {
|
||||||
|
registries = {},
|
||||||
|
loaded_datapacks = {},
|
||||||
|
registry_specs = {
|
||||||
|
["loot_table"] = "json",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Format of `vl_datapacks.registries`:
|
||||||
|
{
|
||||||
|
<registry name>: {
|
||||||
|
<namespace>: {
|
||||||
|
<path string>: resource,
|
||||||
|
}
|
||||||
|
<namespace>: {
|
||||||
|
<path string>: resource,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
<registry name>: {
|
||||||
|
<namespace>: {
|
||||||
|
<path string>: resource,
|
||||||
|
}
|
||||||
|
<namespace>: {
|
||||||
|
<path string>: resource,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
function vl_datapacks.get_resource(registry, resource_string)
|
||||||
|
local namespace, path = split_resource_string(resource_string)
|
||||||
|
return vl_datapacks.registries[registry][namespace][path]
|
||||||
|
end
|
||||||
|
|
||||||
|
for registry_name, _ in pairs(vl_datapacks.registry_specs) do
|
||||||
|
vl_datapacks.registries[registry_name] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile(modpath .. "/resource_loader.lua")
|
||||||
|
|
||||||
|
vl_datapacks.load_datapack("vanilla", default_datapack_path)
|
|
@ -0,0 +1,155 @@
|
||||||
|
--[[ -- Gets the last extension of a filename
|
||||||
|
-- Returns: stem, extension
|
||||||
|
local function split_fname(filename)
|
||||||
|
local matched, _, stem, extension = string.find(filename, "^(.*)%.(.*)$")
|
||||||
|
if not matched then
|
||||||
|
return filename, ""
|
||||||
|
else
|
||||||
|
return stem, extension
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- Loads a resource into `load_into`
|
||||||
|
-- key: filename with last extension stripped
|
||||||
|
-- value: preferred lua format for that resource
|
||||||
|
local function load_resource(path, filename, load_into, strict)
|
||||||
|
local stem, extension = split_fname(filename)
|
||||||
|
local filepath = path .. "/" .. filename
|
||||||
|
local filestring = read_file_string(filepath, strict)
|
||||||
|
if extension == "json" then
|
||||||
|
local parsed_json, err = minetest.parse_json(filestring)
|
||||||
|
if not parsed_json then
|
||||||
|
-- Not valid json
|
||||||
|
error("Error while reading json file " .. filepath .. ": " .. error)
|
||||||
|
end
|
||||||
|
load_into[stem] = parsed_json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Recursively load resources from `path`
|
||||||
|
-- `load_into`: table to load into
|
||||||
|
-- `strict`: whether to error if resources not found
|
||||||
|
local function load_resources_internal(path, expected_resources, load_into, strict)
|
||||||
|
minetest.debug(path, dump(expected_resources))
|
||||||
|
for subfile, subfile_expected in pairs(expected_resources) do
|
||||||
|
if type(subfile_expected) == "table" then
|
||||||
|
if not load_into[subfile] then
|
||||||
|
load_into[subfile] = {}
|
||||||
|
end
|
||||||
|
local subdir_load_into = load_into[subfile]
|
||||||
|
load_resources_internal(path .. "/" .. subfile, subfile_expected, subdir_load_into, strict)
|
||||||
|
else -- subfile is file, not dir
|
||||||
|
load_resource(path, subfile, load_into, strict)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end ]]
|
||||||
|
|
||||||
|
--[[ -- Check if match_string starts with start_check
|
||||||
|
local function startswith(match_string, start_check)
|
||||||
|
return string.sub(match_string, 1, string.len(start_check)) == start_check
|
||||||
|
end ]]
|
||||||
|
|
||||||
|
-- Get file subpath without suffix
|
||||||
|
-- Returns nil if invalid (e.g. file had wrong suffix)
|
||||||
|
local function get_stem(subpath, file_suffix)
|
||||||
|
local _, _, stem = string.find(subpath, "(.*)%." .. file_suffix .. "$")
|
||||||
|
return stem
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Read a file at `filepath` and return its contents as a string
|
||||||
|
-- Raises an error if file could not be opened
|
||||||
|
local function read_file_string(filepath)
|
||||||
|
local file, err = io.open(filepath)
|
||||||
|
if not file then
|
||||||
|
error("Error while loading resources: could not open file " .. filepath .. ": " .. err)
|
||||||
|
end
|
||||||
|
local filestring = file:read("*all")
|
||||||
|
file:close()
|
||||||
|
return filestring
|
||||||
|
end
|
||||||
|
|
||||||
|
-- registry_path must end in a slash
|
||||||
|
local function load_single_resource(registry_path, subpath, namespace_table, file_suffix)
|
||||||
|
local absolute_path = registry_path .. subpath
|
||||||
|
local stem = get_stem(subpath, file_suffix)
|
||||||
|
if not stem then
|
||||||
|
error("Invalid filename in datapack at " .. absolute_path)
|
||||||
|
end
|
||||||
|
local raw_data = read_file_string(absolute_path)
|
||||||
|
local loaded_data, err
|
||||||
|
|
||||||
|
minetest.debug("Loading resource at " .. subpath .. " in " .. registry_path)
|
||||||
|
|
||||||
|
if file_suffix == "json" then
|
||||||
|
loaded_data, err = minetest.parse_json(raw_data)
|
||||||
|
if not loaded_data then
|
||||||
|
-- Not valid json
|
||||||
|
error("Error while reading json file " .. filepath .. ": " .. err)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
error("Can't read file with format " .. file_suffix .. " at " .. absolute_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace_table[stem] = loaded_data
|
||||||
|
end
|
||||||
|
|
||||||
|
-- registry_path must end in a slash
|
||||||
|
local function load_resources_recursive(registry_path, subpath, namespace_table, file_suffix)
|
||||||
|
-- Put a / on the end of the subpath, unless we are in the main registry directory
|
||||||
|
if subpath ~= nil then subpath = subpath .. "/"
|
||||||
|
else subpath = "" end
|
||||||
|
|
||||||
|
local absolute_path = registry_path .. subpath
|
||||||
|
|
||||||
|
minetest.debug("Loading resources from " .. absolute_path)
|
||||||
|
-- Load files in this directory
|
||||||
|
for _, filename in pairs(minetest.get_dir_list(absolute_path, false)) do
|
||||||
|
minetest.debug("Loading resource " .. filename)
|
||||||
|
load_single_resource(registry_path, subpath .. filename, namespace_table, file_suffix)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load from subdirectories
|
||||||
|
for _, dirname in pairs(minetest.get_dir_list(absolute_path, true)) do
|
||||||
|
minetest.debug("Going into subdirectory " .. dirname)
|
||||||
|
load_resources_recursive(registry_path, subpath .. dirname, namespace_table, file_suffix)
|
||||||
|
end
|
||||||
|
minetest.debug("Finished loading resources from " .. absolute_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load_into_registry(path, registry_name, namespace, file_suffix)
|
||||||
|
local registry = vl_datapacks.registries[registry_name]
|
||||||
|
if not registry[namespace] then registry[namespace] = {} end
|
||||||
|
local namespace_table = registry[namespace]
|
||||||
|
load_resources_recursive(path, nil, namespace_table, file_suffix)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function load_into_namespace(namespace, path)
|
||||||
|
for registry_name, file_suffix in pairs(vl_datapacks.registry_specs) do
|
||||||
|
-- registry_path must end in a slash for internal functions
|
||||||
|
load_into_registry(path .. "/" .. registry_name .. "/", registry_name, namespace, file_suffix)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Loads a full datapack into memory, thereby enabling it and any overrides
|
||||||
|
local function load_datapack(name, path)
|
||||||
|
for _, other_name in pairs(vl_datapacks.loaded_datapacks) do
|
||||||
|
if name == other_name then
|
||||||
|
error("Datapack " .. name .. " is already loaded")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local data_path = path .. "/data"
|
||||||
|
|
||||||
|
for _, namespace in pairs(minetest.get_dir_list(data_path, true)) do
|
||||||
|
load_into_namespace(namespace, data_path .. "/" .. namespace)
|
||||||
|
end
|
||||||
|
table.insert(vl_datapacks.loaded_datapacks, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
vl_datapacks.load_datapack = load_datapack
|
|
@ -0,0 +1,144 @@
|
||||||
|
-- Main loot engine
|
||||||
|
vl_loot.engine = {}
|
||||||
|
|
||||||
|
-- Adds all items from array `to_append` into old_table
|
||||||
|
-- Returns nil
|
||||||
|
local function append_table(old_table, to_append)
|
||||||
|
for _, item in ipairs(to_append) do
|
||||||
|
table.insert(old_table, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Modifies each of the stacks in `loot_stacks` according to `modifier_table` (array)
|
||||||
|
-- Edits `loot_stacks` in-place
|
||||||
|
local function modify_stacks(modifier_table, loot_stacks, loot_context)
|
||||||
|
for i, itemstack in pairs(loot_stacks) do
|
||||||
|
loot_stacks[i] = vl_loot.modifier.apply_item_modifier(modifier_table, itemstack, loot_context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Conditionally returns an entry based on whether its conditions passed
|
||||||
|
-- For compound-type entries this can return multiple entries
|
||||||
|
-- Returns array of entries to add to pool
|
||||||
|
local function unpack_entry(entry_provider_table, loot_context)
|
||||||
|
-- ENTRY PROVIDER TYPES (compound have *)
|
||||||
|
-- item
|
||||||
|
-- tag
|
||||||
|
-- loot_table
|
||||||
|
-- dynamic
|
||||||
|
-- empty
|
||||||
|
-- group *
|
||||||
|
-- alternative *
|
||||||
|
-- sequence *
|
||||||
|
|
||||||
|
if not vl_loot.predicate.check_predicates(entry_provider_table.conditions, loot_context) then return {} end
|
||||||
|
|
||||||
|
-- TODO: Support other types
|
||||||
|
if entry_provider_table.type == "item" then
|
||||||
|
return {entry_provider_table}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Should always receive a simple-type entry
|
||||||
|
-- Returns all (already modified) stacks to use as pool loot
|
||||||
|
local function get_entry_loot(entry_table, loot_context)
|
||||||
|
-- SIMPLE ENTRY TYPES
|
||||||
|
-- item
|
||||||
|
-- tag
|
||||||
|
-- loot_table
|
||||||
|
-- dynamic
|
||||||
|
-- empty
|
||||||
|
|
||||||
|
-- Don't check conditions, these were already checked in unpack_entry
|
||||||
|
|
||||||
|
local loot_stacks = {}
|
||||||
|
-- TODO: Support other types
|
||||||
|
if entry_table.type == "item" then
|
||||||
|
local itemstack = ItemStack(entry_table.name)
|
||||||
|
table.insert(loot_stacks, itemstack)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply modifier
|
||||||
|
modify_stacks(entry_table.functions, loot_stacks, loot_context)
|
||||||
|
|
||||||
|
return loot_stacks
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_final_weight(entry_table, loot_context)
|
||||||
|
-- TODO: Add 'quality' based on luck
|
||||||
|
-- TODO: Do some floor stuff?
|
||||||
|
return entry_table.weight
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_final_rolls(pool_table, loot_context)
|
||||||
|
-- TODO: bonus_rolls (also a number provider)
|
||||||
|
local rolls = vl_loot.number_provider.evaluate(pool_table.rolls, loot_context)
|
||||||
|
-- Must be an integer
|
||||||
|
return math.floor(rolls)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- returns list of itemstacks to add to loot
|
||||||
|
local function get_pool_loot(pool_table, loot_context)
|
||||||
|
if not vl_loot.predicate.check_predicates(pool_table.conditions, loot_context) then return {} end
|
||||||
|
local loot_stacks = {}
|
||||||
|
|
||||||
|
-- Calculate how many rolls to do
|
||||||
|
local rolls = get_final_rolls(pool_table, loot_context)
|
||||||
|
|
||||||
|
-- Unpack entries of a compound type into simple entries
|
||||||
|
local unpacked_entries = {}
|
||||||
|
for _, entry_provider_table in ipairs(pool_table.entries) do
|
||||||
|
local new_unpacked_entries = unpack_entry(entry_provider_table, loot_context)
|
||||||
|
append_table(unpacked_entries, new_unpacked_entries)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1,rolls do
|
||||||
|
-- The 'total weight' of all entries combined (= 1 probability)
|
||||||
|
local total_weight = 0
|
||||||
|
-- Array of weights of individual entries
|
||||||
|
local entry_weights = {}
|
||||||
|
|
||||||
|
-- Calculate weights and add to array and total_weight
|
||||||
|
for _, entry_table in ipairs(unpacked_entries) do
|
||||||
|
local current_entry_weight = get_final_weight(entry_table, loot_context)
|
||||||
|
table.insert(entry_weights, current_entry_weight)
|
||||||
|
total_weight = total_weight + current_entry_weight
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get a random value
|
||||||
|
-- TODO: Make this deterministic per-loot table and world seed
|
||||||
|
local random_value = math.random(0, total_weight)
|
||||||
|
|
||||||
|
-- Work out which entry was picked
|
||||||
|
local chosen_entry
|
||||||
|
for i, entry_weight in ipairs(entry_weights) do
|
||||||
|
random_value = random_value - entry_weight
|
||||||
|
-- If the value went <= 0 then this was the chosen entry
|
||||||
|
if random_value <= 0 then
|
||||||
|
chosen_entry = unpacked_entries[i]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.debug(dump(unpacked_entries), dump(entry_weights), total_weight, dump(chosen_entry))
|
||||||
|
-- Get loot from the chosen entry
|
||||||
|
local current_roll_loot = get_entry_loot(chosen_entry, loot_context)
|
||||||
|
append_table(loot_stacks, current_roll_loot)
|
||||||
|
|
||||||
|
end
|
||||||
|
modify_stacks(pool_table.functions, loot_stacks, loot_context)
|
||||||
|
minetest.debug("Pool's loot stacks:", dump(loot_stacks))
|
||||||
|
return loot_stacks
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_loot(loot_table, loot_context)
|
||||||
|
local loot_stacks = {}
|
||||||
|
for _, pool_table in ipairs(loot_table.pools) do
|
||||||
|
local pool_loot_stacks = get_pool_loot(pool_table, loot_context, loot_stacks)
|
||||||
|
append_table(loot_stacks, pool_loot_stacks)
|
||||||
|
end
|
||||||
|
modify_stacks(loot_table.functions, loot_stacks, loot_context)
|
||||||
|
return loot_stacks
|
||||||
|
end
|
||||||
|
|
||||||
|
vl_loot.engine.get_loot = get_loot
|
|
@ -0,0 +1,59 @@
|
||||||
|
local modpath = minetest.get_modpath(minetest.get_current_modname())
|
||||||
|
|
||||||
|
vl_loot = {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dofile(modpath .. "/item_modifier.lua")
|
||||||
|
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 ]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- 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)
|
||||||
|
-- TODO: Provide better context (there are implied fields)
|
||||||
|
local context = {
|
||||||
|
["this"] = opener,
|
||||||
|
["origin"] = pos,
|
||||||
|
}
|
||||||
|
return vl_loot.engine.get_loot(loot_table, context)
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_chatcommand("testloot", {
|
||||||
|
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", "vanilla:chests/testloot")
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
player_inv:set_list("main", inv_list)
|
||||||
|
if #loot > 0 then
|
||||||
|
minetest.debug("Too much loot for inventory!")
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
--load_loot_tables(default_loot_path, expected_loot_tables, true)
|
||||||
|
|
||||||
|
minetest.debug(dump(vl_loot))
|
|
@ -0,0 +1,80 @@
|
||||||
|
-- Item modifiers are lists of item functions
|
||||||
|
-- [
|
||||||
|
-- <item function 1>
|
||||||
|
-- <item function 2>
|
||||||
|
-- ]
|
||||||
|
-- The item functions are applied in order to an itemstack when applying a modifier
|
||||||
|
vl_loot.modifier = {}
|
||||||
|
|
||||||
|
local function apply_item_function(function_table, itemstack, loot_context)
|
||||||
|
-- Only apply function if predicates pass
|
||||||
|
if not vl_loot.predicate.check_predicates(function_table.conditions, loot_context) then
|
||||||
|
return itemstack
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: Make this work
|
||||||
|
-- Item functions:
|
||||||
|
--[[
|
||||||
|
apply_bonus
|
||||||
|
copy_components
|
||||||
|
copy_custom_data
|
||||||
|
copy_name
|
||||||
|
copy_state
|
||||||
|
enchant_randomly
|
||||||
|
enchant_with_levels
|
||||||
|
exploration_map
|
||||||
|
explosion_decay
|
||||||
|
fill_player_head
|
||||||
|
filtered
|
||||||
|
furnace_smelt
|
||||||
|
limit_count
|
||||||
|
enchanted_count_increase
|
||||||
|
modify_contents
|
||||||
|
reference
|
||||||
|
sequence
|
||||||
|
set_attributes
|
||||||
|
set_banner_pattern
|
||||||
|
set_book_cover
|
||||||
|
set_components
|
||||||
|
set_contents
|
||||||
|
set_count
|
||||||
|
set_custom_data
|
||||||
|
set_custom_model_data
|
||||||
|
set_damage
|
||||||
|
set_enchantments
|
||||||
|
set_fireworks
|
||||||
|
set_firework_explosion
|
||||||
|
set_instrument
|
||||||
|
set_item
|
||||||
|
set_loot_table
|
||||||
|
set_lore
|
||||||
|
set_name
|
||||||
|
set_potion
|
||||||
|
set_stew_effect
|
||||||
|
set_writable_book_pages
|
||||||
|
set_written_book_pages
|
||||||
|
toggle_tooltips
|
||||||
|
]]
|
||||||
|
if function_table["function"] == "set_count" then
|
||||||
|
local count = vl_loot.number_provider.evaluate(function_table.count, loot_context)
|
||||||
|
if function_table.add then
|
||||||
|
count = count + itemstack:get_count()
|
||||||
|
end
|
||||||
|
itemstack:set_count(count)
|
||||||
|
return itemstack
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Modify an itemstack based on an item modifier
|
||||||
|
-- Returns the resulting itemstack
|
||||||
|
local function apply_item_modifier(modifier_table, itemstack, loot_context)
|
||||||
|
for _, function_table in ipairs(modifier_table) do
|
||||||
|
itemstack = apply_item_function(function_table, itemstack, loot_context)
|
||||||
|
end
|
||||||
|
return itemstack
|
||||||
|
end
|
||||||
|
|
||||||
|
vl_loot.modifier.apply_item_modifier = apply_item_modifier
|
|
@ -0,0 +1 @@
|
||||||
|
name=vl_loot
|
|
@ -0,0 +1,46 @@
|
||||||
|
-- Evaluates number providers in loot tables
|
||||||
|
vl_loot.number_provider = {}
|
||||||
|
|
||||||
|
local function evaluate(number_provider, loot_context)
|
||||||
|
-- TODO: Support all types
|
||||||
|
-- TODO: Use deterministic random gen
|
||||||
|
-- Possible number provider types:
|
||||||
|
-- [implied constant]
|
||||||
|
-- [implied uniform]
|
||||||
|
-- constant: use .value (f)
|
||||||
|
-- uniform: between .min (f) and .max (f)
|
||||||
|
-- binomial: .n (f), .p (i)
|
||||||
|
-- score: TODO
|
||||||
|
-- storage: TODO
|
||||||
|
-- enchantment_level: TODO (not used for what you think it is)
|
||||||
|
|
||||||
|
-- implied constant
|
||||||
|
if type(number_provider) == "number" then
|
||||||
|
return number_provider
|
||||||
|
-- otherwise we can assume it is a table
|
||||||
|
elseif number_provider.type == "constant" then
|
||||||
|
return number_provider.value
|
||||||
|
elseif number_provider.type == "uniform" then
|
||||||
|
return math.random(number_provider.min, number_provider.max)
|
||||||
|
elseif number_provider.type == "binomial" then
|
||||||
|
-- Sample binomial distribution
|
||||||
|
local total_value = 0
|
||||||
|
local p = number_provider.p
|
||||||
|
for i=1,number_provider.n do
|
||||||
|
if math.random() >= p then
|
||||||
|
total_value = total_value + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return total_value
|
||||||
|
--elseif number_provider.type == "score" then
|
||||||
|
--elseif number_provider.type == "storage" then
|
||||||
|
--elseif number_provider.type == "enchantment_level" then
|
||||||
|
elseif number_provider.min and number_provider.max then
|
||||||
|
return math.random(number_provider.min, number_provider.max)
|
||||||
|
else
|
||||||
|
minetest.log("error", "invalid number provider when calculating loot: ", dump(number_provider))
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
vl_loot.number_provider.evaluate = evaluate
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- Evaluates predicates (conditions) in loot tables
|
||||||
|
vl_loot.predicate = {}
|
||||||
|
|
||||||
|
local function check_predicate(predicate_table, loot_context)
|
||||||
|
-- TODO: Make this work
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function check_predicates(predicate_tables, loot_context)
|
||||||
|
for _, predicate_table in ipairs(predicate_tables) do
|
||||||
|
if not vl_loot.predicate.check_predicate(predicate_table, loot_context) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
vl_loot.predicate.check_predicates = check_predicates
|
Loading…
Reference in New Issue