Cauldrons API, object-based; changes to mcl_buckets and mcl_potions for interaction with water cauldron.

This commit is contained in:
Phaethon H 2021-10-19 21:46:38 -07:00
parent 1b0b5fc733
commit cf194ed51a
11 changed files with 1087 additions and 8 deletions

View File

@ -93,6 +93,14 @@ function mcl_buckets.register_liquid(def)
-- Check if pointing to a buildable node
--local item = itemstack:get_name()
local into_cauldron
local bucket_content = def._bucket_content
-- TODO: guard with test for mcl_cauldrons
into_cauldron = mcl_cauldrons.get_cauldron(place_pos)
if into_cauldron and not into_cauldron:accepts_substance(bucket_content) then
into_cauldron = nil
end
if def.extra_check and def.extra_check(place_pos, user) == true and nodedef and nodedef.buildable_to then
-- buildable; replace the node
local pns = user:get_player_name()
@ -104,6 +112,11 @@ function mcl_buckets.register_liquid(def)
if mod_doc and doc.entry_exists("nodes", node_place) then
doc.mark_entry_as_revealed(user:get_player_name(), "nodes", node_place)
end
elseif into_cauldron then
if into_cauldron:fill_levels(bucket_content, into_cauldron.BUCKETFUL) then
into_cauldron:update_node()
sound_place("mcl_core:water_source", place_pos)
end
else
-- not buildable to; place the liquid above
-- check if the node above can be replaced
@ -201,6 +214,8 @@ minetest.register_craftitem("mcl_buckets:bucket_empty", {
-- Check if pointing to a liquid source
local liquiddef = mcl_buckets.liquids[nn]
local new_bucket
local from_cauldron
from_cauldron = mcl_cauldrons.get_cauldron(pointed_thing.under)
if liquiddef ~= nil and liquiddef.itemname ~= nil and (nn == liquiddef.source_take) then
-- Fill bucket, but not in Creative Mode
@ -218,8 +233,27 @@ minetest.register_craftitem("mcl_buckets:bucket_empty", {
doc.mark_entry_as_revealed(user:get_player_name(), "nodes", nn)
end
elseif from_cauldron then
-- Cauldron
--[[
elseif minetest.get_item_group(nn, "cauldron") then
new_bucket = mcl_cauldrons.take_cauldron(pointed_thing.under, new_bucket, user)
--]]
if from_cauldron:drain_levels("water", from_cauldron.BUCKETFUL) then
-- succeeds only if a water cauldron with enough content.
from_cauldron:update_node()
if not minetest.is_creative_enabled(user:get_player_name()) then
new_bucket = ItemStack({name = "mcl_buckets:bucket_water"})
end
sound_take("mcl_core:water_source", from_cauldron.pos)
elseif from_cauldron:drain_levels("river_water", from_cauldron.BUCKETFUL) then
-- succeeds only if river water cauldron with enough content.
from_cauldron:update_node()
if not minetest.is_creative_enabled(user:get_player_name()) then
new_bucket = ItemStack({name = "mcl_buckets:bucket_river_water"})
end
sound_take("mcl_core:river_water_source", from_cauldron.pos)
end
end
-- Add liquid bucket and put it into inventory, if possible.
@ -227,6 +261,27 @@ minetest.register_craftitem("mcl_buckets:bucket_empty", {
if minetest.is_creative_enabled(user:get_player_name()) then --TODO
itemstack:take_item()
end
-- copied and edited from empty bucket code.
if not minetest.is_creative_enabled(user:get_player_name()) then
-- Add water bucket and put it into inventory, if possible.
-- Drop water bucket otherwise.
if itemstack:get_count() == 1 then
return new_bucket
else
local inv = user:get_inventory()
if inv:room_for_item("main", new_bucket) then
inv:add_item("main", new_bucket)
else
minetest.add_item(user:get_pos(), new_bucket)
end
itemstack:take_item()
return itemstack
end
else
return
end
end,
_on_dispense = function(stack, pos, droppos, dropnode, dropdir)
-- Fill empty bucket with liquid or drop bucket if no liquid

View File

@ -39,7 +39,8 @@ if mod_mcl_core then
name = S("Lava Bucket"),
longdesc = S("A bucket can be used to collect and release liquids. This one is filled with hot lava, safely contained inside. Use with caution."),
usagehelp = S("Get in a safe distance and place the bucket to empty it and create a lava source at this spot. Don't burn yourself!"),
tt_help = S("Places a lava source")
tt_help = S("Places a lava source"),
_bucket_content = "lava"
})
-- Water bucket
@ -66,11 +67,14 @@ if mod_mcl_core then
-- Pour water into cauldron
if minetest.get_item_group(nn, "cauldron") ~= 0 then
-- Put water into cauldron
print("extra check cauldron")
--[[
if nn ~= "mcl_cauldrons:cauldron_3" then
mcl_cauldrons.set_cauldron_level(pos, "water", 3)
end
sound_place("mcl_core:water_source", pos)
return false
--]]
-- Evaporate water if used in Nether (except on cauldron)
else
local dim = mcl_worlds.pos_to_dimension(pos)
@ -81,6 +85,7 @@ if mod_mcl_core then
end
end,
groups = { water_bucket = 1 },
_bucket_content = "water",
})
end
@ -109,11 +114,13 @@ if mod_mclx_core then
-- Pour into cauldron
if minetest.get_item_group(nn, "cauldron") ~= 0 then
-- Put water into cauldron
--[[
if nn ~= "mcl_cauldrons:cauldron_3r" then
mcl_cauldrons.set_cauldron(pos, "river_water", 3)
end
sound_place("mcl_core:water_source", pos)
return false
--]]
else
-- Evaporate water if used in Nether (except on cauldron)
local dim = mcl_worlds.pos_to_dimension(pos)
@ -124,6 +131,7 @@ if mod_mclx_core then
end
end,
groups = { water_bucket = 1 },
_bucket_content = "river_water",
})
end

View File

@ -25,4 +25,185 @@ def can have these fields:
* texture: texture of the flowing liquid e.g: "mcl_core_water_flowing.png"
## mcl_cauldrons.registered_cauldrons
Table containing chauldrons def indexed by name.
Table containing chauldrons def indexed by name.
Behavior
--------
Behavior should mimick Cauldron from Minecraft Java Edition 1.13.
Incomplete notes based on [Cauldron entry from Minecraft wiki](https://minecraft.fandom.com/wiki/Cauldron), with edits derived from what historical/version logs found:
* May be mined
* Drops (empty) cauldron item if mined with pickaxe.
* No drop if mined with anything else.
* When destroyed/mined, contents are lost.
* May be empty
* Completely filled with water by using water bucket (and bucket empties).
* Cannot be filled with fish bucket (despite water), causes normal node placing instead.
* Add one level of water with water bottle (and bottle empties).
* Slowly fills with water when rained upon.
* May hold water
* When full, completely emptied by using empty bucket (and bucket fills).
* Add one level of water with water bottle (and bottle empties).
* Remove one level of water with glass bottle (and bottle fills).
* Slowly fills with water when rained upon.
* Does not absorb explosion.
* Does not make sounds/particles/bubbles at all, including when jumping in.
* Does not damage endermen, striders, blazes.
* Does not deal drowning damage.
* Does not allow fish to breathe.
* Does not allow player to float or swim (emergent from height property?).
* Does not count as a riptide source (for Trident).
* Does not allow any interaction with sponge.
* Washes dye off items, by using (right-clicking) item into cauldron.
- Reduces water level by one with each washing.
- Removes dye from leather armor.
- Removes dye from shulker box.
- Removes dye from top-most layer of banner.
- Water remains undyed.
* Extinguishes entities on fire:
- Entities emit black particles as fire is extinguished.
- Entities must reach the actual water (fall into cauldron).
- Reduces water level by one with each entity extinguished.
- Extinguishes flaming arrows stuck into side of cauldron (not an entity, so does not consume water level).
* Attached redstone comparator emits signal strength proportional to water level (0=empty .. 3=full).
* May not hold:
* milk
* honey
* food items in a bowl (mushroom stew, beetroot soup, rabbit soup, suspicious stew)
* Water texture might vary with biome?
### Other Editions and Future Notes (up to 1.17)
Particular behaviors to avoid as they contradict Minecraft JE 1.13.
Incomplete notes based on wiki and logs:
* BEDROCK - may hold potion.
* BEDROCK - may add dyes (color-mixing supported) to make dyed water, which can dye leather armor or leather horse armor.
* 1.14 - Cauldrons are job site blocks for Leatherworker (villager).
* 1.17 - Filled cauldrons are not job site blocks (only empty cauldrons).
* 1.17 - May be filled with lava (affects light level, burning of entities).
* 1.17 - May be filled with powder snow (affects extinguishing).
* 1.17 - May collect water and lava from Pointed Dripstone.
* 1.17 - Lava Bucket and Powder Snow Bucket may empty into any filled cauldron (old contents lost).
Node Definitions
----------------
Cauldron definitions extends the node definition (as accepted by `minetest.register_node()`) with the following keys:
* `.groups.cauldron`: number = 1 - indicates cauldron behavior
* `.groups.cauldron_filled`: number = fill level of cauldron
* `.groups.cauldron_water`: number = 1 if contains water, else 0
* `.groups.cauldron_river_water`: number = 1 if contains river water, else 0
* `._cauldron_content`: string = substance in cauldron
### `mcl_cauldrons.registered_cauldrons`
Table containing cauldron definitions, indexed by name.
### `mcl_cauldrons.register_cauldron(cauldron_name, cauldron_def)`
Register cauldron node definition. Ultimately passed to `minetest.register_node()`, but also maintains a mod-local `mcl_cauldrons.registered_cauldrons` table.
Misc
----
### `mcl_cauldrons.CauldronRef`
Prototype ("class") for interacting with cauldrons at run-time.
### `mcl_cauldrons.get_cauldron(pos)`
* `pos`: position of node in world
* If you're lost in the API, this is where you want to start.
* Returns an instance of CauldronRef based on node.
* Returns `nil` if not a cauldron.
* Alias for `CauldronRef.make_cauldron_ref`
### `mcl_cauldrons.set_cauldron(pos, cauldron_ref)`
* `pos`: world node position
* `cauldron_ref`: CauldronRef instance
* Update world node to be consistent with CauldronRef (i.e. "commit" CauldronRef to the world)
`CauldronRef`
-------------
Object-based interaction with cauldrons in the game.
Cauldron behavior is based on Cauldron in Minecraft Java Edition 1.13.
In general, the object should be abandoned after its contents are transferred to the world (i.e. after `update_node()` or after `make_node()` contents applied with `minetest.set_node()`).
Exceptions apply if you know what you're doing with the reference.
### Constants
* `BUCKETFUL` - number of levels changed by using a bucket (historically 3).
* `BOTTLEFUL` - number of levels changed by using a bottle (historically 1).
### Prototype ("Class") Methods
* `make_cauldron_ref(pos)`: table, new CauldonRef instance
* `pos`: position of node in world
* Returns an instance of CauldronRef based on node.
* Returns `nil` if not a cauldron.
* `find_nodedef(substance, fill_level)`: table, registered cauldron node definition
* `substance`: string - desired content of cauldron
* `fill_level`: number - desired fill level of cauldron
* Scans registered cauldron nodes for cauldron defintion that best fits the parameters.
### Instance Methods
* `is_empty()`: returns boolean - test if cauldron is empty
* `accept_substance(substance)`: returns boolean
* `substance`: string - value representing a cauldron substance
* `get_substance()`: returns string - contents of cauldron, may be nil
* `set_substance(substance)`:
* `substance`: string - cauldron substance (pending)
* Effect is **NOT** immediate; change is stored for `make_node`.
* `get_level(substance)`: returns number - current fill level of cauldron
* `substance`: string - cauldron substance
* Can also be test for cauldron containing substance (test value > 0)
* 0 does not mean cauldron is empty, it means no amount of *this* substance exists in the cauldron.
* e.g. cauldron of "river\_water" returns 0 level for "water", as only "river\_water" is available and no "water".
* use `is_empty()` to test for an empty cauldron (cauldron actually has nothing inside).
* `set_level(substance, fill_level)`
* `substance`: string - substance to be contained in cauldron, `nil` to preserve content
* `fill_level`: number - fill level of the cauldron (with substance)
* Change cauldron contents, bypassing fill/drain rules.
* Reduces to empty cauldron if `substance == "empty"` or `fill_level == 0`.
* Effect is **NOT** immediate; change is stored for `make_node`.
* `fill_levels(substance, change_levels)`: returns boolean
* `substance`: string - substance to fill cauldron, `nil` to indicate current contents
* `change_levels`: number - number of levels to add
* Fills cauldron with a substance subject to game rules:
* If cauldron contains the same substance, add more levels
* If cauldron contains different substance (or is empty), the old contents are lost during filling.
* Maximum of 3 levels
* Fractional `change_levels` indicates probability of using the next greater whole number (the lesser whole number may be treated as guaranteed minimum);
e.g. 0.4 means 40% chance to fill with 1 (60% with 0);
1.65 means 65% chance to fill with 2 (35% with 1).
* Returns true if cauldron successfully changed (fill succeeded);
returns false if cauldron did not or could not change (fill failed).
* Effect is **NOT** immediate; change is stored for `make_node`.
* `drain_levels(substance, change_levels)`: returns boolean
* `substance`: string - substance to fill cauldron, `nil` to specifiy current content
* `change_levels`: number - number of levels to add
* Drains cauldron of substance subject to game rules:
* Fails if specified substance is not in cauldron.
* Fails if not enough levels are in the cauldron (asking for too much).
* Fractional `change_levels` for probabilistic drain (see `fill_levels`).
* Returns true if cauldron successfully changed (drain succeeded);
returns false if cauldron did not or could not change (drain failed).
* Effect is **NOT** immediate; change is stored for `make_node`.
* `make_node()`: returns node { name, param1, param2 }
* Data-based approach to updating cauldron in world (caller may make additional modifications to return value before passing to `minetest.set_node()`).
* Generated from stored internal states (get, set, fill, drain).
* Suitable for passing to `minetest.set_node()`
* `update_node()`: update node in world with cauldron's internal state.
* Imperative approach to updating cauldron in world (effect is **IMMEDIATE**).
* Updates cauldron based on stored internal states (set, fill, drain).
* Directly invokes `minetest.set_node()`.

View File

@ -1,5 +1,11 @@
local has_doc = minetest.get_modpath(minetest.get_current_modname())
-- API for mcl_cauldrons (Cauldron)
-- namespace.
mcl_cauldrons.registered_cauldrons = {}
--[[
local function survival_give(inv, original_item, itemstack)
if inv:room_for_item("main", itemstack) then
inv:add_item("main", itemstack)
@ -156,4 +162,343 @@ function mcl_cauldrons.take_small_cauldron(pos, itemstack, user, sounds)
end
return itemstack
end
end
end
--]]
--------------------------------
-- Symbolic constants for changing levels by vessel capacity.
mcl_cauldrons.BUCKETFUL = 3
mcl_cauldrons.BOTTLEFUL = 1
-----------------------
---- Base Cauldron ----
-----------------------
---- base prototype for cauldrons
local BaseCauldronRef = {
BUCKETFUL = mcl_cauldrons.BUCKETFUL,
BOTTLEFUL = mcl_cauldrons.BOTTLEFUL,
}
---- class methods
-- find cauldron node defintion best fitting substance and fill_level.
BaseCauldronRef.find_nodedef = function (Self, substance, fill_level)
-- fill_level == nil for any/first cauldron with substance.
if (fill_level == 0) or (substance == "empty") then
-- reduce to empty cauldron.
substance = nil
end
local found
for visit_name, visit_def in pairs(mcl_cauldrons.registered_cauldrons) do
if (visit_def._cauldron_content == substance) then
if (fill_level == nil) or (visit_def.groups.cauldron_filled == fill_level) then
-- any/first fill level, or specific match
found = visit_def
break
end
end
end
return found
end
-- Instantiate BaseCauldronRef (or child prototype) from world node position.
BaseCauldronRef.make_cauldron_ref = function (Self, pos)
local livenode = minetest.get_node(pos)
if (minetest.get_item_group(livenode.name, "cauldron") == 0) then
-- error, cannot make CauldronRef
return nil
end
local nodedef = mcl_cauldrons.registered_cauldrons[livenode.name]
local instance = Self:new(pos, livenode, nodedef)
return instance
end
-- Update world node from BaseCauldronRef (or child prototype) instance.
BaseCauldronRef.set_cauldron_ref = function (Self, pos, cauldron_ref)
-- do any other prepatory work.
-- pass to module-scope function.
mcl_cauldrons.set_cauldron(pos, cauldron_ref)
end
-- Instantiate BaseCauldronRef
BaseCauldronRef.new = function (Self, pos, livenode, nodedef)
local instance = {
__index = Self,
pos = pos,
livenode = livenode,
nodedef = nodedef,
-- specific behavior
specific = nil,
pending_substance = nil,
pending_level = nil,
}
setmetatable(instance, instance)
return instance
end
---- instance methods
-- test cauldron is empty (no contents).
function BaseCauldronRef:is_empty () -- boolean
if self.pending_level == 0 then
return true
elseif (minetest.get_item_group(self.livenode.name, "cauldron_filled") == 0) then
return true
end
return false
end
-- test cauldron accepts substance as its content.
function BaseCauldronRef:accepts_substance (substance) -- boolean
local found_nodedef = self:find_nodedef(substance, 1)
return (found_nodedef ~= nil)
end
-- identify contents of cauldron.
function BaseCauldronRef:get_substance () -- string
if self.pending_substance then
return self.pending_substance
else
return self.nodedef._cauldron_content or "empty"
end
end
-- set contents of cauldron.
function BaseCauldronRef:set_substance (substance) -- nil
self.pending_substance = substance
end
-- maximum capacity (levels) of cauldron.
function BaseCauldronRef:get_capacity (substance)
return 3
end
-- get fill level of cauldron.
function BaseCauldronRef:get_level (substance) -- number
if substance == nil then
substance = self:get_substance()
end
if self:get_substance() == substance then
-- matching substance
if self.pending_level then
return self.pending_level
else
-- current level.
return minetest.get_item_group(self.livenode.name, "cauldron_filled")
end
else
-- different substance.
if self.pending_level then
return self.pending_level
else
-- inconsistent state; use 0 for now.
return 0
end
end
end
-- set fill level of cauldron, bypassing fill/drain rules.
function BaseCauldronRef:set_level (substance, fill_level) -- nil
if substance then
self.pending_substance = substance
end
self.pending_level = fill_level
end
-- add levels subject to game's filling rules.
function BaseCauldronRef:fill_levels (substance, change_levels) -- boolean
-- base rules:
-- 1. Same substance, add more.
-- 2. Different substance, replace.
-- 3. Constrain levels within 0 through 3.
if change_levels <= 0 then
-- invalid change.
return false
end
local old_substance = self:get_substance()
if substance == nil then
-- substance == nil => do not change substance
substance = old_substance
end
local old_level = self:get_level()
local whole, fraction = math.modf(change_levels)
local new_level
if old_substance == substance then
-- same substance, add more.
new_level = old_level + whole
else
-- different substance, lose current contents.
new_level = whole
end
-- probabilistic increase to next whole number.
if fraction > 0 then
if math.random() < fraction then
new_level = new_level + 1
end
end
-- limit 0 through 3
new_level = math.max(0, math.min(new_level, 3))
-- store pending state.
self:set_level(substance, new_level)
-- update world node.
--return self:update_node()
return true
end
-- remove levels subject to game's draining rules.
function BaseCauldronRef:drain_levels (substance, change_levels) -- boolean
-- base rules:
-- 1. Different substance, do nothing.
-- 2. Insufficient amount, do nothing.
-- 3. (same substance of sufficient amount) drain.
-- 3. Constrain levels within 0 through 3.
if change_levels <= 0 then
-- invalid change.
return false
end
local old_substance = self:get_substance()
if substance == nil then
-- substance == nil => do not change substance
substance = old_substance
end
-- different substance, fail.
if old_substance ~= substance then
return false
end
-- insufficient amount, fail.
local old_level = self:get_level()
if change_levels > old_level then
return false
end
-- apply change
local whole, fraction = math.modf(change_levels)
local new_level
new_level = old_level - whole
-- probabilistic decrease to previous whole number.
if fraction > 0 then
if math.random() < fraction then
new_level = new_level - 1
end
end
-- limit 0 through 3
new_level = math.max(0, math.min(new_level, 3))
-- store pending state.
self:set_level(substance, new_level)
-- update world node.
--return self:update_node()
return true
end
-- After state changes are queued up for the cauldron (set, fill, drain),
-- either the caller:
-- a. calls make_node() and passes the return value on to minetest.set_node().
-- OR
-- b. calls update_node() and ends interaction with CauldronRef.
-- generate node table from stored state (functional approach).
function BaseCauldronRef:make_node () -- table { name, param1, param2 }
-- determine node {name,param1,param2} from pending state.
local substance = self.pending_substance
local fill_level = self.pending_level
if (substance == nil) and (fill_level == nil) then
-- no change.
return self.livenode
end
-- get default values from current node.
if substance == nil then
substance = self.nodedef._cauldron_content
end
if fill_level == nil then
fill_level = self.nodedef.groups.cauldron_filled
end
if (fill_level == 0) or (substance == "empty") then
-- parameters for the empty cauldron.
substance = nil
fill_level = nil
end
local pending_nodename
local pending_param1
local pending_param2
local found_nodedef = self:find_nodedef(substance, fill_level)
pending_nodename = found_nodedef.name
pending_param1 = self.livenode.param1 or 0
pending_param2 = self.livenode.param2 or 0
if pending_nodename then
local node = {
name = pending_nodename,
param1 = pending_param1,
param2 = pending_param2,
}
return node
else
-- could not determine new node; no change.
return self.livenode
end
end
-- update world from stored state (imperative approach).
function BaseCauldronRef:update_node ()
-- subject to prototype-inheritance.
self:set_cauldron_ref(self.pos, self)
end
---------------------------
---- Standard Cauldron ----
---------------------------
-- standard cauldron inherits BaseCauldronRef as-is.
local CauldronRef = { __index=BaseCauldronRef }
setmetatable(CauldronRef, CauldronRef)
mcl_cauldrons.CauldronRef = CauldronRef
----------------------------
---- Mod-level functions
----------------------------
-- instantiate CauldronRef based on world node.
function mcl_cauldrons.get_cauldron (pos)
return CauldronRef:make_cauldron_ref(pos)
end
-- Update world node to represent CauldronRef instance.
function mcl_cauldrons.set_cauldron (pos, cauldron_ref)
local node = cauldron_ref:make_node()
if node ~= cauldron_ref.livenode then
-- changed.
minetest.set_node(pos, node)
end
end
----------------------
---- Registration ----
----------------------
function mcl_cauldrons.register_cauldron_node (nodename, cauldron_def, doc_alias)
minetest.register_node(nodename, cauldron_def)
mcl_cauldrons.registered_cauldrons[nodename] = cauldron_def
if doc_alias and has_doc then
doc.add_entry_alias("nodes", doc_alias, "nodes", nodename)
end
end

View File

@ -0,0 +1,167 @@
-- mod-scope (pseudo-global) environment: S, modname, modpath, has_doc
-- Standard Cauldron
-- Convenience function because the cauldron nodeboxes are very similar
local create_cauldron_nodebox = function(water_level)
local floor_y
if water_level == 0 then -- empty
floor_y = -0.1875
elseif water_level == 1 then -- 1/3 filled
floor_y = 1/16
elseif water_level == 2 then -- 2/3 filled
floor_y = 4/16
elseif water_level == 3 then -- full
floor_y = 7/16
end
return {
type = "fixed",
fixed = {
{-0.5, -0.1875, -0.5, -0.375, 0.5, 0.5}, -- Left wall
{0.375, -0.1875, -0.5, 0.5, 0.5, 0.5}, -- Right wall
{-0.375, -0.1875, 0.375, 0.375, 0.5, 0.5}, -- Back wall
{-0.375, -0.1875, -0.5, 0.375, 0.5, -0.375}, -- Front wall
{-0.5, -0.3125, -0.5, 0.5, floor_y, 0.5}, -- Floor
{-0.5, -0.5, -0.5, -0.375, -0.3125, -0.25}, -- Left front foot, part 1
{-0.375, -0.5, -0.5, -0.25, -0.3125, -0.375}, -- Left front foot, part 2
{-0.5, -0.5, 0.25, -0.375, -0.3125, 0.5}, -- Left back foot, part 1
{-0.375, -0.5, 0.375, -0.25, -0.3125, 0.5}, -- Left back foot, part 2
{0.375, -0.5, 0.25, 0.5, -0.3125, 0.5}, -- Right back foot, part 1
{0.25, -0.5, 0.375, 0.375, -0.3125, 0.5}, -- Right back foot, part 2
{0.375, -0.5, -0.5, 0.5, -0.3125, -0.25}, -- Right front foot, part 1
{0.25, -0.5, -0.5, 0.375, -0.3125, -0.375}, -- Right front foot, part 2
}
}
end
mcl_cauldrons.cauldron_nodeboxes = {}
for w=0,3 do
mcl_cauldrons.cauldron_nodeboxes[w] = create_cauldron_nodebox(w)
end
-- Allow for writing inheritance-based definitions.
-- No metatable, as minetest.register_node() breaks/overwrites metatable.
-- Works for declarative objects as they don't change after instantiation.
function merge_tables (a, b)
local result = {}
for k, v in pairs(a) do result[k] = v end
for k, v in pairs(b) do result[k] = v end
return result
end
-- note: defined as global, but should have ended up in mod-specific environment.
------------------------
---- Empty Cauldron ----
------------------------
mcl_cauldrons.cauldron_empty = {
description = S("Cauldron"),
_tt_help = S("Stores water"),
_doc_items_longdesc = S("Cauldrons are used to store water and slowly fill up under rain."),
_doc_items_usagehelp = S("Place a water bucket into the cauldron to fill it with water. Place an empty bucket on a full cauldron to retrieve the water. Place a water bottle into the cauldron to fill the cauldron to one third with water. Place a glass bottle in a cauldron with water to retrieve one third of the water."),
wield_image = "mcl_cauldrons_cauldron.png",
inventory_image = "mcl_cauldrons_cauldron.png",
use_texture_alpha = minetest.features.use_texture_alpha_string_modes and "opaque" or false,
drawtype = "nodebox",
paramtype = "light",
is_ground_content = false,
groups = {pickaxey=1, deco_block=1, cauldron=1},
node_box = mcl_cauldrons.cauldron_nodeboxes[0],
collision_box = mcl_cauldrons.cauldron_nodeboxes[0],
selection_box = { type = "regular" },
tiles = {
"mcl_cauldrons_cauldron_inner.png^mcl_cauldrons_cauldron_top.png",
"mcl_cauldrons_cauldron_inner.png^mcl_cauldrons_cauldron_bottom.png",
"mcl_cauldrons_cauldron_side.png"
},
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_hardness = 2,
_mcl_blast_resistance = 2,
}
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron", mcl_cauldrons.cauldron_empty)
--- Water Cauldrons ---
dosubfile("cauldron_water.lua")
--- River Water Cauldrons ---
if mclx_core then
dosubfile("cauldron_river_water.lua")
end
--- other registration ---
-- crafting
minetest.register_craft({
output = "mcl_cauldrons:cauldron",
recipe = {
{ "mcl_core:iron_ingot", "", "mcl_core:iron_ingot" },
{ "mcl_core:iron_ingot", "", "mcl_core:iron_ingot" },
{ "mcl_core:iron_ingot", "mcl_core:iron_ingot", "mcl_core:iron_ingot" },
}
})
-- Active Block Modifier
--[[
minetest.register_abm({
label = "cauldrons",
nodenames = {"group:cauldron_filled"},
interval = 0.5,
chance = 1,
action = function(pos, node)
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.4)) do
if mcl_burning.is_burning(obj) then
mcl_burning.extinguish(obj)
local new_group = minetest.get_item_group(node.name, "cauldron_filled") - 1
minetest.swap_node(pos, {name = "mcl_cauldrons:cauldron" .. (new_group == 0 and "" or "_" .. new_group)})
break
end
end
end
})
--]]
-- extinguish fire for entities in cauldron
minetest.register_abm({
label = "cauldrons",
nodenames = {"group:cauldron_water", "group:cauldron_river_water"},
interval = 0.5,
chance = 1,
action = function(pos, node)
local cauldron
local EXTINGUISH_RADIUS = 0.4
for _, obj in pairs(minetest.get_objects_inside_radius(pos, EXTINGUISH_RADIUS)) do
if mcl_burning.is_burning(obj) then
if not cauldron then
-- instantiate when needed.
cauldron = mcl_cauldrons.get_cauldron(pos)
end
mcl_burning.extinguish(obj)
cauldron:drain_levels(nil, 1)
local node = cauldron:make_node()
minetest.swap_node(pos, node)
break
end
end
end
})
-- Backwards compability.
local aliases = {
-- key=old/deprecated nodename, value=current nodename
["mcl_cauldrons:cauldron_1"]="mcl_cauldrons:cauldron_water_1",
["mcl_cauldrons:cauldron_2"]="mcl_cauldrons:cauldron_water_2",
["mcl_cauldrons:cauldron_3"]="mcl_cauldrons:cauldron_water_3",
["mcl_cauldrons:cauldron_1r"]="mcl_cauldrons:cauldron_river_water_1",
["mcl_cauldrons:cauldron_2r"]="mcl_cauldrons:cauldron_river_water_2",
["mcl_cauldrons:cauldron_3r"]="mcl_cauldrons:cauldron_river_water_3",
}
for old_nodename, current_nodename in pairs(aliases) do
minetest.register_alias(old_nodename, current_nodename)
end

View File

@ -0,0 +1,57 @@
-- Node definitions to implement River Water Cauldron
-- expects mod-scope (sandboxed) global environment
------------------------------
---- River Water Cauldron ----
------------------------------
-- Consists of 3 levels: 1/3 filled, 2/3 filled, 3/3 filled
-- Redstone comparator signal per level.
-- river water texture
-- yields empty cauldron item when mined (contents are lost).
mcl_cauldrons.cauldron_river_water_1 = merge_tables(mcl_cauldrons.cauldron_empty,{
description = S("Cauldron (1/3 River Water)"),
_doc_items_create_entry = false,
groups = {
pickaxey = 1,
not_in_creative_inventory = 1,
cauldron = 1,
cauldron_filled = 1,
comparator_signal = 1,
cauldron_river_water = 1,
},
node_box = mcl_cauldrons.cauldron_nodeboxes[1],
tiles = {
"(default_river_water_source_animated.png^[verticalframe:16:0)^mcl_cauldrons_cauldron_top.png",
"mcl_cauldrons_cauldron_inner.png^mcl_cauldrons_cauldron_bottom.png",
"mcl_cauldrons_cauldron_side.png"
},
drop = "mcl_cauldrons:cauldron",
_cauldron_content = "river_water",
})
-- river water cauldron, 2/3 filled
mcl_cauldrons.cauldron_river_water_2 = merge_tables(mcl_cauldrons.cauldron_river_water_1, {
description = S("Cauldron (2/3 River Water)"),
groups = merge_tables(mcl_cauldrons.cauldron_river_water_1.groups, {
cauldron_filled = 2,
comparator_signal = 2,
}),
node_box = mcl_cauldrons.cauldron_nodeboxes[2],
})
-- river water cauldron, 3/3 filled (full).
mcl_cauldrons.cauldron_river_water_3 = merge_tables(mcl_cauldrons.cauldron_river_water_1, {
description = S("Cauldron (3/3 River Water)"),
groups = merge_tables(mcl_cauldrons.cauldron_river_water_1.groups, {
cauldron_filled = 3,
comparator_signal = 3,
}),
node_box = mcl_cauldrons.cauldron_nodeboxes[3],
})
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_1", mcl_cauldrons.cauldron_river_water_1, "mcl_cauldrons:cauldron")
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_2", mcl_cauldrons.cauldron_river_water_2, "mcl_cauldrons:cauldron")
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_3", mcl_cauldrons.cauldron_river_water_3, "mcl_cauldrons:cauldron")

View File

@ -0,0 +1,62 @@
-- Node definitions to implement Water Cauldron
-- expects mod-scope (sandboxed) global environment
------------------------
---- Water Cauldron ----
------------------------
-- Consists of 3 levels: 1/3 filled, 2/3 filled, 3/3 filled
-- Redstone comparator signal per level.
-- water texture
-- yields empty cauldron item when mined (contents are lost).
mcl_cauldrons.cauldron_water_1 = merge_tables(mcl_cauldrons.cauldron_empty, {
description = S("Cauldron (1/3 Water)"),
_doc_items_create_entry = false,
groups = {
pickaxey = 1,
not_in_creative_inventory = 1,
cauldron = 1,
cauldron_filled = 1,
cauldron_water = 1,
comparator_signal = 1
},
node_box = mcl_cauldrons.cauldron_nodeboxes[1],
tiles = {
"(default_water_source_animated.png^[verticalframe:16:0)^mcl_cauldrons_cauldron_top.png",
"mcl_cauldrons_cauldron_inner.png^mcl_cauldrons_cauldron_bottom.png",
"mcl_cauldrons_cauldron_side.png"
},
drop = "mcl_cauldrons:cauldron",
_cauldron_content = "water",
})
-- TODO: Node definitions specify both groups.cauldron_<SUBSTANCE> and _cauldron_content=SUBSTANCE; feels almost redundant, maybe pick one?
-- 1. using only groups.cauldron_<SUBSTANCE> may require lookup tables for all valid group keys to determine the substance they represent.
-- 2. using only _cauldron_content would require constantly accessing the node definition table (minetest.registered_nodes) to determine cauldron content.
-- water cauldron, fill level 2 (of 3); extends cauldron_water_1
mcl_cauldrons.cauldron_water_2 = merge_tables(mcl_cauldrons.cauldron_water_1, {
description = S("Cauldron (2/3 Water)"),
groups = merge_tables(mcl_cauldrons.cauldron_water_1.groups, {
cauldron_filled = 2,
comparator_signal = 2,
}),
node_box = mcl_cauldrons.cauldron_nodeboxes[2],
})
-- water cauldron, fill level 3 (of 3)/full, extends cauldron_water_1
mcl_cauldrons.cauldron_water_3 = merge_tables(mcl_cauldrons.cauldron_water_1, {
description = S("Cauldron (3/3 Water)"),
groups = merge_tables(mcl_cauldrons.cauldron_water_1.groups, {
cauldron_filled = 3,
comparator_signal = 3,
}),
node_box = mcl_cauldrons.cauldron_nodeboxes[3],
})
-- keep registration code fully expanded to help with search and code tools.
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_1", mcl_cauldrons.cauldron_water_1, "mcl_cauldrons:cauldron")
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_2", mcl_cauldrons.cauldron_water_2, "mcl_cauldrons:cauldron")
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_3", mcl_cauldrons.cauldron_water_3, "mcl_cauldrons:cauldron")

View File

@ -1,9 +1,72 @@
local S = minetest.get_translator("mcl_cauldron")
local modpath = minetest.get_modpath(minetest.get_current_modname())
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local mclx_core = minetest.get_modpath("mclx_core")
local has_doc = minetest.get_modpath("doc")
mcl_cauldrons = {}
-- Cauldron mod, adds cauldrons.
mcl_cauldrons = {}
-- set up a mod-scope environment.
local _MODENV = {
S = S,
modname = modname,
modpath = modpath,
mclx_core = mclx_core,
has_doc = has_doc,
}
-- chain to global.
_MODENV._MODENV = _MODENV
setmetatable(_MODENV, { __index = _G })
--- execute subcomponents in context of this mod-scope environment.
--- (anything defined global-style stays in this containment environment, without polluting the "true" global environment; that said, true-global still accessible explicitly through _G._G)
--- N.B. "mcl_cauldrons" has already been defined (though empty) in
--- the "true-global" environment, so modifying keys in mcl_cauldrons
--- still affect the "true-global" mcl_cauldrons.
if setfenv then
-- Lua 5.1, environment with setfenv().
setfenv(1, _MODENV)
function dosubfile (submodpath)
local submodule, err
submodule, err = loadfile(modpath .. "/" .. submodpath)
if err then
print(err)
print(debug.traceback())
-- error(err)
error()
end
-- submodule = loadfile(modpath .. "/" .. submodpath)
setfenv(submodule, _MODENV)
submodule()
end
else
-- Lua post-5.1, environment as argument.
_ENV = _MODENV
function dosubfile (submodpath)
return dofile(modpath .. "/" .. submodpath, "bt", _MODENV)
end
end
--[[
-- utilities and preset values used by API.
dofile(modpath.."/utils.lua")
-- API definition.
dofile(modpath.."/api.lua")
-- standard set of cauldrons.
dofile(modpath.."/cauldrons.lua")
-- more registration stuff.
dofile(modpath.."/register.lua")
--]]
dosubfile("utils.lua")
dosubfile("api.lua")
dosubfile("cauldron.lua")
-- _ENV automatically goes out of scope at the end of this chunk/file.

View File

@ -1,5 +1,8 @@
local S = minetest.get_translator(minetest.get_current_modname())
-- All registration activities
--[[
mcl_cauldrons.register_cauldron_type({
name = "water",
bucket = "mcl_buckets:bucket_water",
@ -7,11 +10,20 @@ mcl_cauldrons.register_cauldron_type({
desc = S("Cauldron (%s/3 Water)"),
texture = "default_water_source_animated.png"
})
--]]
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron", mcl_cauldrons.cauldron_empty)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_1", mcl_cauldrons.cauldron_water_1)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_2", mcl_cauldrons.cauldron_water_2)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_3", mcl_cauldrons.cauldron_water_3)
if minetest.get_modpath("mclx_core") then
--register_filled_cauldron(1, S("Cauldron (1/3 River Water)"), true)
--register_filled_cauldron(2, S("Cauldron (2/3 River Water)"), true)
--register_filled_cauldron(3, S("Cauldron (3/3 River Water)"), true)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_1", mcl_cauldrons.cauldron_river_water_1)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_2", mcl_cauldrons.cauldron_river_water_2)
mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_3", mcl_cauldrons.cauldron_river_water_3)
end
minetest.register_craft({
@ -40,9 +52,26 @@ minetest.register_abm({
end
})
--[[
for i = 1, 3 do --Backward compatibility
minetest.register_alias("mcl_cauldrons:cauldron_"..i, "mcl_cauldrons:cauldron_water_"..i)
end
for i = 1, 3 do
minetest.register_alias("mcl_cauldrons:cauldron_"..i.."r", "mcl_cauldrons:cauldron_river_water_"..i)
end
end
--]]
-- Backwards compatibility.
local aliases = {
-- key=old/deprecated nodename, value=current nodename
["mcl_cauldrons:cauldron_1"]="mcl_cauldrons:cauldron_water_1",
["mcl_cauldrons:cauldron_2"]="mcl_cauldrons:cauldron_water_2",
["mcl_cauldrons:cauldron_3"]="mcl_cauldrons:cauldron_water_3",
["mcl_cauldrons:cauldron_1r"]="mcl_cauldrons:cauldron_river_water_1",
["mcl_cauldrons:cauldron_2r"]="mcl_cauldrons:cauldron_river_water_2",
["mcl_cauldrons:cauldron_3r"]="mcl_cauldrons:cauldron_river_water_3",
}
for old_nodename, current_nodename in pairs(aliases) do
minetest.register_alias(old_nodename, current_nodename)
end

View File

@ -1,5 +1,8 @@
local S = minetest.get_translator(minetest.get_current_modname())
-- utility functions and preset values used by mcl_cauldron definitions.
--[[
-- Convenience function because the cauldron nodeboxes are very similar
local create_cauldron_nodebox = function(water_level)
local floor_y
@ -36,7 +39,9 @@ mcl_cauldrons.cauldron_nodeboxes = {}
for w=0,3 do
mcl_cauldrons.cauldron_nodeboxes[w] = create_cauldron_nodebox(w)
end
--]]
--[[
-- Empty cauldron
minetest.register_node("mcl_cauldrons:cauldron", {
description = S("Cauldron"),
@ -60,4 +65,5 @@ minetest.register_node("mcl_cauldrons:cauldron", {
sounds = mcl_sounds.node_sound_metal_defaults(),
_mcl_hardness = 2,
_mcl_blast_resistance = 2,
})
})
--]]

View File

@ -40,6 +40,80 @@ minetest.register_craft({
recipe = { "mcl_mushrooms:mushroom_brown", "mcl_core:sugar", "mcl_mobitems:spider_eye" },
})
local function take_from_cauldron (cauldron, itemstack, placer)
local sounds = mcl_sounds.node_sound_metal_defaults() -- TODO: should come from declarative object
-- check cauldron limits.
local new_bottlename
if cauldron:get_level("water") then
-- has water, try to take water.
if cauldron:drain_levels("water", cauldron.BOTTLEFUL) then
cauldron:update_node()
minetest.sound_play("mcl_potions_bottle_fill", {pos=cauldron.pos, gain=0.5, max_hear_range=16}, true)
new_bottlename = "mcl_potions:water"
end
end
if new_bottlename then
-- byproduct of draining cauldron.
local new_bottle = ItemStack({name=new_bottlename})
local inv = placer:get_inventory()
if minetest.is_creative_enabled(placer:get_player_name()) then
-- creative mode: get for free
if not inv:contains_item("main", new_bottle) then
inv:add_item("main", new_bottle)
end
else
-- survival mode
if inv:room_for_item("main", new_bottle) then
itemstack:take_item()
inv:add_item("main", new_bottle)
else
minetest.add_item(placer:get_pos(), new_bottle)
end
end
return true
end
end
local function give_to_cauldron (cauldron, itemstack, placer, substance)
if substance == nil then
return false
end
local sounds = mcl_sounds.node_sound_metal_defaults() -- TODO: should come from declarative object
-- check cauldron limits.
local new_bottlename
if cauldron:get_level(substance) <= (cauldron:get_capacity(substance) - cauldron.BOTTLEFUL) then
-- sufficiently low level, add.
-- overwrites old content (old contents are lost).
if cauldron:fill_levels(substance, cauldron.BOTTLEFUL) then
cauldron:update_node()
minetest.sound_play("mcl_potions_bottle_pour", {pos=cauldron.pos, gain=0.5, max_hear_range=16}, true)
new_bottlename = "mcl_potions:glass_bottle"
end
end
if new_bottlename then
-- byproduct of filling cauldron.
local new_bottle = ItemStack({name=new_bottlename})
local inv = placer:get_inventory()
if minetest.is_creative_enabled(placer:get_player_name()) then
-- creative mode: get for free
if not inv:contains_item("main", new_bottle) then
inv:add_item("main", new_bottle)
end
else
-- survival mode
if inv:room_for_item("main", new_bottle) then
itemstack:take_item()
inv:add_item("main", new_bottle)
else
minetest.add_item(placer:get_pos(), new_bottle)
end
end
return true
end
end
minetest.register_craftitem("mcl_potions:glass_bottle", {
description = S("Glass Bottle"),
_tt_help = S("Liquid container"),
@ -65,12 +139,18 @@ minetest.register_craftitem("mcl_potions:glass_bottle", {
local get_water = false
--local from_liquid_source = false
local river_water = false
local from_cauldron = mcl_cauldrons.get_cauldron(pointed_thing.under)
if from_cauldron and from_cauldron:is_empty() then
-- cannot take from empty cauldron.
from_cauldron = nil
end
if def and def.groups and def.groups.water and def.liquidtype == "source" then
-- Water source
get_water = true
--from_liquid_source = true
river_water = node.name == "mclx_core:river_water_source"
-- Or reduce water level of cauldron by 1
--[[
elseif mcl_cauldrons.is_cauldron(node.name) then
local pname = placer:get_player_name()
if minetest.is_protected(pointed_thing.under, pname) then
@ -78,6 +158,18 @@ minetest.register_craftitem("mcl_potions:glass_bottle", {
return itemstack
end
mcl_cauldrons.take_small_cauldron(pointed_thing.under, itemstack, placer, {dug = "mcl_potions_bottle_fill"})
--]]
elseif from_cauldron then
if from_cauldron:get_level("water") then
-- take bottle of water.
local pname = placer:get_player_name()
if minetest.is_protected(pointed_thing.under, pname) then
minetest.record_protection_violation(pointed_thing.under, pname)
return itemstack
end
take_from_cauldron(from_cauldron, itemstack, placer)
end
end
end
return itemstack
@ -125,6 +217,7 @@ minetest.register_craftitem("mcl_potions:water", {
end
end
--[[
local cauldron = fill_cauldron(node.name, "mcl_core:water_source")
if cauldron then
local pname = placer:get_player_name()
@ -141,6 +234,18 @@ minetest.register_craftitem("mcl_potions:water", {
return "mcl_potions:glass_bottle"
end
end
--]]
local to_cauldron = mcl_cauldrons.get_cauldron(pointed_thing.under)
-- apply water bottle to empty or water cauldron
-- anything else, replace content.
if to_cauldron then
local pname = placer:get_player_name()
if minetest.is_protected(pointed_thing.under, pname) then
minetest.record_protection_violation(pointed_thing.under, pname)
return itemstack
end
give_to_cauldron(to_cauldron, itemstack, placer, "water")
end
end
-- Drink the water by default
@ -385,4 +490,5 @@ mcl_wip.register_wip_item("mcl_potions:night_vision_plus_splash")
mcl_wip.register_wip_item("mcl_potions:night_vision_lingering")
mcl_wip.register_wip_item("mcl_potions:night_vision_plus_lingering")
mcl_wip.register_wip_item("mcl_potions:night_vision_arrow")
mcl_wip.register_wip_item("mcl_potions:night_vision_plus_arrow")
mcl_wip.register_wip_item("mcl_potions:night_vision_plus_arrow")