sorcery  Artifact [b4ccf2cc5a]

Artifact b4ccf2cc5a3ab725c201aa4bc4f6557e13f505823e958de884b042b90f271e29:

  • File runeforge.lua — part of check-in [96c5289a2a] at 2020-10-21 03:35:35 on branch trunk — add rune forges, runes, amulet frames, write sacrifice spell, touch up amulet graphics, enable enchantment of amulets (though spells cannot yet be cast), defuckulate syncresis core icon, unfuckitize sneaky leycalc bug that's probably been the cause of some long-standing wackiness, add item classes, add some more textures, disbungle various other asstastrophes, remove sneaky old debug code, improve library code, add utility for uploading merge requests (user: lexi, size: 8121) [annotate] [blame] [check-ins using]

local constants = {
	rune_mine_interval = 90;
	-- how often a powered forge rolls for new runes

	rune_cache_max = 4;
	-- how many runes a runeforge can hold at a time
	
	rune_grades = {'Fragile', 'Shoddy', 'Ordinary', 'Pristine'};
	-- how many grades of rune quality/power there are
}
sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune)
	local id = 'sorcery:rune_' .. name
	rune.image = rune.image or string.format('sorcery_rune_%s.png',name)
	rune.item = id
	minetest.register_craftitem(id, {
		description = sorcery.lib.color(rune.tone):readable():fmt(rune.name .. ' Rune');
		short_description = rune.name .. ' Rune';
		inventory_image = rune.image;
		stack_max = 1;
		groups = {
			sorcery_rune = 1;
			not_in_creative_inventory = 1;
		};
		_proto = { id = name, data = rune; };
	})
end)

local rune_set = function(stack,r)
	local m = stack:get_meta()
	local def = stack:get_definition()._proto.data
	local grade
	if r.grade then grade = r.grade
	elseif m:contains('rune_grade') then grade = m:get_int('rune_grade') end

	local qpfx = constants.rune_grades[grade]
	local title = sorcery.lib.color(def.tone):readable():fmt(string.format('%s %s Rune',qpfx,def.name))

	m:set_int('rune_grade',grade)
	m:set_string('description',title)
end

sorcery.amulet = {}
sorcery.amulet.setrune = function(stack,rune)
	local m = stack:get_meta()
	if rune then
		local rp = rune:get_definition()._proto
		local rg = rune:get_meta():get_int('rune_grade')
		m:set_string('amulet_rune', rp.id)
		m:set_int('amulet_rune_grade', rg)
		local spell = sorcery.amulet.getspell(stack)
		if not spell then return nil end

		local name = string.format('Amulet of %s', spell.name)

		m:set_string('description', sorcery.lib.ui.tooltip {
			title = name;
			color = spell.tone;
			desc = spell.desc;
		})
	else
		m:set_string('description','')
		m:set_string('amulet_rune','')
		m:set_string('amulet_rune_grade','')
	end
	return stack
end

sorcery.amulet.getrune = function(stack)
	local m = stack:get_meta()
	if not m:contains('amulet_rune') then return nil end
	local rune = m:get_string('amulet_rune')
	local grade = m:get_int('amulet_rune_grade')
	local rs = ItemStack(sorcery.data.runes[rune].item)
	rune_set(rs, {grade = grade})
	return rs
end

sorcery.amulet.getspell = function(stack)
	local m = stack:get_meta()
	local proto = stack:get_definition()._sorcery.amulet
	local rune = m:get_string('amulet_rune')
	local rd = sorcery.data.runes[rune]
	local spell = rd.amulets[proto.base]
	if not spell then return nil end
	local title,desc,cast = spell.name, spell.desc, spell.cast

	if proto.frame and spell.frame and spell.frame[proto.frame] then
		local sp = spell.frame[proto.frame]
		title = sp.name or title
		desc = sp.desc or desc
		cast = sp.desc or cast
	end
	
	return {
		rune = rune;
		spell = spell;
		name = title;
		desc = desc;
		cast = cast;
		tone = sorcery.lib.color(rd.tone);
	}
end


local runeforge_update = function(pos,time)
	local m = minetest.get_meta(pos)
	local i = m:get_inventory()
	local l = sorcery.ley.netcaps(pos,time or 1)

	local pow_min = l.self.powerdraw >= l.self.minpower
	local pow_max = l.self.powerdraw >= l.self.maxpower

	if time and pow_min then -- roll for runes
		local rolls = math.floor(time/constants.rune_mine_interval)
		local newrunes = {}
		for _=1,rolls do
			local choices = {}
			for name,rune in pairs(sorcery.data.runes) do
				if rune.minpower*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then
					local n = ItemStack(rune.item)
					choices[#choices + 1] = n
				end
			end
			if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end
		end

		print('rolled for runes, got', dump(newrunes))
		for _,r in pairs(newrunes) do
			if i:room_for_item('cache',r) then
				local qual = math.random(#constants.rune_grades)
				rune_set(r,{grade = qual})
				i:add_item('cache',r)
			end
		end
	end

	local spec = string.format([[
		formspec_version[3] size[10.25,8] real_coordinates[true]
		list[context;cache;%f,0.25;%u,1;]
		list[context;amulet;3.40,1.50;1,1;]
		list[context;active;5.90,1.50;1,1;]
		list[current_player;main;0.25,3;8,4;]

		image[0.25,0.50;1,1;sorcery_statlamp_%s.png]
	]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max,
	    pow_max and 'green' or (pow_min and 'yellow') or 'off')
	
	m:set_string('formspec',spec)
	return true
end

local rfbox = {
	type = 'fixed';
	fixed = {
		-0.5, -0.5, -0.5;
		 0.5,  0.1,  0.5;
	};
};
minetest.register_node('sorcery:runeforge', {
	description = 'Rune Forge';
	drawtype = 'mesh';
	mesh = 'sorcery-runeforge.obj';
	sunlight_propagates = true;
	paramtype = 'light';
	paramtype2 = 'facedir';
	selection_box = rfbox;
	collision_box = rfbox;
	groups = {
		choppy = 2;
		oddly_breakable_by_hand = 2;
		sorcery_magitech = 1;
		sorcery_tech = 1;
		sorcery_ley_device = 1;
	};
	tiles = {
		'default_diamond_block.png';
		'default_tin_block.png';
		'sorcery_metal_iridium_shiny.png';
		'sorcery_metal_vidrium_shiny.png';
		'default_copper_block.png';
	};
	_sorcery = {
		ley = {
			mode = 'consume';
			affinity = {'praxic'};
			power = function(pos,time)
				local max,min = 0
				for _,r in pairs(sorcery.data.runes) do
					if r.minpower > max then max = r.minpower end
					if min == nil or r.minpower < min then min = r.minpower end
				end
				return min*time,max*time
			end;
		};
		on_leychange = runeforge_update;
		recipe = {
			note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect';
		};
	};
	on_construct = function(pos)
		local m = minetest.get_meta(pos)
		local i = m:get_inventory()
		i:set_size('cache',constants.rune_cache_max)
		i:set_size('amulet',1)
		i:set_size('active',1)
		m:set_string('infotext','Rune Forge')
		runeforge_update(pos)
		minetest.get_node_timer(pos):start(constants.rune_mine_interval)
	end;
	after_dig_node = sorcery.lib.node.purge_only {'amulet'};
	on_timer = runeforge_update;
	on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
		local inv = minetest.get_meta(pos):get_inventory()
		if fl == 'active' then
			inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1)))
		elseif tl == 'active' then
			inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti)))
		end
	end;
	on_metadata_inventory_put = function(pos, list, idx, stack, user)
		if list == 'amulet' then
			local inv = minetest.get_meta(pos):get_inventory()
			inv:set_stack('active',1,ItemStack(sorcery.amulet.getrune(stack)))
		end
	end;
	on_metadata_inventory_take = function(pos, list, idx, stack, user)
		if list == 'amulet' then
			minetest.get_meta(pos):get_inventory():set_stack('active',1,ItemStack())
		end
	end;
	allow_metadata_inventory_put = function(pos,list,idx,stack,user)
		if list == 'amulet' then
			if minetest.get_item_group(stack:get_name(), 'sorcery_amulet') ~= 0 then
				return 1
			end
		end
		return 0
	end;
	allow_metadata_inventory_take = function(pos,list,idx,stack,user)
		if list == 'amulet' then return 1 end
		return 0
	end;
	allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user)
		if fl == 'cache' then
			if tl == 'cache' then return 1 end
			if tl == 'active' then
				local inv = minetest.get_meta(pos):get_inventory()
				if not inv:is_empty('amulet') then
					local amulet = inv:get_stack('amulet',1)
					local rune = inv:get_stack(fl,fi)
					if sorcery.data.runes[rune:get_definition()._proto.id].amulets[amulet:get_definition()._sorcery.amulet.base] then
						return 1
					end
				end
			end
		end
		if fl == 'active' then
			if tl == 'cache' then return 1 end
		end
		return 0
	end;
})

do local m = sorcery.data.metals
	-- temporary recipe until a fancier multi-part crafting path can be come up with
	-- TODO: better than this
	minetest.register_craft {
		output = 'sorcery:runeforge';
		recipe = {
			{'default:copper_ingot',m.vidrium.parts.block,'default:copper_ingot'};
			{'default:diamond',m.iridium.parts.ingot,'default:diamond'};
			{'default:tin_ingot','sorcery:core_syncretic','default:tin_ingot'};
		};
	}
end