sorcery  lathe.lua

File lathe.lua from the latest check-in


-- the math basically needs to be rewritten from scratch by someone who isn't
-- dyscalculic but 
local L = sorcery.lib
local M = function(i) return sorcery.itemclass.get(i, 'material') end
sorcery.lathe = {
	techs = {
		cut = {dmg = true};
		intaglio = {
			consume = true;
			toolpred = function(tool)
				if minetest.get_item_group(tool, 'sorcery_powder') == 0 then return false end
				local cl = sorcery.itemclass.get(tool, 'metal')
				return cl.data.hardness >= 3
			end;
			validate = function(tool, wkpc)
				return M(tool).data.hardness >= M(wkpc).data.hardness
			end;
		};
	};
	tools = {
		['group:sword'] = 'cut', ['group:knife'] = 'cut', ['group:blade'] = 'cut';
		['group:sorcery_intaglio_powder'] = 'intaglio';
	};
	recipes = {};
	register = function(def)
		local recipes = sorcery.lathe.recipes
		if not recipes[def.input] then recipes[def.input] = {} end
		local rs = recipes[def.input][def.tech]
		if not rs
			then recipes[def.input][def.tech] = { def }
			else rs[#rs+1] = def
		end
	end;
	register_metal = function(def)
		local parts = sorcery.data.metals[def.metal].parts
		local out = ItemStack(def.output)
		for _, ty in pairs {'ingot', 'block', 'fragment'} do
			local pt = parts[ty]
			local ptc = sorcery.itemclass.get(pt, 'metal')
			if ptc and ptc.value then
				if def.mass <= ptc.value then
					local mass
					local vfc = ptc.value / def.mass
					if math.floor(vfc) ~= vfc then
						for i = 1, 50 do
							local v = vfc * i
							if math.floor(v) == v then
								mass = i
								vfc = v
								break
							end
						end
					else
						mass = 1
					end
					if not mass then
						log.err('tried to register a lathe metal recipe for',def.output,'but the mass ratio',vfc,'for part',ty,'has too many digits to the right of the decimal place')
						return false
					end
					sorcery.lathe.register {
						input = pt, mass = mass;
						tech = def.tech, cost = def.cost;
						output = {
							name = out:get_name();
							count = out:get_count() * vfc;
						};
						leftover = def.leftover;
					}
				end
			end
		end
	end;
	tooltech = function(tool)
		if type(tool) ~= 'string' then tool = tool:get_name() end
		local ts = sorcery.lathe.tools
		if ts[tool] then return ts[tool] end

		for id,t in pairs(ts) do
			local q, g = L.str.beginswith(id, 'group:')
			if q and minetest.get_item_group(tool, g) ~= 0 then
				return t
			end
		end

		for tech, t in pairs(sorcery.lathe.techs) do
			if t.toolpred then
				if t.toolpred(tool) then return tech end
			end
		end

		return nil
	end;
}

local R = sorcery.lathe.recipes
sorcery.lathe.get = function(pos,idx,howmany)
	local inv = minetest.get_meta(pos):get_inventory()
	local tool = inv:get_stack('tool',1)
	local tech = sorcery.lathe.tooltech(tool)
	if not tech then return nil end
	local wkpc = inv:get_stack('workpiece',1)
	local rec = R[wkpc:get_name()][tech][idx]
	local outn = ItemStack(rec.output):get_count()
	howmany = howmany or math.floor(wkpc:get_count()/(rec.mass or 1))*outn
	local ntimes = math.floor(howmany / outn)

	local tmat = sorcery.itemclass.get(tool,'material')
	local wmat = sorcery.itemclass.get(wkpc,'material')
	local dur = 100
	local lfac = 1
	if tmat then
		local dur = tmat.data.durability or dur
		lfac = (wmat and wmat.data.level or 1) /
			(tmat.data.maxlevel or tmat.data.level or 1)
	end
	local ch = 65535 / dur
	local wear = (ch * rec.cost * ntimes * lfac)
	return {
		tool = tool, wkpc = wkpc;
		cost = rec.cost * ntimes;
		wear = wear;
		ntimes = ntimes;
		tqty = ntimes * (rec.mass or 1), outn = outn;
		tech = tech;
		rec = rec;
		inv = inv;
	}
end

sorcery.lathe.update = function(pos)
	local inv = minetest.get_meta(pos):get_inventory()
	local tool = inv:get_stack('tool',1)
	local wkpc = inv:get_stack('workpiece',1)
	if tool:is_empty() or wkpc:is_empty() then
		if not inv:is_empty('preview') then
			for i=1, inv:get_size('preview') do
				inv:set_stack('preview',i,ItemStack())
			end
		end
		return
	end

	local tmat = sorcery.itemclass.get(tool:get_name(),'material')
	local wmat = sorcery.itemclass.get(wkpc:get_name(),'material')
	-- obey level restrictions. TODO honor Rend
	if (wmat and wmat.data.level or 0) > (tmat and (tmat.data.maxlevel or tmat.data.level) or 0) then
		return
	end
	
	local tech = sorcery.lathe.tooltech(tool)
	local rec = R[wkpc:get_name()][tech]
	if not rec then
		for g,v in pairs(s_wkpc:get_definition().groups) do
			local gs = R['group:'..g..'='..tostring(v)] 
			local gg = R['group:'..g] 

			rec = (gs and gs[tech]) or (gg and gg[tech])
		end
	end
	tech = sorcery.lathe.techs[tech]

	-- fill in the preview slots
	local j = 1
	for i=1, inv:get_size 'preview'  do
		local stk = ItemStack()
		local os = rec[i] and ItemStack(rec[i].output)
		if rec[i] and minetest.registered_items[os:get_name()] and (rec[i].mass == nil or rec[i].mass <= wkpc:get_count()) then
			local l = sorcery.lathe.get(pos, i)
			local max = l.ntimes
			--math.floor(wkpc:get_count() / (rec[i].mass or 1))
			if tech.dmg then
				local lw = l.wear
				while lw + tool:get_wear() > 65535 do
					max = max - 1
					if max == 0 then break end
					lw = sorcery.lathe.get(pos, i, max).wear
				end
			elseif tech.consume then
				max = math.min(max, tool:get_count())
			end
			if max > 0 then
				stk = ItemStack(rec[i].output)
				local ct = math.min(stk:get_count() * max, stk:get_stack_max())
				ct = ct - (ct % os:get_count())
				stk:set_count(ct)
			end
		end
		inv:set_stack('preview',i,stk)
		j = j + 1
	end

	-- make sure remaining slots are clear
	for i = j, inv:get_size('preview') do
		inv:set_stack('preview',i,ItemStack())
	end

end

local box = {
	type='fixed';
	fixed = {
		-0.7, -0.5, -0.3;
		 0.57,  0.2,  0.3;
	}
}
minetest.register_node('sorcery:lathe', {
	description = 'Lathe';
	drawtype = 'mesh';
	mesh = 'sorcery-lathe.obj';
	sunlight_propagates = true;
	paramtype = 'light';
	paramtype2 = 'facedir';
	selection_box = box;
	collision_box = box;
	groups = { cracky = 2; sorcery_tech = 1; attached_node = 1 }; -- 2=liquid
	after_dig_node = sorcery.lib.node.purge_only { 'workpiece', 'tool' };
	tiles = {
		'default_wood.png';
		'default_steel_block.png';
		'default_bronze_block.png';
	};
	on_construct = function(pos)
		local m = minetest.get_meta(pos)
		local i = m:get_inventory()
		i:set_size('workpiece', 1);
		i:set_size('tool', 1);
		i:set_size('preview', 8);
		m:set_string('formspec', [[
			formspec_version[3] size[10.25,8]
			list[context;tool;1.25,1;1,1]
			list[context;workpiece;3,1;1,1]
			list[context;preview;5.25,0.25;4,2]
			list[current_player;main;0.25,3;8,4]

			listring[current_player;main] listring[context;workpiece]
			listring[current_player;main] listring[context;tool]
			listring[current_player;main] listring[context;preview]
			listring[current_player;main]
		]])
	end;

	allow_metadata_inventory_move = function() return 0 end;
	allow_metadata_inventory_put = function(pos, list, idx, stack, user)
		local inv = minetest.get_meta(pos):get_inventory()
		if list == 'tool' then
			local s_wkpc = inv:get_stack('workpiece', 1)
			local tech = sorcery.lathe.tooltech(stack)
			if not tech then return 0 end
			local vdtr = sorcery.lathe.techs[tech].validate
			if tech and (s_wkpc:is_empty()
						or (R[s_wkpc:get_name()]       ~= nil and
							R[s_wkpc:get_name()][tech] ~= nil and
							(vdtr == nil or vdtr(stack,s_wkpc) )))
					then return stack:get_count() end
			for g,v in pairs(s_wkpc:get_definition().groups) do
				local gs = R['group:'..g..'='..tostring(v)] 
				local gg = R['group:'..g] 

				if (gs and gs[tech])
				or (gg and gg[tech]) then
					if vdtr == nil or vdtr(stack, s_wkpc) then
						return stack:get_count()
					end
				end
			end
		elseif list == 'workpiece' then
			local s_tool = inv:get_stack('tool', 1)
			if R[stack:get_name()] then
				if s_tool:is_empty() then return stack:get_count() end

				local tech = sorcery.lathe.tooltech(s_tool)
				if tech and R[stack:get_name()][tech] then
					local vdtr = sorcery.lathe.techs[tech].validate
					if vdtr == nil or vdtr(s_tool, stack) then
						return stack:get_count()
					end
				end
			end
		end

		return 0
	end;
	allow_metadata_inventory_take = function(pos, list, idx, stack, user)
		if list == 'preview' then
			local l = sorcery.lathe.get(pos,idx,stack:get_count())
			if stack:get_count() % l.outn == 0 then
				return stack:get_count()
			else return 0 end
		else return stack:get_count() end
	end;
	on_metadata_inventory_put = sorcery.lathe.update;
	on_metadata_inventory_take = function(pos, list, idx, stack, user)
		if list == 'preview' then
			local l = sorcery.lathe.get(pos,idx,stack:get_count())
			if sorcery.lathe.techs[l.tech].consume then
				l.tool:take_item(l.cost)
			elseif sorcery.lathe.techs[l.tech].dmg then
				l.tool:add_wear(l.wear)
			end
			l.wkpc:take_item(l.tqty)
			l.inv:set_stack('tool', 1, l.tool)
			l.inv:set_stack('workpiece', 1, l.wkpc)
			if l.rec.leftover then
				sorcery.lib.node.insert(ItemStack(l.rec.leftover), 'workpiece', pos, user, l.inv)
			end
			minetest.sound_play('sorcery_clank', { pos = pos, gain = 0.9 })
		end
		sorcery.lathe.update(pos)
	end;

})

minetest.register_craft {
	output = 'sorcery:lathe';
	recipe = {
		{'default:stick','basic_materials:gear_steel','default:steel_ingot'};
		{'default:bronze_ingot','basic_materials:steel_bar','default:bronze_ingot'};
		{'group:wood','','group:wood'};
	};
}