-- liquid.lua
-- the liquid registry is used to keep track of abstract liquids,
-- their properties, and their representation in-game.
sorcery.registry.mk('liquid', false)
local mkunit = function(unit,fac)
return function(amt)
-- allow division for more accurate results
if fac >= 0 then
amt = amt * fac
else
amt = amt / (-fac)
end
return string.format('%s %s%s', amt, unit, (amt == 1) and '' or 's')
end
end
sorcery.liquid = {
constants = {
drams_per_glass = 64;
pints_per_glass = 0.5;
glasses_per_bottle = 3;
bottles_per_bucket = 3;
bottles_per_trough = 6;
};
unit = mkunit;
}
local constants = sorcery.liquid.constants
sorcery.liquid.units = {
dram = mkunit('dram', constants.drams_per_glass);
pint = mkunit('pint', constants.pints_per_glass);
draught = mkunit('draught', -3);
drink = mkunit('drink', 1);
};
local L = sorcery.lib
local log = sorcery.logger('liquid')
sorcery.liquid.fill_from_basin = function(ctr, liquid, basin)
local liq = sorcery.register.liquid.db[liquid]
local filled
if type(ctr) == 'string'
then filled = liq.containers[ctr] ctr=ItemStack(ctr)
else filled = liq.containers[ctr:get_name()]
end
if type(filled) == 'string' then
local fs = sorcery.itemclass.get(filled, 'container')
if not fs then log.err(filled,'is named as filled container but does not have the required itemclass definition') return end
local item_name = filled
filled = {
min = fs.charge, max = fs.charge, res = 1;
make = function(amt,ct) return ItemStack{
name = item_name, count = ct
} end
}
end
if not filled then return nil end
local num_ctrs = ctr:get_count()
local res = filled.res or 1
local qty = math.min(
math.max((filled.min or 1)*num_ctrs, basin),
(filled.max or 1)*num_ctrs)
if basin >= qty then
return filled.make(qty / num_ctrs, num_ctrs), basin - qty
end
end
sorcery.liquid.mktrough = function(liq)
-- troughs are used for collecting liquid from the environment,
-- like rainwater and tree sap. they hold twice as much as a bucket
local Q = constants.glasses_per_bottle
local trough_mkid = function(l,i)
if type(l) == 'string' then l = sorcery.register.liquid.db[l] end
if (not l) or (not i) or i < 1 then return 'sorcery:trough' end
return string.format('%s:trough_%s_%u', l.mod,l.sid,i)
end
local lid = function(l) return trough_mkid(liq, l) end
local M = constants.bottles_per_trough
local mkbox = function(lvl)
local pxl = function(tbl) -- for mapping to txcoords
return L.tbl.map(tbl, function(x)
return (1/16 * x) - 0.5
end)
end
local h = 12
local geom = {
pxl {2,0,2; 14, 2, 14};
pxl {2,2,2; 4,h,14};
pxl {2,2,2; 14,h,4};
pxl {12,2,2; 14,h,14};
pxl {2,2,12; 14,h,14};
}
if lvl > 0 then
local fac = lvl / M
return L.tbl.append({
pxl {4,2,4; 12, 2 + ((h-3)*fac), 12};
}, geom)
else return geom end
end
local f = liq and 1 or 0
for i = 1*f,M*f do
local top = L.image('sorcery_trough_top_overlay.png')
if liq then top = top:blit(
L.image('sorcery_node_liquid.png'):multiply(L.color(liq.color))
) else top=top:blit(
L.image('sorcery_trough_bottom.png')
) end
local ttlc = function(liq,i)
if type(liq) == 'string' then liq = sorcery.register.liquid.db[liq] end
return
liq and string.format('%s Trough', L.str.capitalize(liq.name)),
liq and string.format('%s of %s', liq.measure(i * Q), liq.name)
end
local trough_title, trough_content = ttlc(liq,i)
local function trough_caption(pos,i,l)
local trough_title, trough_content = ttlc(l or liq,i)
minetest.get_meta(pos):set_string('infotext', i > 0 and string.format(
'%s\n(%s)', trough_title, trough_content
) or 'Empty Trough')
end
sorcery.register.residue.link(lid(i),lid(0))
minetest.register_node(':'..lid(i), {
description = liq and L.ui.tooltip {
title = trough_title;
color = L.color(liq.color);
desc = trough_content;
} or 'Trough';
short_description = liq and string.format('%s Trough', L.str.capitalize(liq.name)) or 'Trough';
drawtype = 'nodebox';
paramtype = 'light';
groups = {
dig_immediate = 3; not_in_creative_inventory = liq and 1;
attached_node = 1;
sorcery_trough = 1; sorcery_container = 2; metal = 1;
sorcery_collect_rainwater = (liq == nil or (liq.collect_rainwater and i ~= M)) and 1 or nil;
};
on_construct = function(pos)
trough_caption(pos,i)
end;
on_rightclick = i > 0 and function(pos, node, who, stack)
if not stack or stack:is_empty() then return end
if liq then
local filled, amtleft = sorcery.liquid.fill_from_basin(stack, liq.id, i * Q)
if filled then
sorcery.liquid.sound_dip(i - amtleft, i, pos)
minetest.swap_node(pos, {name = lid(amtleft / Q)})
trough_caption(pos,amtleft/Q)
return filled
end
end
end;
node_box = { type = 'fixed', fixed = mkbox(i) };
tiles = {
top:render();
'sorcery_trough_side.png';
'sorcery_trough_bottom.png';
};
_sorcery = {
material = liq == nil and {
metal = true;
name = 'aluminum';
data = sorcery.data.metals.aluminum;
value = 7*4;
} or nil;
container = {
type = 'bucket';
hold = 'liquid';
has = liq and liq.id;
charge = liq and Q * i;
empty = 'sorcery:trough';
max = constants.bottles_per_trough * Q;
set_node_vol = liq and function(pos, vol)
log.act('putting', vol, liq, 'in trough at', pos)
vol = math.min(M, math.max(0, math.floor(vol / Q)))
minetest.swap_node(pos, {name = lid(vol)})
trough_caption(pos, vol)
return vol * Q
end;
set_node_liq = function(pos, liq, vol)
log.act('putting', vol, liq, 'in trough at', pos)
vol = vol or Q * i
local idx = math.min(M, math.floor(vol/Q))
minetest.swap_node(pos, {name = trough_mkid(liq, idx)})
trough_caption(pos, idx, liq)
return idx * Q
end
}
};
})
end
end
sorcery.liquid.mktrough()
sorcery.liquid.setctr = function(pos, liq, vol, sameliq)
local n = sorcery.lib.node.force(pos)
if minetest.get_item_group(n.name, 'sorcery_container') ~= 2 then return false end
local def = minetest.registered_items[n.name]._sorcery
if not (def and def.container and def.container.set_node_liq) then
log.err('node',n.name,'marked as liquid container but is missing container.set_node_liq callback')
return false
end
if sameliq then
if def.container.has ~= nil and def.container.has ~= liq then
return false
end
end
return def.container.set_node_liq(pos, liq, vol)
end
sorcery.liquid.measure_default = sorcery.liquid.units.dram
sorcery.liquid.container = function(liq, ctr)
return liq.containers[({
bottle = 'vessels:glass_bottle';
glass = 'vessels:drinking_glass';
keg = 'sorcery:keg';
trough = 'sorcery:trough';
})[ctr] or ctr]
end
sorcery.liquid.register = function(liq)
local fmt = string.format
local Q = constants.glasses_per_bottle
liq.sid = liq.sid or liq.id:gsub('^[^:]+:','')
liq.mod = liq.mod or liq.id:gsub('^([^:]+):.*','%1')
if not liq.measure then
liq.measure = sorcery.liquid.measure_default
end
if liq.autogen then
local glass = fmt('%s:liquid_%s_glass', liq.mod, liq.sid);
local bottle = fmt('%s:liquid_%s_bottle', liq.mod, liq.sid);
liq.containers = liq.containers or {}
-- liq.containers['vessels:drinking_glass'] = glass;
liq.containers['vessels:glass_bottle'] = bottle;
local img_bottle = liq.img_bottle or L.image('vessels_glass_bottle.png'):blit(
L.image(fmt('sorcery_liquid_%s.png', liq.imgvariant or 'dull'))
:multiply(L.color(liq.color))):render()
-- local img_glass = L.image('vessels_drinking_glass.png'):blit(
-- L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull'))
-- :multiply(L.color(liq.color)))
sorcery.lib.node.reg_autopreserve(':'..bottle, {
description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name));
inventory_image = img_bottle;
drawtype = 'plantlike', tiles = {img_bottle};
is_ground_content = false, walkable = false;
sunlight_propagates = true, paramtype = 'light';
light_source = liq.glow or 0;
selection_box = { type = 'fixed', fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} };
sounds = default.node_sound_glass_defaults();
groups = L.tbl.merge({dig_immediate = 3; attached_node = 1; vessel = 1}, liq.bottle_groups or {});
_sorcery = {
container = {
type = 'vessel', hold = 'liquid';
has = liq.id;
empty = 'vessels:glass_bottle';
charge = Q;
}
};
})
end
sorcery.register.liquid.link(liq.id, liq)
if liq.usetrough then
sorcery.liquid.mktrough(liq)
liq.containers = liq.containers or {}
liq.containers['sorcery:trough'] = {
max = constants.bottles_per_trough * Q, res = Q;
make = function(amt,ct)
return ItemStack{
name = string.format('%s:trough_%s_%u', liq.mod, liq.sid, math.min(amt/Q, constants.bottles_per_trough));
count = ct;
}
end;
}
end
end;
sorcery.liquid.sound_pour = function(amt_input, amt_basin, pos)
log.act('playing sound at',pos)
minetest.sound_play('default_water_footstep', {
gain = math.min(0.5 + amt_input / 9.0,3.5);
-- pitch = 1.0;
pos = pos;
}, true)
end;
sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos)
sorcery.liquid.sound_pour(amt_output, amt_basin, pos)
end;
-- pre-register basic liquids used in Sorcery and common ones sorcery depends on
sorcery.liquid.register{
id = 'default:water';
name = 'water';
kind = 'default:drink';
color = {10,85,255};
proto = nil;
src = 'default:water_source';
usetrough = true;
collect_rainwater = true;
containers = {
['vessels:glass_bottle'] = 'sorcery:potion_water';
['bucket:bucket_empty'] = 'bucket:bucket_water';
};
}
sorcery.liquid.register {
id = 'farming:ethanol';
name = 'ethanol';
kind = 'default:fuel';
color = {175,185,130};
proto = nil;
measure = function(u) return string.format('%s pints', u * 5) end;
containers = {
['vessels:glass_bottle'] = 'farming:ethanol_bottle';
};
}
sorcery.liquid.register {
id = 'sorcery:blood';
name = 'blood';
kind = 'sorcery:reagent';
color = {255,10,30};
proto = nil;
usetrough = true;
measure = function(u) return string.format('%s cc', u * 236.5) end;
containers = {
['vessels:glass_bottle'] = 'sorcery:blood';
};
}
minetest.register_abm {
label = 'Rainfall';
nodenames = {'group:sorcery_collect_rainwater'};
interval = 120;
chance = 27;
min_y = -400;
catch_up = true;
action = function(pos, node)
-- TODO vary by season and biome?
if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then
if node.name == 'sorcery:trough' then
node.name = 'default:trough_water_1'
else
local lvl = minetest.registered_nodes[node.name]._sorcery.container.charge / constants.glasses_per_bottle
node.name = 'default:trough_water_' .. tostring(lvl+1)
end
minetest.set_node(pos, node)
end
end;
}