Simplify stored data by throwing out irrelevant information

Also, add a debug table to aid in testing the current and future versions of the auto-detection code.
master
Alexand(er|ra) Yst 2021-09-30 17:27:53 -07:00
parent e2b40bf34d
commit e90370f1dc
3 changed files with 757 additions and 340 deletions

422
API.lua
View File

@ -33,7 +33,7 @@
-- versions.
--
-- Deprecated on 2020-02-29; DO NOT REMOVE FOR AT LEAST TWO YEARS.
if minetest.settings:get("deprecated_lua_api_handling") == "legacy" then
if minetest.settings:get("deprecated_lua_api_handling") ~= "error" then
local liblevelup_database = io.open(minetest.get_worldpath().."/mod_storage/liblevelup", "r")
if liblevelup_database then
liblevelup_database:close()
@ -59,24 +59,15 @@ end
-- this table because the table's not made public. However, if you're
-- editing this file's code, the structure is:
--
-- registered_countables[node_name][drop_name] == true
-- registered_countables[node_name][drop_key(drop_list)] == true
--
-- A single countable node might be able to drop multiple countable
-- drops, so this table structure allows us to account for that fact.
local registered_countables = {}
-- This table is used for reverse lookups. Given an item drop, this
-- table can tell us which keys are needed to look up all forms of that
-- drop that can be counted.
local registered_drops = {}
-- We need a way to store our data, so let's try this:
local storage = minetest.get_mod_storage()
-- This table is used internally to keep track of the alphabetical
-- order of all registered node/drop pairs.
local sorted_pairs = {}
-- This table is used internally to keep track of the alphabetical
-- order of all registered drops.
local sorted_drops = {}
@ -161,66 +152,6 @@ local S = minetest.get_translator("liblevelup")
------------------------- Internal functions: -------------------------
-----------------------------------------------------------------------
-- Need to know if something's counted? Look no further!
local function is_counted(node_name, drop_name)
if registered_countables[node_name]
and registered_countables[node_name][drop_name] then
return true
else
return false
end
end
-- This method simply gets a string from the database and casts it to
-- an integer. All other functions that get data from the database
-- aren't interacting with the database directly, but are merely
-- calling this function and using the return value in some way.
local function get_stat(key)
-- Sometimes tonumber() returns nil instead of a number.
return tonumber(storage:get_string(key)) or 0
end
-- Need to know how many of a given drop a given player has mined?
-- This here's your function.
local function get_pair_stat(player_name, node, drop)
return get_stat(player_name..";"..node..";"..drop)
end
-- This method returns a stat in terms of how many of an item have been
-- harvested, regardless of number dropped in a single dig and
-- regardless of what node dropped it.
local function get_drop_stat(player_name, drop)
local total = 0
if registered_drops[drop] then
for key, multiplier in next, registered_drops[drop] do
total = total + (get_stat(player_name..";"..key) * multiplier)
end
end
return total
end
-- This method returns the number of stacks of the drop a player has
-- mined. Fractional values are returned, to allow for levels to
-- account for everything a player has mined in a unified way.
local function get_stacks_mined(player_name, drop_name)
return get_drop_stat(player_name, drop_name) / drop_max_stack_size[drop_name]
end
-- We'll overwrite these functions later in the script (see "External
-- API"), so we need to create local copies of the old versions to call
-- from our new versions.
local builtin_get_node_drops = minetest.get_node_drops
-- This second function needs to be copied after the game begins
-- though, not now. That makes it more likely our version is the
-- outermost version of the function. If ours is not the outermost one,
-- we can't even make sure our version is called at all. Specifically,
-- Minetest Game's creative mod overwrites the node drop handler (iff
-- creative mode is enabled) and doesn't call the previous version of
-- it. If we don't wait to put our own version of the function in
-- place, liblevelup will be incompatible with the Minetest Game
-- creative mod.
local builtin_handle_node_drops
-- This method normalises drops so we can better handle them.
-- Sometimes, a node will drop multiple stacks of the same item or will
-- drop unnormalised stacks, such as in the form of legacy item
@ -287,6 +218,85 @@ local function normalise_drops(drops)
return normalised
end
-- Checking to see if a given list of items would be counted as a drop
-- requires putting the entire drop list into a single string to check
-- against a table.
local function drop_key(drop_list)
-- Multiple stacks in the same drop list might be the same type of
-- item, which should be treated the same as if the same quantity of
-- items in the drop list were present but as a single stack. Stack
-- order also shouldn't influence how drop lists are treated, so we
-- need to sort the drop list. Normalisation of the drop list takes
-- care of both.
local drops = normalise_drops(drop_list)
-- Te list could be empty. If so, the list isn't counted. Return an
-- empty string anyway instead of nil to avoid errors caused by
-- assuming the return value of this function is a string. If not,
-- build the table key needed to check.
if drops[1] then
local key = drops[1]
if drops[2] then
for i = 2, #drops do
key = key..";"..drops[i]
end
end
return key
else
return ""
end
end
-- Need to know if something's counted? Look no further! Keep in mind
-- that drops are counted based on context, so the full drop list needs
-- to be passed to this function.
local function is_counted(node_name, drop_list)
local key = drop_key(drop_list)
if registered_countables[node_name]
and registered_countables[node_name][key] then
return true
else
return false
end
end
-- This method simply gets a string from the database and casts it to
-- an integer. All other functions that get data from the database
-- aren't interacting with the database directly, but are merely
-- calling this function and using the return value in some way.
local function get_stat(key)
-- Sometimes tonumber() returns nil instead of a number.
return tonumber(storage:get_string(key)) or 0
end
-- This method returns a stat in terms of how many of an item have been
-- harvested, regardless of number dropped in a single dig and
-- regardless of what node dropped it.
local function get_drop_stat(player_name, drop)
return get_stat(player_name..";"..drop)
end
-- This method returns the number of stacks of the drop a player has
-- mined. Fractional values are returned, to allow for levels to
-- account for everything a player has mined in a unified way.
local function get_stacks_mined(player_name, drop_name)
return get_drop_stat(player_name, drop_name) / drop_max_stack_size[drop_name]
end
-- We'll overwrite these functions later in the script (see "External
-- API"), so we need to create local copies of the old versions to call
-- from our new versions.
local builtin_get_node_drops = minetest.get_node_drops
-- This second function needs to be copied after the game begins
-- though, not now. That makes it more likely our version is the
-- outermost version of the function. If ours is not the outermost one,
-- we can't even make sure our version is called at all. Specifically,
-- Minetest Game's creative mod overwrites the node drop handler (iff
-- creative mode is enabled) and doesn't call the previous version of
-- it. If we don't wait to put our own version of the function in
-- place, liblevelup will be incompatible with the Minetest Game
-- creative mod.
local builtin_handle_node_drops
-- Actually solving this problem is incredibly difficult, and I don't
-- know how to do it yet. For now, this function is dummied out.
--
@ -475,7 +485,7 @@ end
-- rather not code this same thing twice in case a later Minetest
-- update requires me to modify it, so I made it a separate function.
-- Being separate, I added this function call to a third branch as well
-- thatotherwise would have hooked into an existing branch using a set
-- that otherwise would have hooked into an existing branch using a set
-- variable.
--
-- This function's variable name was already declared local before. If
@ -564,7 +574,7 @@ end
-- material. Rather than an outright number of stacks mined, like
-- get_stacks_mined() returns, this function calculates on a curve,
-- requiring players to mine/farm more and more of the material to
-- reach the next level. The maximum level is 65535.
-- reach the next level.
local function get_unscaled_partial_level(player_name, material)
-- We've got a cache, so we should use it.
if unscaled_level_cache[player_name] then
@ -592,7 +602,7 @@ end
-- callbacks need to be called.
local function calculate_scaled_partial_levels(player_name)
local raw_value = {}
for drop, _ in next, registered_drops do
for _, drop in next, sorted_drops do
raw_value[drop] = math.sqrt(
get_unscaled_partial_level(player_name, drop) * (get_unscaled_floating_point_level(player_name) / #sorted_drops)
)
@ -633,7 +643,7 @@ setmetatable(unscaled_level_cache, {
local raw_value = rawget(unscaled_level_cache, player_name)
if not raw_value then
raw_value = {}
for material, _ in next, registered_drops do
for _, material in next, sorted_drops do
raw_value[material] = calculate_unscaled_partial_level(player_name, material)
end
rawset(unscaled_level_cache, player_name, raw_value)
@ -714,12 +724,11 @@ end
-- If even one registered function says a drop is valid, this function
-- will reply that the drop is valid.
local function is_countable(node_name, item_string, drop_opts)
if node_name ~= item_string then
for mod_name, funct in next, is_countable_callbacks do
if funct(node_name, item_string, drop_opts) then
return true
end
local function is_countable(node_name, drop_list, drop_opts, drop_key)
drop_list = normalise_drops(drop_list)
for mod_name, funct in next, is_countable_callbacks do
if funct(node_name, drop_list, drop_opts, drop_key) then
return true
end
end
return false
@ -755,7 +764,7 @@ local function get_scaled_player_level(player_name)
for drop_name, drop_level in next, scaled_level_cache[player_name] do
level = level + math.min(drop_max_stack_size[drop_name], math.floor(drop_level))
end
return math.floor(level)
return level
end
-- I was running into a lot of bugs in the code related to next level
@ -875,7 +884,7 @@ local increment
-- precision limit, but instead at the RAM limit.
--
-- If we weren't using the default Minetest implementation by default,
-- and thus didn't need the default Minetest implentation to be
-- and thus didn't need the default Minetest implementation to be
-- compatible with our own implementation, we could save space by using
-- hexadecimal instead of decimal. The default Minetest implementation
-- uses decimal though, so we must use decimal too. It doesn't actually
@ -883,7 +892,7 @@ local increment
-- implementation, so the default Minetest implementation is the one
-- that matters here.
if Settings(minetest.get_worldpath().."/world.conf"):get_bool("liblevelup.enable_infinite_counter", false) or true then
function increment(key)
function increment(key, quantity)
local count = storage:get_string(key)
local continue = true
local offset = 0
@ -905,44 +914,53 @@ if Settings(minetest.get_worldpath().."/world.conf"):get_bool("liblevelup.enable
-- segment is empty, we can ignore the prefix, which must also be
-- empty. We also need to treat the empty segment as zero, but there's
-- no need to set it to zero and increment, because incrementing zero
-- will always yield one. So we skip right to using one as the final
-- value, and append the suffix to it. Here, we terminate the loop, as
-- we've finished the incrementing.
-- will always yield the value we incremented by. So we skip right to
-- using the value we're adding as the final value, and append the
-- suffix to it. Here, we terminate the loop, as we've finished the
-- incrementing.
if working_segment == "" then
count = "1" .. suffix
count = quantity .. suffix
continue = false
-- if the segment isn't empty, we need to add the value to it.
else
working_segment = working_segment + quantity
-- If there's no prefix, we're working with the most-significant
-- segment and can just write it as-is. Overflow, if any, into the next
-- segment can just be dealt with by writing this segment longer than
-- it should be. As a side note, a more-robust check would be needed if
-- arbitrarily-large values could be added, but in fact, the value
-- added will never be more than the maximum stack size of 65535, so
-- the value to carry to the next segment, if any, will always be
-- exactly one. No need for a special check.
if prefix == "" then
count = prefix .. string.format("%d", working_segment) .. suffix
continue = false
-- If the segment is maxed out, we need to set it back to all zeros and
-- increment the next segment. Here, we change the offset, but the
-- incrementing hasn't completed yet, so the loop continues.
elseif working_segment == "999999999999999" then
count = prefix .. "000000000000000" .. suffix
offset = offset - 15
-- If the two special cases above aren't relevant, we just increment.
-- However, how we format the segment depends on whether it's the
-- most-significant segment of the number.
--
-- If it is, we just format as a string of digits of variable length.
elseif prefix == "" then
count = prefix .. string.format("%d", working_segment + 1) .. suffix
continue = false
-- increment the next segment by one. Here, we change the offset, but
-- the incrementing hasn't completed yet, so the loop continues.
elseif working_segment > 999999999999999 then
quantity = 1
count = prefix .. string.format("%015d", working_segment - 1000000000000000) .. suffix
offset = offset - 15
-- If it's not the most-significant segment, we need to pad the segment
-- out with zeros.
else
count = prefix .. string.format("%015d", working_segment + 1) .. suffix
continue = false
else
count = prefix .. string.format("%015d", working_segment) .. suffix
continue = false
end
end
end
-- Finally, we save the incremented value.
storage:set_string(key, count)
end
-- If unlimited counting is disabled though, we just add one to the
-- current value, without doing anything special. The Minetest mod
-- storage API will automatically overflow for us at the 32-bit signed
-- integer limit. Realistically, no one will hit this limit, so this is
-- just fine for normal gameplay.
-- If unlimited counting is disabled though, we just add the quantity
-- to the current value, without doing anything special. The Minetest
-- mod storage API will automatically overflow for us at the 32-bit
-- signed integer limit. Realistically, no one will hit this limit, so
-- this is just fine for normal gameplay.
else
function increment(key)
storage:set_int(key, storage:get_int(key) + 1)
function increment(key, quantity)
storage:set_int(key, storage:get_int(key) + quantity)
end
end
@ -968,27 +986,25 @@ function liblevelup_handle_node_drops(pos, original_drops, digger)
-- Otherwise, this function must have been called not by a dig, but by
-- some other mod.
if original_drops.node_dug then
-- Let's normalise the drops for consistency.
local drops = normalise_drops(original_drops)
local player_name = digger:get_player_name()
if registered_countables[original_drops.node_dug] then
-- Is this drop even counted?
if is_counted(original_drops.node_dug, original_drops) then
update_stats = true
local player_name = digger:get_player_name()
for _, item in next, drops do
if registered_countables[original_drops.node_dug][item] then
local key = player_name..";"..original_drops.node_dug..";"..item
increment(key)
update_stats = true
local material_name = ItemStack(item):get_name()
local stack = ItemStack(item)
local count = stack:get_count()
stack:set_count(1)
increment(player_name..";"..stack:to_string(), count)
-- We need to check the player's level now, before updating it, so we
-- can compare it to the new value.
local previous_level = get_scaled_player_level(player_name)
local previous_level = get_scaled_player_level(player_name)
-- Now we can recalculate the player's level.
unscaled_level_cache[player_name][material_name] = calculate_unscaled_partial_level(player_name, material_name)
calculate_scaled_partial_levels(player_name)
unscaled_level_cache[player_name][material_name] = calculate_unscaled_partial_level(player_name, material_name)
calculate_scaled_partial_levels(player_name)
-- With both the new and old level available, we can check to see if
-- there was an increase.
if previous_level ~= get_scaled_player_level(player_name, material_name) then
update_level = true
end
if previous_level ~= get_scaled_player_level(player_name, material_name) then
update_level = true
end
end
end
@ -1009,16 +1025,6 @@ function liblevelup_handle_node_drops(pos, original_drops, digger)
end
end
-- This just returns a list of registered countables that other mods
-- can use. PLEASE NOTE: The node/drop pair list isn't built up until
-- after the game starts, as other mods might not be done registering
-- nodes and items until then. If called during load time, this
-- function will always return an empty table. This function is
-- therefore only useful at run time.
local function get_all_pairs()
return table.copy(sorted_pairs)
end
-- Again, this function returns an empty table if called at load time.
-- Wait until run time to call it.
local function get_all_drops()
@ -1093,6 +1099,13 @@ function minetest.get_node_drops(node, tool_name)
end
minetest.register_on_mods_loaded(function()
-- We'll need to iterate over a list of the drops later on so we can
-- build the sorted_drops table, so we should build that list as we go.
-- Once this function completes though, the list will no longer be
-- needed in this form and can be discarded. From there, we'll only
-- need the sorted_drops table to get this information if we need to
-- later iterate over it again.
local registered_drops = {}
-- After all mods have loaded, we need to go through the node table to
-- figure out which countable node/drop pairs have been defined in the
-- game.
@ -1114,14 +1127,7 @@ minetest.register_on_mods_loaded(function()
-- accounted for later, that can be added in as well.
for _, form in next, node_forms(node_name) do
local possibilities = node_drop_possibilities(node_name, form.palette_index)
local may_be_countable = {}
local blacklisted = {}
for _, possibility in next, possibilities do
possibility = normalise_drops(possibility)
for _, stack_string in next, possibility do
may_be_countable[stack_string] = true
end
end
-- If one of the recipes matches one of the drops, that drop is
-- basically just the node dropping itself in another form. Nodes that
-- do this include stone, which drops cobble that can be smelted back
@ -1130,73 +1136,51 @@ minetest.register_on_mods_loaded(function()
local recipes = minetest.get_all_craft_recipes(node_name)
if recipes then
for _, recipe in next, recipes do
local ingredients = normalise_drops(recipe.items)
for _, ingredient in next, ingredients do
blacklisted[ingredient] = true
end
blacklisted[drop_key(recipe.items)] = true
end
end
for item, _ in next, may_be_countable do
local item_ID = ItemStack(item):get_name()
if not blacklisted[item]
and is_countable(form.string, item, possibilities) then
for _, possibility in next, possibilities do
local key = drop_key(possibility)
if not blacklisted[key] and form.string ~= key and key ~= ""
and is_countable(form.string, possibility, possibilities, key) then
if not registered_countables[form.string] then
registered_countables[form.string] = {}
end
registered_countables[form.string][item] = true
registered_countables[form.string][key] = true
for _, item in next, possibility do
local stack = ItemStack(item)
if not stack:is_empty() then
stack:set_count(1)
registered_drops[stack:to_string()] = stack:get_name()
end
end
end
end
end
end
end
-- Here, we set up several important tables. It's quicker to work with
-- them all at once instead of building them separately, as the
-- server doesn't have to cycle through the drop materials as many
-- times. First, we have the registered_drops table, which is used for
-- reverse lookups, as well as the sorted_drops drops table, of which a
-- copy of is given to any mod that requests it. While we're building
-- these tables, we also take the opportunity to build the
-- levelling_exponent table, used to provide a levelling mechanic in
-- which reaching the next level requires exponentially higher numbers
-- of drops. We also build the drop_max_stack_size table here, used in
-- the levelling equation of each drop stat.
-- them all at once instead of building them separately, as the server
-- doesn't have to cycle through the drop materials as many times.
-- First, we have the sorted_drops drops table, of which a copy of is
-- given to any mod that requests it. While we're building this table,
-- we also take the opportunity to build the levelling_exponent table,
-- used to provide a levelling mechanic in which reaching the next
-- level requires exponentially higher numbers of drops. We also build
-- the drop_max_stack_size table here, used in the levelling equation
-- of each drop stat.
local denominator = math.log(max_stack_size)
for node_name, drop_names in next, registered_countables do
for drop_name, _ in next, drop_names do
sorted_pairs[#sorted_pairs+1] = {
node = node_name,
drop = drop_name,
}
local itemstack = ItemStack(drop_name)
local count = itemstack:get_count()
itemstack:set_count(1)
local drop_key = itemstack:to_string()
local key = node_name..";"..drop_name
if registered_drops[drop_key] then
registered_drops[drop_key][key] = count
else
registered_drops[drop_key] = {
[key] = count,
}
sorted_drops[#sorted_drops+1] = drop_key
if minetest.registered_items[itemstack:get_name()] then
drop_max_stack_size[drop_key] = minetest.registered_items[itemstack:get_name()].stack_max
else
drop_max_stack_size[drop_key] = minetest.registered_items.unknown.stack_max
end
local numerator = math.log(drop_max_stack_size[drop_key])
levelling_exponent[drop_key] = numerator/denominator
reverse_levelling_exponent[drop_key] = denominator/numerator
end
end
end
table.sort(sorted_pairs, function(pair_0, pair_1)
if pair_0.node == pair_1.node then
return pair_0.drop < pair_1.drop
for drop_string, drop_name in next, registered_drops do
sorted_drops[#sorted_drops+1] = drop_string
if minetest.registered_items[drop_name] then
drop_max_stack_size[drop_string] = minetest.registered_items[drop_name].stack_max
else
return pair_0.node < pair_1.node
drop_max_stack_size[drop_string] = minetest.registered_items.unknown.stack_max
end
end)
local numerator = math.log(drop_max_stack_size[drop_string])
levelling_exponent[drop_string] = numerator/denominator
reverse_levelling_exponent[drop_string] = denominator/numerator
end
table.sort(sorted_drops)
-- It's time! Let's put our implementation of the drop handler in
-- place.
@ -1291,9 +1275,8 @@ liblevelup = {
-- organisation, but also consistency, so all future functions will
-- follow this convention as well.
get = {
drop_stat = get_drop_stat ,
stat = get_drop_stat ,
next_level_at = next_level_at ,
pair_stat = get_pair_stat ,
player_level = get_scaled_player_level ,
player_material_level = get_scaled_per_material_player_level,
player_material_progress_bar_length = player_material_progress_bar_length ,
@ -1304,7 +1287,6 @@ liblevelup = {
meta = {
drops_list = get_all_drops,
is_counted = is_counted ,
pairs_list = get_all_pairs,
},
}
@ -1316,7 +1298,7 @@ liblevelup = {
-- legacy support is enabled in minetest.conf. By default, legacy
-- support is enabled for release versions but not development
-- versions.
if minetest.settings:get("deprecated_lua_api_handling") == "legacy" then
if minetest.settings:get("deprecated_lua_api_handling") ~= "error" then
-- I deprecated the entire API. It was a bit of a mess, and the mod's
-- name wasn't very fitting of its purpose either, so I renamed the mod
-- and rebuilt the API from the ground up with consistency and
@ -1327,9 +1309,11 @@ if minetest.settings:get("deprecated_lua_api_handling") == "legacy" then
-- Deprecated on 2020-02-29; DO NOT REMOVE FOR AT LEAST TWO YEARS.
__minestats__ = {
get_all_drops = get_all_drops ,
get_all_pairs = get_all_pairs ,
get_all_pairs = function()
return {}
end,
get_drop_stat = get_drop_stat ,
get_pair_stat = get_pair_stat ,
get_pair_stat = get_drop_stat ,
get_total_level = get_scaled_player_level ,
is_counted = is_counted ,
next_level_at = get_scaled_player_level ,
@ -1380,4 +1364,38 @@ if minetest.settings:get("deprecated_lua_api_handling") == "legacy" then
-- Deprecated on 2020-12-16; DO NOT REMOVE FOR AT LEAST TWO YEARS.
liblevelup.get.scaled_player_level = get_scaled_player_level
liblevelup.get.next_material_level_at = next_level_at
-- Node data is no longer recorded, as it's not useful to the purpose
-- of liblevelup and merely inflates the database size.
--
-- Deprecated on 2021-09-30; DO NOT REMOVE FOR AT LEAST TWO YEARS.
liblevelup.get.pair_stat = liblevelup.get.stat
liblevelup.get.drop_stat = liblevelup.get.stat
function liblevelup.meta.pairs_list()
return {}
end
startup_functions["*DEPRECATED 2021-09-30*"] = function()
local updated = false
local new_fields = {}
local fields = storage:to_table().fields
for key, value in next, fields do
local parts = string.split(key, ";")
if #parts == 2 then
new_fields[key] = (new_fields[key] or 0) + value
else
local stack = ItemStack(parts[#parts])
local count = stack:get_count()
stack:set_count(1)
local new_key = parts[1]..";"..stack:to_string()
new_fields[new_key] = (new_fields[new_key] or 0) + value * count
updated = true
end
end
if updated then
storage:from_table({
fields = new_fields,
})
end
end
end

403
debug.lua Normal file
View File

@ -0,0 +1,403 @@
-- This table is used during development to quickly check against a
-- hard-coded list of what should be counted and what should not. The
-- function should use generic tests to attempt to classify potential
-- drops based on their characteristics and avoid hard-coding, but the
-- hard-coded list the function is checked against determines whether
-- the function is performing correctly or not. Anything on the
-- hard-coded list has a specific correct classification and must not
-- get misclassified. Anything not on the list will be classified based
-- on a best guess based on its characteristic based on what
-- characteristics needed to be checked to get everything on the list
-- correct.
--
-- To put it simply, this table contains every drop from every node in
-- all versions of all subgames that were ever official to Minetest
-- (Build, Development Test, Minetest, Minetest Game, Minimal
-- development test, Survival) liblevelup doesn't automatically rule
-- out in the main code. What's ruled out in the main code cannot be
-- overridden by other mods, and does not need to be checked. Every
-- entry in this table has a specific correct answer based on whether
-- the dropped item qualifies as a different form of the node that was
-- dropped. If so, it should not be counted (false), and if not, it
-- should be counted (true). Using these basic cases from canonical
-- Minetest, we can extrapolate and try to predict the right answers
-- for other subgames and mods. However, the correct answers cannot be
-- directly queried in most cases (the core code handles most of the
-- cases in which we actually *can* know for certain) due to not being
-- able to query the semantics of the drop, so we can only do our best
-- to guess. The default implementation of stat detection is written
-- such that it passes the tests in this table, but it can be disabled
-- from another mod using the following line:
--
-- liblevelup.unregister.is_countable("liblevelup")
--
-- This must be done at load time though, not after the game has begun.
return {
-- 0.4.0
["[default:dirt_with_grass][default:dirt]"] = false,
["[default:dirt_with_grass_footsteps][default:dirt]"] = false,
["[default:leaves][default:sapling]"] = true,
["[default:sandstone][default:sand]"] = true,
["[default:stone_with_coal][default:coal_lump]"] = true,
["[default:stone_with_iron][default:iron_lump]"] = true,
["[doors:door_wood_a_c][doors:door_wood]"] = false,
["[doors:door_wood_a_o][doors:door_wood]"] = false,
["[doors:door_wood_b_c][doors:door_wood]"] = false,
["[doors:door_wood_b_o][doors:door_wood]"] = false,
-- 0.4.5
["[default:stone_with_mese][default:mese_crystal]"] = true,
["[stairs:slab_brickupside_down][stairs:slab_brick]"] = false,
["[stairs:slab_cobbleupside_down][stairs:slab_cobble]"] = false,
["[stairs:slab_sandstoneupside_down][stairs:slab_sandstone]"] = false,
["[stairs:slab_stoneupside_down][stairs:slab_stone]"] = false,
["[stairs:slab_woodupside_down][stairs:slab_wood]"] = false,
["[stairs:stair_brickupside_down][stairs:stair_brick]"] = false,
["[stairs:stair_cobbleupside_down][stairs:stair_cobble]"] = false,
["[stairs:stair_sandstoneupside_down][stairs:stair_sandstone]"] = false,
["[stairs:stair_stoneupside_down][stairs:stair_stone]"] = false,
["[stairs:stair_woodupside_down][stairs:stair_wood]"] = false,
-- 0.4.6
["[default:grass_2][default:grass_1]"] = false,
["[default:grass_3][default:grass_1]"] = false,
["[default:grass_4][default:grass_1]"] = false,
["[default:grass_5][default:grass_1]"] = false,
["[default:jungleleaves][default:junglesapling]"] = true,
["[default:stone_with_copper][default:copper_lump]"] = true,
["[default:stone_with_diamond][default:diamond]"] = true,
["[default:stone_with_gold][default:gold_lump]"] = true,
["[stairs:slab_junglewoodupside_down][stairs:slab_junglewood]"] = false,
["[stairs:slab_stonebrickupside_down][stairs:slab_stonebrick]"] = false,
["[stairs:stair_junglewoodupside_down][stairs:stair_junglewood]"] = false,
["[stairs:stair_stonebrickupside_down][stairs:stair_stonebrick]"] = false,
-- 0.4.7
["[default:dirt_with_snow][default:dirt]"] = false,
["[default:grass_1][farming:seed_wheat]"] = true,
["[default:grass_2][farming:seed_wheat]"] = true,
["[default:grass_3][farming:seed_wheat]"] = true,
["[default:grass_4][farming:seed_wheat]"] = true,
["[default:grass_5][farming:seed_wheat]"] = true,
["[default:junglegrass][farming:seed_cotton]"] = true,
["[farming:cotton_1][farming:seed_cotton]"] = false,
["[farming:cotton_1][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_1][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_1][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_1][farming:seed_cotton 2]"] = true,
["[farming:cotton_1][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_1][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_1][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_1][farming:seed_cotton 3]"] = true,
["[farming:cotton_1][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_1][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_1][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_1][farming:string]"] = true,
["[farming:cotton_1][farming:string 2]"] = true,
["[farming:cotton_1][farming:string 3]"] = true,
["[farming:cotton_2][farming:seed_cotton]"] = false,
["[farming:cotton_2][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_2][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_2][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_2][farming:seed_cotton 2]"] = true,
["[farming:cotton_2][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_2][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_2][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_2][farming:seed_cotton 3]"] = true,
["[farming:cotton_2][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_2][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_2][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_2][farming:string]"] = true,
["[farming:cotton_2][farming:string 2]"] = true,
["[farming:cotton_2][farming:string 3]"] = true,
["[farming:cotton_3][farming:seed_cotton]"] = false,
["[farming:cotton_3][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_3][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_3][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_3][farming:seed_cotton 2]"] = true,
["[farming:cotton_3][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_3][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_3][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_3][farming:seed_cotton 3]"] = true,
["[farming:cotton_3][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_3][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_3][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_3][farming:string]"] = true,
["[farming:cotton_3][farming:string 2]"] = true,
["[farming:cotton_3][farming:string 3]"] = true,
["[farming:cotton_4][farming:seed_cotton]"] = false,
["[farming:cotton_4][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_4][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_4][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_4][farming:seed_cotton 2]"] = true,
["[farming:cotton_4][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_4][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_4][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_4][farming:seed_cotton 3]"] = true,
["[farming:cotton_4][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_4][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_4][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_4][farming:string]"] = true,
["[farming:cotton_4][farming:string 2]"] = true,
["[farming:cotton_4][farming:string 3]"] = true,
["[farming:cotton_5][farming:seed_cotton]"] = false,
["[farming:cotton_5][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_5][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_5][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_5][farming:seed_cotton 2]"] = true,
["[farming:cotton_5][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_5][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_5][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_5][farming:seed_cotton 3]"] = true,
["[farming:cotton_5][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_5][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_5][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_5][farming:string]"] = true,
["[farming:cotton_5][farming:string 2]"] = true,
["[farming:cotton_5][farming:string 3]"] = true,
["[farming:cotton_6][farming:seed_cotton]"] = false,
["[farming:cotton_6][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_6][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_6][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_6][farming:seed_cotton 2]"] = true,
["[farming:cotton_6][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_6][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_6][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_6][farming:seed_cotton 3]"] = true,
["[farming:cotton_6][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_6][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_6][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_6][farming:string]"] = true,
["[farming:cotton_6][farming:string 2]"] = true,
["[farming:cotton_6][farming:string 3]"] = true,
["[farming:cotton_7][farming:seed_cotton]"] = false,
["[farming:cotton_7][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_7][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_7][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_7][farming:seed_cotton 2]"] = true,
["[farming:cotton_7][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_7][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_7][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_7][farming:seed_cotton 3]"] = true,
["[farming:cotton_7][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_7][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_7][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:cotton_7][farming:string]"] = true,
["[farming:cotton_7][farming:string 2]"] = true,
["[farming:cotton_7][farming:string 3]"] = true,
["[farming:cotton_8][farming:seed_cotton;farming:string]"] = true,
["[farming:cotton_8][farming:seed_cotton;farming:string 2]"] = true,
["[farming:cotton_8][farming:seed_cotton;farming:string 3]"] = true,
["[farming:cotton_8][farming:seed_cotton 2;farming:string]"] = true,
["[farming:cotton_8][farming:seed_cotton 2;farming:string 2]"] = true,
["[farming:cotton_8][farming:seed_cotton 2;farming:string 3]"] = true,
["[farming:cotton_8][farming:seed_cotton 3;farming:string]"] = true,
["[farming:cotton_8][farming:seed_cotton 3;farming:string 2]"] = true,
["[farming:cotton_8][farming:seed_cotton 3;farming:string 3]"] = true,
["[farming:soil][default:dirt]"] = false,
["[farming:soil_wet][default:dirt]"] = false,
["[farming:wheat_1][farming:seed_wheat]"] = false,
["[farming:wheat_1][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_1][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_1][farming:seed_wheat 2]"] = true,
["[farming:wheat_1][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_1][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_1][farming:wheat]"] = true,
["[farming:wheat_1][farming:wheat 2]"] = true,
["[farming:wheat_2][farming:seed_wheat]"] = false,
["[farming:wheat_2][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_2][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_2][farming:seed_wheat 2]"] = true,
["[farming:wheat_2][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_2][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_2][farming:wheat]"] = true,
["[farming:wheat_2][farming:wheat 2]"] = true,
["[farming:wheat_3][farming:seed_wheat]"] = false,
["[farming:wheat_3][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_3][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_3][farming:seed_wheat 2]"] = true,
["[farming:wheat_3][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_3][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_3][farming:wheat]"] = true,
["[farming:wheat_3][farming:wheat 2]"] = true,
["[farming:wheat_4][farming:seed_wheat]"] = false,
["[farming:wheat_4][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_4][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_4][farming:seed_wheat 2]"] = true,
["[farming:wheat_4][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_4][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_4][farming:wheat]"] = true,
["[farming:wheat_4][farming:wheat 2]"] = true,
["[farming:wheat_5][farming:seed_wheat]"] = false,
["[farming:wheat_5][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_5][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_5][farming:seed_wheat 2]"] = true,
["[farming:wheat_5][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_5][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_5][farming:wheat]"] = true,
["[farming:wheat_5][farming:wheat 2]"] = true,
["[farming:wheat_6][farming:seed_wheat]"] = false,
["[farming:wheat_6][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_6][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_6][farming:seed_wheat 2]"] = true,
["[farming:wheat_6][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_6][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_6][farming:wheat]"] = true,
["[farming:wheat_6][farming:wheat 2]"] = true,
["[farming:wheat_7][farming:seed_wheat]"] = false,
["[farming:wheat_7][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_7][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_7][farming:seed_wheat 2]"] = true,
["[farming:wheat_7][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_7][farming:seed_wheat 2;farming:wheat 2]"] = true,
["[farming:wheat_7][farming:wheat]"] = true,
["[farming:wheat_7][farming:wheat 2]"] = true,
["[farming:wheat_8][farming:seed_wheat;farming:wheat]"] = true,
["[farming:wheat_8][farming:seed_wheat;farming:wheat 2]"] = true,
["[farming:wheat_8][farming:seed_wheat 2;farming:wheat]"] = true,
["[farming:wheat_8][farming:seed_wheat 2;farming:wheat 2]"] = true,
-- 0.4.10
["[doors:trapdoor_open][doors:trapdoor]"] = false,
["[farming:desert_sand_soil_wet][default:desert_sand]"] = false,
["[farming:cotton_1][farming:cotton]"] = true,
["[farming:cotton_1][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_1][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_1][farming:cotton 2]"] = true,
["[farming:cotton_1][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_1][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_2][farming:cotton]"] = true,
["[farming:cotton_2][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_2][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_2][farming:cotton 2]"] = true,
["[farming:cotton_2][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_2][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_3][farming:cotton]"] = true,
["[farming:cotton_3][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_3][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_3][farming:cotton 2]"] = true,
["[farming:cotton_3][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_3][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_4][farming:cotton]"] = true,
["[farming:cotton_4][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_4][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_4][farming:cotton 2]"] = true,
["[farming:cotton_4][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_4][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_5][farming:cotton]"] = true,
["[farming:cotton_5][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_5][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_5][farming:cotton 2]"] = true,
["[farming:cotton_5][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_5][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_6][farming:cotton]"] = true,
["[farming:cotton_6][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_6][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_6][farming:cotton 2]"] = true,
["[farming:cotton_6][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_6][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_7][farming:cotton]"] = true,
["[farming:cotton_7][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_7][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_7][farming:cotton 2]"] = true,
["[farming:cotton_7][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_7][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[farming:cotton_8][farming:cotton;farming:seed_cotton]"] = true,
["[farming:cotton_8][farming:cotton;farming:seed_cotton 2]"] = true,
["[farming:cotton_8][farming:cotton 2;farming:seed_cotton]"] = true,
["[farming:cotton_8][farming:cotton 2;farming:seed_cotton 2]"] = true,
["[xpanes:bar_1][xpanes:bar]"] = false,
["[xpanes:bar_10][xpanes:bar]"] = false,
["[xpanes:bar_11][xpanes:bar]"] = false,
["[xpanes:bar_12][xpanes:bar]"] = false,
["[xpanes:bar_13][xpanes:bar]"] = false,
["[xpanes:bar_14][xpanes:bar]"] = false,
["[xpanes:bar_15][xpanes:bar]"] = false,
["[xpanes:bar_2][xpanes:bar]"] = false,
["[xpanes:bar_3][xpanes:bar]"] = false,
["[xpanes:bar_4][xpanes:bar]"] = false,
["[xpanes:bar_5][xpanes:bar]"] = false,
["[xpanes:bar_6][xpanes:bar]"] = false,
["[xpanes:bar_7][xpanes:bar]"] = false,
["[xpanes:bar_8][xpanes:bar]"] = false,
["[xpanes:bar_9][xpanes:bar]"] = false,
["[xpanes:pane_1][xpanes:pane]"] = false,
["[xpanes:pane_10][xpanes:pane]"] = false,
["[xpanes:pane_11][xpanes:pane]"] = false,
["[xpanes:pane_12][xpanes:pane]"] = false,
["[xpanes:pane_13][xpanes:pane]"] = false,
["[xpanes:pane_14][xpanes:pane]"] = false,
["[xpanes:pane_15][xpanes:pane]"] = false,
["[xpanes:pane_2][xpanes:pane]"] = false,
["[xpanes:pane_3][xpanes:pane]"] = false,
["[xpanes:pane_4][xpanes:pane]"] = false,
["[xpanes:pane_5][xpanes:pane]"] = false,
["[xpanes:pane_6][xpanes:pane]"] = false,
["[xpanes:pane_7][xpanes:pane]"] = false,
["[xpanes:pane_8][xpanes:pane]"] = false,
["[xpanes:pane_9][xpanes:pane]"] = false,
-- 0.4.11
["[default:pine_needles][default:pine_sapling]"] = true,
["[farming:desert_sand_soil][default:desert_sand]"] = false,
-- 0.4.13
["[default:acacia_leaves][default:acacia_sapling]"] = true,
["[default:dirt_with_dry_grass][default:dirt]"] = false,
["[default:dry_grass_2][default:dry_grass_1]"] = false,
["[default:dry_grass_3][default:dry_grass_1]"] = false,
["[default:dry_grass_4][default:dry_grass_1]"] = false,
["[default:dry_grass_5][default:dry_grass_1]"] = false,
["[flowers:mushroom_fertile_brown][flowers:mushroom_brown]"] = true,
["[flowers:mushroom_fertile_brown][flowers:mushroom_brown;flowers:mushroom_spores_brown]"] = true,
["[flowers:mushroom_fertile_brown][flowers:mushroom_brown;flowers:mushroom_spores_brown 2]"] = true,
["[flowers:mushroom_fertile_brown][flowers:mushroom_brown;flowers:mushroom_spores_brown 3]"] = true,
["[flowers:mushroom_fertile_red][flowers:mushroom_red]"] = true,
["[flowers:mushroom_fertile_red][flowers:mushroom_red;flowers:mushroom_spores_red]"] = true,
["[flowers:mushroom_fertile_red][flowers:mushroom_red;flowers:mushroom_spores_red 2]"] = true,
["[flowers:mushroom_fertile_red][flowers:mushroom_red;flowers:mushroom_spores_red 3]"] = true,
-- 0.4.14
["[default:aspen_leaves][default:aspen_sapling]"] = true,
["[default:gravel][default:flint]"] = true,
["[doors:gate_acacia_wood_open][doors:gate_acacia_wood_closed]"] = false,
["[doors:gate_aspen_wood_open][doors:gate_aspen_wood_closed]"] = false,
["[doors:gate_junglewood_open][doors:gate_junglewood_closed]"] = false,
["[doors:gate_pine_wood_open][doors:gate_pine_wood_closed]"] = false,
["[doors:gate_wood_open][doors:gate_wood_closed]"] = false,
-- 0.4.15
["[default:coral_brown][default:coral_skeleton]"] = true,
["[default:coral_orange][default:coral_skeleton]"] = true,
["[default:torch_ceiling][default:torch]"] = false,
["[default:torch_wall][default:torch]"] = false,
["[doors:door_glass_a][doors:door_glass]"] = false,
["[doors:door_glass_b][doors:door_glass]"] = false,
["[doors:door_obsidian_glass_a][doors:door_obsidian_glass]"] = false,
["[doors:door_obsidian_glass_b][doors:door_obsidian_glass]"] = false,
["[doors:door_wood_a][doors:door_wood]"] = false,
["[doors:door_wood_b][doors:door_wood]"] = false,
["[xpanes:bar][xpanes:bar_flat]"] = false,
["[xpanes:pane][xpanes:pane_flat]"] = false,
-- 0.4.16
["[default:acacia_bush_leaves][default:acacia_bush_sapling]"] = true,
["[default:bush_leaves][default:bush_sapling]"] = true,
["[default:dirt_with_rainforest_litter][default:dirt]"] = false,
["[default:stone_with_tin][default:tin_lump]"] = true,
-- 5.0.0
["[default:blueberry_bush_leaves][default:blueberry_bush_sapling]"] = true,
["[default:blueberry_bush_leaves_with_berries][default:blueberries]"] = true,
["[default:cave_ice][default:ice]"] = true,
["[default:dirt_with_coniferous_litter][default:dirt]"] = false,
["[default:fern_2][default:fern_1]"] = false,
["[default:fern_3][default:fern_1]"] = false,
["[default:marram_grass_2][default:marram_grass_1]"] = false,
["[default:marram_grass_3][default:marram_grass_1]"] = false,
["[default:pine_bush_needles][default:pine_bush_sapling]"] = true,
["[xpanes:obsidian_pane][xpanes:obsidian_pane_flat]"] = false,
-- 5.1.0
["[default:dry_dirt_with_dry_grass][default:dry_dirt]"] = true,
["[farming:dry_soil][default:dry_dirt]"] = false,
["[farming:dry_soil_wet][default:dry_dirt]"] = false,
["[flowers:waterlily_waving][flowers:waterlily]"] = false,
-- 5.3.0
["[doors:door_glass_c][doors:door_glass]"] = false,
["[doors:door_glass_d][doors:door_glass]"] = false,
["[doors:door_obsidian_glass_c][doors:door_obsidian_glass]"] = false,
["[doors:door_obsidian_glass_d][doors:door_obsidian_glass]"] = false,
["[doors:door_wood_c][doors:door_wood]"] = false,
["[doors:door_wood_d][doors:door_wood]"] = false,
["[farming:cotton_wild][farming:seed_cotton]"] = true,
}

272
init.lua
View File

@ -30,154 +30,150 @@ dofile(minetest.get_modpath("liblevelup").."/API.lua")
-- Minetest Game version.
--
-- Version 0.4.0 was the first to support mods, and corresponds to 0 as
-- this mod's detection level for that version. There have been 24
-- this mod's detection level for that version. There have been 26
-- versions released since then, all of which are properly detected
-- with the below algorithm, so we're now at detection level 24. (This
-- is Minetest Game version 5.3.0.)
liblevelup.meta.minetest_game_detection = 24
-- with the below algorithm, so we're now at detection level 26. (This
-- is Minetest Game version 5.4.1.)
liblevelup.meta.minetest_game_detection = 26
liblevelup.register.is_countable(function(node_string, drop, drop_opts)
local node_name = ItemStack(node_string):get_name()
local item_ID = ItemStack(drop):get_name()
-- This handles one bizarre corner case in Minetest Game, but should be
-- completely safe in any even somewhat coherent subgame. Specifically,
-- it catches the bizarre sandstone-to-sand conversion in Minetest
-- Game 0.4.3 and prior.
local recipes = minetest.get_all_craft_recipes(ItemStack(node_string):get_name())
if recipes then
local drop_stack = ItemStack(drop)
for _, recipe in next, recipes do
local ingredients = {}
for _, ingredient in next, recipe.items do
if ingredients[ingredient] then
ingredients[ingredient] = ingredients[ingredient] + 1
else
ingredients[ingredient] = 1
end
local debug_table = false
-- Uncomment this line for testing.
--debug_table = dofile(minetest.get_modpath("liblevelup").."/debug.lua")
local function generic_default_is_countable_function(node_string, drops, drop_opts, drop_key)
-- If multiple items are dropped, this is probably something we should
-- count.
if #drops > 1 then
return true
end
local drop_stack = ItemStack(drops[1])
local drop_def = drop_stack:get_definition()
local node_stack = ItemStack(node_string)
local node_def = node_stack:get_definition()
if drop_def.type ~= "node" then
-- If it's not a node and doesn't place as a node, count it.
if drop_def.on_place == minetest.craftitemdef_default.on_place then
return true
-- If it's not a node but does place as one, it's probably a door or a
-- 0.4.7 seed.
--
-- Seeds should be counted if they're dropped by non-farm plants,
-- dropped in multiples, or dropped along with other items. If dropped
-- with other items, they'll be taken care of by an above block.
else
-- Doors can be interacted with, so check for that.
if node_def.on_rightclick then
return false
end
for ingredient, count in next, ingredients do
if ingredient == drop_stack:get_name() and count ~= drop_stack:get_count() then
return true
end
-- Checking for multiples is easy.
if drop_stack:get_count() > 1 then
return true
end
-- Checking for farm plants isn't so easy. What we want to determine is
-- whether the single seed came from a plant that can be grown from
-- that single seed, but there's no way to do this as growth is handled
-- in another function, not handled by a string property of the seed.
-- Instead, we check to see if the plant's drop table seems similar to
-- that of a farm plant, and if it is, assume the seed-like item it
-- dropped was the single seed needed to regrow that same plant.
--
-- Basically, wild grasses tend to drop themselves if not their seed,
-- so only the grass *or* the seed will drop. This should be counted,
-- as the seed cannot be used to regrow the grass. Farm crops can drop
-- multiple items though, and they should all be counted unless only
-- one item drops and that one item is a single seed. This is because
-- that single seed could be converted back into the original farm
-- plant by planting it and waiting for it to grow, so it's not a true
-- drop.
if type(node_def.drop) == "table" and node_def.drop.max_items ~= 1 then
return false
end
return true
end
end
-- I mention in the next section that doors in Minetest Game are poorly
-- implemented. They used to be so much worse though. This block
-- catches 0.4.3's doors and rejects them.
if minetest.registered_nodes[drop]
and (minetest.registered_nodes[drop].paramtype2 == "wallmounted"
or minetest.registered_nodes[drop].paramtype2 == "facedir")
and minetest.registered_nodes[drop].paramtype2 == minetest.registered_nodes[node_name].paramtype2
and minetest.registered_nodes[drop].on_place == minetest.nodedef_default.on_place then
-- This prevents the catching of 0.4.0-style doors.
if node_def.legacy_wallmounted then
return false
end
-- This causes dry dirt dropped by dry dirt with dry grass to be
-- counted without causing dirt dropped by dirt variants to be counted.
if minetest.registered_nodes[item_ID]
and #minetest.registered_nodes[item_ID].tiles == 1
and #minetest.registered_nodes[node_name].tiles == 3
and minetest.registered_nodes[item_ID].tiles[1] == minetest.registered_nodes[node_name].tiles[2]then
local countable = true
for _, ABM in next, minetest.registered_abms do
for _, ABM_target in next, ABM.nodenames do
if ABM_target == item_ID then
countable = false
end
-- Nodes often get swapped for other nodes when interacted with, while
-- the new nodes drop the original ones for better stacking. This is
-- case with doors, gates, and chests, for example.
if drop_def.on_rightclick then
return false
end
-- Prevent grass from being caught.
if node_def.drawtype ~= "normal" and node_def.drawtype == drop_def.drawtype then
-- This prevents catching of 0.4.15 xpanes. Again, xpanes are a pain.
if node_def.drawtype == "nodebox" then
return false
end
-- We still want to catch mushrooms though.
if node_def.tiles and node_def.tiles[1] == drop_def.tiles[1] then
return true
else
return false
end
end
if countable then
-- The only way to prevent catching wet-soil-to-dirt drops when dealing
-- with 0.4.7 soil without hard-coding specific node names seems to be
-- to hard-code the soil group, which is far from ideal.
if minetest.get_item_group(node_stack:get_name(), "soil") > minetest.get_item_group(drop_stack:get_name(), "soil") then
return false
end
-- xpanes are such a pain. This check for the "airlike" draw type
-- prevents catching of 0.4.10 xpanes.
if drop_def.drawtype == "airlike" then
return false
end
-- This catches things such as saplings and modern seeds. We don't want
-- to catch single seeds dropped by farm plants though.
if node_def.drawtype ~= drop_def.drawtype then
if drop_stack:get_count() > 1 then
return true
end
if type(node_def.drop) == "table" and node_def.drop.max_items ~= 1 then
return false
end
return true
end
local drop_tiles = drop_def.tiles or drop_def.tile_images or {}
local node_tiles = node_def.tiles or node_def.tile_images or {}
if #drop_tiles == 1 then
-- This catches ice.
if #node_tiles == 1 then
return true
end
-- This catches sand without catching dirt.
for _, tile in next, node_tiles do
if tile:split("^")[1] == drop_tiles[1] then
-- Instead of outright rejecting dirt though, we need to see if the
-- dirt likely converts itself to other forms. That lets us catch dry
-- dirt without catching regular dirt.
for _, def in next, minetest.registered_abms do
for _, node in next, def.nodenames do
if node == drops[1] then
return false
end
end
end
return true
end
end
return true
end
end
-- Doors are poorly implemented. They are a craft item in the
-- inventory, but when placed, are represented by a node. This isn't
-- because the doors are multi-node objects; beds have the same
-- characteristic of taking up multiple nodes in the game world, yet
-- they're implemented in a much cleaner way. Namely, placing one node
-- causes the other node to be added to the world as well, but the
-- in-inventory representation of that object is one of the two nodes.
-- This function lets us treat nodes as nodes, even when said nodes are
-- implemented as craft items.
--
-- To put it simply, this ignores whether an item is technically
-- defined as a craft item, and instead checks to see if the item
-- *acts* like a craft item.
return (minetest.registered_items[item_ID]
and minetest.registered_items[item_ID].on_place == minetest.craftitemdef_default.on_place)
-- Nodes with a different draw type than the node that dropped it are
-- likely to be legitimate countable drops. In theory, these are no
-- different than "placeable" craft items semantically, but in
-- practice, "placeable" craft items are usually poorly-programmed
-- garbage. Legitimate nodes with differing draw types should be safe
-- though.
--
-- It's kind of hacky to specifically check for the airlike drawtype,
-- but it handles the hacky code in an older version of xpanes, as
-- well as third-party mods that copied that hacky code (*cough*
-- pipeworks *cough*).
or (minetest.registered_nodes[item_ID]
and minetest.registered_nodes[node_name].drawtype ~= minetest.registered_nodes[item_ID].drawtype
and minetest.registered_nodes[item_ID].drawtype ~= "airlike")
-- Some nodes have a secondary form used for the code, but that is
-- effectively the same exact node to the player. Examples of this
-- include mushrooms (a separate node is used to mark whether spores
-- have been collected from it or not) and ice (a separate node is used
-- by the map generator to allow caves to only spawn in ice placed in
-- certain areas). In these situations, both versions of the node will
-- drop the same version (so one should be countable) and both versions
-- usually use the same exact texture files (which we can use to
-- distinguish these nodes as being basically the same node). The check
-- for having the same node box is because of xpanes, which uses the
-- same texture for everything related, but uses different node boxes
-- for different versions of the node. The conversions between
-- different xpanes node representations are two-way, not one-way, so
-- we don't count them.
or (minetest.registered_nodes[item_ID]
and minetest.registered_nodes[item_ID].drawtype == minetest.registered_nodes[node_name].drawtype
and minetest.registered_nodes[item_ID].node_box == minetest.registered_nodes[node_name].node_box
and minetest.registered_nodes[item_ID].mesh == minetest.registered_nodes[node_name].mesh
and minetest.registered_nodes[item_ID].tiles
and minetest.registered_nodes[node_name].tiles
and #minetest.registered_nodes[item_ID].tiles == #minetest.registered_nodes[node_name].tiles
and minetest.registered_nodes[item_ID].tiles[1] == minetest.registered_nodes[node_name].tiles[1]
and minetest.registered_nodes[item_ID].tiles[2] == minetest.registered_nodes[node_name].tiles[2]
and minetest.registered_nodes[item_ID].tiles[3] == minetest.registered_nodes[node_name].tiles[3]
and minetest.registered_nodes[item_ID].tiles[4] == minetest.registered_nodes[node_name].tiles[4]
and minetest.registered_nodes[item_ID].tiles[5] == minetest.registered_nodes[node_name].tiles[5]
and minetest.registered_nodes[item_ID].tiles[6] == minetest.registered_nodes[node_name].tiles[6])
-- This causes coral skeletons to be counted. If all of a node's tiles
-- are different than those of its drop, it's probably something we
-- should count. By making sure every last texture differs, we avoid
-- counting dirt dropped by its various forms. By checking for the
-- on_place callback, we avoid counting grass dropped by its various
-- forms. By not allowing meshes, we rule out gates, and by not
-- allowing nodeboxes, we rule out doors. By requiring the number of
-- textures on both the node and the drop to match, we rule out dirt.
-- We should probably make the dirt check more specific, later.
or (minetest.registered_nodes[item_ID]
and minetest.registered_items[item_ID].on_place == minetest.nodedef_default.on_place
and minetest.registered_items[item_ID].drawtype ~= "mesh"
and minetest.registered_items[item_ID].drawtype ~= "nodebox"
and minetest.registered_nodes[item_ID].tiles
and minetest.registered_nodes[node_name].tiles
and #minetest.registered_nodes[item_ID].tiles == #minetest.registered_nodes[node_name].tiles
and (minetest.registered_nodes[item_ID].tiles[1] ~= minetest.registered_nodes[node_name].tiles[1]
or not minetest.registered_nodes[node_name].tiles[1])
and (minetest.registered_nodes[item_ID].tiles[2] ~= minetest.registered_nodes[node_name].tiles[2]
or not minetest.registered_nodes[node_name].tiles[2])
and (minetest.registered_nodes[item_ID].tiles[3] ~= minetest.registered_nodes[node_name].tiles[3]
or not minetest.registered_nodes[node_name].tiles[3])
and (minetest.registered_nodes[item_ID].tiles[4] ~= minetest.registered_nodes[node_name].tiles[4]
or not minetest.registered_nodes[node_name].tiles[4])
and (minetest.registered_nodes[item_ID].tiles[5] ~= minetest.registered_nodes[node_name].tiles[5]
or not minetest.registered_nodes[node_name].tiles[5])
and (minetest.registered_nodes[item_ID].tiles[6] ~= minetest.registered_nodes[node_name].tiles[6]
or not minetest.registered_nodes[node_name].tiles[6]))
-- This causes the seeds from 0.4.7 to be counted properly without
-- counting the modern doors. Non-nodes are counted as long as they're
-- not the only drop possibility.
or (minetest.registered_items[item_ID]
and minetest.registered_items[item_ID].type ~= "node"
and #drop_opts > 1)
end)
if debug_table then
-- For debugging purposes
liblevelup.register.is_countable(function(node_string, drop, drop_opts, drop_key)
local result = generic_default_is_countable_function(node_string, drop, drop_opts, drop_key)
local key = "["..node_string.."]["..drop_key.."]"
if debug_table[key] == nil or debug_table[key] ~= result then
minetest.debug(key.." => "..dump(result == true))
end
return result
end)
else
liblevelup.register.is_countable(generic_default_is_countable_function)
end