Merge pull request #2 from EmptyStar/feature_20241226

Give nodes limited fill potential
This commit is contained in:
EmptyStar 2024-12-26 15:02:15 -06:00 committed by GitHub
commit eef06b4bc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 158 additions and 57 deletions

View File

@ -5,7 +5,11 @@ Fill empty glass bottles with a variety of earthly materials including water, di
On their own, filled bottles are only good for collecting and decoration. However, other mods may wish to make use of filled bottles for other purposes, such as for ingredients in cooking or chemistry. It's also possible to define your own filled bottles; see the API section below for details.
Bottles function in a very similar fashion to buckets, but one key difference is that bottles *do not displace nor dispense any nodes*. Filling a bottle from a target node simply 'transforms' the empty glass bottle into a filled bottle, and emptying a filled bottle turns it back into an empty glass bottle. On a technical level, bottles don't 'carry' their contents the way buckets do.
Bottles function in a very similar fashion to buckets with two key differences. First, many bottles may be filled from one single node before the node is exhausted (10 bottles by default), and an exhausted node may be fully consumed (e.g., sand or water) or replaced with a "stripped" version of itself (e.g., dirt with grass becomes only dirt). Secondly, emptying a filled bottle does not dispense its contents, it simply becomes an empty glass bottle when emptied. In this way, bottles are effectively "transformed" into other types of bottles but they do not actually carry their contents the way buckets do.
Also note that partially bottled nodes have a chance to yield no drops when dug. This chance scales with how many times the node is bottled such that bottling a node more times increases the chance that it will not yield drops when dug. This is a balance measure to prevent infinite bottling of a single node.
The chance to yield no drops does not apply to liquids by default as liquids can easily be exploited to bypass this restriction, but this can be changed via settings if needed for a special case.
Supported games/mods
--------------------
@ -29,6 +33,9 @@ Want to create your own filled bottles? Use the `bottles.register_filled_bottle`
-- from; either a string for a single node name
-- or an array that is a list of node names
replacement = <string>, -- the node to replace the target node(s) with when
-- a target node is fully drained; default is "air"
name = <string>, -- a name for the filled bottle item; will be prefixed with
-- `bottles:`, so don't include any such namespace; default
-- value is "bottle_of_" + the name of the target node

View File

@ -7,8 +7,28 @@ local function do_spill(...)
return bottles.spill(...)
end
-- Local map for liquid category to int for simple comparison
local liquid_map = {
none = 0,
source = 1,
flowing = 2,
}
-- Globals
bottles = {
-- Settings
settings = {
node_fill_limit = tonumber(core.settings:get("bottles.node_fill_limit",10) or 10),
liquid_fill_unlimited = (function(value)
if value == "all" then
return 1
elseif value == "flowing" then
return 2
else
return 3
end
end)(core.settings:get("bottles.liquid_fill_unlimited","all") or "all"),
},
-- Registry of filled bottles
registered_filled_bottles = {},
@ -16,6 +36,9 @@ bottles = {
-- Index target nodes to their filled bottle types
target_node_map = {},
-- Cache node liquid statuses for special liquid handling
target_liquid_map = {},
-- Play a sound whenever a bottle is filled or spilled
play_bottle_sound = function(pos,sound)
if sound then
@ -32,7 +55,8 @@ bottles = {
fill = function(itemstack,placer,target)
if target.type == "node" and placer:is_player() then
-- Get targeted node
local node = minetest.get_node(target.under)
local pos = target.under
local node = minetest.get_node(pos)
local filled_bottle = bottles.target_node_map[node.name]
if filled_bottle then
-- Play contents sound
@ -53,11 +77,24 @@ bottles = {
end
end
-- Set filled node metadata if enabled; special handling for liquids
local liquid = bottles.target_liquid_map[node.name]
if bottles.settings.node_fill_limit > 0 and liquid < bottles.settings.liquid_fill_unlimited then
local meta = core.get_meta(pos)
local limit = meta:get_int("bottles.node_fill_limit")
limit = limit + 1
if limit >= bottles.settings.node_fill_limit then
core.set_node(pos,{ name = filled_bottle.replacement })
else
meta:set_int("bottles.node_fill_limit",limit)
end
end
-- Return value
return retval
return retval, filled_bottle.name
end
end
return itemstack
return itemstack, nil
end,
-- Spill the contents out of a filled bottle
@ -112,6 +149,10 @@ bottles = {
return false
end
spec.description = spec.description or ("Bottle of " .. contents_node.description:split("\n")[1])
spec.replacement = spec.replacement or "air"
if spec.image then
-- do nothing
elseif type(contents_node.tiles[1]) == "string" then
@ -134,9 +175,9 @@ bottles = {
spec.target = {spec.target}
end
-- Ensure that target nodes are not already in use, fail registration if so
-- Ensure that target nodes exist and are not already in use, fail registration if so
for _,target in ipairs(spec.target) do
if bottles.target_node_map[target] then
if bottles.target_node_map[target] or not core.registered_nodes[target] then
return false
end
end
@ -146,9 +187,10 @@ bottles = {
spec.sound = contents_node.sounds.footstep.name
end
-- Map target nodes to spec
-- Map target nodes and liquid status to spec
for _,target in ipairs(spec.target) do
bottles.target_node_map[target] = spec
bottles.target_liquid_map[target] = liquid_map[core.registered_nodes[target].liquidtype or "none"]
end
-- Put bottle into map of registered filled bottles
@ -156,7 +198,7 @@ bottles = {
-- Register new bottle node
minetest.register_node(":" .. spec.name,{
description = spec.description or ("Bottle of " .. contents_node.description:split("\n")[1]),
description = spec.description,
drawtype = "plantlike",
tiles = {spec.image},
inventory_image = spec.image,
@ -203,4 +245,18 @@ bottles = {
minetest.override_item("vessels:glass_bottle",{
liquids_pointable = true,
on_use = do_fill,
})
})
-- Nodes that have been partially filled have a chance to drop nothing when dug
if bottles.settings.node_fill_limit > 0 then
local oghnd = core.handle_node_drops
core.handle_node_drops = function(pos, drops, digger)
local meta = core.get_meta(pos)
local limit = meta:get_int("bottles.node_fill_limit")
if limit > 0 and math.random(1,bottles.settings.node_fill_limit) <= limit then
-- do nothing, node will drop nothing
else
return oghnd(pos, drops, digger)
end
end
end

View File

@ -1 +1,4 @@
bottles.register_filled_bottle({ target = "badland:badland_grass" })
bottles.register_filled_bottle({
target = "badland:badland_grass",
replacement = "default:dirt",
})

View File

@ -1,14 +1,17 @@
bottles.register_filled_bottle({
target = "caverealms:stone_with_moss",
description = "Bottle of Cave Moss",
replacement = "default:cobble",
})
bottles.register_filled_bottle({
target = "caverealms:stone_with_lichen",
description = "Bottle of Lichen",
replacement = "default:cobble",
})
bottles.register_filled_bottle({
target = "caverealms:stone_with_algae",
description = "Bottle of Algae",
replacement = "default:cobble",
})

View File

@ -51,6 +51,7 @@ bottles.register_filled_bottle({
sound = "default_grass_footstep",
name = "bottle_of_grass",
description = "Bottle of Grass",
replacement = "default:dirt",
})
bottles.register_filled_bottle({
@ -58,6 +59,7 @@ bottles.register_filled_bottle({
sound = "default_grass_footstep",
name = "bottle_of_dry_grass",
description = "Bottle of Dry Grass",
replacement = "default:dry_dirt",
})
bottles.register_filled_bottle({
@ -65,6 +67,7 @@ bottles.register_filled_bottle({
sound = "default_grass_footstep",
name = "bottle_of_coniferous_litter",
description = "Bottle of Coniferous Litter",
replacement = "default:dirt",
})
bottles.register_filled_bottle({
@ -72,6 +75,7 @@ bottles.register_filled_bottle({
sound = "default_grass_footstep",
name = "bottle_of_rainforest_litter",
description = "Bottle of Rainforest Litter",
replacement = "default:dirt",
})
bottles.register_filled_bottle({
@ -79,6 +83,7 @@ bottles.register_filled_bottle({
sound = "default_grass_footstep",
name = "bottle_of_moss",
description = "Bottle of Tundra Moss",
replacement = "default:permafrost_with_stones",
})
bottles.register_filled_bottle({

View File

@ -1 +1,4 @@
bottles.register_filled_bottle({ target = "dorwinion:dorwinion_grass" })
bottles.register_filled_bottle({
target = "dorwinion:dorwinion_grass",
replacement = "dorwinion:dorwinion",
})

View File

@ -12,5 +12,6 @@ for _,dirt in ipairs({
bottles.register_filled_bottle({
target = "ethereal:" .. dirt:lower() .. "_dirt",
description = "Bottle of " .. dirt .. " Dirt",
replacement = "default:dirt",
})
end

View File

@ -1,35 +1,38 @@
for node,description in pairs({
["everness:coral_desert_stone_with_moss"] = "Coral Cave Moss",
["everness:mold_stone_with_moss"] = "Moldy Moss",
["everness:coral_dirt"] = false,
["everness:cursed_dirt"] = false,
["everness:crystal_dirt"] = false,
["everness:forsaken_tundra_dirt"] = false,
["everness:forsaken_tundra_dirt_with_grass"] = "Forsaken Tundra Grass",
["everness:dirt_with_coral_grass"] = "Coral Grass",
["everness:dirt_with_cursed_grass"] = "Cursed Grass",
["everness:dirt_with_crystal_grass"] = "Crystal Grass",
["everness:dry_ocean_dirt"] = false,
["everness:crystal_cave_dirt"] = false,
["everness:crystal_cave_dirt_with_moss"] = "Crystal Cave Moss",
["everness:moss_block"] = "Moss",
["everness:crystal_moss_block"] = "Crystal Moss",
["everness:coral_sand"] = false,
["everness:coral_white_sand"] = false,
["everness:cursed_sand"] = false,
["everness:crystal_sand"] = false,
["everness:forsaken_tundra_beach_sand"] = false,
["everness:forsaken_desert_sand"] = false,
["everness:coral_forest_deep_ocean_sand"] = false,
["everness:cursed_lands_deep_ocean_sand"] = false,
["everness:crystal_forest_deep_ocean_sand"] = false,
["everness:mineral_sand"] = false,
["everness:frosted_snowblock"] = "Frosted Snow",
["everness:cursed_mud"] = false,
for node,data in pairs({
["everness:coral_desert_stone_with_moss"] = { "Coral Cave Moss", "everness:coral_desert_stone" },
["everness:mold_stone_with_moss"] = { "Moldy Moss" },
["everness:coral_dirt"] = {},
["everness:cursed_dirt"] = {},
["everness:crystal_dirt"] = { "Crystal Dirt", "air", "everness_crystal_dirt" },
["everness:forsaken_tundra_dirt"] = {},
["everness:forsaken_tundra_dirt_with_grass"] = { "Forsaken Tundra Grass", "everness:forsaken_tundra_dirt" },
["everness:dirt_with_coral_grass"] = { "Coral Grass", "everness:coral_dirt" },
["everness:dirt_with_cursed_grass"] = { "Cursed Grass", "everness:cursed_dirt" },
["everness:dirt_with_crystal_grass"] = { "Crystal Grass", "everness:crystal_dirt" },
["everness:dry_ocean_dirt"] = {},
["everness:crystal_cave_dirt"] = {},
["everness:crystal_cave_dirt_with_moss"] = { "Crystal Cave Moss" },
["everness:moss_block"] = { "Moss" },
["everness:crystal_moss_block"] = { "Crystal Moss" },
["everness:coral_sand"] = {},
["everness:coral_white_sand"] = {},
["everness:cursed_sand"] = {},
["everness:crystal_sand"] = {},
["everness:forsaken_tundra_beach_sand"] = {},
["everness:forsaken_desert_sand"] = {},
["everness:coral_forest_deep_ocean_sand"] = {},
["everness:cursed_lands_deep_ocean_sand"] = {},
["everness:crystal_forest_deep_ocean_sand"] = {},
["everness:mineral_sand"] = {},
["everness:frosted_snowblock"] = { "Frosted Snow" },
["everness:cursed_mud"] = {},
["everness:mineral_lava_stone_with_moss"] = { "Mineral Cave Moss", "everness:mineral_cave_stone" }
}) do
bottles.register_filled_bottle({
name = data[3],
target = node,
description = description and ("Bottle of " .. description) or nil,
description = data[1] and ("Bottle of " .. data[1]) or nil,
replacement = data[2]
})
end

View File

@ -1 +1,4 @@
bottles.register_filled_bottle({ target = "frost_land:frost_land_grass" })
bottles.register_filled_bottle({
target = "frost_land:frost_land_grass",
replacement = "default:dirt",
})

View File

@ -1,4 +1,5 @@
bottles.register_filled_bottle({
target = "japaneseforest:japanese_dirt_with_grass",
description = "Bottle of Japanese Grass",
replacement = "default:dirt",
})

View File

@ -2,5 +2,8 @@ for _,node in ipairs({
"livingjungle:jungleground",
"livingjungle:leafyjungleground",
}) do
bottles.register_filled_bottle({ target = node })
bottles.register_filled_bottle({
target = node,
replacement = "default:dirt",
})
end

View File

@ -1,20 +1,22 @@
for node,description in pairs({
["naturalbiomes:alderswamp_litter"] = "Alder Swamp Grass",
["naturalbiomes:alpine_litter"] = "Alpine Grass",
["naturalbiomes:bambooforest_litter"] = "Bamboo Litter",
["naturalbiomes:bushland_bushlandlitter"] = false,
["naturalbiomes:bushland_bushlandlitter2"] = false,
["naturalbiomes:bushland_bushlandlitter3"] = false,
["naturalbiomes:heath_litter"] = "Heath Litter",
["naturalbiomes:heath_litter2"] = "Heath Litter",
["naturalbiomes:heath_litter3"] = "Heath Litter",
["naturalbiomes:mediterran_litter"] = "Mediterranean Grass",
["naturalbiomes:outback_litter"] = "Outback Grass",
["naturalbiomes:palmbeach_sand"] = "Beach Sand",
["naturalbiomes:savannalitter"] = "Savanna Litter",
for node,data in pairs({
["naturalbiomes:alderswamp_litter"] = { "Alder Swamp Grass", "naturalbiomes:alderswamp_dirt" },
["naturalbiomes:alderswamp_dirt"] = {},
["naturalbiomes:alpine_litter"] = { "Alpine Grass", "default:dirt" },
["naturalbiomes:bambooforest_litter"] = { "Bamboo Litter" },
["naturalbiomes:bushland_bushlandlitter"] = { false, "default:dirt" },
["naturalbiomes:bushland_bushlandlitter2"] = {},
["naturalbiomes:bushland_bushlandlitter3"] = {},
["naturalbiomes:heath_litter"] = { "Heath Litter", "default:sand" },
["naturalbiomes:heath_litter2"] = { "Heath Litter", "default:sand" },
["naturalbiomes:heath_litter3"] = { "Heath Litter", "default:sand" },
["naturalbiomes:mediterran_litter"] = { "Mediterranean Grass", "default:dirt" },
["naturalbiomes:outback_litter"] = { "Outback Grass", "naturalbiomes:outback_ground" },
["naturalbiomes:palmbeach_sand"] = { "Beach Sand" },
["naturalbiomes:savannalitter"] = { "Savanna Litter", "default:dirt" },
}) do
bottles.register_filled_bottle({
target = node,
description = description and ("Bottle of " .. description) or nil,
description = data[1] and ("Bottle of " .. data[1]) or nil,
replacement = data[2],
})
end

View File

@ -1,4 +1,5 @@
bottles.register_filled_bottle({
target = "nightshade:nightshade_dirt_with_grass",
description = "Bottle of Nightshade Grass",
replacement = "default:dirt",
})

View File

@ -1,4 +1,5 @@
bottles.register_filled_bottle({
target = "prairie:prairie_dirt_with_grass",
description = "Bottle of Prairie Grass",
replacement = "default:dirt",
})

9
settingtypes.txt Normal file
View File

@ -0,0 +1,9 @@
# How many times a node can be collected via bottle before the target node is used up. Used up nodes will be replaced with a registered replacement node and have a chance to drop nothing when mined. Set to 0 for infinite bottle fills per node.
bottles.node_fill_limit (Node fill limit) int 10 0 99
# Allow infinite bottling for liquids:
# "all" = all liquids allow infinite bottling
# "flowing" = only flowing liquids allow infinite bottling
# "none" = no liquids allow infinite bottling
# NOTE: This setting can easily be circumvented for most typical liquids
bottles.liquid_fill_unlimited (Liquid fill unlimited) enum all all,flowing,none