Fix river water bucket interaction.

Added explicits tests for river water bucket.
Minor updates to code comments.
This commit is contained in:
Phaethon H 2021-11-10 12:36:10 -08:00
parent c43223ad59
commit 4e6f06619b
3 changed files with 105 additions and 31 deletions

View File

@ -61,14 +61,15 @@ end
MODENV.merge_tables = merge_tables MODENV.merge_tables = merge_tables
---- --------
--------
-- Item Proxies -- Item Proxies
-- --
-- Interaction with items are done through a proxy that presume certain -- Interaction with items are done through a proxy that presume certain
-- behaviors of actual/registered items. -- behaviors of actual/registered items.
-- The idea is to avoid invoking mod-specific functions by name -- The idea is to avoid invoking mod-specific functions by name
-- (relying on callbacks from node definition), as mods may be extended, -- (therefore relying on callbacks from node definition),
-- replaced, or disabled. -- as mods may be extend, replace, or disable items.
-- --
-- Initial item proxies include: -- Initial item proxies include:
-- BucketProxy - interacting with bucket-like items (change fill level by 3) -- BucketProxy - interacting with bucket-like items (change fill level by 3)
@ -76,6 +77,14 @@ MODENV.merge_tables = merge_tables
-- BannerProxy - banner-like items (wash banner, deplete 1 level) -- BannerProxy - banner-like items (wash banner, deplete 1 level)
-- Items using rule-based FSM transition for interacting with cauldron. -- Items using rule-based FSM transition for interacting with cauldron.
-- State-transition rules to specify transitions.
-- State vector is tuple(substance:string, fill_level:number)
-- Item is either item name string, or function(string,table) predicate.
-- Input is Item
-- Next State is tuple(next_substance:string, next_level:number)
-- Output is another Item
-- SoundSpecifier is soundspec string or function(itemstack,node) callback.
mcl_cauldrons.RuleBasedProxyItem = { mcl_cauldrons.RuleBasedProxyItem = {
-- prototype derivation to keep separate .rules -- prototype derivation to keep separate .rules
__call = function(self, ...) __call = function(self, ...)
@ -134,14 +143,6 @@ mcl_cauldrons.RuleBasedProxyItem = {
setmetatable(mcl_cauldrons.RuleBasedProxyItem, mcl_cauldrons.RuleBasedProxyItem) setmetatable(mcl_cauldrons.RuleBasedProxyItem, mcl_cauldrons.RuleBasedProxyItem)
-- Item/Cauldron interactions based on Finite State Machine,
-- rules to specify transitions.
-- State vector is tuple(substance:string, fill_level:number)
-- Item is either item name, or function(string,table) predicate.
-- Input is Item
-- Next State is tuple(next_substance:string, next_level:number)
-- Output is another Item
-- add an item-exchange rule following the basic cauldron fill/empty and bucket fill/empty pattern: -- add an item-exchange rule following the basic cauldron fill/empty and bucket fill/empty pattern:
-- * cauldron consistes of a particular substance and specific level. -- * cauldron consistes of a particular substance and specific level.
-- * cauldron turns into one of a particular substance and level. -- * cauldron turns into one of a particular substance and level.
@ -151,15 +152,15 @@ setmetatable(mcl_cauldrons.RuleBasedProxyItem, mcl_cauldrons.RuleBasedProxyItem)
-- match_level: cauldron fill level matching this rule (nil means any, true maps to full) -- match_level: cauldron fill level matching this rule (nil means any, true maps to full)
-- input_constraint: -- input_constraint:
-- string => exact match with item name to trigger rule. -- string => exact match with item name to trigger rule.
-- function(string) => predicate given item name, to trigger rule. -- function(ItemStack) => predicate given item stack, to trigger rule.
-- next_substance: new cauldron content (nil means empty) -- next_substance: new cauldron content (nil means empty)
-- next_level: new cauldron fill level (true means full, 0 means empty) -- next_level: new cauldron fill level (true means full, 0 means empty)
-- output_specifer: -- output_specifer:
-- string => specific item name to generate after successful rule match -- string => specific item name to generate after successful rule match
-- function(string, table) => given old item name and new cauldron node, generate new item name on rule match -- function(ItemStack, table) => given old item stack and new cauldron node, generate new item stack on rule match
-- sound_specifier: -- sound_specifier:
-- string => specific item name to play 'place' sound on match rule. -- string => specific item name to play 'place' sound on match rule.
-- function(string, table) => given old item name and new cauldron node, return (soundspec, soundparam) -- function(ItemStack, table) => given ItemStack and new cauldron node, return (soundspec, soundparam)
function mcl_cauldrons.RuleBasedProxyItem:add_basic_rule(match_substance, match_level, input_constraint, next_substance, next_level, output_specifier, sound_specifier) function mcl_cauldrons.RuleBasedProxyItem:add_basic_rule(match_substance, match_level, input_constraint, next_substance, next_level, output_specifier, sound_specifier)
local rule = { local rule = {
@ -221,7 +222,6 @@ function mcl_cauldrons.RuleBasedProxyItem:apply_basic_rules(itemstack, cauldron_
-- `true` as alias for maximum. -- `true` as alias for maximum.
next_level = mcl_cauldrons.BUCKET_FILL_LEVELS next_level = mcl_cauldrons.BUCKET_FILL_LEVELS
end end
--local newnode = mcl_cauldrons.normalize_cauldron(match_rule.next_substance, next_level)
local newnode = mcl_cauldrons.node_set_level(cauldron_node, next_level, match_rule.next_substance) local newnode = mcl_cauldrons.node_set_level(cauldron_node, next_level, match_rule.next_substance)
local newitemstack local newitemstack
if type(match_rule.output_specifier) == "string" then if type(match_rule.output_specifier) == "string" then
@ -267,7 +267,8 @@ end
--- -------
-------
-- Bucket(-like) interactions with cauldron. -- Bucket(-like) interactions with cauldron.
-- * empty bucket: requires full cauldron, produce something bucket, cauldron becomes empty. -- * empty bucket: requires full cauldron, produce something bucket, cauldron becomes empty.
-- * something bucket: requires empty cauldron, produce empty bucket, cauldron becomes full. -- * something bucket: requires empty cauldron, produce empty bucket, cauldron becomes full.
@ -323,8 +324,8 @@ BucketProxy:add_basic_rule("water", nil, BucketProxy.is_water, "water", true, "
BucketProxy:add_basic_rule(nil, 0, BucketProxy.is_water, "water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water) BucketProxy:add_basic_rule(nil, 0, BucketProxy.is_water, "water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water)
--- -------
-------
-- Bottle(-like) interaction with Cauldron -- Bottle(-like) interaction with Cauldron
-- * empty bottle: requires non-empty cauldron, generate something bottle, reduce cauldron level by 1 -- * empty bottle: requires non-empty cauldron, generate something bottle, reduce cauldron level by 1
-- * something bottle: empty cauldron or same-liquid cauldron, generate empty bottle, increase cauldron level by 1 -- * something bottle: empty cauldron or same-liquid cauldron, generate empty bottle, increase cauldron level by 1
@ -371,7 +372,8 @@ BottleProxy:add_basic_rule("water", 1, BottleProxy.is_water, "water", 2, BottleP
BottleProxy:add_basic_rule("water", 2, BottleProxy.is_water, "water", 3, BottleProxy.make_empty, BottleProxy.sound_water) BottleProxy:add_basic_rule("water", 2, BottleProxy.is_water, "water", 3, BottleProxy.make_empty, BottleProxy.sound_water)
--- -------
-------
-- Banner(-like) interaction with Cauldron. -- Banner(-like) interaction with Cauldron.
local BannerProxy = mcl_cauldrons.RuleBasedProxyItem() local BannerProxy = mcl_cauldrons.RuleBasedProxyItem()
@ -386,6 +388,7 @@ function BannerProxy.is_banner(itemstack)
-- other tests. -- other tests.
end end
-- In the case of banners, the resulting itemstack is the old itemstack with metadata modification (handled by on_wash()).
function BannerProxy.wash_banner(itemstack, cauldron_node) function BannerProxy.wash_banner(itemstack, cauldron_node)
local nodedef = itemstack:get_definition() local nodedef = itemstack:get_definition()
local washed_itemstack local washed_itemstack
@ -477,10 +480,8 @@ function mcl_cauldrons.Cauldron:node_drain_levels(node, change_levels, substance
end end
-- carry out all the duties of on_rightclick(). -- carry out all the duties of on_rightclick().
-- returns itemstack for item in hand (may be same `itemstack`).
function mcl_cauldrons.Cauldron:apply_item(pos, node, user, itemstack) function mcl_cauldrons.Cauldron:apply_item(pos, node, user, itemstack)
-- list of actions to carry out as a result of applying item.
-- allows for rollback/cancelling in case of errors or exceptions.
local plan = {}
for _, item_handler in ipairs(self.registered_proxies) do for _, item_handler in ipairs(self.registered_proxies) do
local handled, retval = item_handler:do_apply(pos, node, user, itemstack) local handled, retval = item_handler:do_apply(pos, node, user, itemstack)
if handled then return retval end if handled then return retval end
@ -518,9 +519,7 @@ mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron", {
_mcl_hardness = 2, _mcl_hardness = 2,
_mcl_blast_resistance = 2, _mcl_blast_resistance = 2,
-- delegated by mcl_buckets and mcl_potions -- delegated to by mcl_buckets and mcl_potions.
-- returns true to indicate event is finished (no further processing)
-- returns false if event not handled (expect caller to fallthrough to non-cauldron behavior).
on_rightclick = function(place_pos, node, user, itemstack) on_rightclick = function(place_pos, node, user, itemstack)
return mcl_cauldrons.Cauldron:apply_item(place_pos, node, user, itemstack) return mcl_cauldrons.Cauldron:apply_item(place_pos, node, user, itemstack)
end, end,
@ -567,10 +566,12 @@ mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_water_3",
}), "mcl_cauldrons:cauldron") }), "mcl_cauldrons:cauldron")
-- River Water Cauldron
if minetest.get_modpath("mclx_core") then if minetest.get_modpath("mclx_core") then
dosubfile("x_river_water.lua") dosubfile("x_river_water.lua")
end end
-- Lava Cauldron
if minetest.get_modpath("mclx_core") then if minetest.get_modpath("mclx_core") then
dosubfile("x_lava.lua") dosubfile("x_lava.lua")
end end

View File

@ -467,6 +467,78 @@ describe("MineClone2 cauldrons test", function()
end) end)
end) end)
describe("apply river water bucket to cauldron #riverwaterbucket", function()
it(", implementation details", function()
assert.is_not_nil(minetest.registered_nodes["mcl_cauldrons:cauldron_river_water_1"])
assert.is_not_nil(minetest.registered_nodes["mcl_cauldrons:cauldron_river_water_2"])
assert.is_not_nil(minetest.registered_nodes["mcl_cauldrons:cauldron_river_water_3"])
----
-- test the implementation details (gets messy).
local itemstack0 = ItemStack(s_bucket1r)
-- river water bucket + empty cauldrons = full river water cauldron, empty bucket
local pos = CAULDRON_POS
local node = { name=s_cauldron0 }
local newnode, newitemstack = mcl_cauldrons.BucketProxy:apply_basic_rules(itemstack0, node)
assert.is_not_nil(newnode)
assert.is_not_nil(newitemstack)
assert.equals(s_cauldron3r, newnode.name)
assert.equals(s_bucket0, newitemstack:get_name())
-- river water bucket + 1/3 cauldron = full river water cauldron, empty bucket
node = { name=s_cauldron1r }
local newnode, newitemstack = mcl_cauldrons.BucketProxy:apply_basic_rules(itemstack0, node)
assert.equals(s_cauldron3r, newnode.name)
assert.equals(s_bucket0, newitemstack:get_name())
-- river water bucket + 2/3 cauldron = full river water cauldron, empty bucket
node = { name=s_cauldron2r }
local newnode, newitemstack = mcl_cauldrons.BucketProxy:apply_basic_rules(itemstack0, node)
assert.equals(s_cauldron3r, newnode.name)
assert.equals(s_bucket0, newitemstack:get_name())
-- river water bucket + full cauldron = full river water cauldron, empty bucket (wastes the river water bucket)
node = { name=s_cauldron3r }
local newnode, newitemstack = mcl_cauldrons.BucketProxy:apply_basic_rules(itemstack0, node)
assert.equals(s_cauldron3r, newnode.name)
assert.equals(s_bucket0, newitemstack:get_name())
end)
----
-- mimick world activity (validate for gameplay)
it(", mimick world activity", function()
-- apply river water bucket to full cauldron -> cauldron_water_3, empty bucket (bucket wasted)
force_cauldron(s_cauldron3r)
force_inv(s_bucket1r)
rclick_cauldron()
assert_cauldron(s_cauldron3r)
assert_inv(s_bucket0)
-- apply river water bucket to 2/3 cauldron -> cauldron_water_3, empty bucket
force_cauldron(s_cauldron2r)
force_inv(s_bucket1r)
rclick_cauldron()
assert_cauldron(s_cauldron3r)
assert_inv(s_bucket0)
-- apply river water bucket to 1/3 cauldron -> cauldron_water_3, empty bucket
force_cauldron(s_cauldron1r)
force_inv(s_bucket1r)
rclick_cauldron()
assert_cauldron(s_cauldron3r)
assert_inv(s_bucket0)
-- apply river water bucket to empty cauldron -> cauldron_water_3, empty bucket
force_cauldron(s_cauldron0)
force_inv(s_bucket1r)
rclick_cauldron()
assert_cauldron(s_cauldron3r)
assert_inv(s_bucket0)
end)
end)
describe("apply lava bucket to cauldron #lavabucket", function() describe("apply lava bucket to cauldron #lavabucket", function()
it(", implementation details", function() it(", implementation details", function()
assert.is_not_nil(minetest.registered_nodes["mcl_cauldrons:cauldron_lava_1"]) assert.is_not_nil(minetest.registered_nodes["mcl_cauldrons:cauldron_lava_1"])

View File

@ -41,21 +41,22 @@ mcl_cauldrons.register_cauldron_node("mcl_cauldrons:cauldron_river_water_3",
local BucketProxy = mcl_cauldrons.BucketProxy local BucketProxy = mcl_cauldrons.BucketProxy
function BucketProxy.is_river_water(bucket_name, bucket_def) function BucketProxy.is_river_water(itemstack)
if (bucket_name == "mcl_buckets:bucket_river_water") then return true end if (itemstack:get_name() == "mcl_buckets:bucket_river_water") then return true end
-- other tests. -- other tests.
end end
function BucketProxy.make_river_water(bucket_name, cauldron_node)
function BucketProxy.make_river_water(itemstack, cauldron_node)
for scan_name, scan_def in pairs(minetest.registered_items) do for scan_name, scan_def in pairs(minetest.registered_items) do
-- TODO: expand mcl_buckets node definitions. -- TODO: expand mcl_buckets node definitions.
end end
end end
-- empty bucket + full river water cauldron -> empty cauldron, river water bucket -- full river water cauldron + empty bucket -> empty cauldron, river water bucket
BucketProxy:add_basic_rule("river_water", 3, BucketProxy.is_empty, nil, 0, "mcl_buckets:bucket_river_water", BucketProxy.sound_water) BucketProxy:add_basic_rule("river_water", 3, BucketProxy.is_empty, nil, 0, "mcl_buckets:bucket_river_water", BucketProxy.sound_water)
-- river water bucket + any river water cauldron -> full river water cauldron, empty bucket (may waste bucket) -- any river water cauldron + river water bucket -> full river water cauldron, empty bucket (may waste bucket)
BucketProxy:add_basic_rule("river_water", nil, BucketProxy.is_river_water, "river_water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water) BucketProxy:add_basic_rule("river_water", nil, BucketProxy.is_river_water, "river_water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water)
-- river water bucket + empty cauldron -> full river water cauldron, empty bucket -- empty cauldron + river water bucket -> full river water cauldron, empty bucket
BucketProxy:add_basic_rule(nil, 0, BucketProxy.is_river_water, "river_water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water) BucketProxy:add_basic_rule(nil, 0, BucketProxy.is_river_water, "river_water", true, "mcl_buckets:bucket_empty", BucketProxy.sound_water)