chemistry/lab.lua

363 lines
11 KiB
Lua

local formspec =
"size[8,8.5]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"list[current_name;reactant;0,0;2,3;]"..
"list[current_name;catalyst;3.5,0;1,1;]"..
"list[current_name;tool;3.5,2;1,1;]"..
"list[current_name;product;6,0;2,3;]"..
"list[current_player;main;0,4.25;8,1;]"..
"list[current_player;main;0,5.5;8,3;8]"..
"listring[current_name;reactant]"..
"listring[current_player;main]"..
"listring[current_name;catalyst]"..
"listring[current_player;main]"..
"listring[current_name;tool]"..
"listring[current_player;main]"..
"listring[current_name;product]"..
"listring[current_player;main]"..
default.get_hotbar_bg(0, 4.25)
EPSILON = 1e-7
reactions = {}
--[[
This is how a formula looks like:
{
tool: (itemstring of tool needed for the reaction, or nil)
catalyst: (--"--)
reactants: {
(ItemStack's of up to six reactants, indexes more than six are silently ignored; no duplicate itemstrings please)
}
possible_outputs: {
{
probability: (...)
products: {
(ItemStack's of up to six products)
}
}
}
sound: (description of a sound to be played, or nil)
}
--]]
function register_reaction(formula)
local err = false
-- Basic checks of formula
if formula.reactants == nil then
minetest.log("error", "No reactants supplied to register_reaction() (nil)")
err = true
end
if #formula.reactants == 0 then
minetest.log("error", "No reactants supplied to register_reaction() (empty)")
err = true
end
local reactant_count = 0
for i=1, 6 do
if not (formula.reactants[i] == nil) then
reactant_count = reactant_count + 1
end
end
if not (#formula.reactants == reactant_count) then
minetest.log("error", "Extra reactants supplied to register_reaction() ")
err = true
end
if formula.possible_outputs == nil then
minetest.log("error", "No possible outputs supplied to register_reaction() (nil)")
err = true
end
if #formula.possible_outputs == 0 then
minetest.log("error", "No possible outputs supplied to register_reaction() (empty)")
err = true
end
local total_probability = 0.0
for i=1, #formula.possible_outputs do
local output = formula.possible_outputs[i]
if output.probability <= 0 or output.probability > 1 then
minetest.log("error", "Invalid probability for output supplied to register_reaction()")
err = true
end
total_probability = total_probability + output.probability
end
if math.abs(total_probability - 1.0) > EPSILON then
minetest.log("error", "Probabilities which do not sum to one supplied to register_reaction()")
err = true
end
if not err then
reactions[#reactions + 1] = formula
end
end
local function can_dig(pos, player)
local meta = minetest.get_meta(pos);
local inv = meta:get_inventory()
return inv:is_empty("reactant") and inv:is_empty("catalyst") and inv:is_empty("tool") and inv:is_empty("product")
end
local function allow_metadata_inventory_put(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if listname == "catalyst" then
return 1 -- There can be only one catalyst
elseif listname == "tool" then
return 1 -- There can be only one tool
elseif listname == "reactant" then
return stack:get_count()
elseif listname == "product" then
return 0
end
end
local function allow_metadata_inventory_move(pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack(from_list, from_index)
return allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
end
local function allow_metadata_inventory_take(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end
local function reaction_possible(pos, products)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
-- Actually not quite, this checks whether the reaction would be possible if we needed an empty stack for every product
-- TODO do this properly
local needed_stacks = #products
local free_stacks = 0
for i=1, 6 do
local prod = inv:get_stack("product", i)
if prod:is_empty() then
free_stacks = free_stacks + 1
end
end
return free_stacks >= needed_stacks
end
local function get_node_group(name, group)
if not minetest.registered_nodes[name] or not minetest.registered_nodes[name].groups[group] then
return 0
end
return minetest.registered_nodes[name].groups[group]
end
local function bunsen_sound(pos)
minetest.sound_play("chemistry_bunsen", {
pos = pos,
max_hear_distance = 16,
gain = 1.0,
})
end
local function explosion_sound(pos)
minetest.sound_play("chemistry_explosion", {
pos = pos,
max_hear_distance = 64,
gain = 1.0,
})
end
local function mortar_sound(pos)
minetest.sound_play("chemistry_mortar", {
pos = pos,
max_hear_distance = 8,
gain = 1.0,
})
end
local function pouring_sound(pos)
minetest.sound_play("chemistry_pouring", {
pos = pos,
max_hear_distance = 8,
gain = 1.0,
})
end
local function stirring_sound(pos)
minetest.sound_play("chemistry_stirring", {
pos = pos,
max_hear_distance = 8,
gain = 1.0,
})
end
local function check_item_match(item, stack)
if item == nil then
return stack:is_empty()
else
return stack:get_name() == item
end
end
local function check_name_match(actual, needed)
if needed:sub(1, 6) == "group:" then
return get_node_group(actual, needed:sub(7)) ~= 0
else
return actual == needed
end
end
local function lab_node_timer(pos, elapsed)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local result = false
local catalyst = inv:get_stack("catalyst", 1)
local tool = inv:get_stack("tool", 1)
local r = {}
r[1] = inv:get_stack("reactant", 1)
r[2] = inv:get_stack("reactant", 2)
r[3] = inv:get_stack("reactant", 3)
r[4] = inv:get_stack("reactant", 4)
r[5] = inv:get_stack("reactant", 5)
r[6] = inv:get_stack("reactant", 6)
local reaction = nil
for i=1, #reactions do
local formula = reactions[i]
local needed_tool = formula.tool
local needed_catalyst = formula.catalyst
local needed_reactants = formula.reactants
local reaction_works = true
for j=1, 6 do
local needed_reactant = needed_reactants[j]
local ok = false
if needed_reactant == nil then
ok = true
else
for k=1, 6 do
local supplied_reactant = r[k]
if check_name_match(supplied_reactant:get_name(), needed_reactant:get_name()) and supplied_reactant:get_count() >= needed_reactant:get_count() then
ok = true
end
end
end
if not ok then
reaction_works = false
end
end
local reactant_count = 0
for k = 1, 6 do
local supplied_reactant = r[k]
if not r[k]:is_empty() then
reactant_count = reactant_count + 1
end
end
if reactant_count ~= #needed_reactants then
reaction_works = false
end
if not check_item_match(needed_tool, tool) then
reaction_works = false
end
if not check_item_match(needed_catalyst, catalyst) then
reaction_works = false
end
if reaction_works then
reaction = formula
end
end
if not (reaction == nil) then
local rand = math.random()
local total_probability = 0.0
local selected_output = nil
while selected_output == nil do
for i=1, #reaction.possible_outputs do
local output = reaction.possible_outputs[i]
total_probability = total_probability + output.probability
if total_probability > rand then
selected_output = output
break
end
end
if selected_output == nil then
minetest.log("info", "Hit epsilon, retry")
end
end
local products = selected_output.products
if reaction_possible(pos, products) then
local reactants = reaction.reactants
for j=1, 6 do
local reactant = reactants[j]
if not (reactant == nil) then
for k=1, 6 do
if check_name_match(r[k]:get_name(), reactant:get_name()) then
r[k]:set_count(r[k]:get_count() - reactant:get_count())
inv:set_stack("reactant", k, r[k])
end
end
end
end
for k=1, #products do
inv:add_item("product", products[k])
end
if reaction.sound then
local sound = reaction.sound
if sound == "bunsen" then
bunsen_sound()
elseif sound == "explosion" then
explosion_sound()
elseif sound == "mortar" then
mortar_sound()
elseif sound == "pouring" then
pouring_sound()
elseif sound == "stirring" then
stirring_sound()
end
end
result = true
end
end
return result
end
minetest.register_node("chemistry:lab", {
description = "Laboratory",
tiles = {"chemistry_lab.png"},
groups = {cracky = 1},
drop = "chemistry:lab",
sounds = default.node_sound_stone_defaults(),
can_dig = can_dig,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", formspec)
local inv = meta:get_inventory()
inv:set_size("reactant", 6)
inv:set_size("catalyst", 1)
inv:set_size("tool", 1)
inv:set_size("product", 6)
end,
on_blast = function(pos)
local drops = {}
default.get_inventory_drops(pos, "reactant", drops)
default.get_inventory_drops(pos, "catalyst", drops)
default.get_inventory_drops(pos, "tool", drops)
default.get_inventory_drops(pos, "product", drops)
drops[#drops+1] = "chemistry:lab"
minetest.remove_node(pos)
return drops
end,
on_timer = lab_node_timer,
-- The timer will check whether the reaction works
on_metadata_inventory_move = function(pos)
minetest.get_node_timer(pos):start(1.0)
end,
on_metadata_inventory_put = function(pos)
minetest.get_node_timer(pos):start(1.0)
end,
allow_metadata_inventory_put = allow_metadata_inventory_put,
allow_metadata_inventory_move = allow_metadata_inventory_move,
allow_metadata_inventory_take = allow_metadata_inventory_take,
})