Differences From
Artifact [a02ac21c24]:
1 1 -- liquid.lua
2 2 -- the liquid registry is used to keep track of abstract liquids,
3 3 -- their properties, and their representation in-game.
4 4
5 5 sorcery.registry.mk('liquid', false)
6 -sorcery.liquid = {}
6 +sorcery.liquid = {
7 + constants = {
8 + drams_per_glass = 64;
9 + glasses_per_bottle = 3;
10 + bottles_per_bucket = 3;
11 + bottles_per_trough = 6;
12 + }
13 +}
14 +local constants = sorcery.liquid.constants
15 +
16 +local L = sorcery.lib
17 +local log = sorcery.logger('liquid')
18 +
19 +sorcery.liquid.fill_from_basin = function(ctr, liquid, basin)
20 + local liq = sorcery.register.liquid.db[liquid]
21 + local filled
22 + if type(ctr) == 'string'
23 + then filled = liq.containers[ctr] ctr=ItemStack(ctr)
24 + else filled = liq.containers[ctr:get_name()]
25 + end
26 + if type(filled) == 'string' then
27 + local fs = sorcery.itemclass.get(filled, 'container')
28 + if not fs then log.err(filled,'is named as filled container but does not have the required itemclass definition') return end
29 +
30 + local item_name = filled
31 + filled = {
32 + min = fs.charge, max = fs.charge, res = 1;
33 + make = function(amt,ct) return ItemStack{
34 + name = item_name, count = ct
35 + } end
36 + }
37 + end
38 + if not filled then return nil end
39 +
40 + local num_ctrs = ctr:get_count()
41 + local res = filled.res or 1
42 + local qty = math.min(
43 + math.max((filled.min or 1)*num_ctrs, basin),
44 + (filled.max or 1)*num_ctrs)
45 +
46 + if basin >= qty then
47 + return filled.make(qty / num_ctrs, num_ctrs), basin - qty
48 + end
49 +end
50 +
51 +sorcery.liquid.mktrough = function(liq)
52 + -- troughs are used for collecting liquid from the environment,
53 + -- like rainwater and tree sap. they hold twice as much as a bucket
54 + local Q = constants.glasses_per_bottle
55 + local trough_mkid = function(l,i)
56 + if type(l) == 'string' then l = sorcery.register.liquid.db[l] end
57 + if not l or not i then return 'sorcery:trough' end
58 + return string.format('%s:trough_%s_%u', l.mod,l.sid,i)
59 + end
60 + local lid = function(l) return trough_mkid(liq, l) end
61 +
62 + local M = constants.bottles_per_trough
63 + local mkbox = function(lvl)
64 + local pxl = function(tbl) -- for mapping to txcoords
65 + return L.tbl.map(tbl, function(x)
66 + return (1/16 * x) - 0.5
67 + end)
68 + end
69 + local h = 12
70 + local geom = {
71 + pxl {2,0,2; 14, 2, 14};
72 + pxl {2,2,2; 4,h,14};
73 + pxl {2,2,2; 14,h,4};
74 +
75 + pxl {12,2,2; 14,h,14};
76 + pxl {2,2,12; 14,h,14};
77 +
78 + }
79 + if lvl > 0 then
80 + local fac = lvl / M
81 + return L.tbl.append({
82 + pxl {4,2,4; 12, 2 + ((h-3)*fac), 12};
83 + }, geom)
84 + else return geom end
85 + end
86 + local f = liq and 1 or 0
87 + for i = 1*f,M*f do
88 + local top = L.image('sorcery_trough_top_overlay.png')
89 + if liq then top = top:blit(
90 + L.image('sorcery_node_liquid.png'):multiply(L.color(liq.color))
91 + ) else top=top:blit(
92 + L.image('sorcery_trough_bottom.png')
93 + ) end
94 + local trough_title = liq and string.format('%s Trough', L.str.capitalize(liq.name))
95 + local trough_content = liq and string.format('%s of %s', liq.measure(i * Q), liq.name)
96 + local function trough_caption(pos,i)
97 + minetest.get_meta(pos):set_string('infotext', i > 0 and string.format(
98 + '%s\n(%s)', trough_title, trough_content
99 + ) or 'Empty Trough')
100 + end
101 + sorcery.register.residue.link(lid(i),lid(0))
102 + minetest.register_node(':'..lid(i), {
103 + description = liq and L.ui.tooltip {
104 + title = trough_title;
105 + color = L.color(liq.color);
106 + desc = trough_content;
107 + } or 'Trough';
108 + short_description = liq and string.format('%s Trough', L.str.capitalize(liq.name)) or 'Trough';
109 + drawtype = 'nodebox';
110 + paramtype = 'light';
111 + groups = {
112 + dig_immediate = 3; not_in_creative_inventory = liq and 1;
113 + attached_node = 1;
114 + sorcery_trough = 1; sorcery_container = 2; metal = 1;
115 + sorcery_collect_rainwater = (liq == nil or (liq.collect_rainwater and i ~= M)) and 1 or nil;
116 + };
117 + on_construct = function(pos)
118 + trough_caption(pos,i)
119 + end;
120 + on_rightclick = i > 0 and function(pos, node, who, stack)
121 + if not stack or stack:is_empty() then return end
122 + if liq then
123 + local filled, amtleft = sorcery.liquid.fill_from_basin(stack, liq.id, i * Q)
124 + if filled then
125 + sorcery.liquid.sound_dip(i - amtleft, i, pos)
126 + minetest.swap_node(pos, {name = lid(amtleft / Q)})
127 + trough_caption(pos,amtleft/Q)
128 + return filled
129 + end
130 + end
131 + end;
132 + node_box = { type = 'fixed', fixed = mkbox(i) };
133 + tiles = {
134 + top:render();
135 + 'sorcery_trough_side.png';
136 + 'sorcery_trough_bottom.png';
137 + };
138 + _sorcery = {
139 + container = {
140 + type = 'bucket';
141 + hold = 'liquid';
142 + has = liq and liq.id;
143 + charge = liq and Q * i;
144 + empty = 'sorcery:trough';
145 + max = constants.bottles_per_trough * Q;
146 + set_node_vol = liq and function(pos, vol)
147 + vol = math.min(M, math.max(0, math.floor(vol / Q)))
148 + minetest.swap_node(pos, {name = lid(vol)})
149 + trough_caption(pos, vol)
150 + end;
151 + set_node_liq = function(pos, liq, vol)
152 + log.act('adding', vol, 'to trough at', liq)
153 + vol = vol or Q * i
154 + local idx = math.min(M, math.floor(vol/Q))
155 + minetest.swap_node(pos, {name = trough_mkid(liq, idx)})
156 + trough_caption(pos, idx)
157 + end
158 + }
159 + };
160 + })
161 + end
162 +end
163 +sorcery.liquid.mktrough()
164 +
165 +sorcery.liquid.measure_default = function(amt)
166 + return string.format('%s drams', amt*constants.drams_per_glass)
167 +end
168 +sorcery.liquid.register = function(liq)
169 + local fmt = string.format
170 + local Q = constants.glasses_per_bottle
171 + liq.sid = liq.sid or liq.id:gsub('^[^:]+:','')
172 + liq.mod = liq.mod or liq.id:gsub('^([^:]+):.*','%1')
173 + if not liq.measure then
174 + liq.measure = sorcery.liquid.measure_default
175 + end
176 + if liq.autogen then
177 + local glass = fmt('%s:liquid_%s_glass', liq.mod, liq.sid);
178 + local bottle = fmt('%s:liquid_%s_bottle', liq.mod, liq.sid);
179 + liq.containers = liq.containers or {}
180 + -- liq.containers['vessels:drinking_glass'] = glass;
181 + liq.containers['vessels:glass_bottle'] = bottle;
182 +
183 + local img_bottle = liq.img_bottle or L.image('vessels_glass_bottle.png'):blit(
184 + L.image(fmt('sorcery_liquid_%s.png', liq.imgvariant or 'dull'))
185 + :multiply(L.color(liq.color))):render()
186 +
187 + -- local img_glass = L.image('vessels_drinking_glass.png'):blit(
188 + -- L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull'))
189 + -- :multiply(L.color(liq.color)))
190 +
191 + minetest.register_node(':'..bottle, {
192 + description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name));
193 + inventory_image = img_bottle;
194 + drawtype = 'plantlike', tiles = {img_bottle};
195 + is_ground_content = false, walkable = false;
196 + sunlight_propagates = true, paramtype = 'light';
197 + light_source = liq.glow or 0;
198 + selection_box = { type = 'fixed', fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} };
199 + sounds = default.node_sound_glass_defaults();
200 + groups = L.tbl.merge({dig_immediate = 3; attached_node = 1; vessel = 1}, liq.bottle_groups or {});
201 + _sorcery = {
202 + container = {
203 + type = 'vessel', hold = 'liquid';
204 + has = liq.id;
205 + empty = 'vessels:glass_bottle';
206 + charge = Q;
207 + }
208 + };
209 + })
210 + end
211 +
212 + sorcery.register.liquid.link(liq.id, liq)
213 +
214 + if liq.usetrough then
215 + sorcery.liquid.mktrough(liq)
216 + liq.containers = liq.containers or {}
217 + liq.containers['sorcery:trough'] = {
218 + max = constants.bottles_per_trough * Q, res = Q;
219 + make = function(amt,ct)
220 + return ItemStack{
221 + name = string.format('%s:trough_%s_%u', liq.mod, liq.sid, math.min(amt/Q, constants.bottles_per_trough));
222 + count = ct;
223 + }
224 + end;
225 + }
226 + end
227 +end;
228 +
229 +sorcery.liquid.sound_pour = function(amt_input, amt_basin, pos)
230 + log.act('playing sound at',pos)
231 + minetest.sound_play('default_water_footstep', {
232 + gain = math.min(0.5 + amt_input / 9.0,3.5);
233 + -- pitch = 1.0;
234 + pos = pos;
235 + }, true)
236 +end;
237 +
238 +sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos)
239 + sorcery.liquid.sound_pour(amt_output, amt_basin, pos)
240 +end;
7 241
8 242 -- pre-register basic liquids used in Sorcery and common ones sorcery depends on
9 243
10 -sorcery.register.liquid.link('default:water', {
244 +sorcery.liquid.register{
245 + id = 'default:water';
11 246 name = 'water';
12 247 kind = 'default:drink';
13 248 color = {10,85,255};
14 249 proto = nil;
15 250 src = 'default:water_source';
251 + usetrough = true;
252 + collect_rainwater = true;
16 253 containers = {
17 254 ['vessels:glass_bottle'] = 'sorcery:potion_water';
18 255 ['bucket:bucket_empty'] = 'bucket:bucket_water';
19 256 };
20 -})
257 +}
21 258
22 -sorcery.register.liquid.link('farming:ethanol', {
259 +sorcery.liquid.register {
260 + id = 'farming:ethanol';
23 261 name = 'ethanol';
24 262 kind = 'default:fuel';
25 263 color = {175,185,130};
26 264 proto = nil;
27 265 measure = function(u) return string.format('%s pints', u * 5) end;
28 266 containers = {
29 267 ['vessels:glass_bottle'] = 'farming:ethanol_bottle';
30 268 };
31 -})
269 +}
32 270
33 -sorcery.register.liquid.link('sorcery:blood', {
271 +sorcery.liquid.register {
272 + id = 'sorcery:blood';
34 273 name = 'blood';
35 274 kind = 'sorcery:reagent';
36 275 color = {255,10,30};
37 276 proto = nil;
277 + usetrough = true;
38 278 measure = function(u) return string.format('%s cc', u * 236.5) end;
39 279 containers = {
40 280 ['vessels:glass_bottle'] = 'sorcery:blood';
41 281 };
42 -})
282 +}
283 +
284 +minetest.register_abm {
285 + label = 'Rainfall';
286 + nodenames = {'group:sorcery_collect_rainwater'};
287 + interval = 230;
288 + chance = 40;
289 + min_y = -400;
290 + catch_up = true;
291 + action = function(pos, node)
292 + -- TODO vary by season and biome?
293 + if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then
294 + if node.name == 'sorcery:trough' then
295 + node.name = 'default:trough_water_1'
296 + else
297 + local lvl = minetest.registered_nodes[node.name]._sorcery.container.charge / constants.glasses_per_bottle
298 + node.name = 'default:trough_water_' .. tostring(lvl+1)
299 + end
300 + minetest.set_node(pos, node)
301 + end
302 + end;
303 +}