@@ -2,9 +2,11 @@ -- the nearest leyline and its currents sorcery.ley = {} --- leylines are invisible force-currents that rise up from the core of the earth, carrying magical energy upwards. they weaken as they get closer to the surface. each leyline also has between one and three 'affinities', which control how easily they can be wielded to perform particular sorts of magic. for instance, telestratic-affine leylines will charge wands enchanted with telestratic spells more quickly than leylines lacking this affinity. +-- leylines are invisible force-currents that rise up from the core of the earth, carrying magical energy upwards. they weaken as they get closer to the surface. each leyline also has between one and three 'affinities', which control how easily they can be wielded to perform particular sorts of magic. for instance, praxic-affine leylines will charge wands enchanted with praxic spells more quickly than leylines lacking this affinity. +-- leylines are one of two mystic energy forms; the other is aetheric energy, which is beamed down from Heaven by the sun during the day and is the power wielded by the gods. mortals can make limited use of aetheric force by collecting it and beaming it from place to place -- see aether.lua (aether is stored and manipulated by use of diamond) +-- leylines are always available, unlike aetheric force, which can only be collected during the day. but aetheric force is accessible wherever one can see the sky, and the higher up you are, the more you can collect, whereas leylines vary randomly in strength and affinity by position. sorcery.ley.estimate = function(pos) local affs = { 'praxic'; 'counterpraxic'; 'cognic'; @@ -45,4 +47,312 @@ local ley = sorcery.ley.estimate(pos) minetest.chat_send_player(caller, 'Leyline force ' .. tostring(ley.force) .. ' with affinities ' .. table.concat(ley.aff, ',')) end; }) + +-- leyline energy can be transmitted via a conduit from a leysink. however, it cannot be stored like aetheric energy can be; leyline energy must be drawn when needed unless it is bound up in an enchantment (which simply delays its expression). leysinks provide a constant source of ley-force. +-- there are two nodes for transmitting leyline energy, wires and conduits. wires transmit a limited amount of energy, but are cheap and small. conduits transmit much more, but are expensive and take up full blocks. both are composed of electrum, the carrier, and copper, which prevents the ley-force from leaking out as dangerous radiance. + +minetest.register_node('sorcery:conduit', { + description = 'Conduit'; + tiles = { + 'sorcery_conduit_copper_top.png'; + 'sorcery_conduit_copper_top.png'; + 'sorcery_conduit_copper_side.png'; + }; + groups = { + sorcery_ley_device = 1; + cracky = 3; + }; + _sorcery = { + ley = { mode = 'signal'; power = 10 }; + }; +}) +minetest.register_craft { + output = 'sorcery:conduit 4'; + recipe = { + {'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'}; + {'default:copper_ingot', 'sorcery:electrumblock', 'default:copper_ingot'}; + {'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'}; + }; +}; +minetest.register_craft { + output = 'sorcery:wire 4'; + recipe = { + {'', 'basic_materials:copper_wire',''}; + {'', 'sorcery:fragment_electrum', ''}; + {'', 'basic_materials:copper_wire',''}; + } +}; + +sorcery.ley.field_to_current = function(strength,time) + local ley_factor = 0.25 + -- a ley harvester will produce this much current with + -- access to a full-strength leyline + + return strength * ley_factor * time; +end + +do -- register condenser + local gem = sorcery.lib.image('default_diamond_block.png') + local amethyst = gem:multiply(sorcery.lib.color(sorcery.data.gems.amethyst.tone)) + local emerald = gem:multiply(sorcery.lib.color(sorcery.data.gems.emerald.tone)) + local box = { + type = 'fixed'; + fixed = { + -0.5, -0.5, -0.5; + 0.5, 1.2, 0.5; + }; + }; + minetest.register_node('sorcery:condenser', { + description = 'Condenser'; + drawtype = 'mesh'; + mesh = 'sorcery-condenser.obj'; + selection_box = box; + collision_box = box; + tiles = { + amethyst:render(); + 'sorcery_condenser.png'; + 'default_tin_block.png'; + 'default_stone.png'; + 'default_copper_block.png'; + emerald:render(); + }; + groups = { + cracky = 2; + sorcery_ley_device = 1; + }; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string('infotext','Condenser') + end; + _sorcery = { + ley = { mode = 'produce' }; + on_leycalc = function(pos,time) + local l = sorcery.ley.estimate(pos) + return { + power = sorcery.ley.field_to_current(l.force, time); + affinity = l.aff; + } + end; + }; + }) +end + +minetest.register_craft { + output = 'sorcery:condenser'; + recipe = { + {'sorcery:accumulator'}; + {'sorcery:conduit'}; + }; +} +sorcery.ley.mapnet = function(startpos,power) + -- this function returns a list of all the nodes accessible from + -- a ley network and their associated positions + local net = {} + power = power or 0 + + local devices = { + consume = {}; + produce = {}; + signal = {}; + } + local numfound = 0 + local maxconduct = 0 + local minconduct + local foundp = function(p) + for k in pairs(net) do + if vector.equals(p,k) then return true end + end + return false + end + -- we're implementing this with a recursive function to start with + -- but this could rapidly lead to stack overflows so we should + -- replace it with a linear one at some point + local function find(positions) + local searchnext = {} + for _,pos in pairs(positions) do + for _,p in pairs { + {x = 0, z = 0, y = 0}; + {x = -1, z = 0, y = 0}; + {x = 1, z = 0, y = 0}; + {x = 0, z = -1, y = 0}; + {x = 0, z = 1, y = 0}; + {x = 0, z = 0, y = -1}; + {x = 0, z = 0, y = 1}; + } do local sum = vector.add(pos,p) + if not foundp(sum) then + local nodename = minetest.get_node(sum).name + if minetest.get_item_group(nodename,'sorcery_ley_device') ~= 0 + or sorcery.data.compat.ley[nodename] then + local d = sorcery.ley.sample(pos,1,nodename) + assert(d.mode == 'signal' + or d.mode == 'consume' + or d.mode == 'produce') + devices[d.mode][#(devices[d.mode]) + 1] = { + id = nodename; pos = sum; + } + if d.mode == 'signal' then + if d.power > power then + if minconduct then + if d.power < minconduct then + minconduct = d.power + end + else minconduct = d.power end + if d.power > maxconduct then + maxconduct = d.power + end + end + end + numfound = numfound + 1; + net[sum] = nodename; + searchnext[#searchnext + 1] = sum; + end + end + end + end + if #searchnext > 0 then find(searchnext) end + end + + find{startpos} + + if numfound > 0 then + return { + count = numfound; + map = net; + devices = devices; + conduct = { + min = minconduct; + max = maxconduct; + }; + } + else return nil end +end + +do local afftbl = { + [1] = 'praxic'; [2] = 'counterpraxic'; + [3] = 'cognic'; [4] = 'syncretic'; + [5] = 'mandatic'; [6] = 'occlutic'; + [7] = 'imperic'; [8] = 'entropic'; + } + local modetbl = { + [0] = 'none'; + [1] = 'consume'; + [2] = 'produce'; + [3] = 'signal'; + } + for i=1,#afftbl do afftbl [afftbl [i]] = i end + for i=1,#modetbl do modetbl[modetbl[i]] = i end + local m = sorcery.lib.marshal + local enc, dec = m.transcoder { + mode = m.t.u8; + power = m.t.u32; -- power generated/consumed * 10,000 + affinity = m.g.array(m.t.u8); -- indexes into afftbl + } + sorcery.ley.encode = function(l) + local idxs = {} + for _,k in pairs(l.affinity) do + idxs[#idxs+1] = afftbl[k] + end + return meta_armor(enc { + mode = modetbl[l.mode]; + power = l.power * 10000; + affinity = idxs; + }, true) + end + sorcery.ley.decode = function(str) + local obj = dec(meta_dearmor(str,true)) + local affs = {} + for _,k in pairs(obj.affinity) do + affs[#affs+1] = afftbl[k] + end + return { + mode = modetbl[obj.mode]; + power = obj.power / 10000.0; + affinity = affs; + } + end +end +sorcery.ley.setnode = function(pos,l) + local meta = minetest.get_node(pos) + meta:set_string('sorcery:ley',sorcery.ley.encode(l)) +end + +sorcery.ley.sample = function(pos,timespan,name) + -- returns how much ley-force can be transmitted by a + -- device over timespan + name = name or minetest.get_node(pos).name + local props = minetest.registered_nodes[name]._sorcery + local callback = props and props.on_leycalc or nil + local p,a,m + if callback then + local gen = callback(pos,timespan) + p = gen.power + a = gen.affinity + m = gen.mode + end + + if not (p and a and m) then + local nm = minetest.get_meta(pos) + if nm:contains('sorcery:ley') then + local l = sorcery.ley.decode(nm:get_string('sorcery:ley')) + p = p or sorcery.ley.field_to_current(l.power,timespan) + a = a or l.affinity + m = m or l.mode + end + end + + if (not (p and a and m)) and props and props.ley then + p = p or sorcery.ley.field_to_current(props.ley.power,timespan) + a = a or props.ley.affinity + m = m or props.ley.mode + end + + if (not (p and a and m)) then + local compat = sorcery.data.compat.ley[name] + if compat then + p = p or sorcery.ley.field_to_current(compat.power,timespan) + a = a or compat.affinity + m = m or compat.mode + end + end + + return { + power = p or 0; + mode = m or 'none'; + affinity = a or {}; + } +end + +sorcery.ley.netcaps = function(pos,timespan,exclude) + local net = sorcery.ley.mapnet(pos) + local maxpower = 0 + local freepower = 0 + local affs,usedaffs = {},{} + for _,n in pairs(net.devices.produce) do + if not exclude or not vector.equals(n.pos,exclude) then + local ln = sorcery.ley.sample(n.pos,timespan,n.id) + maxpower = maxpower + ln.power + for _,a in pairs(ln.affinity) do + affs[a] = (affs[a] or 0) + 1 + end + end + end + freepower = maxpower; + for _,n in pairs(net.devices.consume) do + if not exclude or not vector.equals(n.pos,exclude) then + local ln = sorcery.ley.sample(n.pos,timespan,n.id) + freepower = freepower - ln.power + for _,a in pairs(ln.affinity) do + usedaffs[a] = (usedaffs[a] or 0) + 1 + end + end + end + + return { + net = net; + freepower = freepower; + maxpower = maxpower; + affinity = affs; + affinity_balance = usedaffs; + } +end