-- eventually this will need to be a powered node,
-- but for now i'm just overlooking the need for
-- power and making it work for free
local constants = {
mill_refresh = 1;
-- interval at which the mill progress is checked
-- the formspec is updated
grind_range = 20;
-- maximum difference between the hardness of
-- any two metals in the database
grind_grace_range = 2;
-- how many levels of hardness a grind head
-- can grind above its own (while taking massive
-- levels of damage)
grind_grace_penalty = 3;
-- factor by which wear is increased when grinding
-- above your paygrade
grind_factor = 10;
-- adjusts the amount of time it takes to grind
-- metal into powder
grind_wear = 1000;
-- amount of damage done to a grind head grinding
-- metal of equal hardness. increased or decreased
-- depending on differential
grind_torque_factor = 0.1;
-- how many points of hardness translate into how
-- much ley-current needed (per second)
metal_grindvalue = 4;
-- how many powders an ingot is worth
default_grindvalue = 1;
-- number of items produced when not otherwise
-- specified
default_grindcost = 1;
-- number of items needed to perform a grind when
-- not otherwise specified
default_hardness = 1;
-- difficulty to grind an item when not otherwise
-- specified
metal_grindcost = 1;
-- number of metal items needed to perform a grind
}
local mill_formspec_update = function(pos,pct,stat1,stat2)
-- eventually we'll want to display available
-- energy here
-- local meta = minetest.get_meta(pos)
-- local inv = meta:get_inventory()
-- local torque = 20
stat1 = stat1 or 'off'
minetest.get_meta(pos):set_string('formspec', string.format([[
size[8,7.2]
list[context;grinder;2.5,1;1,1;0]
list[context;grinder;4.5,1;1,1;1]
list[context;input;3.5,0.2;1,1]
list[context;output;2,2.2;4,1]
image[3.5,1.2;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR180]
image[1.6,1.0;1,1;sorcery_statlamp_%s.png]
image[5.4,1.0;1,1;sorcery_statlamp_%s.png]
image[2.5,1;1,1;sorcery_mill_grindhead_shade.png]
image[4.5,1;1,1;sorcery_mill_grindhead_shade.png]
list[current_player;main;0,3.5;8,4]
]], 100*pct, stat1, stat2 or stat1))
end
local mill_update = function(pos)
minetest.get_node_timer(pos):start(constants.mill_refresh)
end
local mill_fits_in_slot = function(slot,item)
if slot == 'grinder' then
if minetest.get_item_group(item:get_name(), 'sorcery_mill_grindhead')~=0
then return 1 end
elseif slot == 'input' then
if sorcery.itemclass.get(item,'grindable') then
return item:get_count()
end
-- local metal = sorcery.data.metallookup[item:get_name()]
-- local mat = sorcery.matreg.lookup[item:get_name()]
-- local comp = sorcery.data.compat.grindables[item:get_name()]
-- if metal or (mat and mat.metal) or comp then
-- return item:get_count()
-- else
-- mat = item:get_definition()._sorcery and
-- item:get_definition()._sorcery.material
-- if mat and mat.metal or mat.powder then
-- return item:get_count()
-- end
-- end
end
return 0
end
local matprops = function(item)
local metal = sorcery.data.metallookup[item:get_name()]
local props = item:get_definition()._sorcery
if not metal then
-- allow grinding of armor and tools back to their
-- original components
local mat = sorcery.matreg.lookup[item:get_name()]
if mat and mat.metal then
metal = mat
elseif props and props.material and props.material.metal then
metal = props.material
end
end
local mp = (props and props.material)
or sorcery.data.compat.grindables[item:get_name()]
or {}
if metal then mp = {
hardness = mp.hardness or metal.data.hardness;
grindvalue = ((mp.grindvalue or metal.value) or (metal and constants.metal_grindvalue));
powder = mp.powder or metal.data.parts.powder;
grindcost = mp.grindcost or constants.metal_grindcost; -- invariant for metal
} end
mp.hardness = mp.hardness or constants.default_hardness;
mp.torque = constants.grind_torque_factor * mp.hardness
mp.grindvalue = mp.grindvalue or constants.default_grindvalue
mp.grindcost = mp.grindcost or constants.default_grindcost
if item:get_wear() ~= 0 then
-- prevent cheating by recovering metal from items before they
-- are destroyed
local wearfac = (item:get_wear() / 65535)
mp.grindvalue = math.max(1,math.ceil(mp.grindvalue * wearfac))
mp.hardness = math.max(1,math.ceil(mp.grindcost * wearfac))
mp.torque = math.max(1,math.ceil(mp.torque * wearfac))
end
return mp
end
minetest.register_node('sorcery:mill',{
description = 'Mill';
groups = {
cracky = 2;
sorcery_ley_device = 1;
sorcery_metallurgy = 1;
};
paramtype2 = 'facedir';
after_dig_node = sorcery.lib.node.purge_container;
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size('input',1)
inv:set_size('output',4)
inv:set_size('grinder',2)
meta:set_float('grindtime',0)
meta:set_int('active',0)
mill_formspec_update(pos, 0)
end;
on_metadata_inventory_put = function(pos,listname)
if listname == 'input' then
minetest.get_meta(pos):set_float('grindtime',0)
end
mill_update(pos)
end;
on_metadata_inventory_take = function(pos,listname)
if listname == 'input' then
minetest.get_meta(pos):set_float('grindtime',0)
end
mill_update(pos)
end;
on_metadata_inventory_move = function(pos,fl,fi,tl,ti)
if fl == 'input' or tl == 'input' then
minetest.get_meta(pos):set_float('grindtime',0)
end
mill_update(pos)
end;
_sorcery = {
ley = {
mode = 'consume', affinity = {'praxic'};
power = function(pos,time)
local meta = minetest.get_meta(pos)
local active = meta:get_int('active') == 1
if not active then return 0 end
local inv = meta:get_inventory()
local item = inv:get_stack('input',1)
if item:is_empty() then
meta:set_int('active', 0)
return 0
end
return matprops(item).torque * time;
end;
};
on_leychange = mill_update;
};
on_timer = function(pos,delta)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local elapsed = meta:get_float('grindtime') + delta
local ley = sorcery.ley.netcaps(pos,delta,pos)
local again = false
local active = false
local reqtime -- sigh
local statcolor = 'off'
local grinders_on
if inv:is_empty('input') or inv:is_empty('grinder') then
elapsed = 0
mill_formspec_update(pos, 0)
else
local item = inv:get_stack('input',1)
local mp = matprops(item)
if mp.grindcost > item:get_count() then
elapsed = 0
mill_formspec_update(pos, 0)
goto stop
end
if ley.maxpower < (mp.torque * delta) then
-- not enough potential energy in the system to grind
-- so don't bother
statcolor = 'red'
elapsed = 0 goto stop
elseif ley.freepower < (mp.torque * delta) then
-- the net has enough potential energy to supply us,
-- but too much of it is in use right now. give up
-- on this round, but try again in a bit to see if
-- more energy is available
statcolor = 'yellow'
elapsed = 0 again = true goto stop
end
local grinders = 0
local grindpower = 0
local grind_wear = {}
local grinders_present = false
grinders_on = {false,false}
for i=1,inv:get_size('grinder') do
local gh = inv:get_stack('grinder',i)
if not gh:is_empty() then
grinders_present = true
local gproto = gh:get_definition()._proto
local hh = 0
if gproto.metal then
hh = sorcery.data.metals[gproto.metal].hardness
elseif gproto.gem then
hh = sorcery.data.gems[gproto.gem].hardness
end
local dif = mp.hardness - hh
local wear
if dif == 0 then
wear = constants.grind_wear
elseif dif < 0 then
wear = constants.grind_wear * ((dif * -1)/constants.grind_range)
elseif dif > 0 then
if dif > constants.grind_grace_range then
wear = 0
goto reject
else
wear = constants.grind_wear * (1 + (dif/constants.grind_range)) * constants.grind_grace_penalty
end
end
::accept:: grinders = grinders + 1
grindpower = grindpower + hh
grinders_on[i] = true
::reject:: grind_wear[i] = wear
end
end
if grinders == 0 then
if grinders_present then
statcolor = 'purple'
end
elapsed = 0
mill_formspec_update(pos, 0)
else
active = true again = true
if grindpower < mp.hardness then
statcolor = 'yellow'
else statcolor='green' end
grindpower = grindpower / grinders
-- if there is more power available than needed,
-- and/or if the blades are stronger than needed,
-- speed up the grind
local speedboost = math.max(0.05,((grindpower - mp.hardness)/constants.grind_range) * grinders)
reqtime = mp.grindvalue * mp.hardness * constants.grind_factor * (1-speedboost)
if elapsed >= reqtime then
item:take_item(mp.grindcost)
inv:set_stack('input',1,item)
local pw = ItemStack{
name=mp.powder;
count=math.floor(mp.grindvalue);
}
if inv:room_for_item('output',pw) then
inv:add_item('output',pw)
else
minetest.add_item(pos,pw)
end
elapsed = 0
for i,d in pairs(grind_wear) do
local item = inv:get_stack('grinder',i)
item:add_wear(grind_wear[i])
inv:set_stack('grinder',i,item)
end
end
end
end
::stop:: meta:set_float('grindtime',elapsed)
local sc1, sc2 = 'off','off'
if grinders_on then
sc1 = grinders_on[1] and statcolor or sc1
sc2 = grinders_on[2] and statcolor or sc2
else
sc1 = statcolor sc2=sc1
end
mill_formspec_update(pos, active and (elapsed/reqtime) or 0, sc1,sc2)
meta:set_int('active',active and 1 or 0)
return again
end;
allow_metadata_inventory_put = function(pos,list,idx,stack,user)
return mill_fits_in_slot(list,stack) end;
allow_metadata_inventory_move = function(pos,fromlist,fromidx,tolist,toidx,count,user)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = inv:get_stack(fromlist,fromidx)
return mill_fits_in_slot(tolist,stack)
end;
tiles = {
'sorcery_mill_top.png^[transformR90';
'sorcery_mill_bottom.png';
'sorcery_mill_side.png';
'sorcery_mill_side.png';
'sorcery_mill_back.png';
'sorcery_mill_front.png';
};
})
minetest.register_craft {
output = 'sorcery:mill';
recipe = {
{'basic_materials:chain_steel','basic_materials:gear_steel','basic_materials:chain_steel'};
{'basic_materials:gear_steel', 'basic_materials:steel_bar', 'basic_materials:gear_steel'};
{'default:tin_ingot','default:steelblock','default:tin_ingot'};
};
}
for name,metal in pairs(sorcery.data.metals) do
if metal.no_tools and not metal.grindhead then goto skip end
local i,f = metal.parts.ingot, metal.parts.fragment
local id = 'sorcery:mill_grindhead_' .. name
minetest.register_tool(id,{
description = sorcery.lib.str.capitalize(name) .. ' Grinding Head';
inventory_image = sorcery.lib.image('sorcery_mill_grindhead.png'):multiply(sorcery.lib.color(metal.tone)):render();
groups = { sorcery_mill_grindhead = 1, sorcery_metallurgy = 1 };
_proto = {
metal = name;
};
_sorcery = {
recipe = {
note = 'Needed by mills in order to operate. The stronger the metal, the longer the head lasts, the harder the materials it can pulverize, and the faster it grinds.';
};
};
});
minetest.register_craft {
output = id;
recipe = {
{f,i,f};
{i,'',i};
{f,i,f};
};
}
::skip::end
for name,gem in pairs(sorcery.data.gems) do
if not gem.tools and not gem.grindhead then goto skip end
local i,f = gem.parts.item, gem.parts.shard
local id = 'sorcery:mill_grindhead_' .. name
minetest.register_tool(id,{
description = sorcery.lib.str.capitalize(name) .. ' Grinding Head';
inventory_image = sorcery.lib.image('sorcery_mill_grindhead.png'):multiply(sorcery.lib.color(gem.tone)):render();
groups = { sorcery_mill_grindhead = 1, sorcery_metallurgy = 1 };
_proto = {
gem = name;
};
});
minetest.register_craft {
output = id;
recipe = {
{f,i,f};
{i,'',i};
{f,i,f};
};
}
::skip::end