sorcery  Artifact [ec3123df49]

Artifact ec3123df49e9233d8499810e33ab447dd410265169ba8e62b8399cc406487304:

  • File displacer.lua — part of check-in [147592b8e9] at 2020-10-26 03:58:08 on branch trunk — add over-time spellcasting abstraction to enable metamagic and in particular disjunction, add more animations and sound effects, add excavation spell, possibly some others, forget when the last commit was, edit a bunch of magitech to make it subject to the disjunction mechanism (throw up a disjunction aura and waltz right through those force fields bby, wheee), also illumination spells, tweak runeforge and rune frequence to better the balance and also limit player frustration, move some math functions into their own library category, various tweaks and bugfixes, probably other shit i don't remember (user: lexi, size: 9825) [annotate] [blame] [check-ins using]

local constants = {
	xmit_wattage = 0.4;
	-- the amount of power per second needed to transmit an item from
	-- one displacer to another

	rcpt_wattage = 0.15;
	-- the amount of power needed to broadcast a receptor's availability
}

local gettxr = function(pos)
	local txrcomps = {
		'sorcery:displacer';
		'sorcery:displacer_transmit_gem';
		'sorcery:displacer_transmit_attune';
		'sorcery:displacer_receive_gem';
		'sorcery:displacer_receive_attune';
	}

	local devs = sorcery.lib.node.amass(pos,txrcomps,sorcery.lib.node.offsets.neighbors)
	local r = {
		receptacles = {};
		connections = {};
		counts = {
			receptacles = 0;
			transmitters = 0;
			receptors = 0;
		};
	}
	local getcode = function(pos)
		local inv = minetest.get_meta(pos):get_inventory()
		local code = {}
		local empty = true
		for i=1,inv:get_size('code') do
			if not inv:get_stack('code',i):is_empty()
				then empty = false end

			code[i] = inv:get_stack('code',i):get_name()
		end
		if empty then return nil
		         else return code end
	end
	for pos, dev in pairs(devs) do
		if dev == 'sorcery:displacer_receive_gem' then
			r.counts.receptors = r.counts.receptors + 1
			r.connections[#r.connections+1] = {
				pos = pos; mode = 'receive';
				code = getcode(pos); -- TODO retrieve code
			}
		elseif dev == 'sorcery:displacer_receive_attune' then
			local tune = sorcery.attunement.verify(pos)
			if tune then
				r.counts.receptors = r.counts.receptors + 1
				r.connections[#r.connections+1] = {
					pos = pos; mode = 'receive';
					partner = tune.partner;
				}
			end
		elseif dev == 'sorcery:displacer_transmit_gem' then
			r.counts.transmitters = r.counts.transmitters + 1
			r.connections[#r.connections+1] = {
				pos = pos; mode = 'transmit';
				code = getcode(pos); -- TODO retrieve code
			}
		elseif dev == 'sorcery:displacer_transmit_attune' then
			local tune = sorcery.attunement.verify(pos)
			if tune then
				r.counts.transmitters = r.counts.transmitters + 1
				r.connections[#r.connections+1] = {
					pos = pos; mode = 'transmit';
					partner = tune.partner;
				}
			end
		elseif dev == 'sorcery:displacer' then
			r.counts.receptacles = r.counts.receptacles + 1
			r.receptacles[#r.receptacles+1] = pos
		end
	end
	return r
end

local autoselect = function(pos)
	local dev = gettxr(pos)
	local active
	if dev.counts.receptors == 0 and dev.counts.transmitters == 1 then
		active = dev.connections[1].pos
	end
	for _,rcp in pairs(dev.receptacles) do
		local meta = minetest.get_meta(rcp)
		meta:set_string('active-device',active and minetest.pos_to_string(active) or '')
	end
	return active ~= nil
end

minetest.register_node('sorcery:displacer', {
	description = 'Displacer Receptacle';
	paramtype2 = 'facedir';
	on_construct = function(pos)
		local meta = minetest.get_meta(pos)
		local inv = meta:get_inventory()
		minetest.get_node_timer(pos):start(1)
		inv:set_size('cache', 6)
		meta:set_string('infotext','displacer')
		meta:set_string('active-device','')
		meta:set_string('formspec', [[
			formspec_version[3] size[10.25,8]
			list[context;cache;3.125,0.25;3,2]
			list[current_player;main;0.25,3;8,4]
			listring[]
		]])
	end;

	-- vararg wrapping necessary to discard the return value,
	-- as a return value of true will prevent the item from
	-- being removed from the user's inventory after placement
	after_place_node = function(...) autoselect(...) end;
	after_dig_node = function(...)
		autoselect(...)
		sorcery.lib.node.purge_container(...)
	end;
	on_metadata_inventory_put = function(pos)
		minetest.get_node_timer(pos):start(1)
	end;
	on_timer = function(pos,delta)
		local meta = minetest.get_meta(pos)
		if not meta:contains('active-device') then return false end

		local probe = sorcery.spell.probe(pos)
		if probe.disjunction then return true end

		local inv = meta:get_inventory()
		if inv:is_empty('cache') then return false end

		local dev = gettxr(pos)
		local active = minetest.string_to_pos(meta:get_string('active-device'))

		local ad
		for _,d in pairs(dev.connections) do
			if vector.equals(d.pos, active) then ad = d break end
		end
		if not ad then
			meta:set_string('active-device','')
			return false
		end

		local remote
		if ad.partner then
			remote = gettxr(ad.partner)
		elseif ad.code then
			local net = sorcery.farcaster.junction(pos,constants.xmit_wattage)
			for _,n in pairs(net) do
				for _,d in pairs(n.caps.net.devices.consume) do
					if d.id == 'sorcery:displacer' then
						local dp = sorcery.spell.probe(d.pos)
						if not dp.disjunction then
							local t = gettxr(d.pos)
							for _,d in pairs(t.connections) do
								if d.mode == 'receive' and d.code then
									local match = true
									for i=1,#d.code do
										if d.code[i] ~= ad.code[i] then
											match = false break
										end
									end
									if match then
										remote = t
										break
									end
								end
							end
						end
					end
					if remote then break end
				end
				if remote then break end
			end
		end

		if not remote then return false end


		local n = sorcery.ley.netcaps(pos,delta,nil,constants.xmit_wattage)
		if n.self.powerdraw == n.self.maxpower then
			-- fully powered for transmission; find an object to transmit
			local transmission
			for i=1,inv:get_size('cache') do
				local s = inv:get_stack('cache',i)
				if not (s:is_empty() or minetest.get_item_group(s:get_name(), 'sorcery_nontranslocatable') ~= 0) then
					local quantity = 1
					local tq = minetest.get_item_group(s:get_name(), 'sorcery_translocate_pack')
					if tq ~= 0 then quantity = math.min(tq, s:get_count()) end
					transmission = s:take_item(quantity)
					inv:set_stack('cache',i,s)
					break
				end
			end
			if not transmission then return false end
			-- iterate through available receptacles and see if there's room
			-- in any of them. otherwise, fail
			for _,r in pairs(remote.receptacles) do
				local i = minetest.get_meta(r):get_inventory()
				transmission = i:add_item('cache',transmission)
				if transmission:is_empty() then break end
			end
			if not transmission:is_empty() then inv:add_item('cache',transmission) end
			return true
		elseif n.maxpower >= n.self.maxpower then
			-- other devices are currently drawing power and might stop,
			-- making enough available for us; keep iterating just in case
			return true
		else
			-- the system does not have the capability to generate
			-- sufficient power, no point in continuing to fuck around
			return false
		end
	end;
	groups = {
		cracky = 2;
		sorcery_ley_device = 1;
		sorcery_magitech = 1;
	};
	tiles = {
		'sorcery_displacer_top.png';
		'sorcery_displacer_top.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_side.png';
		'sorcery_displacer_front.png';
	};

	_sorcery = {
		on_leychange = function(pos)
			minetest.get_node_timer(pos):start(1)
		end;
		ley = {
			mode = 'consume', affinity = {'mandatic'};
			power = function(pos,time)
				local meta = minetest.get_meta(pos)
				local power = 0
				if meta:contains('active-device') then
					power = constants.xmit_wattage
				end

				local dev = gettxr(pos)
				power = power + constants.rcpt_wattage * dev.counts.receptors

				return (power / dev.counts.receptacles) * time
			end;
		};
	};
})

for mode,m in pairs {
	gem={
		desc = 'Gem-Coded';
		construct = function(pos)
			local meta = minetest.get_meta(pos)
			local inv = meta:get_inventory()
			inv:set_size('code',6)
			meta:set_string('formspec', [[
				formspec_version[3] size[10.25,7]
				list[context;code;1.5,0.25;6,1]
				list[current_player;main;0.25,1.75;8,4]
				listring[]
			]])
		end;
		allowput = function(pos,list,idx,stack)
			if list == 'code' then
				if sorcery.itemclass.get(stack:get_name(),'gem') then return 1 end
			end
			return 0
		end;
	};
	attune={
		desc = 'Attuned';
	};
} do for kind,n in pairs {
		transmit = {
			name = 'Transmission';
			button = function(pos)
				minetest.sound_play('doors_steel_door_open', {pos = pos})
				local n = minetest.get_node(pos)
				local dev = gettxr(pos)
				if dev.counts.receptacles > 0 then
					for _,r in pairs(dev.receptacles) do
						local m = minetest.get_meta(r)
						m:set_string('active-device',minetest.pos_to_string(pos))
						minetest.get_node_timer(r):start(1)
					end
				end
			end;
			attune = {
				target = true, accepts = 'sorcery:displacer';
				reciprocal = true;
			};
		};
		receive = {
			name = 'Reception';
			attune = {
				source = true, class = 'sorcery:displacer';
				reciprocal = true;
			}
		};
	} do local id = 'sorcery:displacer_' .. kind .. '_' .. mode
		minetest.register_node(id, {
			description = m.desc .. ' ' .. n.name .. ' Module';
			paramtype2 = 'facedir';
			tiles = {
				'sorcery_displacer_top.png';
				'sorcery_displacer_top.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_side.png';
				'sorcery_displacer_module_' .. kind .. '.png';
			};
			on_construct = m.construct;
			on_rightclick = mode ~= 'gem' and n.button or nil;
			on_punch = n.button and function(pos,node,puncher)
				if puncher and puncher:get_wielded_item():is_empty() then
					n.button(pos)
				end
			end or nil;
			after_place_node = function(...)
				autoselect(...)
				-- these are not ley devices but they still affect the
				-- energy consumption of the ley device they are attached
				-- to, so we need to manually run the notification routine
				-- when they are placed or dug
				sorcery.ley.notify(...)
			end;
			allow_metadata_inventory_put = m.allowput;
			_sorcery = {
				attune = (mode == 'attune') and n.attune or nil;
			};
			after_dig_node = function(...)
				autoselect(...)
				sorcery.lib.node.purge_container(...)
				sorcery.lib.node.notifyneighbors(...)
			end;
			groups = {
				cracky = 2;
				sorcery_magitech = 1;
			};
		})
	end
end