Index: altar.lua ================================================================== --- altar.lua +++ altar.lua @@ -122,13 +122,14 @@ y = range(-0.6, 0.6); }; acceleration = { x = 0; y = range(0,1); z = 0; }; - texture = 'sorcery_sparkle.png' .. - '^[transform' .. (math.random(8) - 1) .. - '^[multiply:' .. color:brighten(1.7):hex(); + texture = sorcery.lib.image('sorcery_sparkle.png'): + transform(math.random(8) - 1): + multiply(color:brighten(1.7)): + render(); glow = 14; } end for i=0,48 do minetest.add_particle{ Index: coins.lua ================================================================== --- coins.lua +++ coins.lua @@ -103,11 +103,10 @@ local inv = meta:get_inventory() inv:set_size('ingot',1) inv:set_size('gem',1) inv:set_size('output',1) meta:set_string('formspec', [[ - formspec_version[3] real_coordinates[true] size[8,6] list[context;ingot;2,0.5;1,1;] image[2,0.5;1,1;sorcery_ingot_outline.png] list[context;gem;3,0.5;1,1;] image[3,0.5;1,1;sorcery_diamond_outline.png] Index: data/enchants.lua ================================================================== --- data/enchants.lua +++ data/enchants.lua @@ -1,51 +1,171 @@ +-- an optional 'enchant' function can be defined, and will +-- be called when the enchantment is placed on the object +local allgroups = { + 'sword'; 'pick'; 'pickaxe'; + 'sickle'; 'scythe'; 'shovel'; + 'hoe'; 'helmet'; 'leggings'; + 'chestplate'; 'boots'; +} return { endure = { -- withstand more blows name = 'Endure'; + cost = 1; tone = {232,102,255}; desc = 'durability magnified'; affinity = 'counterpraxic'; - apply = function(stack,power) + groups = allgroups; + recipe = { + {lens = 'convex', gem = 'amethyst', dmg = 2}; + {lens = 'rectifier', gem = 'emerald', dmg = 4}; + {lens = 'convex', gem = 'emerald', dmg = 2}; + }; + apply = function(stack,power,base) + local caps = table.copy(stack:get_definition().tool_capabilities) + for g,v in pairs(caps.groupcaps) do + local unit = base.groupcaps[g].uses * 0.6 + caps.groupcaps[g].uses = v.uses + unit*power + end + stack:get_meta():set_tool_capabilities(caps) + return stack end; }; - drain = {}; -- health vampirism + drain = { + groups = {'sword'}; + cost = 4; + }; -- health vampirism harvest = { -- kills or digging ore replenish durability name = 'Harvest'; + cost = 0; -- energy is only depleted when repair takes place tone = {255,84,187}; affinity = 'syncretic'; + groups = { + 'pick'; 'pickaxe'; 'sword'; + }; + recipe = { + {lens = 'amplifier', gem = 'ruby', dmg = 5}; + {lens = 'concave', gem = 'mese', dmg = 1}; + {lens = 'concave', gem = 'sapphire', dmg = 1}; + }; desc = 'some damage is repaired when used to mine ore or kill an attacker'; - apply = function(stack,power) + on_dig = function(ctx) + local orepfx = "stone_with_" -- }:< + -- local oredrop = ' lump' + local dug = minetest.get_node(ctx.target.under) + local barename = string.sub(dug.name, string.find(dug.name, ':') + 1) + print('is ore? ',dug.name,barename) + if minetest.get_item_group(dug.name, ore) ~= 0 or + string.sub(barename,1,string.len(orepfx)) == orepfx + then + print('is ore!') + ctx.tool:add_wear(-(sorcery.enchant.strength(ctx.tool,'harvest') * 2000)) + ctx.cost = 3 + end end; }; conserve = { -- use less magical energy name = 'Conserve'; tone = {84,255,144}; + cost = 0; desc = 'enchantments last longer before running out of power to sustain them.'; + groups = allgroups; affinity = 'syncretic'; - apply = function(stack,power) - end; + recipe = { + {lens = 'rectifier', gem = 'mese', dmg = 7}; + {lens = 'rectifier', gem = 'sapphire', dmg = 2}; + {lens = 'rectifier', gem = 'amethyst', dmg = 2}; + }; + -- implemented in sorcery/enchanter.lua:register_on_dig }; dowse = { -- send up flare when valuable ores are nearby name = 'Dowse'; tone = {241,251,113}; + cost = 1; desc = 'strike colored sparks when used to dig near valuable ore.'; + groups = {'pick','pickaxe'}; affinity = 'cognic'; - apply = function(stack,power) + recipe = { + {lens = 'concave', gem = 'ruby', dmg = 3}; + {lens = 'concave', gem = 'emerald', dmg = 3}; + {lens = 'concave', gem = 'sapphire', dmg = 3}; + }; + on_dig = function(ctx) + local range = 4*sorcery.enchant.strength(ctx.tool,'dowse') + local colors = { + ['default:stone_with_gold' ] = {255,234,182}; + ['default:stone_with_mese' ] = {231,255,151}; + ['default:stone_with_diamond' ] = {180,253,255}; + ['sorcery:stone_with_iridium' ] = {243,180,255}; + ['sorcery:stone_with_tungsten'] = {119,234,196}; + } + local search = {} for k in pairs(colors) + do search[#search+1] = k end + local nodes = minetest.find_nodes_in_area( + vector.subtract(ctx.pos,range), + vector.add(ctx.pos,range), search) + for _,n in pairs(nodes) do + -- we're going to use some ugly math tricks + -- to avoid having to do a square root, since + -- that's an expensive operation and we don't + -- need that level of precision; we can + -- approximate the distance without it + local delta = vector.subtract(n,ctx.pos) + local dstsq = (delta.x^2) + (delta.y^2) + (delta.z^2) + if dstsq < range^2 then + local dstfac = 1 - (dstsq / range^2) + ctx.sparks[#ctx.sparks+1] = { + color = sorcery.lib.color(colors[minetest.get_node(n).name]); + count = 100 * dstfac; + } + end + end end; }; pierce = { -- faster mining speed name = 'Pierce'; + cost = 3; tone = {113,240,251}; - desc = 'rip through solid stone like a hot knife through butter'; + groups = { + 'pick';'pickaxe';'axe';'shovel';'sickle'; + }; + desc = 'rip through solid stone or wood like a hot knife through butter'; + recipe = { + {lens = 'amplifier', gem = 'diamond', dmg = 4}; + {lens = 'amplifier', gem = 'ruby', dmg = 4}; + {lens = 'rectifier', gem = 'diamond', dmg = 2}; + }; affinity = 'praxic'; - apply = function(stack,power) + apply = function(stack,power,base) + local caps = table.copy(stack:get_definition().tool_capabilities) + for g,v in pairs(caps.groupcaps) do + for i,t in pairs(v.times) do + local unit = base.groupcaps[g].times[i] * 0.15 + caps.groupcaps[g].times[i] = math.max(0.01, t - unit*power) + end + end + stack:get_meta():set_tool_capabilities(caps) + return stack end; }; rend = { -- more damage / mine higher level blocks name = 'Rend'; affinity = 'praxic'; tone = {251,203,113}; + groups = {'sword';'pick';'pickaxe';}; + recipe = { + {lens = 'convex', gem = 'mese', dmg = 3}; + {lens = 'amplifier', gem = 'emerald', dmg = 7}; + {lens = 'amplifier', gem = 'diamond', dmg = 7}; + }; + cost = 5; desc = 'cleave through sturdy ores and tear mortal flesh with fearsome ease'; - apply = function(stack,power) + apply = function(stack,power,base) + local caps = table.copy(stack:get_definition().tool_capabilities) + for g,v in pairs(caps.groupcaps) do + local unit = 2 + caps.groupcaps[g].maxlevel = caps[g].maxlevel + math.floor(unit*power) + end + stack:get_meta():set_tool_capabilities(caps) + return stack end; }; } Index: data/gems.lua ================================================================== --- data/gems.lua +++ data/gems.lua @@ -20,10 +20,11 @@ diamond = { foreign = 'default:diamond'; tone = {137,240,255}; items = default_items('diamond'); tools = true, armor = true; + maxenergy = 2000; slots = { {affinity = {'praxic','counterpraxic'}, confluence = 1}; {affinity = {'praxic','syncretic'}, confluence = 0.6}; {affinity = {'counterpraxic', 'entropic'}, confluence = 0.7}; }; @@ -30,10 +31,12 @@ }; mese = { foreign = 'default:mese_crystal'; foreign_shard = 'default:mese_crystal_fragment'; tone = {255,253,94}; + energysource = 5; + maxenergy = 600; items = default_items('mese'); tools = true, armor = true; slots = { {affinity = {'praxic'}, confluence = 1}; {affinity = {'praxic'}, confluence = 0.5}; Index: data/gods.lua ================================================================== --- data/gods.lua +++ data/gods.lua @@ -28,11 +28,11 @@ "flowerpot:flowers_geranium"; "flowerpot:flowers_chrysanthemum_green"; "flowerpot:flowers_dandelion_white"; "flowerpot:flowers_dandelion_yellow"; }}; - ["sorcery:dagger"] = {4, "sorcery:dagger_consecrated"}; + ["sorcery:dagger"] = {8, "sorcery:dagger_consecrated"}; ["sorcery:oil_mystic"] = {6, "sorcery:oil_purifying"}; ["sorcery:potion_water"] = {2, "sorcery:holy_water"}; -- ["default:steel_ingot"] = {15, "sorcery:holy_token_harvest"}; }; sacrifice = { @@ -90,28 +90,28 @@ ["default:bucket_lava"] = -12; ["sorcery:blood"] = -15; ["default:bones"] = -35; }; gifts = { - -- gift specs = {favor, chance} where favor is the likelihood of a god bestowing the gift - ["default:blueberry_bush_sapling"] = {35,15}; - ["farming:coffee_beans"] = {31, 9}; - ["farming:rhubarb"] = {29, 8}; - ["farming:corn"] = {27, 5}; - ["farming:grapes"] = {26, 6}; - ["farming:seed_hemp"] = {26, 8}; - ["farming:raspberries"] = {25, 5}; - ["flowers:mushroom_red"] = {25, 2}; - ["farming:garlic_clove"] = {25, 7}; - ["farming:seed_mint"] = {25, 7}; - ["farming:seed_barley"] = {24, 6}; - ["farming:beans"] = {24, 6}; + -- gift specs = {favor, chance} where chance is the likelihood of a god bestowing the gift + ["default:blueberry_bush_sapling"] = {120,15}; + ["default:blueberries"] = {60, 4}; + ["farming:coffee_beans"] = {58, 9}; + ["farming:seed_hemp"] = {55, 8}; + ["farming:garlic_clove"] = {50, 7}; + ["farming:seed_mint"] = {50, 7}; + ["flowers:mushroom_red"] = {45, 2}; + ["farming:grapes"] = {43, 6}; + ["farming:seed_barley"] = {40, 6}; + ["farming:rhubarb"] = {38, 8}; + ["farming:beans"] = {35, 6}; + ["farming:raspberries"] = {30, 5}; + ["farming:corn"] = {27, 2}; + ["farming:sugar"] = {24, 4}; + ["farming:salt"] = {24, 3}; ["farming:onion"] = {20, 7}; - ["farming:carrot"] = {19, 7}; - ["farming:sugar"] = {17, 3}; - ["farming:salt"] = {17, 3}; - ["default:apple"] = {16, 2}; - ["default:blueberries"] = {15, 4}; + ["farming:carrot"] = {20, 7}; + ["default:apple"] = {18, 2}; ["farming:wheat"] = {14, 2}; }; }; } Index: data/metals.lua ================================================================== --- data/metals.lua +++ data/metals.lua @@ -44,12 +44,13 @@ ingot = 'default:bronze_ingot'; block = 'default:bronzeblock'; artificial = true; tone = {229,115,52}; items = default_items('bronze'); + maxenergy = 150; slots = { - {affinity = {'counterpraxic'}; confluence = 0.7;}; + {affinity = {'counterpraxic'}; confluence = 0.7}; }; mix = { metals = { copper = 4; tin = 1; @@ -59,58 +60,64 @@ steel = { ingot = 'default:steel_ingot'; block = 'default:steelblock'; tone = {240,240,240}; items = default_items('steel'); + maxenergy = 200; slots = { - {affinity = {'praxic'}; confluence = 0.5;}; + {affinity = {'praxic'}; confluence = 0.5}; }; }; aluminum = { tone = {196,64,32}, alpha = 128; meltpoint = 1; rarity = 12; depth = 158; power = 3; speed = 2.4; durability = 700; cooktime = 25; armor_weight = 0.3; + maxenergy = 400; slots = { - {affinity = {'syncretic'}; confluence = 0.7;}; - {affinity = {'praxic'}; confluence = 0.4;}; + {affinity = {'syncretic'}; confluence = 0.7}; + {affinity = {'praxic'}; confluence = 0.4}; }; }; levitanium = { tone = {17,255,191}, alpha = 40; meltpoint = 4; rarity = 17; depth = 870; power = 1; durability = 50; cooktime = 70; armor_weight = -2.2; armor_protection = 1; + maxenergy = 5000; no_tools = true; }; platinum = { tone = {255,233,118}, alpha = 50; meltpoint = 1; rarity = 15; depth = 580; power = 4; speed = 3; durability = 1400; cooktime = 40; armor_weight = 0.7; + maxenergy = 1000; slots = { {affinity = {'praxic','counterpraxic'}; confluence = 0.3}; {affinity = {'counterpraxic'}; confluence = 0.8}; } }; gold = { ingot = 'default:gold_ingot'; block = 'default:goldblock'; tone = {255,225,47}; + maxenergy = 3000; slots = { {affinity = {'praxic','counterpraxic'}; confluence = 1.4}; {affinity = {'praxic','counterpraxic'}; confluence = 1.2}; } }; silver = { tone = {218,255,246}; + maxenergy = 2000; depth = 380; rarity = 13.5; no_armor = true; no_tools = true; power = 1; cooktime = 8; hardness = 1; }; electrum = { @@ -125,41 +132,51 @@ no_tools = true; }; tungsten = { tone = {0,255,163}, alpha = 70; rarity = 14; - speed = 2.5; + speed = 2.8; power = 4; meltpoint = 4; cooktime = 100; - durability = 2500; + durability = 2700; + maxenergy = 1500; slots = { {affinity = {'counterpraxic'}, confluence = 0.6}; - {affinity = {'praxic','counterpraxic'}, confluence = 0.8}; + {affinity = {'praxic','counterpraxic'}, confluence = 1}; {affinity = {'praxic'}, confluence = 0.5}; }; }; cobalt = { tone = {48,101,255}, alpha = 90; - rarity = 15; - durabilty = 650; + rarity = 17; + durabilty = 900; power = 3; speed = 3.5; cooktime = 30; + maxenergy = 3500; + slots = { + { + affinity = {'counterpraxic'}; + confluence = 0.65; + interference = {speed = 1}; + }; + } }; lithium = { tone = {255,252,93}, alpha = 80; - rarity = 12.5; + rarity = 13; no_tools = true; no_armor = true; }; iridium = { tone = {209,88,241}, alpha = 80; - rarity = 17; + rarity = 18; meltpoint = 3; cooktime = 340; - durability = 1700; + maxenergy = 1800; + durability = 1900; speed = 3.2; img = { -- ingot = 'sorcery_iridium_ingot.png'; -- block = 'sorcery_iridium_block.png'; }; @@ -166,11 +183,11 @@ slots = { {affinity={'counterpraxic','syncretic'}, confluence = 1.1}; {affinity={'cognic','entropic'}, confluence = 0.8}; }; }; - duranium = { + duridium = { tone = {255,64,175}, alpha = 70; cooktime = 120; artificial = true; durability = 3400; speed = 3.1; @@ -183,10 +200,11 @@ }; }; img = { -- ingot = 'sorcery_duranium_ingot.png'; }; + maxenergy = 1300; slots = { {affinity={'counterpraxic'}, confluence = 0.6}; {affinity={'counterpraxic'}, confluence = 0.4}; }; }; @@ -195,20 +213,21 @@ cooktime = 260; meltpoint = 5; artificial = true; speed = 2.1; durability = 5300; + maxenergy = 2300; watercool = true; mix = { metals = { duranium = 4; iridium = 2; levitanium = 1; }; }; slots = { - {affinity={'praxic'}, confluence = 1.2}; + {affinity={'praxic'}, confluence = 1.2, interference={durability=2}}; {affinity={'praxic','syncretic'}, confluence = 0.8}; {affinity={'cognic'}, confluence = 0.9}; }; }; eternium = { @@ -215,10 +234,11 @@ tone = {156,82,222}, alpha = 100; cooktime = 500; meltpoint = 6; artificial = true; speed = 2; + maxenergy = 1200; durability = 8100; watercool = true; mix = { metals = { iridium = 2; @@ -234,10 +254,11 @@ unobtanium = { tone = {114,255,214}, alpha = 120; meltpoint = 3; cooktime = 330; artificial = true; + maxenergy = 4000; durability = 3300; speed = 3.4; slots = { {affinity={'praxic'}, confluence = 0.7}; {affinity={'counterpraxic'}, confluence = 1.2}; Index: data/oils.lua ================================================================== --- data/oils.lua +++ data/oils.lua @@ -7,11 +7,11 @@ mix = { 'farming:sugar'; 'group:food_blueberries'; 'farming:raspberries'; 'sorcery:extract_wheat'; - 'sorcery:extract_wheat'; + 'xdecor:honey'; }; }; berry = { color = {151,67,183}; core = {}; Index: data/philters.lua ================================================================== --- data/philters.lua +++ data/philters.lua @@ -2,21 +2,21 @@ dark = { color = {86,16,42}; infusion = "default:obsidian_shard"; }; shimmering = { - color = {112,255,170}; + color = {224,255,155}; infusion = "default:mese_crystal_fragment"; }; silent = { color = {249,193,42}; infusion = "sorcery:extract_cotton"; }; verdant = { - infusion = "default:apple_sapling"; + infusion = "default:sapling"; color = {78,222,113}; }; blazing = { infusion = "sorcery:oil_flame"; color = {229,32,53}; }; } Index: data/potions.lua ================================================================== --- data/potions.lua +++ data/potions.lua @@ -17,11 +17,11 @@ }; Luminous = { color = {255,237,160}; style = 'dull'; glow = 12; - infusion = 'xdecor:honey'; + infusion = 'sorcery:gem_luxite'; }; Soft = { color = {174,210,85}; style = 'sparkle'; infusion = 'sorcery:extract_cotton'; Index: data/register.lua ================================================================== --- data/register.lua +++ data/register.lua @@ -3,9 +3,12 @@ sorcery.data.infusions[#sorcery.data.infusions + 1] = r end; alloy = function(mix) sorcery.data.alloys[#sorcery.data.alloys + 1] = mix end; + kiln_recipe = function(mix) + sorcery.data.kilnrecs[#sorcery.data.kilnrecs + 1] = mix + end; infusion_leftover = function(orig, leave) sorcery.data.infusion_leftovers[orig] = leave end; } Index: data/spells.lua ================================================================== --- data/spells.lua +++ data/spells.lua @@ -43,22 +43,122 @@ -- out a candidate slot, criteria being that the slot -- possesses the proper affinity, and that the corresponding -- slot on the item enchantment record is empty, then pick -- the final slot at random from this table if #candidates>0 -- (otherwise, we fail unceremoniously). - -- + + local current_enchant = sorcery.enchant.get(subj) + local material, eligible_spells = sorcery.enchant.getsubj(subj) + if eligible_spells == nil + or #eligible_spells == 0 + or material == nil + or material.data.slots == nil + or #material.data.slots == 0 + then return false end + + -- determine the properties the enchantment will have + local power = 10 + local energy = material.data.maxenergy + local reliability = 100 + if ctx.base.gem == 'sapphire' then power = power + 5 + elseif ctx.base.gem == 'amethyst' then + energy = energy * (math.random() * 0.7) + elseif ctx.base.gem == 'diamond' then + if math.random(5) == 1 then + power = power * 2 + reliability = reliability - (reliability / 4) + else + power = power + 5 + reliability = reliability - 10 + end + end + + local viable_slots = {} + for i,slot in pairs(material.data.slots) do + if sorcery.lib.tbl.has(slot.affinity,aff) then + for _,spell in pairs(current_enchant.spells) do + if spell.slot == i then goto nonviable end + end + viable_slots[#viable_slots + 1] = i + end + ::nonviable::end + if #viable_slots == 0 then return false end + + local target_slot = viable_slots[math.random(#viable_slots)] -- once we have selected an appropriate slot, we iterate -- through the list of known enchantments to check the -- affinity of each against `aff`. if the affinity matches -- the wand, we then check whether the 'recipe' matches. -- if so, we've found the enchantment -- apply it to the -- object and update its enchantment data. otherwise, we -- move on to the next. if no matching recipe is found, - -- we give up and bail, returning 'nil' to signify that + -- we give up and bail, returning 'false' to signify that -- a spell was not cast. - enchantment_sparkle(ctx,affcolor) + local focus_match = function(spec,stack) + local default_mode + if spec.lens then + default_mode = 'dmg' + if minetest.get_item_group(stack:get_name(), 'sorcery_enchanting_lens') == 0 + then return false end + local proto = stack:get_definition()._proto + if proto.kind ~= spec.lens or proto.gem ~= spec.gem + then return false end + elseif spec.item then + default_mode = 'consume' + if stack:get_name() ~= spec.item then + return false end + else + return false + end + + local mode + if spec.dmg then mode = 'dmg' + elseif spec.consume then mode = 'consume' + else mode = default_mode end + + if mode == 'dmg' then + stack:add_wear((spec.dmg or 1) * 1000) + return stack + elseif mode == 'consume' then + stack:take_item(spec.consume or 1) + return stack + end + end + for ench,data in pairs(sorcery.data.enchants) do + if data.affinity ~= aff or data.recipe == nil then goto skip end + local newinv = {} + for s,v in pairs(data.recipe) do + newinv[s] = focus_match(v,inv:get_stack('foci',s)) + if newinv[s] == false then goto skip end + end + -- is the tool compatible with the chosen spell? we check its groups to + -- see if any of them grant it eligibility; if not, we skip to the next + -- spell. NOTE: this means the same recipe could mean different things + -- for different kinds of tools, if one were so inclined. + for g,v in pairs(subj:get_definition().groups) do + if v ~= 0 and sorcery.lib.tbl.has(data.groups, g) + then goto enchant end + end + goto skip + -- spell matches! + ::enchant:: current_enchant.spells[#current_enchant.spells + 1] = { + id = ench; + slot = target_slot; + boost = power; + reliability = reliability; + } + current_enchant.energy = math.max(current_enchant.energy, energy) + + sorcery.enchant.set(subj, current_enchant) + inv:set_stack('item',1,subj) + for i,v in pairs(newinv) do inv:set_stack('foci',i,v) end + sorcery.enchant.update_enchanter(ctx.target.under) + enchantment_sparkle(ctx,affcolor) + do return nil end + ::skip::end + return false end }; end -- note: this was written before terminology was standardized, -- and "leytype" corresponds to what is otherwise known as an @@ -95,17 +195,18 @@ cast = function(ctx) if ctx.target == nil or ctx.target.type ~= 'node' then return false end local meta = minetest.get_meta(ctx.target.under) -- first we need to check if the wand has an identifying 'key' yet, -- and set one if not. - local wandmode = ctx.base.gem == 'amethyst' + local wandmode = ctx.base.gem == 'sapphire' local keycode if ctx.meta:contains('sorcery_wand_key') then keycode = ctx.meta:get_string('sorcery_wand_key') else keycode = sorcery.lib.str.rand(32) ctx.meta:set_string('sorcery_wand_key', keycode) + -- ctx.meta:mark_as_private('sorcery_wand_key') end if meta:contains('owner') then -- owner is already set -- can we break the enchantment? if meta:get_string('sorcery_wand_key') == keycode then meta:set_string('owner','') @@ -113,10 +214,11 @@ meta:set_string('sorcery_seal_mode','') enchantment_sparkle(ctx,sorcery.lib.color(101,255,142)) else return false end else meta:set_string('sorcery_wand_key',keycode) + meta:mark_as_private('sorcery_wand_key') meta:set_string('owner',ctx.caster:get_player_name()) if wandmode then meta:set_string('sorcery_seal_mode','wand') end enchantment_sparkle(ctx,sorcery.lib.color(255,201,27)) @@ -124,29 +226,31 @@ end; }; leyspark = { name = 'Leyspark'; leytype = 'cognic'; + color = {255,246,142}; -- bright yellow affinity = {'apple','silent'}; - uses = 64; + uses = 128; desc = 'Reveal the strength and affinities of the local leyline'; cast = function(ctx) local color = ctx.base.gem == 'sapphire'; + local duration = (ctx.base.gem == 'amethyst' and 4) or 2; local ley = sorcery.ley.estimate(ctx.caster:get_pos()) local emit = function(color,strength) minetest.add_particlespawner { amount = 70 * strength; - time = 2 * strength; + time = duration * strength; attached = ctx.caster; texture = sorcery.lib.image('sorcery_spark.png'): - multiply(color):render(); - minpos = { x = 0.5, z = 0.0, y = 1.5}; - maxpos = { x = 0.5, z = 0.0, y = 1.5}; + multiply(color:brighten(1.3)):render(); + minpos = { x = -0.1, z = 0.5, y = 1.2}; + maxpos = { x = 0.1, z = 0.3, y = 1.6}; minvel = { x = -0.5, z = -0.5, y = -0.5}; maxvel = { x = 0.5, z = 0.5, y = 0.5}; minacc = { x = 0.0, z = 0.0, y = 0.5}; - minacc = { x = 0.0, z = 0.0, y = 0.5}; + maxacc = { x = 0.0, z = 0.0, y = 0.5}; minsize = 0.4, maxsize = 0.8; minexptime = 1, maxexptime = 1; glow = 14; animation = { type = 'vertical_frames'; @@ -170,12 +274,13 @@ end; }; dowse = { name = 'dowsing'; leytype = 'cognic'; + color = {65,116,255}; affinity = {'acacia','dark','silent'}; - uses = 128; + uses = 176; desc = 'Send up sparks of radia to indicate nearness or absence of attuned blocks'; }; verdant = { name = 'verdant'; color = {16,29,255}; @@ -190,58 +295,114 @@ syncretic = anchorwand('syncretic', 12, {'aspen','verdant','shimmering','blazing'}); cognic = anchorwand('cognic', 36, {'acacia','verdant','dark'}); shape = { name = 'shaping'; uses = 24; + color = {255,114,65}; + leytype = 'praxic'; affinity = {'apple','blazing'}; desc = 'With an enchanter, physically alter the mundane qualities of an object'; }; attune = { name = 'attunement'; uses = 38; + color = {255,65,207}; leytype = 'syncretic'; affinity = {'pine','verdant','dark'}; desc = 'Establish a connection between mystic mechanisms, like connecting two sides of a portal or impressing targets onto a dowsing wand in an enchanter'; }; meld = { name = 'melding'; uses = 48; leytype = 'syncretic'; + color = {172,65,255}; affinity = {'apple','verdant'}; desc = 'Meld the properties of three balanced items on an enchanter to create a new one with special properties, but destroying the old ones and losing two thirds of the mass in the process. The precise outcome is not always predictable.'; }; divide = { name = 'division'; uses = 19; leytype = 'syncretic'; + color = {255,65,121}; affinity = {'apple','shimmering'}; desc = 'Shatter an item on an enchanter, dividing its essence equally into three parts and precipitating it into new items embodying various properties of the destroyed item. The outcome is not always predictable.'; }; obliterate = { name = 'obliteration'; uses = 129; + color = {175,6,212}; affinity = {'aspen','dark'}; leytype = 'occlutic'; desc = 'Totally and irreversibly obliterate all items on an enchanter.'; }; sacrifice = { name = 'sacrifice'; uses = 58; + color = {212,6,63}; affinity = {'aspen','blazing'}; leytype = 'syncretic'; desc = 'Transform the matter of one to three items on an enchanter into energy and empower the item on the center of the enchanter with it. Useful to recharge wands in areas with weak leylines.'; }; transfer = { name = 'transfer'; uses = 65; + color = {6,212,121}; leytype = 'syncretic'; affinity = {'aspen','shimmering','silent'}; desc = 'Transfer ley-current from items on an enchanter into the item in the center, but at a 50% loss if they are of mismatched affinities. One third of maximum current is transferred, and when used on items with little power may destroy them or their enchantments'; }; + transmute = { + name = 'transmutation'; + uses = 7; + color = {255,90,18}; + leytype = 'imperic'; + affinity = {'aspen','shimmering','dark','blazing'}; + desc = 'Transmute three ingots into one of a different metal, determined by chance and influenced by configuration of the wand'; + }; disjoin = { name = 'disjunction'; uses = 32; + color = {17,6,212}; leytype = 'occlutic'; affinity = {'jungle','silent'}; desc = 'With an enchanter, disjoin the anchor holding a spell into an object so a new spell can instead be bound in'; + }; + luminate = { + name = 'lumination'; + desc = 'Banish darkness all about you for a few moments'; + uses = 40; + color = {244,255,157}; + affinity = {'acacia','shimmering','blazing'}; + leytype = 'cognic'; + cast = function(ctx) + local center = ctx.heading.pos + local maxpower = 20 + local power = (ctx.base.gem == 'sapphire' and maxpower) or maxpower/2 + local range = (ctx.base.gem == 'emerald' and 10) or 5 + local duration = (ctx.base.gem == 'amethyst' and 60) or 30 + if ctx.base.gem == 'diamond' then + power = power * (math.random()*2) + range = range * (math.random()*2) + duration = duration * (math.random()*2) + end + local lum = math.ceil((power/maxpower) * minetest.LIGHT_MAX) + print('setting lum',lum) + for i=1,power do + local pos = vector.add(center, { + x = math.random(-range,range); + z = math.random(-range,range); + y = math.random(0,range/2); + }) + local delta = vector.subtract(pos,center) + local near = 1 - (delta.x^2 + delta.y^2 + delta.z^2)/(range^2) + if minetest.get_node(pos).name == 'air' then + minetest.set_node(pos,{name='sorcery:air_glimmer_' .. tostring(lum)}) + do local lm = minetest.get_meta(pos) + lm:set_float('duration',duration) + lm:set_float('timeleft',duration) + lm:set_float('power',lum * near) + end + end + end + end; }; } Index: enchanter.lua ================================================================== --- enchanter.lua +++ enchanter.lua @@ -4,118 +4,22 @@ -0.5, -0.5, -0.5; 0.5, 0.1, 0.5; }; } -local enchantable_tools = { - pickaxe = {}, pick = {}; - axe = {}; - sword = {}; - sickle = {}; - shovel = {}; - hoe = {}; -}; - -sorcery.enchant = {} do - local m = sorcery.lib.marshal - local ench_t = m.g.struct { - id = m.t.str; - slot = m.t.u8; - boost = m.t.u8; -- every enchantment has an intrinsic force - -- separate from the confluence of the slot, which is - -- determined by the composition of the wand used to generate - -- it (for instance, a gold-wired wand at low wear, or a wand - -- with specific gemstones, may have a boost level above 0) - } - local pack, unpack = m.transcoder { - spells = m.g.array(8, ench_t); - energy = m.t.u16; - } - local key = 'sorcery_enchantment_recs' - sorcery.enchant.set = function(stack, data) - local meta = stack:get_meta() - meta:set_string(key, pack(data)) - end - sorcery.enchant.get = function(stack) - local meta = stack:get_meta() - if meta:contains(key) then - local data = meta:get_string(key) - return unpack(data) - else - return { - spells = {}; - energy = 0; - } - end - end - sorcery.enchant.strength = function(stack,id) - -- this functions should be used whenever you need to - -- determine the power of a particular enchantment on - -- an enchanted item. - local e = sorcery.enchant.get(stack) - local p = 0.0 - local slots = sorcery.matreg.lookup[stack:get_name()].data.slots - -- TODO handle strength-boosting spells! - for _,s in pairs(e.spells) do - if s.id == id then p = p + slots[s.slot] end - end - return p - end - sorcery.enchant.stackup = function(stack) - -- stack update function. this should be called whenever - -- the enchantment status of a stack changes; it will - -- alter/reset tool capabilities and tooltip as necessary - local e = sorcery.enchant.get(stack) - local meta = stack:get_meta() - local def = stack:get_definition() - meta:set_string('tool_capabilities','') - local done = {} - local props = {} - for _,s in pairs(e.spells) do - if done[s.id] then goto skip end - done[s.id] = true - local pwr = sorcery.enchant.strength(stack,s.id) - -- somewhat wasteful… - local name, color, desc = sorcery.data.enchants[s.id].apply(stack,pwr) - props[#props+1] = { - title = name; - desc = desc; - color = sorcery.lib.color(desc); - } - ::skip::end - if #e.spells > 0 then - meta:set_string('description', sorcery.lib.ui.tooltip { - title = 'Enchanted ' .. def.description; - props = props; - }) - else - meta:set_string('description','') - end - end -end - -local enchanter_getsubj = function(item) - if not item:is_empty() then - for group, spells in pairs(enchantable_tools) do - if minetest.get_item_group(item:get_name(), group) ~= 0 then - return group, sorcery.matreg.lookup[item:get_name()] - end - end - end - return false -end local enchanter_update = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() local item = inv:get_stack('item',1) local slots = '' - local itype, imat = enchanter_getsubj(item) - if itype and imat and imat.data.slots then + local imat = sorcery.enchant.getsubj(item) + if imat and imat.data.slots then local n = #imat.data.slots local sw, sh = 2.1, 2.1; local w = sw * n; - local spells = sorcery.enchant.get(item).spells + local item_enchantment = sorcery.enchant.get(item) + local spells = item_enchantment.spells for i=1,n do local slot=imat.data.slots[i] local x = (4 - (w/2) + (sw * (i-1))) + 0.2 local offtbl = { [1] = {0}; @@ -141,14 +45,38 @@ } end local hovertitle = 'Empty spell slot'; local conf = tostring(math.floor(slot.confluence*100)) .. '%' local hoverdesc = 'An enchantment of one the following affinities can be anchored into this slot at ' .. conf .. ' confluence'; + local hovercolor = nil for _,sp in pairs(spells) do + local spdata = sorcery.data.enchants[sp.id] if sp.slot == i then + hovercolor = sorcery.lib.color(spdata.tone):readable() hovertitle = sorcery.lib.str.capitalize(sp.id) - hoverdesc = 'An enchantment is anchored in this slot at ' .. conf .. ' confluence' + hoverdesc = sorcery.lib.str.capitalize(spdata.desc) .. '. Anchored in this slot at ' .. conf .. ' confluence' + if sp.boost > 10 then + hoverdesc = hoverdesc .. ' and boosted by ' .. tostring((sp.boost - 10) * 10) .. '%' + elseif sp.boost < 10 then + hoverdesc = hoverdesc .. ' but weakened by ' .. tostring((10 - sp.boost) * 10) .. '%' + end + hoverdesc = hoverdesc .. '.' + local addrune = function(tex) + pwr = pwr .. string.format([[ + image[%f,%f;%f,%f;%s] + ]], x+0.43,y+0.6,sw/2.7,sh/2.7, tex) + end + local rune = 'sorcery_enchant_' .. sp.id .. '.png' + local energy = item_enchantment.energy + local fullenergy = imat.data.maxenergy + if energy <= fullenergy then + addrune(rune .. '^[opacity:110^[lowpart:' .. tostring(math.floor((energy/fullenergy) * 100)) .. '%:' .. rune) + elseif energy == fullenergy then addrune(rune) + elseif energy >= fullenergy then + addrune(rune .. '^[colorize:#ffffff:' .. + tostring(255 * math.min(1,(energy / fullenergy * 2)))) + end break end end slots = slots .. string.format([[ image[%f,%f;%f,%f;sorcery_pentacle.png] @@ -157,10 +85,11 @@ x,y, sw,sh, x+0.20,y+0.16, sw-0.84,sh-0.76, minetest.formspec_escape(sorcery.lib.ui.tooltip { title = hovertitle; desc = hoverdesc; + color = hovercolor; props = ap; }), '#37002C','#FFC8F5' ) .. pwr end @@ -177,10 +106,127 @@ list[current_player;main;0,4.7;8,4;] listring[current_player;main] listring[context;item] ]] .. slots) end + +sorcery.enchant = {} do + sorcery.enchant.update_enchanter = enchanter_update + local m = sorcery.lib.marshal + local ench_t = m.g.struct { + id = m.t.str; + slot = m.t.u8; + boost = m.t.u8; -- every enchantment has an intrinsic force + -- separate from the confluence of the slot, which is + -- determined by the composition of the wand used to generate + -- it (for instance, a gold-wired wand at low wear, or a wand + -- with specific gemstones, may have a boost level above 10) + -- boost is divided by 10 to yield a float + reliability = m.t.u8; + -- reliability is a value between 0 and 100, where 0 means the + -- spell fails every time and 100 means it never fails. not + -- relevant for every spell + } + local pack, unpack = m.transcoder { + spells = m.g.array(8, ench_t); + energy = m.t.u16; + } + sorcery.enchant.getsubj = function(item) + if not item:is_empty() then + local eligible = {} + for name, spell in pairs(sorcery.data.enchants) do + for g,v in pairs(item:get_definition().groups) do + if v~= 0 and sorcery.lib.tbl.has(spell.groups,g) then + eligible[#eligible+1] = name + goto skip + end + end + ::skip::end + return sorcery.matreg.lookup[item:get_name()], eligible + else return nil end + end + local key = 'sorcery_enchantment_recs' + sorcery.enchant.set = function(stack, data, noup) + local meta = stack:get_meta() + meta:set_string(key, sorcery.lib.str.meta_armor(pack(data),true)) + if not noup then stack=sorcery.enchant.stackup(stack) end + end + sorcery.enchant.get = function(stack) + local meta = stack:get_meta() + if meta:contains(key) then + local data = sorcery.lib.str.meta_dearmor(meta:get_string(key),true) + return unpack(data) + else + return { + spells = {}; + energy = 0; + } + end + end + sorcery.enchant.strength = function(stack,id) + -- this functions should be used whenever you need to + -- determine the power of a particular enchantment on + -- an enchanted item. + local e = sorcery.enchant.get(stack) + local p = 0.0 + local slots = sorcery.matreg.lookup[stack:get_name()].data.slots + -- TODO handle strength-boosting spells! + for _,s in pairs(e.spells) do + print(dump(s)) + if s.id == id then p = p + ((s.boost * slots[s.slot].confluence)/10) end + end + return p + end + sorcery.enchant.stackup = function(stack) + -- stack update function. this should be called whenever + -- the enchantment status of a stack changes; it will + -- alter/reset tool capabilities and tooltip as necessary + local e = sorcery.enchant.get(stack) + local meta = stack:get_meta() + local def = stack:get_definition() + local mat = sorcery.enchant.getsubj(stack) + local done = {} + local props = {} + local interference = {} + -- meta:set_string('tool_capabilities','') + meta:set_tool_capabilities(nil); -- TODO this probably only works + -- in >5.3; maybe bring in the old JSON mechanism so it works in + -- older versions as well? + local basecaps = def.tool_capabilities + for _,s in pairs(e.spells) do + if done[s.id] then goto skip end + done[s.id] = true + local pwr = sorcery.enchant.strength(stack,s.id) + -- somewhat wasteful… + local e = sorcery.data.enchants[s.id] + if e.apply then stack = e.apply(stack,pwr,basecaps) end + props[#props+1] = { + title = e.name; + desc = e.desc; + color = sorcery.lib.color(e.tone); + } + local inf = mat.data.slots[s.slot].interference + if inf then for k,v in pairs(inf) do + interference[k] = interference[k] + v + end end + ::skip::end + if #interference > 0 then + if interference.speed then stack = sorcery.data.enchants.pierce.apply(stack,-interference.speed,basecaps) end + if interference.durability then stack = sorcery.data.enchants.endure.apply(stack,-interference.durability,basecaps) end + end + meta = stack:get_meta() -- necessary? unclear + if #e.spells > 0 then + meta:set_string('description', sorcery.lib.ui.tooltip { + title = 'Enchanted ' .. def.description; + props = props; + }) + else + meta:set_string('description',def.description) + end + return stack + end +end minetest.register_node('sorcery:enchanter', { description = 'Enchanter'; drawtype = 'mesh'; mesh = 'sorcery-enchanter.obj'; @@ -198,19 +244,56 @@ "default_gold_block.png"; }; on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() + meta:set_string('infotext','Enchanter') inv:set_size('item', 1) inv:set_size('foci', 3) enchanter_update(pos) end; on_metadata_inventory_put = enchanter_update; on_metadata_inventory_move = enchanter_update; on_metadata_inventory_take = enchanter_update; }) +minetest.register_craftitem('sorcery:enchanter_channeler',{ + inventory_image = 'sorcery_enchanter_channeler.png'; + description = 'Channeler'; +}) +minetest.register_craftitem('sorcery:enchanter_pedestal',{ + inventory_image = 'sorcery_enchanter_pedestal.png'; + description = 'Pedestal'; +}) +minetest.register_craft { + output = 'sorcery:enchanter_channeler'; + recipe = { + {'','default:bronze_ingot',''}; + {'basic_materials:gold_wire','basic_materials:steel_strip','basic_materials:gold_wire'}; + {'','sorcery:electrum_ingot',''}; + }; + replacements = { + {'basic_materials:gold_wire','basic_materials:empty_spool'}; + {'basic_materials:gold_wire','basic_materials:empty_spool'}; + }; +} +minetest.register_craft { + output = 'sorcery:enchanter_pedestal'; + recipe = { + {'basic_materials:copper_strip','group:wood','basic_materials:copper_strip'}; + {'','default:bronze_ingot',''}; + {'','group:wood',''}; + }; +} +minetest.register_craft { + output = 'sorcery:enchanter'; + recipe = { + {'','sorcery:enchanter_channeler',''}; + {'sorcery:enchanter_channeler','sorcery:enchanter_pedestal','sorcery:enchanter_channeler'}; + {'group:wood','group:wood','group:wood'}; + }; +} for i=1,10 do minetest.register_node('sorcery:air_flash_' .. i, { drawtype = 'airlike'; pointable = false; walkable = false; buildable_to = true; @@ -228,30 +311,59 @@ }); end minetest.register_on_dignode(function(pos, node, puncher) if puncher == nil then return end -- i don't know why - -- this is necessary but you get rare crashed without it + -- this is necessary but you get rare crashes without it -- we're goint to do something VERY evil here and -- replace the air with a "glow-air" that removes -- itself after a short period of time, to create -- a flash of light when an enchanted tool's used -- to dig out a node local tool = puncher:get_wielded_item() - local meta = tool:get_meta() + local ench = sorcery.enchant.get(tool) + if #ench.spells == 0 then return end + print('enchanted!') local sparks = {} - local spark = function(name,color) - if meta:contains('enchant_' .. name) then - sparks[#sparks + 1] = { - color = color; - count = meta:get_int('enchant_' .. name); - } - end + -- local spark = function(name,color) + local material = sorcery.enchant.getsubj(tool) + local totalcost = 0 + do local done = {} for _,sp in pairs(ench.spells) do + if done[sp.id] then goto skip end + done[sp.id] = true + + local data = sorcery.data.enchants[sp.id] + local strength = sorcery.enchant.strength(tool,sp.id) + local ch = math.random(1,100) + local props = { + fail = ch > sp.reliability; + user = puncher; + pos = pos; + node = node; + tool = tool; + material = material.data; + enchantment = ench; + power = strength; + spell = sp; + sparks = sparks; + cost = data.cost; + } + if data.on_dig then data.on_dig(props) end + if props.cost ~= 0 then totalcost = totalcost + math.max(1,math.floor(props.cost * strength)) end + + if ch > sp.reliability then goto skip end + sparks[#sparks + 1] = { + color = sorcery.lib.color(data.tone):brighten(1.1); + count = strength * 7; + } + ::skip::end end + if totalcost > 0 then + local conservation = sorcery.enchant.strength(tool,'conserve') * 0.5 + totalcost = totalcost - (totalcost * conservation) + ench.energy = math.max(0,ench.energy - (totalcost - (material.data.energysource or 0))) end - spark('durable',sorcery.lib.color(0,89,245)) - spark('fast',sorcery.lib.color(245,147,89)) if #sparks == 0 then return end if math.random(5) == 1 then minetest.set_node(pos, {name='sorcery:air_flash_' .. tostring(math.random(10))}) end local range = function(min, max) @@ -258,11 +370,11 @@ local span = max - min local val = math.random() * span return val + min end for _,s in pairs(sparks) do - for i=0,math.floor(s.count * range(1,3)) do + for i=1,math.floor(s.count * range(1,3)) do local life = range(0.3,1); minetest.add_particle { pos = { x = pos.x + range(-0.5,0.5); z = pos.z + range(-0.5,0.5); @@ -278,18 +390,41 @@ z = range(-1.3,1.3); y = range( 0.3,0.9); }; expirationtime = life; size = range(0.5,1.5); - vertical = true; - texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color:brighten(1.2)):render(); + texture = sorcery.lib.image('sorcery_spark.png'):multiply(s.color):render(); glow = 14; animation = { type = "vertical_frames"; aspect_w = 16; aspect_h = 16; - length = life * 1.1; + length = life + 0.1; }; } end end + + -- destroy spells that can no longer be sustained + if ench.energy == 0 then + ench.spells = {} + sorcery.enchant.set(tool,ench) + else + sorcery.enchant.set(tool,ench,true) + end + puncher:set_wielded_item(tool) end) + +minetest.register_chatcommand('enchants', { + description = 'Log information about the currently held object\'s enchantment'; + privs = { server = true }; + func = function(caller,params) + local tool = minetest.get_player_by_name(caller):get_wielded_item() + minetest.chat_send_player(caller, dump(sorcery.enchant.get(tool))) + local binary = tool:get_meta():get_string('sorcery_enchantment_recs') + local dumpout = '' + for i=1,string.len(binary) do + dumpout = dumpout .. string.format('%x%s',string.byte(binary,i),(i%16==0 and '\n') or ' ') + end + print(dumpout) + end; +}) Index: gems.lua ================================================================== --- gems.lua +++ gems.lua @@ -1,20 +1,21 @@ --- gemstones sorcery.register_gem = function(name,gem) local itemname = gem.foreign or 'sorcery:gem_' .. name local shardname = gem.foreign_shard or 'sorcery:gem_' .. name .. '_shard' + local amuletname = gem.foreign_amulet or 'sorcery:gem_' .. name .. '_amulet' local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors if gem.tools then for _,t in pairs(tools) do sorcery.matreg.lookup[(gem.items and gem.items[t]) or ('sorcery:' .. t .. '_' .. name)] = { gem = true; id = name; data = gem; } end end if gem.armor then for _,a in pairs(armors) do - sorcery.matreg.lookup[(gem.items and gem.items[t]) or ('sorcery:' .. a .. '_' .. name)] = { + sorcery.matreg.lookup[(gem.items and gem.items[a]) or ('sorcery:' .. a .. '_' .. name)] = { gem = true; id = name; data = gem; } end end @@ -25,10 +26,20 @@ description = sorcery.lib.str.capitalize(name) .. ' shard'; inventory_image = 'sorcery_gem_' .. name .. '_shard.png'; groups = { sorcery_shard = 1; }; _proto = gem; }) + end + if not gem.foreign_amulet then + minetest.register_craftitem(amuletname, { + description = sorcery.lib.str.capitalize(name) .. ' amulet'; + inventory_image = sorcery.lib.image('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)):render(); + _proto = { + id = name; + data = gem; + }; + }) end minetest.register_craft { type = 'shapeless'; recipe = (minetest.get_modpath('xdecor') and { 'xdecor:hammer', itemname; @@ -45,21 +56,33 @@ shardname, shardname, shardname; shardname, shardname, shardname; }; output = itemname; }; + minetest.register_craft { + recipe = { + {shardname,itemname,shardname}; + {itemname,itemname,itemname}; + {shardname,itemname,shardname}; + }; + output = amuletname; + }; -- generate lenses and crafting recipes for _, kind in pairs { 'amplifier','rectifier','concave','convex' } do local id = 'sorcery:lens_' .. kind .. '_' .. name minetest.register_tool(id, { inventory_image = sorcery.lib.image('sorcery_lens_overlay_gold.png'): blit(sorcery.lib.image('sorcery_lens_' .. kind .. '.png'): multiply(sorcery.lib.color(gem.tone):brighten(1.1))): - render(); + render(); description = sorcery.lib.str.capitalize(name) .. ' ' .. kind .. ' lens'; - group = { sorcery_enchanting_lens = 1 }; + groups = { sorcery_enchanting_lens = 1 }; + _proto = { + gem = name; + kind = kind; + }; }) end do local casing = 'sorcery:fragment_gold' minetest.register_craft { output = 'sorcery:lens_convex_' .. name; Index: harvester.lua ================================================================== --- harvester.lua +++ harvester.lua @@ -38,21 +38,34 @@ if inv:is_empty('charge') then return false end local ley = sorcery.ley.estimate(pos) local charged = false for i=1,inv:get_size('charge') do - local repair_per_tick = (65536 / 64) * (15 * ley.force) + local curve = function(x) return ((x*10)^2)/10 end local item = inv:get_stack('charge',i) if minetest.get_item_group(item:get_name(), 'sorcery_wand') ~= 0 then + local mese = 1 + if sorcery.wands.util.getproto(item).gem == 'mese' then + mese = 1.5 end + local repair_per_tick = (65536 / 80) * curve(ley.force) * mese local spell = item:get_meta():get_string('sorcery_wand_spell') if spell == '' then goto skip end local aff = sorcery.data.spells[spell].leytype if aff == ley.aff[1] or aff == ley.aff[2] then repair_per_tick = repair_per_tick * 2 end - else goto skip end -- item is not magical - print('repair cycle! repairing item'..item:get_name()..' by',repair_per_tick * (elapse / 60)) - item:set_wear(math.max(0,item:get_wear() - repair_per_tick * (elapse / 60))) + item:set_wear(math.max(0,item:get_wear() - repair_per_tick * (elapse / 60))) + else + local e = sorcery.enchant.get(item) + if #e.spells == 0 then goto skip end -- item is not magical + local mat = sorcery.enchant.getsubj(item) + if e.energy < mat.data.maxenergy then + local energy_per_tick = (100 * curve(ley.force)) + ((mat.data.energysource or 0)*2) + e.energy = math.min(e.energy + energy_per_tick, mat.data.maxenergy) + sorcery.enchant.set(item,e,true) + else goto skip end -- already fully charged + end + -- print('repair cycle! repairing item'..item:get_name()..' by',repair_per_tick * (elapse / 60)) inv:set_stack('charge',i,item) charged = true ::skip::end return charged @@ -60,10 +73,11 @@ on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() inv:set_size('charge', 3) + meta:set_string('infotext','Harvester') meta:set_string('formspec', [[ size[8,5] list[context;charge;2.5,0;3,1;] list[current_player;main;0,1.3;8,4;] listring[] ADDED hotmetallurgy.lua Index: hotmetallurgy.lua ================================================================== --- hotmetallurgy.lua +++ hotmetallurgy.lua @@ -0,0 +1,442 @@ +-- alloying furnace +-- +-- there are several kinds of alloy furnace, with varying +-- capabilities. the simplest can alloy two simple metals; +-- the most complex can alloy four high-grade metals such +-- as titanium, platinum, iridium, or levitanium. +-- +-- furnace recipes follow a pattern: the number of crucibles +-- determines the input slots, the type of crucible determines +-- how hot the furnace can get, and various other components +-- (like a coolant circulator) can be added to allow the +-- creation of exotic metals. +-- +-- alloy furnaces produce ingots that can later be melted down +-- again and cast into a mold to produce items of particular +-- shapes. + +-- there are five kinds of crucibles: clay, aluminum, platinum, +-- duridium, and impervium. clay crucibles are made by molding +-- clay into the proper shape and then firing it in a furnace. +-- others are made by casting. + +local fragments_per_ingot = 4 + +for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do + minetest.register_craftitem('sorcery:crucible_' .. c, { + description = sorcery.lib.str.capitalize(c .. ' crucible'); + inventory_image = 'sorcery_crucible_' .. c .. '.png'; + }) +end + +minetest.register_craftitem('sorcery:crucible_clay_molding', { + description = sorcery.lib.str.capitalize('Crucible molding'); + inventory_image = 'sorcery_crucible_clay_molding.png'; +}) + +minetest.register_craft { + recipe = { + { 'default:clay_lump', '', 'default:clay_lump'}; + { 'default:clay_lump', '', 'default:clay_lump'}; + { 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'}; + }; + output = 'sorcery:crucible_clay_molding'; +} +minetest.register_craft { + type = 'shapeless'; + recipe = { 'sorcery:crucible_clay_molding' }; + output = 'default:clay_lump 7'; +} +minetest.register_craft { + type = 'cooking'; + recipe = 'sorcery:crucible_clay_molding'; + cooktime = 40; + output = 'sorcery:crucible_clay'; +} + +local burn_layout = function(v) + local layouts = { + [1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1}; + [4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2}; + [7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3}; + } + local inpos = { x = 2.8, y = 1 } + local insize = layouts[v.in] + local outpos = { x = 5.2, y = 2.4 } + local outsize = layouts[v.out] + local fuelpos = { x = 2.8, y = 3.4 } + local fuelsize = layouts[v.fuel] + return string.format([[ + list[context;input;%f,%f;%f,%f;] + list[context;output;%f,%f;%f,%f;] + list[context;fuel;%f,%f;%f,%f;] + + image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png] + image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270] + + listring[context;output] listring[current_player;main] + listring[context;input] listring[current_player;main] + listring[context;fuel] listring[current_player;main] + ]], + + --input + inpos.x - insize.w/2, -- pos + inpos.y - insize.h/2, + insize.w, insize.h, -- size + --output + outpos.x - outsize.w/2, -- pos + outpos.y - outsize.h/2, + outsize.w, outsize.h, -- size + --fuel + fuelpos.x - fuelsize.w/2, -- pos + fuelpos.y - fuelsize.h/2, + fuelsize.w, fuelsize.h, -- size + + v.burn, v.prog + ) +end + +local kiln_formspec = function(kind, fuel_progress, cook_progress) + return [[ + size[8,8] + list[current_player;main;0,4.2;8,4] + list[context;wax;0,2;1,1] + ]] .. burn_layout{ + in = kind.size^2; + out = kind.outsize; + fuel = kind.fuelsize; + burn = fuel_progress; + prog = cook_progress; + } +end + +local smelter_formspec = function(kind, fuel_progress, smelt_progress) + return [[ + size[8,8] + list[current_player;main;0,4.2;8,4] + ]] .. burn_layout { + in = kind.size; + out = kind.outsize; + fuel = kind.fuelsize; + burn = fuel_progress; + prog = smelt_progress; + } +end + +local find_recipe = function(inv) + local mix = {} + local count = 0 + for i=1,inv:get_size('input') do + local m = inv:get_stack('input',i) + if m:is_empty() then goto skip end + local l = sorcery.data.metallookup[m:get_name()] + if not l then return false end + mix[l.id] = (mix[l.id] or 0) + l.value + count = count + l.value + ::skip::end + -- everything is metal, we've finished summing it up. + -- let's see if the assembled items match the ratio + -- specified in any of the smelting recipes. + local matches = 0 + for _,rec in pairs(sorcery.data.alloys) do + local fac = nil + local meltpoint = 1 + if rec.metals == nil then goto skip_recipe end + for metal, ratio in pairs(rec.metals) do + if mix[metal] and mix[metal] % ratio == 0 then + if fac then + if mix[metal] / ratio ~= fac then goto skip_recipe end + else fac = math.floor(mix[metal] / ratio) end + local m = sorcery.data.metals[metal] + if m.meltpoint then + meltpoint = math.max(meltpoint, m.meltpoint) end + else goto skip_recipe end + end + do return rec, count, fac, meltpoint end + ::skip_recipe::end + return false +end + +local update_smelter = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto + local recipe, count, factor, meltpoint = find_recipe(inv) + if recipe and proto.temp >= meltpoint then + minetest.get_node_timer(pos):start(1) + else + meta:set_float('burntime',0) + end +end + +local smelter_step = function(kind,active,pos,delta) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local recipe, count, factor = find_recipe(inv) + local cooktime + local elapsed = meta:get_float('burntime') + delta + meta:set_float('burnleft',meta:get_float('burnleft') - delta) + if (not active) and (not recipe) then return false end + if meta:get_float('burnleft') <= 0 or not active then + if recipe then + local burn, frep = minetest.get_craft_result { + method = 'fuel', width = 1; + items = { inv:get_stack('fuel',1) }; + } + if burn.time == 0 then goto nofuel end + inv:set_stack('fuel', 1, frep.items[1]) + meta:set_float('burnleft',burn.time) + meta:set_float('burnmax',burn.time) + if not active then + minetest.swap_node(pos, + sorcery.lib.tbl.merge(minetest.get_node(pos), { + name = kind.id .. '_active' + })) + active = true + end + else goto nofuel end + end + + if not recipe then goto update end + + cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor + if elapsed >= cooktime then + elapsed = 0 + -- remove used items + for i=1,inv:get_size('input') do + local s = inv:get_stack('input',i) + if s:is_empty() then goto skip end + s:take_item(1) inv:set_stack('input',i,s) + ::skip::end + + local outstack + if count % fragments_per_ingot == 0 then + outstack = ItemStack { + name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot'; + count = count / fragments_per_ingot; + } + else + outstack = ItemStack { + name = 'sorcery:fragment_' .. recipe.output; + count = count; + } + end + + local leftover = inv:add_item('output',outstack) + if not leftover:is_empty() then + minetest.add_item(pos, leftover) + end + end + + ::update:: + meta:set_float('burntime',elapsed) + meta:set_string('formspec', smelter_formspec(kind, + math.min(1, meta:get_float('burnleft') / + meta:get_float('burnmax') + ) * 100, -- fuel + (cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt + )) + do return active end + + ::nofuel:: + if active then + minetest.swap_node(pos, + sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id })) + end + meta:set_float('burnleft',0) -- just in case + ::noburn:: + meta:set_float('burntime',0) -- just in case + meta:set_string('formspec', smelter_formspec(kind, 0, 0)) + return false +end +local register_kiln = function(kind) + local box = { + open = { + type = 'fixed'; + fixed = { + -0.5,-0.5,-0.5, + 0.5, 1.6, 0.5 + }; + }; + closed = { + type = 'fixed'; + fixed = { + -0.5,-0.5,-0.5, + 0.5, 0.7, 0.5 + }; + }; + } + local id = 'sorcery:kiln_' .. kind.material + local metal = sorcery.data.metals[kind.material] + -- use some iffy heuristics to guess the texture + local metaltex + if kind.material == 'clay' then + metaltex = 'default_brick.png'; + else + metaltex = (metal.img and metal.img.block) or + (metal.ingot and 'default_' .. kind.material .. '_block.png') or + sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render(); + end + local tex = { + open = { + 'default_furnace_front.png'; + metaltex; + 'default_copper_block.png'; + 'default_stone.png'; + }; + closed = { + 'default_furnace_front_active.png'; + 'default_copper_block.png'; + metaltex; + 'default_stone.png'; + }; + }; + for _,state in pairs{'open','closed'} do + local id_closed = id .. '_closed' + local id_current = (state == 'closed' and id_closed) or id + local desc = sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln'; + minetest.register_node(id_current, { + _active = (state == 'closed'); + _proto = kind; + description = desc; + drawtype = "mesh"; + mesh = 'sorcery-kiln-' .. state .. '.obj'; + drop = id; + sunlight_propagates = true; + paramtype1 = 'light'; + paramtype2 = 'facedir'; + selection_box = box[state]; + collision_box = box[state]; + tiles = tex[state]; + light_source = (state == 'closed' and 7) or 0; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('input', kind.size^2) + inv:set_size('output', kind.outsize) + inv:set_size('fuel', kind.fuelsize) + inv:set_size('wax', 1) + + meta:set_float('burnleft',0) + meta:set_float('burnmax',0) + meta:set_float('burntime',0) + + meta:set_string('infotext',desc) + meta:set_string('formspec',kiln_formspec(kind,0,0)) + end; + on_timer = function(pos,delta) + end; + + on_metadata_inventory_put = function(pos) + minetest.get_node_timer(pos):start(1) end; + on_metadata_inventory_move = function(pos) + minetest.get_node_timer(pos):start(1) end; + on_metadata_inventory_take = function(pos) + minetest.get_node_timer(pos):start(1) end; + }) + end + local ingot + if kind.material == 'clay' then + ingot = 'default:clay_brick'; + else + ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot' + end + minetest.register_craft = { + output = id_open; + recipe = { + {'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'}; + {ingot,'',ingot}; + {ingot,'default:furnace',ingot}; + }; + } +end + +local register_smelter = function(kind) + local recipe = {{},{}; + {'default:stone','default:furnace','default:stone'}; + } do + local on = kind.crucible + local ti = 'default:tin_ingot' + local cu = 'default:copper_ingot' + local crucmap = { + [2] = { {cu,cu,cu}, {on,ti,on} }; + [3] = { {cu,on,cu}, {on,ti,on} }; + [4] = { {on,cu,on}, {on,ti,on} }; + [5] = { {on,cu,on}, {on,on,on} }; + [6] = { {on,on,on}, {on,on,on} }; + }; + for y=1,2 do recipe[y] = crucmap[kind.size][y] end + end + + local desc = 'smelter'; + if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end + if kind.size_name then desc = kind.size_name .. ' ' .. desc end + desc = sorcery.lib.str.capitalize(desc); + local id = 'sorcery:smelter_' .. kind.material .. kind.size_name + kind.id = id + for _, active in pairs {false, true} do + minetest.register_node((active and id .. '_active') or id, { + _proto = kind; + description = desc; + drop = id; + groups = { cracky = 2; }; + paramtype2 = 'facedir'; + light_source = (active and 9) or 0; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('input',kind.size) + inv:set_size('output',kind.outsize) + inv:set_size('fuel',kind.fuelsize) + meta:set_string('infotext', desc) + meta:set_string('formspec', smelter_formspec(kind, 0, 0)) + meta:set_float('burnleft',0) + meta:set_float('burnmax',0) + meta:set_float('burntime',0) + end; + on_metadata_inventory_put = update_smelter; + on_metadata_inventory_move = update_smelter; + on_metadata_inventory_take = update_smelter; + on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end; + -- allow_metadata_inventory_put = function(pos, listname, index, stack, player) + -- end; + tiles = { + 'sorcery_smelter_top_' .. tostring(kind.size) .. '.png'; + 'sorcery_smelter_bottom.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png'; + }; + }) + end + minetest.register_craft { + recipe = recipe; + output = id; + } +end + +for _, t in pairs { + {1, nil, 'clay'}; + {2, 'hot','aluminum'}; + {3, 'volcanic','platinum'}; + {4, 'stellar','duridium'}; + {5, 'nova','impervium'}; +} do register_kiln { + temp = t[1], temp_name = t[2]; + material = t[3]; + size = 3, outsize = 4, fuelsize = 1; -- future-proofing + } + + for _, s in pairs { + {2, 'small'}; + {3, 'large'}; + {4, 'grand'}; + } do register_smelter { + size = s[1], size_name = s[2]; + temp = t[1], temp_name = t[2]; + material = t[3]; + crucible = 'sorcery:crucible_' .. t[3]; + outsize = 4, fuelsize = 1; -- future-proofing + } end +end Index: init.lua ================================================================== --- init.lua +++ init.lua @@ -51,8 +51,8 @@ for _,u in pairs { 'leylines'; 'ores'; 'gems'; 'potions'; 'infuser'; 'altar'; 'wands'; 'tools'; 'enchanter'; 'harvester'; - 'smelter'; 'entities'; 'recipes'; 'coins'; - 'interop'; + 'metallurgy-hot'; 'entities'; 'recipes'; + 'coins'; 'interop'; 'tnodes'; } do sorcery.load(u) end Index: lib/color.lua ================================================================== --- lib/color.lua +++ lib/color.lua @@ -45,17 +45,61 @@ cast = { number = function(n) return { red = n; green = n; blue = n; } end; table = function(t) return { - red = t[1]; green = t[2]; blue = t[3]; + red = t[1]; green = t[2]; blue = t[3]; alpha = t[4]; } end; }; construct = function(r,g,b,a) local clip = function(v) return math.max(0,math.min(255,v)) + end; + local from_hsl = function(hsl, alpha) + -- Based on the algorithm in Computer Graphics: Principles and Practice, by + -- James D. Foley et. al., 2nd ed., p. 596 + -- Degree version, though radian is more natural, I don't want to translate it yet + local h = hsl.hue + local s = hsl.saturation + local l = hsl.luminosity + local value = function(n1, n2, hue) + if hue > 360 then + hue = hue - 360 + elseif hue < 0 then + hue = hue + 360 + end + if hue < 60 then + return n1 + (n2 - n1) * hue/60 + elseif hue < 180 then + return n2 + elseif hue < 240 then + return n1 + (n2 - n1) * (240 - hue)/60 + else + return n1 + end + end + local m2 + if l < 0.5 then + m2 = l * (1 + s) + else + m2 = l + s - l * s + end + local m1 = 2 * l - m2 + if s == 0 then + -- Achromatic, there is no hue + -- In book this errors if hue is not undefined, but we set hue to 0 in this case, not nil or something, so + return color(l, l, l, alpha) + else + -- Chromatic case, so there is a hue + return color( + clip(value(m1, m2, h + 120)*255), + clip(value(m1, m2, h)*255), + clip(value(m1, m2, h - 120)*255), + alpha + ) + end end; local warp = function(f) return function(self, ...) local n = color(self) f(n, ...) @@ -73,24 +117,59 @@ luminosity = function(self) return (self.red + self.green + self.blue) / 3 end; - readable = function(self, target) - target = target or 200 - local lum = self:luminosity() - if lum < target then - local f = 1.0 + (target - lum) / 255 - local nc = self:brighten(f * 1.5) - -- i don't know why the *1.5 is necessary. it's - -- an ugly hack to work around broken math, - -- because i'm too much of a mathtard to actually - -- find what's wrong - return nc + to_hsl = function(self) + -- Based on the algorithm in Computer Graphics: Principles and Practice, by + -- James D. Foley et. al., 2nd ed., p. 595 + -- We need the rgb between 0 and 1 + local r = self.red/255 + local g = self.green/255 + local b = self.blue/255 + local max = math.max(r, g, b) + local min = math.min(r, g, b) + local luminosity = (max + min)/2 + local hue = 0 + local saturation = 0 + if max == min then + -- Achromatic case, because r=g=b + saturation = 0 + hue = 0 -- Undefined, so just replace w/ 0 for usability else - return self + -- Chromatic case + if luminosity <= 0.5 then + saturation = (max - min)/(max + min) + else + saturation = (max - min)/(2 - max - min) + end + -- Next calculate the hue + local delta = max - min + if r == max then + hue = (g - b)/delta + elseif g == max then + hue = 2 + (b - r)/delta + else -- blue must be max, so no point in checking + hue = 4 + (r - g)/delta + end + hue = hue * 60 -- degrees + --hue = hue * (math.pi / 3) -- for hue in radians instead of degrees + if hue < 0 then + hue = hue + 2 * math.pi + end end + -- print("r"..self.red.."g"..self.green.."b"..self.blue.." is h"..hue.."s"..saturation.."l"..luminosity) + local temp = from_hsl({hue=hue,saturation=saturation,luminosity=luminosity},self.alpha) + -- print("back is r"..temp.red.."g"..temp.green.."b"..temp.blue) + return { hue = hue, saturation = saturation, luminosity = luminosity } + end; + + readable = function(self, target) + target = target or 0.5 + local hsl = self:to_hsl() + hsl.luminosity = target + return from_hsl(hsl, self.alpha) end; bg = function(self, text) return text .. minetest.get_background_escape_sequence(self:hex()) end; @@ -97,20 +176,24 @@ fade = warp(function(new, fac) new.alpha = math.min(255, (new.alpha or 255) * fac) end); - brighten = warp(function(new, fac) - local lum = new:luminosity() - local newlum = lum * fac - local delta = (newlum - lum) - new.red = clip(new.red + delta) - new.blue = clip(new.blue + delta) - new.green = clip(new.green + delta) - end); + brighten = function(self, fac) + -- Use HSL to brighten + -- To HSL + local hsl = self:to_hsl() + -- Do the calculation, clamp to 0-1 instead of the clamp fn + hsl.luminosity = math.min(math.max(hsl.luminosity * fac, 0), 1) + -- Turn back into RGB color + local t = from_hsl(hsl, self.alpha) + -- print("brighten is r"..t.red.."g"..t.green.."b"..t.blue) + return from_hsl(hsl, self.alpha) + end; darken = warp(function(new, fac) + -- TODO: is there any point to this being different than brighten? Probably especially not now. new.red = clip(new.red - (new.red * fac)) new.blue = clip(new.blue - (new.blue * fac)) new.green = clip(new.green - (new.green * fac)) end); } Index: lib/marshal.lua ================================================================== --- lib/marshal.lua +++ lib/marshal.lua @@ -63,11 +63,11 @@ end; dec = function(str) local val = 0 for i = 0, bytes-1 do local b = string.byte(str,bytes - i) - val = (val * 0x100) + b + val = (val * 0x100) + (b or 0) end if signed then if val > spoint then val = 0 - (val - spoint) end end return val, string.sub(str, 1 + bytes) Index: lib/str.lua ================================================================== --- lib/str.lua +++ lib/str.lua @@ -1,5 +1,16 @@ +local sanitable = { + ['\xfe'] = '\xf0'; + ['\1'] = '\xf1'; + ['\2'] = '\xf2'; + ['\3'] = '\xf3'; + + ['\xf0'] = '\xfe'; + ['\xf1'] = '\1'; + ['\xf2'] = '\2'; + ['\xf3'] = '\3'; +} return { capitalize = function(str) return string.upper(string.sub(str, 1,1)) .. string.sub(str, 2) end; @@ -34,6 +45,32 @@ if string.sub(str, #str,#str) == ' ' then str = string.sub(str, 1, #str - 1) end return str end; + + meta_armor = function(str,mark_struct) + -- binary values stored in metadata need to be sanitized so + -- they don't contain values that will disrupt parsing of the + -- KV store, as minetest (stupidly) uses in-band signalling + local sanitized = string.gsub(str, '[\xfe\1\2\3]', function(char) + return '\xfe' .. sanitable[char] + end) + if sanitized ~= str and mark_struct then + -- use different type code to mark struct headers for + -- back-compat + return string.gsub(sanitized,'^\xfe\xf0\x99','\xfe\x98') + else return sanitized end + end; + meta_dearmor = function(str,cond) + local dearmor = function(s) + return string.gsub(s, '\xfe([\xf0\xf1\xf2\xf3])', function(char) + return sanitable[char] + end) + end + if cond then + if string.sub(str,1,2) == '\xfe\x98' then + return dearmor(string.gsub(str,'^\xfe\x98','\xfe\xf0\x99')) + else return str end + else return dearmor(str) end + end; } Index: lib/ui.lua ================================================================== --- lib/ui.lua +++ lib/ui.lua @@ -22,11 +22,11 @@ end str = str .. '\n ' .. c:fmt('* ') if prop.title then - str = str .. c:brighten(1.5):fmt(prop.title) .. ': ' + str = str .. c:brighten(1.3):fmt(prop.title) .. ': ' end local lines = minetest.wrap_text(prop.desc, 50, true) str = str .. c:fmt(lines[1]) for i=2,#lines do ADDED metallurgy-hot.lua Index: metallurgy-hot.lua ================================================================== --- metallurgy-hot.lua +++ metallurgy-hot.lua @@ -0,0 +1,445 @@ +-- alloying furnace +-- +-- there are several kinds of alloy furnace, with varying +-- capabilities. the simplest can alloy two simple metals; +-- the most complex can alloy four high-grade metals such +-- as titanium, platinum, iridium, or levitanium. +-- +-- furnace recipes follow a pattern: the number of crucibles +-- determines the input slots, the type of crucible determines +-- how hot the furnace can get, and various other components +-- (like a coolant circulator) can be added to allow the +-- creation of exotic metals. +-- +-- alloy furnaces produce ingots that can later be melted down +-- again and cast into a mold to produce items of particular +-- shapes. + +-- there are four kinds of crucibles: clay, aluminum, platinum, +-- and duranium. clay crucibles are made by molding clay into +-- the proper shape and then firing it in a furnace. others +-- are made by casting. + +local fragments_per_ingot = 4 + +for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do + minetest.register_craftitem('sorcery:crucible_' .. c, { + description = sorcery.lib.str.capitalize(c .. ' crucible'); + inventory_image = 'sorcery_crucible_' .. c .. '.png'; + }) +end + +minetest.register_craftitem('sorcery:crucible_clay_molding', { + description = sorcery.lib.str.capitalize('Crucible molding'); + inventory_image = 'sorcery_crucible_clay_molding.png'; +}) + +minetest.register_craft { + recipe = { + { 'default:clay_lump', '', 'default:clay_lump'}; + { 'default:clay_lump', '', 'default:clay_lump'}; + { 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'}; + }; + output = 'sorcery:crucible_clay_molding'; +} +minetest.register_craft { + type = 'shapeless'; + recipe = { 'sorcery:crucible_clay_molding' }; + output = 'default:clay_lump 7'; +} +minetest.register_craft { + type = 'cooking'; + recipe = 'sorcery:crucible_clay_molding'; + cooktime = 40; + output = 'sorcery:crucible_clay'; +} + +local burn_layout = function(v) + local layouts = { + [1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1}; + [4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2}; + [7] = {w = 4, h = 2}; [8] = {w = 4, h = 2}; [9] = {w = 3, h = 3}; + } + local inpos = { x = 2.8, y = 1 } + local insize = layouts[v.inp] + local outpos = { x = 5.2, y = 2.4 } + local outsize = layouts[v.out] + local fuelpos = { x = 2.8, y = 3.4 } + local fuelsize = layouts[v.fuel] + return string.format([[ + list[context;input;%f,%f;%f,%f;] + list[context;output;%f,%f;%f,%f;] + list[context;fuel;%f,%f;%f,%f;] + + image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png] + image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270] + + listring[context;output] listring[current_player;main] + listring[context;input] listring[current_player;main] + listring[context;fuel] listring[current_player;main] + ]], + + --input + inpos.x - insize.w/2, -- pos + inpos.y - insize.h/2, + insize.w, insize.h, -- size + --output + outpos.x - outsize.w/2, -- pos + outpos.y - outsize.h/2, + outsize.w, outsize.h, -- size + --fuel + fuelpos.x - fuelsize.w/2, -- pos + fuelpos.y - fuelsize.h/2, + fuelsize.w, fuelsize.h, -- size + + v.burn, v.prog + ) +end + +local kiln_formspec = function(kind, fuel_progress, cook_progress) + return [[ + size[8,8] + list[current_player;main;0,4.2;8,4] + list[context;wax;0,2;1,1] + ]] .. burn_layout{ + inp = kind.size^2; + out = kind.outsize; + fuel = kind.fuelsize; + burn = fuel_progress; + prog = cook_progress; + } +end + +local smelter_formspec = function(kind, fuel_progress, smelt_progress) + return [[ + size[8,8] + list[current_player;main;0,4.2;8,4] + ]] .. burn_layout { + inp = kind.size; + out = kind.outsize; + fuel = kind.fuelsize; + burn = fuel_progress; + prog = smelt_progress; + } +end + +local find_recipe = function(inv) + local mix = {} + local count = 0 + for i=1,inv:get_size('input') do + local m = inv:get_stack('input',i) + if m:is_empty() then goto skip end + local l = sorcery.data.metallookup[m:get_name()] + if not l then return false end + mix[l.id] = (mix[l.id] or 0) + l.value + count = count + l.value + ::skip::end + -- everything is metal, we've finished summing it up. + -- let's see if the assembled items match the ratio + -- specified in any of the smelting recipes. + local matches = 0 + for _,rec in pairs(sorcery.data.alloys) do + local fac = nil + local meltpoint = 1 + if rec.metals == nil then goto skip_recipe end + for metal, ratio in pairs(rec.metals) do + if mix[metal] and mix[metal] % ratio == 0 then + if fac then + if mix[metal] / ratio ~= fac then goto skip_recipe end + else fac = math.floor(mix[metal] / ratio) end + local m = sorcery.data.metals[metal] + if m.meltpoint then + meltpoint = math.max(meltpoint, m.meltpoint) end + else goto skip_recipe end + end + do return rec, count, fac, meltpoint end + ::skip_recipe::end + return false +end + +local update_smelter = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto + local recipe, count, factor, meltpoint = find_recipe(inv) + if recipe and proto.temp >= meltpoint then + minetest.get_node_timer(pos):start(1) + else + meta:set_float('burntime',0) + end +end + +local smelter_step = function(kind,active,pos,delta) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + local recipe, count, factor = find_recipe(inv) + local cooktime + local elapsed = meta:get_float('burntime') + delta + meta:set_float('burnleft',meta:get_float('burnleft') - delta) + if (not active) and (not recipe) then return false end + if meta:get_float('burnleft') <= 0 or not active then + if recipe then + local burn, frep = minetest.get_craft_result { + method = 'fuel', width = 1; + items = { inv:get_stack('fuel',1) }; + } + if burn.time == 0 then goto nofuel end + inv:set_stack('fuel', 1, frep.items[1]) + meta:set_float('burnleft',burn.time) + meta:set_float('burnmax',burn.time) + if not active then + minetest.swap_node(pos, + sorcery.lib.tbl.merge(minetest.get_node(pos), { + name = kind.id .. '_active' + })) + active = true + end + else goto nofuel end + end + + if not recipe then goto update end + + cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor + if elapsed >= cooktime then + elapsed = 0 + -- remove used items + for i=1,inv:get_size('input') do + local s = inv:get_stack('input',i) + if s:is_empty() then goto skip end + s:take_item(1) inv:set_stack('input',i,s) + ::skip::end + + local outstack + if count % fragments_per_ingot == 0 then + outstack = ItemStack { + name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot'; + count = count / fragments_per_ingot; + } + else + outstack = ItemStack { + name = 'sorcery:fragment_' .. recipe.output; + count = count; + } + end + + local leftover = inv:add_item('output',outstack) + if not leftover:is_empty() then + minetest.add_item(pos, leftover) + end + end + + ::update:: + meta:set_float('burntime',elapsed) + meta:set_string('formspec', smelter_formspec(kind, + math.min(1, meta:get_float('burnleft') / + meta:get_float('burnmax') + ) * 100, -- fuel + (cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt + )) + do return active end + + ::nofuel:: + if active then + minetest.swap_node(pos, + sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id })) + end + meta:set_float('burnleft',0) -- just in case + ::noburn:: + meta:set_float('burntime',0) -- just in case + meta:set_string('formspec', smelter_formspec(kind, 0, 0)) + return false +end +local register_kiln = function(kind) + local box = { + open = { + type = 'fixed'; + fixed = { + -0.5,-0.5,-0.5, + 0.5, 1.6, 0.5 + }; + }; + closed = { + type = 'fixed'; + fixed = { + -0.5,-0.5,-0.5, + 0.5, 0.7, 0.5 + }; + }; + } + local id = 'sorcery:kiln_' .. kind.material + local metal = sorcery.data.metals[kind.material] + -- use some iffy heuristics to guess the texture + local metaltex + if kind.material == 'clay' then + metaltex = 'default_brick.png'; + else + metaltex = (metal.img and metal.img.block) or + (metal.ingot and 'default_' .. kind.material .. '_block.png') or + sorcery.lib.image('default_steel_block.png'):multiply(sorcery.lib.color(metal.tone)):render(); + end + local tex = { + open = { + 'default_furnace_front.png'; + metaltex; + 'default_copper_block.png'; + 'default_stone.png'; + }; + closed = { + 'default_furnace_front_active.png'; + 'default_copper_block.png'; + metaltex; + 'default_stone.png'; + }; + }; + for _,state in pairs{'open','closed'} do + local id_closed = id .. '_closed' + local id_current = (state == 'closed' and id_closed) or id + local desc = (kind.temp_name and sorcery.lib.str.capitalize(kind.temp_name) .. ' kiln') or 'Kiln' + minetest.register_node(id_current, { + description = desc; + drawtype = "mesh"; + mesh = 'sorcery-kiln-' .. state .. '.obj'; + drop = id; + groups = { + cracky = (state == 'open' and 2) or nil; + }; + sunlight_propagates = true; + paramtype1 = 'light'; + paramtype2 = 'facedir'; + selection_box = box[state]; + collision_box = box[state]; + tiles = tex[state]; + light_source = (state == 'closed' and 7) or 0; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('input', kind.size^2) + inv:set_size('output', kind.outsize) + inv:set_size('fuel', kind.fuelsize) + inv:set_size('wax', 1) + + meta:set_float('burnleft',0) + meta:set_float('burnmax',0) + meta:set_float('burntime',0) + + meta:set_string('infotext',desc) + meta:set_string('formspec',kiln_formspec(kind,0,0)) + end; + on_timer = function(pos,delta) + end; + + on_metadata_inventory_put = function(pos) + minetest.get_node_timer(pos):start(1) end; + on_metadata_inventory_move = function(pos) + minetest.get_node_timer(pos):start(1) end; + on_metadata_inventory_take = function(pos) + minetest.get_node_timer(pos):start(1) end; + }) + end + local ingot + if kind.material == 'clay' then + ingot = 'default:clay_brick'; + else + ingot = metal.ingot or 'sorcery:' .. kind.material .. '_ingot' + end + minetest.register_craft { + output = id; + recipe = { + {'default:copper_ingot', 'default:copper_ingot', 'default:copper_ingot'}; + {ingot,'',ingot}; + {ingot,'default:furnace',ingot}; + }; + } +end + +local register_smelter = function(kind) + local recipe = {{},{}; + {'default:stone','default:furnace','default:stone'}; + } do + local on = kind.crucible + local ti = 'default:tin_ingot' + local cu = 'default:copper_ingot' + local crucmap = { + [2] = { {cu,cu,cu}, {on,ti,on} }; + [3] = { {cu,on,cu}, {on,ti,on} }; + [4] = { {on,cu,on}, {on,ti,on} }; + [5] = { {on,cu,on}, {on,on,on} }; + [6] = { {on,on,on}, {on,on,on} }; + }; + for y=1,2 do recipe[y] = crucmap[kind.size][y] end + end + + local desc = 'smelter'; + if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end + if kind.size_name then desc = kind.size_name .. ' ' .. desc end + desc = sorcery.lib.str.capitalize(desc); + local id = 'sorcery:smelter_' .. kind.material .. kind.size_name + kind.id = id + for _, active in pairs {false, true} do + minetest.register_node((active and id .. '_active') or id, { + _proto = kind; + description = desc; + drop = id; + groups = { + cracky = (active and 2) or nil; + }; + paramtype2 = 'facedir'; + light_source = (active and 9) or 0; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + inv:set_size('input',kind.size) + inv:set_size('output',kind.outsize) + inv:set_size('fuel',kind.fuelsize) + meta:set_string('infotext', desc) + meta:set_string('formspec', smelter_formspec(kind, 0, 0)) + meta:set_float('burnleft',0) + meta:set_float('burnmax',0) + meta:set_float('burntime',0) + end; + on_metadata_inventory_put = update_smelter; + on_metadata_inventory_move = update_smelter; + on_metadata_inventory_take = update_smelter; + on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end; + -- allow_metadata_inventory_put = function(pos, listname, index, stack, player) + -- end; + tiles = { + 'sorcery_smelter_top_' .. tostring(kind.size) .. '.png'; + 'sorcery_smelter_bottom.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_side.png'; + 'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png'; + }; + }) + end + minetest.register_craft { + recipe = recipe; + output = id; + } +end + +for _, t in pairs { + {1, nil, 'clay'}; + {2, 'hot','aluminum'}; + {3, 'volcanic','platinum'}; + {4, 'stellar','duridium'}; + {5, 'nova','impervium'}; +} do register_kiln { + temp = t[1], temp_name = t[2]; + material = t[3]; + size = 3, outsize = 4, fuelsize = 1; -- future-proofing + } + + for _, s in pairs { + {2, 'small'}; + {3, 'large'}; + {4, 'grand'}; + } do register_smelter { + size = s[1], size_name = s[2]; + temp = t[1], temp_name = t[2]; + material = t[3]; + crucible = 'sorcery:crucible_' .. t[3]; + outsize = 4, fuelsize = 1; -- future-proofing + } end +end ADDED models/sorcery-kiln-closed.obj Index: models/sorcery-kiln-closed.obj ================================================================== --- models/sorcery-kiln-closed.obj +++ models/sorcery-kiln-closed.obj @@ -0,0 +1,221 @@ +# Blender v2.82 (sub 7) OBJ File: 'kiln.blend' +# www.blender.org +mtllib sorcery-kiln-closed.mtl +o furnace-front_Cube.002 +v -0.288540 -0.410913 -0.500000 +v 0.288540 -0.410913 -0.500000 +v 0.288540 0.152026 -0.500000 +v -0.288540 0.152026 -0.500000 +vt 0.000000 0.987748 +vt 0.000000 0.012252 +vt 1.000000 0.012252 +vt 1.000000 0.987748 +vn 0.0000 0.0000 1.0000 +g furnace-front_Cube.002_furnace.001 +usemtl furnace.001 +s off +f 4/1/1 1/2/1 2/3/1 3/4/1 +o bronze.001_bronze +v 0.500000 0.593635 -0.500000 +v 0.500000 0.593635 0.500000 +v -0.500000 0.593635 -0.500000 +v -0.500000 0.593635 0.500000 +v 0.500000 0.365016 -0.500000 +v -0.500000 0.365016 0.500000 +v 0.500000 0.365016 0.500000 +v -0.500000 0.365016 -0.500000 +vt 1.000000 0.385690 +vt 1.000000 0.614310 +vt 0.000000 0.614310 +vt 0.000000 0.385690 +vt 1.000000 0.614310 +vt 1.000000 0.385690 +vt 1.000000 0.385690 +vt 1.000000 0.614310 +vt 0.000000 0.614310 +vt 0.000000 0.385690 +vt 0.000000 0.385690 +vt 0.000000 0.614310 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vn 0.0000 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn -0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g bronze.001_bronze_bronze +usemtl bronze +s off +f 11/5/2 6/6/2 8/7/2 10/8/2 +f 10/8/3 8/7/3 7/9/3 12/10/3 +f 9/11/4 5/12/4 6/13/4 11/14/4 +f 12/15/5 7/16/5 5/12/5 9/11/5 +f 12/17/6 9/18/6 11/19/6 10/20/6 +f 6/21/7 5/22/7 7/23/7 8/24/7 +o metal +v 0.500000 -0.500000 -0.500000 +v 0.500000 -0.500000 0.500000 +v -0.500000 -0.500000 -0.500000 +v -0.500000 -0.500000 0.500000 +v 0.500000 -0.410913 -0.500000 +v -0.500000 -0.410913 0.500000 +v 0.500000 -0.410913 0.500000 +v -0.500000 -0.410913 -0.500000 +v -0.288539 -0.410913 -0.452614 +v 0.288539 -0.410913 -0.452614 +v -0.288540 -0.410913 -0.500000 +v 0.288540 -0.410913 -0.500000 +v 0.500000 0.593635 -0.500000 +v 0.500000 0.593635 0.500000 +v -0.500000 0.593635 -0.500000 +v -0.500000 0.593635 0.500000 +v 0.500000 0.692600 -0.500000 +v -0.500000 0.593635 0.500000 +v 0.500000 0.593635 -0.500000 +v -0.500000 0.593635 -0.500000 +v 0.500000 0.692600 0.500000 +v -0.500000 0.692600 0.500000 +v -0.500000 0.692600 -0.500000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.440608 +vt 0.000000 0.529696 +vt 0.333333 0.529696 +vt 0.666667 0.529696 +vt 1.000000 0.529696 +vt 1.000000 0.440608 +vt 0.000000 0.529696 +vt 0.000000 0.440608 +vt 0.000000 0.440608 +vt 0.000000 0.529696 +vt 1.000000 0.529696 +vt 1.000000 0.440608 +vt 1.000000 0.440608 +vt 1.000000 0.529696 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.450518 +vt 0.000000 0.450518 +vt 0.000000 0.549482 +vt 1.000000 0.549482 +vt 0.000000 0.450518 +vt 1.000000 0.450518 +vt 1.000000 0.549482 +vt 0.000000 0.549482 +vt 0.000000 0.450518 +vt 0.000000 0.549482 +vt 1.000000 0.450518 +vt 1.000000 0.549482 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 1.0000 -0.0000 +g metal_metal_metal.001 +usemtl metal.001 +s off +f 15/25/8 13/26/8 14/27/8 16/28/8 +f 15/29/9 20/30/9 23/31/9 24/32/9 17/33/9 13/34/9 +f 13/34/10 17/33/10 19/35/10 14/36/10 +f 16/37/11 18/38/11 20/39/11 15/40/11 +f 14/41/12 19/42/12 18/38/12 16/37/12 +f 17/33/13 20/39/13 18/38/13 19/42/13 +f 31/43/8 26/44/8 30/45/8 32/46/8 +f 29/47/13 35/48/13 34/49/13 33/50/13 +f 31/51/9 32/52/9 35/53/9 29/54/9 +f 30/55/12 26/56/12 33/57/12 34/58/12 +f 26/59/10 31/51/10 29/54/10 33/60/10 +f 32/61/11 30/55/11 34/58/11 35/62/11 +l 21 22 +l 28 32 +l 27 25 +o stone_Cube.008 +v 0.452614 0.365016 -0.452614 +v 0.452614 -0.410913 -0.452614 +v -0.452614 -0.410913 0.452614 +v -0.452614 0.365016 0.452614 +v 0.452614 0.365016 0.452614 +v 0.452614 -0.410913 0.452614 +v -0.452614 0.365016 -0.452614 +v -0.452614 -0.410913 -0.452614 +v 0.452614 0.152026 -0.452614 +v -0.452614 0.152026 -0.452614 +v -0.288539 -0.410913 -0.452614 +v 0.288539 -0.410913 -0.452614 +v -0.288539 0.152026 -0.452614 +v 0.288539 0.152026 -0.452614 +v -0.288540 -0.410913 -0.500000 +v 0.288540 -0.410913 -0.500000 +v 0.288540 0.152026 -0.500000 +v -0.288540 0.152026 -0.500000 +vt 0.000000 0.718717 +vt 0.000000 0.990645 +vt 1.000000 0.990645 +vt 1.000000 0.718717 +vt 0.818747 0.718717 +vt 0.181253 0.718717 +vt 1.000000 -0.000000 +vt 1.000000 0.723899 +vt 1.000000 0.997788 +vt 0.000000 0.997788 +vt 0.000000 -0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.995330 +vt 1.000000 0.995330 +vt 1.000000 0.722116 +vt 1.000000 -0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.987517 +vt 0.000000 0.987518 +vt 0.818747 0.000000 +vt 0.000000 0.000000 +vt 0.181253 0.000000 +vt 0.458944 -0.150330 +vt 0.541056 -0.150330 +vt 0.541056 0.825165 +vt 0.458944 0.825165 +vt 0.000003 0.458944 +vt 0.000000 0.541056 +vt 1.000000 0.541056 +vt 0.999997 0.458944 +vt 0.458944 0.825165 +vt 0.541056 0.825165 +vt 0.541056 -0.150330 +vt 0.458944 -0.150330 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn -0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.0000 1.0000 -0.0000 +g stone_Cube.008_stone.001 +usemtl stone.001 +s off +f 45/63/14 42/64/14 36/65/14 44/66/14 49/67/14 48/68/14 +f 37/69/15 44/70/15 36/71/15 40/72/15 41/73/15 +f 38/74/16 39/75/16 42/76/16 45/77/16 43/78/16 +f 41/79/17 40/80/17 39/81/17 38/74/17 +f 47/82/14 49/67/14 44/66/14 37/69/14 +f 43/83/14 45/63/14 48/68/14 46/84/14 +f 46/84/14 48/68/14 49/67/14 47/82/14 +f 46/85/15 50/86/15 53/87/15 48/88/15 +f 48/89/18 53/90/18 52/91/18 49/92/18 +f 49/93/16 52/94/16 51/95/16 47/96/16 +f 47/82/18 37/69/18 41/79/18 38/74/18 43/83/18 46/84/18 +f 42/76/19 39/75/19 40/80/19 36/71/19 ADDED models/sorcery-kiln-open.obj Index: models/sorcery-kiln-open.obj ================================================================== --- models/sorcery-kiln-open.obj +++ models/sorcery-kiln-open.obj @@ -0,0 +1,593 @@ +# Blender v2.82 (sub 7) OBJ File: 'kiln.blend' +# www.blender.org +mtllib sorcery-kiln-open.mtl +o furnace +v -0.288540 -0.410913 -0.500000 +v 0.288540 -0.410913 -0.500000 +v 0.288540 0.152026 -0.500000 +v -0.288540 0.152026 -0.500000 +vt 0.000000 0.987748 +vt 0.000000 0.012252 +vt 1.000000 0.012252 +vt 1.000000 0.987748 +vn 0.0000 0.0000 1.0000 +g furnace_furnace_furnace.002 +usemtl furnace.002 +s off +f 4/1/1 1/2/1 2/3/1 3/4/1 +o metal.001 +v 0.508325 1.576141 0.354696 +v 0.508325 0.586871 0.500792 +v -0.491675 1.576141 0.354696 +v -0.491675 0.586871 0.500792 +v 0.508325 1.590600 0.452599 +v -0.491675 0.586871 0.500792 +v 0.508325 1.576141 0.354696 +v -0.491675 1.576141 0.354696 +v 0.508325 0.601329 0.598694 +v -0.491675 0.601329 0.598694 +v -0.491675 1.590600 0.452599 +v 0.508325 -0.506765 -0.499208 +v 0.508325 -0.506765 0.500792 +v -0.491675 -0.506765 -0.499208 +v -0.491675 -0.506765 0.500792 +v 0.508325 -0.417677 -0.499208 +v -0.491675 -0.417677 0.500792 +v 0.508325 -0.417677 0.500792 +v -0.491675 -0.417677 -0.499208 +v 0.460939 -0.417677 -0.451822 +v -0.444289 -0.417677 0.453406 +v 0.460939 -0.417677 0.453406 +v -0.444289 -0.417677 -0.451822 +v 0.344038 0.275759 0.336506 +v -0.327389 0.275759 0.336506 +v 0.344038 0.275759 -0.334922 +v -0.327389 0.275759 -0.334922 +v 0.344038 -0.302932 0.336506 +v -0.327389 -0.302932 0.336506 +v 0.344038 -0.302932 -0.334922 +v -0.327389 -0.302932 -0.334922 +v 0.344038 0.497017 0.336506 +v 0.344038 0.441703 0.336506 +v 0.344038 0.386388 0.336506 +v 0.344038 0.331074 0.336506 +v -0.327389 0.497017 -0.334922 +v -0.327389 0.441703 -0.334922 +v -0.327389 0.386388 -0.334921 +v -0.327389 0.331074 -0.334922 +v -0.327389 0.331074 0.336506 +v -0.327389 0.386388 0.336506 +v -0.327389 0.441703 0.336506 +v -0.327389 0.497017 0.336506 +v 0.344038 0.331074 -0.334922 +v 0.344038 0.386388 -0.334921 +v 0.344038 0.441703 -0.334922 +v 0.344038 0.497017 -0.334922 +v 0.381991 0.275759 0.374458 +v -0.365341 0.275759 0.374458 +v 0.381991 0.275759 -0.372874 +v -0.365341 0.275759 -0.372874 +v 0.381991 0.331074 0.374458 +v -0.365341 0.331074 -0.372874 +v -0.365341 0.497017 0.374458 +v -0.365341 0.552332 0.374458 +v 0.381991 0.497017 -0.372874 +v 0.381991 0.552332 -0.372874 +v -0.365341 0.552332 -0.372874 +v 0.381991 0.552332 0.374458 +v 0.381991 0.497017 0.374458 +v 0.381991 0.441703 0.374458 +v 0.381991 0.386388 0.374458 +v -0.365341 0.497017 -0.372874 +v -0.365341 0.441703 -0.372874 +v -0.365341 0.386388 -0.372874 +v -0.365341 0.331074 0.374458 +v -0.365341 0.386388 0.374458 +v -0.365341 0.441703 0.374458 +v 0.381991 0.331074 -0.372874 +v 0.381991 0.386388 -0.372874 +v 0.381991 0.441703 -0.372874 +v -0.280214 -0.417677 -0.451822 +v 0.296863 -0.417677 -0.451822 +v -0.280215 -0.417677 -0.499208 +v 0.296865 -0.417677 -0.499208 +v -0.378077 0.552332 0.387194 +v 0.394726 0.552332 -0.385610 +v -0.378077 0.552332 -0.385610 +v 0.394726 0.552332 0.387194 +v -0.378077 -0.323989 0.387194 +v 0.394726 -0.323989 -0.385610 +v -0.378077 -0.323989 -0.385610 +v 0.394726 -0.323989 0.387194 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.450518 +vt 0.000000 0.450518 +vt 0.000000 0.549482 +vt 1.000000 0.549482 +vt 0.000000 0.450518 +vt 1.000000 0.450518 +vt 1.000000 0.549482 +vt 0.000000 0.549482 +vt 0.000000 0.450518 +vt 0.000000 0.549482 +vt 1.000000 0.450518 +vt 1.000000 0.549482 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.440608 +vt 0.000000 0.529696 +vt 0.333333 0.529696 +vt 0.666667 0.529696 +vt 1.000000 0.529696 +vt 1.000000 0.440608 +vt 0.000000 0.529696 +vt 0.000000 0.440608 +vt 1.000000 1.000000 +vt 0.666667 1.000000 +vt 0.333333 1.000000 +vt 0.000000 1.000000 +vt 0.047386 0.952614 +vt 0.349129 0.952614 +vt 0.650871 0.952614 +vt 0.952614 0.952614 +vt 0.000000 0.440608 +vt 0.000000 0.529696 +vt 1.000000 0.529696 +vt 1.000000 0.440608 +vt 1.000000 0.000000 +vt 0.952614 0.047386 +vt 1.000000 0.440608 +vt 1.000000 0.529696 +vt 0.000000 0.000000 +vt 0.047386 0.047386 +vt 0.949216 0.949216 +vt 0.050784 0.949216 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 0.050784 0.949216 +vt 0.949216 0.949216 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt -0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.949216 0.611024 +vt 0.050784 0.611024 +vt 0.050784 0.537008 +vt 0.949216 0.537008 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 0.949216 0.462992 +vt 0.050784 0.462992 +vt 0.050784 0.388976 +vt 0.949216 0.388976 +vt 0.949216 0.949216 +vt 0.050784 0.949216 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 0.050784 0.611024 +vt 0.949216 0.611024 +vt 0.949216 0.537008 +vt 0.050784 0.537008 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 0.050784 0.462992 +vt 0.949216 0.462992 +vt 0.949216 0.388976 +vt 0.050784 0.388976 +vt 0.050784 0.949216 +vt 0.949216 0.949216 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.050784 0.611024 +vt 0.050784 0.537008 +vt 0.949216 0.050784 +vt 1.000000 0.000000 +vt 0.050784 0.462992 +vt 0.050784 0.388976 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 0.949216 0.611024 +vt 0.949216 0.537008 +vt 0.050784 0.949216 +vt 0.949216 0.949216 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.949216 0.462992 +vt 0.949216 0.388976 +vt 0.000000 0.388976 +vt 1.000000 0.388976 +vt 1.000000 0.314960 +vt 0.000000 0.314960 +vt 1.000000 0.388976 +vt 0.000000 0.388976 +vt 0.000000 0.314960 +vt 1.000000 0.314960 +vt 0.000000 0.388976 +vt 0.000000 0.314960 +vt 1.000000 0.388976 +vt 1.000000 0.314960 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 0.000000 0.611024 +vt 1.000000 0.611024 +vt 1.000000 0.537008 +vt 0.000000 0.537008 +vt 0.000000 0.462992 +vt 1.000000 0.462992 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 1.000000 0.611024 +vt 0.000000 0.611024 +vt 0.000000 0.537008 +vt 1.000000 0.537008 +vt 1.000000 0.462992 +vt 0.000000 0.462992 +vt 0.000000 0.685040 +vt 0.000000 0.611024 +vt 0.000000 0.537008 +vt 0.000000 0.462992 +vt 1.000000 0.685040 +vt 1.000000 0.611024 +vt 1.000000 0.537008 +vt 1.000000 0.462992 +vt 0.949216 0.050784 +vt 1.000000 0.000000 +vt 0.949216 0.050784 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.949216 0.050784 +vt 1.000000 0.000000 +vt 0.949216 0.050784 +vt 1.000000 0.000000 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vt 1.000000 0.685040 +vt 0.000000 0.685040 +vn 0.0000 -0.1461 -0.9893 +vn 0.0000 0.1461 0.9893 +vn 0.0000 0.9893 -0.1461 +vn 0.0000 -0.9893 0.1461 +vn 1.0000 0.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -0.0000 1.0000 +g metal.001_metal.001_metal.002 +usemtl metal.002 +s off +f 11/5/2 6/6/2 10/7/2 12/8/2 +f 9/9/3 15/10/3 14/11/3 13/12/3 +f 11/13/4 12/14/4 15/15/4 9/16/4 +f 10/17/5 6/18/5 13/19/5 14/20/5 +f 6/21/6 11/13/6 9/16/6 13/22/6 +f 12/23/7 10/17/7 14/20/7 15/24/7 +f 18/25/8 16/26/8 17/27/8 19/28/8 +f 18/29/9 23/30/9 78/31/9 79/32/9 20/33/9 16/34/9 +f 16/34/6 20/33/6 22/35/6 17/36/6 +f 20/37/10 79/38/10 78/39/10 23/40/10 27/41/10 76/42/10 77/43/10 24/44/10 +f 19/45/7 21/46/7 23/47/7 18/48/7 +f 22/49/10 20/37/10 24/44/10 26/50/10 +f 17/51/11 22/52/11 21/46/11 19/45/11 +f 23/40/10 21/53/10 25/54/10 27/41/10 +f 21/53/10 22/49/10 26/50/10 25/54/10 +f 48/55/8 43/56/8 57/57/8 73/58/8 +f 30/59/11 31/60/11 35/61/11 34/62/11 +f 43/56/8 44/63/8 70/64/8 57/57/8 +f 42/65/10 49/66/10 74/67/10 69/68/10 +f 34/69/10 35/70/10 33/71/10 32/72/10 +f 28/73/7 30/59/7 34/62/7 32/74/7 +f 31/75/6 29/76/6 33/77/6 35/70/6 +f 29/76/9 28/78/9 32/79/9 33/77/9 +f 40/80/6 47/81/6 46/82/6 41/83/6 +f 45/84/10 42/65/10 69/68/10 71/85/10 +f 42/86/6 45/87/6 44/88/6 43/89/6 +f 50/90/8 41/91/8 68/92/8 75/93/8 +f 36/94/7 51/95/7 50/96/7 37/97/7 +f 41/91/8 46/98/8 72/99/8 68/92/8 +f 38/100/7 49/101/7 48/102/7 39/103/7 +f 40/104/10 51/105/10 60/106/10 67/107/10 +f 51/95/11 40/108/11 41/109/11 50/96/11 +f 44/63/8 39/110/8 56/111/8 70/64/8 +f 49/101/11 42/112/11 43/113/11 48/102/11 +f 47/114/10 40/104/10 67/107/10 58/115/10 +f 47/81/9 36/116/9 37/117/9 46/82/9 +f 31/118/10 30/119/10 54/120/10 55/121/10 +f 45/87/9 38/122/9 39/123/9 44/88/9 +f 70/124/9 56/125/9 52/126/9 53/127/9 +f 73/128/11 57/129/11 55/130/11 54/131/11 +f 56/132/7 73/128/7 54/131/7 52/133/7 +f 57/134/6 70/124/6 53/127/6 55/135/6 +f 62/136/6 59/137/6 58/138/6 67/139/6 +f 68/140/6 72/141/6 71/142/6 69/143/6 +f 63/144/7 61/145/7 60/146/7 64/147/7 +f 65/148/7 75/149/7 74/150/7 66/151/7 +f 61/145/11 62/152/11 67/153/11 60/146/11 +f 75/149/11 68/154/11 69/155/11 74/150/11 +f 59/137/9 63/156/9 64/157/9 58/138/9 +f 72/141/9 65/158/9 66/159/9 71/142/9 +f 51/105/10 36/160/10 64/161/10 60/106/10 +f 28/162/10 29/163/10 53/164/10 52/165/10 +f 36/160/10 47/114/10 58/115/10 64/161/10 +f 37/166/8 50/90/8 75/93/8 65/167/8 +f 30/119/10 28/162/10 52/165/10 54/120/10 +f 46/98/8 37/166/8 65/167/8 72/99/8 +f 49/66/10 38/168/10 66/169/10 74/67/10 +f 29/163/10 31/118/10 55/121/10 53/164/10 +f 38/168/10 45/84/10 71/85/10 66/169/10 +f 39/110/8 48/55/8 73/58/8 56/111/8 +f 62/152/10 61/145/10 81/170/10 82/171/10 +f 59/137/10 62/136/10 82/172/10 80/173/10 +f 63/156/10 59/137/10 80/173/10 83/174/10 +f 61/145/10 63/144/10 83/175/10 81/170/10 +f 82/171/9 81/170/9 85/176/9 86/177/9 +f 80/173/7 82/172/7 86/178/7 84/179/7 +f 83/174/11 80/173/11 84/179/11 87/180/11 +f 81/170/6 83/175/6 87/181/6 85/176/6 +f 84/179/8 86/177/8 85/176/8 87/180/8 +l 8 12 +l 7 5 +o bronze_Cube.006 +v 0.500000 0.593635 -0.500000 +v 0.500000 0.593635 0.500000 +v -0.500000 0.593635 -0.500000 +v -0.500000 0.593635 0.500000 +v 0.500000 0.365016 -0.500000 +v -0.500000 0.365016 0.500000 +v 0.500000 0.365016 0.500000 +v -0.500000 0.365016 -0.500000 +v 0.452614 0.365016 -0.452614 +v -0.452614 0.365016 0.452614 +v 0.452614 0.365016 0.452614 +v -0.452614 0.365016 -0.452614 +v 0.335714 0.593635 0.335714 +v -0.335714 0.593635 0.335714 +v 0.335714 0.593635 -0.335714 +v -0.335714 0.593635 -0.335714 +v 0.335714 0.559096 0.335714 +v -0.335714 0.559096 0.335714 +v -0.335714 0.559096 -0.335714 +v 0.335714 0.559096 -0.335714 +v -0.373666 0.559096 0.373666 +v 0.373666 0.559096 -0.373666 +v -0.373666 0.559096 -0.373666 +v 0.373666 0.559096 0.373666 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 0.164286 0.164286 +vt 0.164286 0.835714 +vt 1.000000 0.385690 +vt 1.000000 0.614310 +vt 0.000000 0.614310 +vt 0.000000 0.385690 +vt 1.000000 0.614310 +vt 1.000000 0.385690 +vt 1.000000 0.385690 +vt 1.000000 0.614310 +vt 0.000000 0.614310 +vt 0.000000 0.385690 +vt 0.000000 0.385690 +vt 0.000000 0.614310 +vt 1.000000 0.000000 +vt 0.000000 0.000000 +vt 0.047386 0.047386 +vt 0.952614 0.047386 +vt 0.000000 1.000000 +vt 0.047386 0.952614 +vt 1.000000 1.000000 +vt 0.952614 0.952614 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.835714 0.835714 +vt 0.835714 0.164286 +vt 0.949216 0.050784 +vt 0.949216 0.949216 +vt 1.000000 1.000000 +vt 1.000000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 0.750000 +vt 0.625000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 1.000000 +vt 0.050784 0.949216 +vt 0.050784 0.050784 +vt 0.000000 0.000000 +vt 0.000000 1.000000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn -0.0000 -1.0000 -0.0000 +vn 0.0000 -0.3768 0.9263 +vn -0.9263 -0.3768 0.0000 +vn 0.0000 -0.3768 -0.9263 +vn 0.9263 -0.3768 0.0000 +g bronze_Cube.006_bronze.002 +usemtl bronze.002 +s off +f 90/182/12 91/183/12 101/184/12 103/185/12 +f 94/186/13 89/187/13 91/188/13 93/189/13 +f 93/189/14 91/188/14 90/190/14 95/191/14 +f 92/192/15 88/193/15 89/194/15 94/195/15 +f 95/196/16 90/197/16 88/193/16 92/192/16 +f 94/198/17 93/199/17 97/200/17 98/201/17 +f 93/199/17 95/202/17 99/203/17 97/200/17 +f 92/204/17 94/198/17 98/201/17 96/205/17 +f 95/202/17 92/204/17 96/205/17 99/203/17 +f 89/206/12 88/207/12 102/208/12 100/209/12 +f 88/207/12 90/182/12 103/185/12 102/208/12 +f 91/183/12 89/206/12 100/209/12 101/184/12 +f 104/210/17 107/211/17 109/212/17 111/213/17 +f 103/214/15 101/215/15 105/216/15 106/217/15 +f 100/218/14 102/219/14 107/220/14 104/221/14 +f 102/219/13 103/214/13 106/217/13 107/220/13 +f 101/222/16 100/218/16 104/221/16 105/223/16 +f 106/224/17 105/225/17 108/226/17 110/227/17 +f 107/211/17 106/224/17 110/227/17 109/212/17 +f 105/225/17 104/210/17 111/213/17 108/226/17 +f 96/205/18 109/212/18 110/227/18 99/203/18 +f 98/201/19 111/213/19 109/212/19 96/205/19 +f 97/200/20 108/226/20 111/213/20 98/201/20 +f 99/203/21 110/227/21 108/226/21 97/200/21 +o stone.001_Cube.009 +v 0.452614 0.365016 -0.452614 +v 0.452614 -0.410913 -0.452614 +v -0.452614 -0.410913 0.452614 +v -0.452614 0.365016 0.452614 +v 0.452614 0.365016 0.452614 +v 0.452614 -0.410913 0.452614 +v -0.452614 0.365016 -0.452614 +v -0.452614 -0.410913 -0.452614 +v 0.452614 0.152026 -0.452614 +v -0.452614 0.152026 -0.452614 +v -0.288539 -0.410913 -0.452614 +v 0.288539 -0.410913 -0.452614 +v -0.288539 0.152026 -0.452614 +v 0.288539 0.152026 -0.452614 +v -0.288540 -0.410913 -0.500000 +v 0.288540 -0.410913 -0.500000 +v 0.288540 0.152026 -0.500000 +v -0.288540 0.152026 -0.500000 +v 0.412127 0.365016 -0.420224 +v 0.412127 -0.410913 -0.420224 +v -0.412127 -0.410913 0.404029 +v -0.428808 0.361504 0.401848 +v 0.412127 0.365016 0.404029 +v 0.412127 -0.410913 0.404029 +v -0.428808 0.361503 -0.422405 +v -0.412127 -0.410913 -0.420224 +v -0.262728 -0.410913 -0.420224 +v 0.262728 -0.410913 -0.420224 +vt 0.000000 0.718717 +vt 0.000000 0.990645 +vt 1.000000 0.990645 +vt 1.000000 0.718717 +vt 0.818747 0.718717 +vt 0.181253 0.718717 +vt 1.000000 -0.000000 +vt 1.000000 0.723899 +vt 1.000000 0.997788 +vt 0.000000 0.997788 +vt 0.000000 -0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.995330 +vt 1.000000 0.995330 +vt 1.000000 0.722116 +vt 1.000000 -0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.987517 +vt 0.000000 0.987518 +vt 0.818747 0.000000 +vt 0.000000 0.000000 +vt 0.181253 0.000000 +vt 0.458944 -0.150330 +vt 0.541056 -0.150330 +vt 0.541056 0.825165 +vt 0.458944 0.825165 +vt 0.000003 0.458944 +vt 0.000000 0.541056 +vt 1.000000 0.541056 +vt 0.999997 0.458944 +vt 0.458944 0.825165 +vt 0.541056 0.825165 +vt 0.541056 -0.150330 +vt 0.458944 -0.150330 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 0.997788 +vt 0.000000 0.997788 +vt 1.000000 0.000000 +vt 0.818747 0.000000 +vt 1.000000 0.987517 +vt 0.000000 0.987518 +vt 1.000000 -0.000000 +vt 0.181253 0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.990645 +vt 1.000000 0.990645 +vt 0.000000 0.995330 +vt 1.000000 0.995330 +vt 0.000000 -0.000000 +vt 0.000000 0.000000 +vt 0.000000 0.000000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn -0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.0000 1.0000 0.0000 +vn -0.0020 0.9994 -0.0350 +vn -0.0021 0.9985 0.0555 +vn 0.1460 0.9893 -0.0000 +vn 0.0000 0.0028 1.0000 +vn -0.0016 0.0014 1.0000 +vn 0.0013 -0.0014 -1.0000 +vn 0.9998 0.0216 -0.0000 +g stone.001_Cube.009_stone.002 +usemtl stone.002 +s off +f 121/228/22 118/229/22 112/230/22 120/231/22 125/232/22 124/233/22 +f 113/234/23 120/235/23 112/236/23 116/237/23 117/238/23 +f 114/239/24 115/240/24 118/241/24 121/242/24 119/243/24 +f 117/244/25 116/245/25 115/246/25 114/239/25 +f 123/247/22 125/232/22 120/231/22 113/234/22 +f 119/248/22 121/228/22 124/233/22 122/249/22 +f 122/249/22 124/233/22 125/232/22 123/247/22 +f 122/250/23 126/251/23 129/252/23 124/253/23 +f 124/254/26 129/255/26 128/256/26 125/257/26 +f 125/258/24 128/259/24 127/260/24 123/261/24 +f 117/244/26 114/239/26 132/262/26 135/263/26 +f 116/237/27 112/236/27 130/264/27 134/265/27 +f 123/247/26 113/234/26 131/266/26 139/267/26 +f 115/246/28 116/245/28 134/268/28 133/269/28 +f 114/239/26 119/243/26 137/270/26 132/262/26 +f 119/248/26 122/249/26 138/271/26 137/272/26 +f 112/230/29 118/229/29 136/273/29 130/274/29 +f 118/241/30 115/240/30 133/275/30 136/276/30 +f 122/249/26 123/247/26 139/267/26 138/271/26 +f 113/234/26 117/238/26 135/277/26 131/266/26 +f 138/271/31 136/278/31 137/272/31 +f 130/279/25 139/267/25 131/266/25 +f 130/279/32 136/278/32 138/271/32 139/267/32 +f 135/277/24 134/265/24 130/264/24 131/266/24 +f 132/262/33 133/269/33 134/268/33 135/277/33 +f 137/272/34 136/278/34 133/275/34 132/262/34 Index: ores.lua ================================================================== --- ores.lua +++ ores.lua @@ -1,17 +1,18 @@ local fragments_per_ingot = 4 minetest.register_lbm { - label = "delete duranium ore"; - name = "sorcery:delete_duranium_ore"; + label = "delete duranium ore again"; + name = "sorcery:delete_duranium_ore_again"; nodenames = {'sorcery:stone_with_duranium'}; action = function(pos,node) minetest.set_node(pos, {name = 'default:stone'}) end } sorcery.data.alloys = {} +sorcery.data.kilnrecs = {} sorcery.data.metallookup = { -- compat bullshit ['moreores:silver_ingot'] = { id = 'silver'; data = sorcery.data.metals.silver; value = fragments_per_ingot; @@ -41,19 +42,20 @@ local tools, armors = sorcery.matreg.tools, sorcery.matreg.armors for name, metal in pairs(sorcery.data.metals) do local ingot = metal.ingot or 'sorcery:' .. name .. '_ingot' local block = metal.block or 'sorcery:' .. name .. '_block' + local screw = 'sorcery:screw_' .. name local fragment = 'sorcery:fragment_' .. name if not metal.no_tools then for _,t in pairs(tools) do sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. t .. '_' .. name)] = { metal = true; id = name; data = metal; } end end if not metal.no_armor then for _,a in pairs(armors) do - sorcery.matreg.lookup[(metal.items and metal.items[t]) or ('sorcery:' .. a .. '_' .. name)] = { + sorcery.matreg.lookup[(metal.items and metal.items[a]) or ('sorcery:' .. a .. '_' .. name)] = { metal = true; id = name; data = metal; } end end sorcery.data.metallookup[ingot] = { @@ -65,10 +67,27 @@ value = fragments_per_ingot * 9; } sorcery.data.metallookup[fragment] = { id = name; data = metal; value = 1; + } + sorcery.data.metallookup[screw] = { + id = name; data = metal; + value = 0; -- prevent use in smelting + } + minetest.register_craftitem(screw, { + description = sorcery.lib.str.capitalize(name) .. ' screw'; + inventory_image = sorcery.lib.image('sorcery_screw.png'):multiply(sorcery.lib.color(metal.tone)):render(); + }) + -- TODO: replace crafting recipe with kiln recipe + minetest.register_craft { + output = screw.. ' 4'; + recipe = { + {fragment,fragment,fragment}; + {'', fragment,''}; + {'', fragment,''}; + }; } if not sorcery.compat.defp(ingot) then -- TODO: remove instant_ores dependency instant_ores.register_metal { name = 'sorcery:' .. name; Index: potions.lua ================================================================== --- potions.lua +++ potions.lua @@ -30,10 +30,13 @@ walkable = false; selection_box = { type = "fixed", fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} }; + on_construct = function(pos) + minetest.get_meta(pos):set_string('infotext',label) + end; sounds = default.node_sound_glass_defaults(); } if extra then for k,v in pairs(extra) do node[k] = v end end if not node.groups then node.groups = {} end node.groups.dig_immediate = 3; DELETED smelter.lua Index: smelter.lua ================================================================== --- smelter.lua +++ smelter.lua @@ -1,309 +0,0 @@ --- alloying furnace --- --- there are several kinds of alloy furnace, with varying --- capabilities. the simplest can alloy two simple metals; --- the most complex can alloy four high-grade metals such --- as titanium, platinum, iridium, or levitanium. --- --- furnace recipes follow a pattern: the number of crucibles --- determines the input slots, the type of crucible determines --- how hot the furnace can get, and various other components --- (like a coolant circulator) can be added to allow the --- creation of exotic metals. --- --- alloy furnaces produce ingots that can later be melted down --- again and cast into a mold to produce items of particular --- shapes. - --- there are four kinds of crucibles: clay, aluminum, platinum, --- and duranium. clay crucibles are made by molding clay into --- the proper shape and then firing it in a furnace. others --- are made by casting. - -local fragments_per_ingot = 4 - -for _, c in pairs { 'clay', 'aluminum', 'platinum', 'duranium' } do - minetest.register_craftitem('sorcery:crucible_' .. c, { - description = sorcery.lib.str.capitalize(c .. ' crucible'); - inventory_image = 'sorcery_crucible_' .. c .. '.png'; - }) -end - -minetest.register_craftitem('sorcery:crucible_clay_molding', { - description = sorcery.lib.str.capitalize('Crucible molding'); - inventory_image = 'sorcery_crucible_clay_molding.png'; -}) - -minetest.register_craft { - recipe = { - { 'default:clay_lump', '', 'default:clay_lump'}; - { 'default:clay_lump', '', 'default:clay_lump'}; - { 'default:clay_lump', 'default:clay_lump', 'default:clay_lump'}; - }; - output = 'sorcery:crucible_clay_molding'; -} -minetest.register_craft { - type = 'shapeless'; - recipe = { 'sorcery:crucible_clay_molding' }; - output = 'default:clay_lump 7'; -} -minetest.register_craft { - type = 'cooking'; - recipe = 'sorcery:crucible_clay_molding'; - cooktime = 40; - output = 'sorcery:crucible_clay'; -} - -local smelter_formspec = function(kind, fuel_progress, smelt_progress) - local layouts = { - [1] = {w = 1, h = 1}; [2] = {w = 2, h = 1}; [3] = {w = 3, h = 1}; - [4] = {w = 2, h = 2}; [5] = {w = 3, h = 2}; [6] = {w = 3, h = 2}; - } - local inpos = { x = 2.8, y = 1 } - local insize = layouts[kind.size] - local outpos = { x = 5.2, y = 2.4 } - local outsize = layouts[kind.outsize] - local fuelpos = { x = 2.8, y = 3.4 } - local fuelsize = layouts[kind.fuelsize] - return string.format([[ - size[8,8] - list[context;input;%f,%f;%f,%f;] - list[context;output;%f,%f;%f,%f;] - list[context;fuel;%f,%f;%f,%f;] - list[current_player;main;0,4.2;8,4] - - image[2.3,1.9;1,1;default_furnace_fire_bg.png^[lowpart:%u%%:default_furnace_fire_fg.png] - image[3.2,1.9;1,1;gui_furnace_arrow_bg.png^[lowpart:%u%%:gui_furnace_arrow_fg.png^[transformR270] - - listring[context;output] listring[current_player;main] - listring[context;input] listring[current_player;main] - listring[context;fuel] listring[current_player;main] - ]], - --input - inpos.x - insize.w/2, -- pos - inpos.y - insize.h/2, - insize.w, insize.h, -- size - --output - outpos.x - outsize.w/2, -- pos - outpos.y - outsize.h/2, - outsize.w, outsize.h, -- size - --fuel - fuelpos.x - fuelsize.w/2, -- pos - fuelpos.y - fuelsize.h/2, - fuelsize.w, fuelsize.h, -- size - - fuel_progress, smelt_progress - ) -end - -local find_recipe = function(inv) - local mix = {} - local count = 0 - for i=1,inv:get_size('input') do - local m = inv:get_stack('input',i) - if m:is_empty() then goto skip end - local l = sorcery.data.metallookup[m:get_name()] - if not l then return false end - mix[l.id] = (mix[l.id] or 0) + l.value - count = count + l.value - ::skip::end - -- everything is metal, we've finished summing it up. - -- let's see if the assembled items match the ratio - -- specified in any of the smelting recipes. - local matches = 0 - for _,rec in pairs(sorcery.data.alloys) do - local fac = nil - local meltpoint = 1 - if rec.metals == nil then goto skip_recipe end - for metal, ratio in pairs(rec.metals) do - if mix[metal] and mix[metal] % ratio == 0 then - if fac then - if mix[metal] / ratio ~= fac then goto skip_recipe end - else fac = math.floor(mix[metal] / ratio) end - local m = sorcery.data.metals[metal] - if m.meltpoint then - meltpoint = math.max(meltpoint, m.meltpoint) end - else goto skip_recipe end - end - do return rec, count, fac, meltpoint end - ::skip_recipe::end - return false -end - -local update_smelter = function(pos) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local proto = minetest.registered_nodes[minetest.get_node(pos).name]._proto - local recipe, count, factor, meltpoint = find_recipe(inv) - if recipe and proto.temp >= meltpoint then - minetest.get_node_timer(pos):start(1) - else - meta:set_float('burntime',0) - end -end - -local smelter_step = function(kind,active,pos,delta) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - local recipe, count, factor = find_recipe(inv) - local cooktime - local elapsed = meta:get_float('burntime') + delta - meta:set_float('burnleft',meta:get_float('burnleft') - delta) - if (not active) and (not recipe) then return false end - if meta:get_float('burnleft') <= 0 or not active then - if recipe then - local burn, frep = minetest.get_craft_result { - method = 'fuel', width = 1; - items = { inv:get_stack('fuel',1) }; - } - if burn.time == 0 then goto nofuel end - inv:set_stack('fuel', 1, frep.items[1]) - meta:set_float('burnleft',burn.time) - meta:set_float('burnmax',burn.time) - if not active then - minetest.swap_node(pos, - sorcery.lib.tbl.merge(minetest.get_node(pos), { - name = kind.id .. '_active' - })) - active = true - end - else goto nofuel end - end - - if not recipe then goto update end - - cooktime = ((recipe.cooktime / fragments_per_ingot) * count) / factor - if elapsed >= cooktime then - elapsed = 0 - -- remove used items - for i=1,inv:get_size('input') do - local s = inv:get_stack('input',i) - if s:is_empty() then goto skip end - s:take_item(1) inv:set_stack('input',i,s) - ::skip::end - - local outstack - if count % fragments_per_ingot == 0 then - outstack = ItemStack { - name = sorcery.data.metals[recipe.output].ingot or 'sorcery:' .. recipe.output .. '_ingot'; - count = count / fragments_per_ingot; - } - else - outstack = ItemStack { - name = 'sorcery:fragment_' .. recipe.output; - count = count; - } - end - - local leftover = inv:add_item('output',outstack) - if not leftover:is_empty() then - minetest.add_item(pos, leftover) - end - end - - ::update:: - meta:set_float('burntime',elapsed) - meta:set_string('formspec', smelter_formspec(kind, - math.min(1, meta:get_float('burnleft') / - meta:get_float('burnmax') - ) * 100, -- fuel - (cooktime and math.min(1, elapsed / cooktime) * 100) or 0 -- smelt - )) - do return active end - - ::nofuel:: - if active then - minetest.swap_node(pos, - sorcery.lib.tbl.merge(minetest.get_node(pos), { name = kind.id })) - end - meta:set_float('burnleft',0) -- just in case - ::noburn:: - meta:set_float('burntime',0) -- just in case - meta:set_string('formspec', smelter_formspec(kind, 0, 0)) - return false -end - -local register_smelter = function(kind) - local recipe = {{},{}; - {'default:stone','default:furnace','default:stone'}; - } do - local on = kind.crucible - local ti = 'default:tin_ingot' - local cu = 'default:copper_ingot' - local crucmap = { - [2] = { {cu,cu,cu}, {on,ti,on} }; - [3] = { {cu,on,cu}, {on,ti,on} }; - [4] = { {on,cu,on}, {on,ti,on} }; - [5] = { {on,cu,on}, {on,on,on} }; - [6] = { {on,on,on}, {on,on,on} }; - }; - for y=1,2 do recipe[y] = crucmap[kind.size][y] end - end - - local desc = 'smelter'; - if kind.temp_name then desc = kind.temp_name .. ' ' .. desc end - if kind.size_name then desc = kind.size_name .. ' ' .. desc end - desc = sorcery.lib.str.capitalize(desc); - local id = 'sorcery:smelter_' .. kind.material .. kind.size_name - kind.id = id - for _, active in pairs {false, true} do - minetest.register_node((active and id .. '_active') or id, { - _proto = kind; - description = desc; - drop = id; - groups = { cracky = 2; }; - paramtype2 = 'facedir'; - light_source = (active and 9) or 0; - on_construct = function(pos) - local meta = minetest.get_meta(pos) - local inv = meta:get_inventory() - inv:set_size('input',kind.size) - inv:set_size('output',kind.outsize) - inv:set_size('fuel',kind.fuelsize) - meta:set_string('infotext', desc) - meta:set_string('formspec', smelter_formspec(kind, 0, 0)) - meta:set_float('burnleft',0) - meta:set_float('burnmax',0) - meta:set_float('burntime',0) - end; - on_metadata_inventory_put = update_smelter; - on_metadata_inventory_move = update_smelter; - on_metadata_inventory_take = update_smelter; - on_timer = function(pos,delta) return smelter_step(kind, active, pos, delta) end; - -- allow_metadata_inventory_put = function(pos, listname, index, stack, player) - -- end; - tiles = { - 'sorcery_smelter_top_' .. tostring(kind.size) .. '.png'; - 'sorcery_smelter_bottom.png'; - 'sorcery_smelter_side.png'; - 'sorcery_smelter_side.png'; - 'sorcery_smelter_side.png'; - 'sorcery_smelter_front' .. ((active and '_hot') or '') .. '.png'; - }; - }) - end - minetest.register_craft { - recipe = recipe; - output = id; - } -end - -for _, s in pairs { - {2, 'small'}; - {3, 'large'}; - {4, 'grand'}; -} do for _, t in pairs { - {1, nil, 'clay'}; - {2, 'hot','aluminum'}; - {3, 'volcanic','platinum'}; - {4, 'stellar','duranium'}; - {5, 'nova','impervium'}; -} do - register_smelter { - size = s[1], size_name = s[2]; - temp = t[1], temp_name = t[2]; - material = t[3]; - crucible = 'sorcery:crucible_' .. t[3]; - outsize = 4, fuelsize = 1; - } -end end ADDED test/common.lua Index: test/common.lua ================================================================== --- test/common.lua +++ test/common.lua @@ -0,0 +1,37 @@ +local qv = 0 +local testn = 0 +test = function(name,expr) + testn = testn + 1 + print(string.format('% 3u ',testn) .. + (expr and '\x1b[32mPASS\x1b[m' or '\x1b[31mFAIL\x1b[m') .. + ' ' .. name) + if not expr and qv == 0 then qv = testn end +end +report = function() os.exit(qv) end + +hexdump = function(s) + local hexlines, charlines = {},{} + for i=1,#s do + local line = math.floor((i-1)/16) + 1 + hexlines[line] = (hexlines[line] or '') .. string.format("%02x ",string.byte(s, i)) + charlines[line] = (charlines[line] or '') .. ' ' .. string.gsub(string.sub(s, i, i), '[^%g ]', '\x1b[;35m·\x1b[36;1m') .. ' ' + end + local str = '' + for i=1,#hexlines do + str = str .. '\x1b[1;36m' .. charlines[i] .. '\x1b[m\n' .. hexlines[i] .. '\n' + end + return str +end + +function dump(o) + if type(o) == "table" then + local str = '' + for k,p in pairs(o) do + str = str .. (k .. ' = {' .. dump(p) ..'}\n') + end + return str + else + return tostring(o) + end +end + ADDED test/encode.lua Index: test/encode.lua ================================================================== --- test/encode.lua +++ test/encode.lua @@ -0,0 +1,32 @@ +dofile'test/common.lua' +local m = dofile'lib/marshal.lua' +local s = dofile'lib/str.lua' +local ench_t = m.g.struct { + id = m.t.str; + slot = m.t.u8; + boost = m.t.u8; + reliability = m.t.u8; +} +local pack, unpack = m.transcoder { + spells = m.g.array(8, ench_t); + energy = m.t.u16; +} + +local packed = pack{ + energy = 1550; + spells = { + { slot = 0; boost = 15; reliability = 100; id = 'dowse' }; + { slot = 1; boost = 3; reliability = 0; id = 'e' }; + }; +} + +print('\x1b[1munarmored\x1b[m\n' .. hexdump(packed)) +print('\x1b[1marmored\x1b[m\n' .. hexdump(s.meta_armor(packed))) +print('\x1b[1marmored (struct mode)\x1b[m\n' .. hexdump(s.meta_armor(packed,true))) +print('\x1b[1mdearmored\x1b[m\n' .. hexdump(s.meta_dearmor(s.meta_armor(packed)))) +print('\x1b[1mdearmored (struct mode)\x1b[m\n' .. hexdump(s.meta_dearmor(s.meta_armor(packed,true),true))) + +test('dearmor(armor(x)) == x',s.meta_dearmor(s.meta_armor(packed)) == packed) +test('struct_dearmor(struct_armor(x)) == x',s.meta_dearmor(s.meta_armor(packed,true),true) == packed) +test('struct_dearmor("string") == "string"',s.meta_dearmor('string',true) == 'string') +report() Index: test/marshal.lua ================================================================== --- test/marshal.lua +++ test/marshal.lua @@ -1,5 +1,6 @@ +dofile'test/common.lua' local m = dofile"lib/marshal.lua" local car_t = m.g.struct { brand = m.t.str; year = m.t.u16; } @@ -37,26 +38,10 @@ {brand = 'subaru', year = 321}; }; } if m.wrong(s) then print(s.exp) os.exit(1) end -local str = 'serialized:' -for i=1,#s do - str = str ..' '.. string.format("%x",string.byte(str, i)) -end -print(str) +print(hexdump(s)) local v = unpack(s) -local function dump(o) - if type(o) == "table" then - local str = '' - for k,p in pairs(o) do - str = str .. (k .. ' = {' .. dump(p) ..'}\n') - end - return str - else - return tostring(o) - end -end - print(dump(v)) Index: test/rand.lua ================================================================== --- test/rand.lua +++ test/rand.lua @@ -1,4 +1,8 @@ +dofile'test/common.lua' local str = dofile "lib/str.lua" local rnd = str.rand(5,45) print(rnd) -print(str.capitalize(rnd)) + +test('random strings do not match',str.rand(5,45) ~= str.rand(5,45)) +test('strings are correct length',#str.rand(44,45) == 45) +report() ADDED textures/sorcery_amulet.png Index: textures/sorcery_amulet.png ================================================================== --- textures/sorcery_amulet.png +++ textures/sorcery_amulet.png cannot compute difference between binary files Index: textures/sorcery_enchant_conserve.png ================================================================== --- textures/sorcery_enchant_conserve.png +++ textures/sorcery_enchant_conserve.png cannot compute difference between binary files Index: textures/sorcery_enchant_dowse.png ================================================================== --- textures/sorcery_enchant_dowse.png +++ textures/sorcery_enchant_dowse.png cannot compute difference between binary files Index: textures/sorcery_enchant_endure.png ================================================================== --- textures/sorcery_enchant_endure.png +++ textures/sorcery_enchant_endure.png cannot compute difference between binary files Index: textures/sorcery_enchant_harvest.png ================================================================== --- textures/sorcery_enchant_harvest.png +++ textures/sorcery_enchant_harvest.png cannot compute difference between binary files Index: textures/sorcery_enchant_pierce.png ================================================================== --- textures/sorcery_enchant_pierce.png +++ textures/sorcery_enchant_pierce.png cannot compute difference between binary files Index: textures/sorcery_enchant_rend.png ================================================================== --- textures/sorcery_enchant_rend.png +++ textures/sorcery_enchant_rend.png cannot compute difference between binary files ADDED textures/sorcery_enchanter_channeler.png Index: textures/sorcery_enchanter_channeler.png ================================================================== --- textures/sorcery_enchanter_channeler.png +++ textures/sorcery_enchanter_channeler.png cannot compute difference between binary files ADDED textures/sorcery_enchanter_pedestal.png Index: textures/sorcery_enchanter_pedestal.png ================================================================== --- textures/sorcery_enchanter_pedestal.png +++ textures/sorcery_enchanter_pedestal.png cannot compute difference between binary files ADDED textures/sorcery_gem_diamond_shard.png Index: textures/sorcery_gem_diamond_shard.png ================================================================== --- textures/sorcery_gem_diamond_shard.png +++ textures/sorcery_gem_diamond_shard.png cannot compute difference between binary files ADDED textures/sorcery_screw.png Index: textures/sorcery_screw.png ================================================================== --- textures/sorcery_screw.png +++ textures/sorcery_screw.png cannot compute difference between binary files ADDED textures/sorcery_transparent.png Index: textures/sorcery_transparent.png ================================================================== --- textures/sorcery_transparent.png +++ textures/sorcery_transparent.png cannot compute difference between binary files ADDED tnodes.lua Index: tnodes.lua ================================================================== --- tnodes.lua +++ tnodes.lua @@ -0,0 +1,55 @@ +for i=1,minetest.LIGHT_MAX do + minetest.register_node('sorcery:air_glimmer_' .. tostring(i), { + drawtype = 'airlike'; + light_source = 5 + math.ceil(i * (11/minetest.LIGHT_MAX)); + sunlight_propagates = true; + buildable_to = true; + pointable = false; + walkable = false; + floodable = true; + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_float('duration',10) + meta:set_float('timeleft',10) + meta:set_int('power',minetest.LIGHT_MAX) + minetest.get_node_timer(pos):start(1) + end; + on_timer = function(pos,dtime) + local meta = minetest.get_meta(pos) + local elapsed = dtime + meta:get_float('duration') - meta:get_float('timeleft') + local level = 1 - (elapsed / meta:get_float('duration')) + local lum = math.ceil(level*meta:get_int('power')) + print('elapsed time',elapsed) + print('light level',level) + print('lum',lum) + if lum ~= i then + if lum <= 0 then + minetest.remove_node(pos) + return false + else + minetest.swap_node(pos,{name='sorcery:air_glimmer_'..tostring(lum)}) + end + end + minetest.add_particlespawner { + amount = 3 * meta:get_int('power'); + time = 1.1; + minpos = vector.subtract(pos,2); + maxpos = vector.add(pos,2); + minvel = {x = -0.5; z = -0.5; y = -0.3}; + maxvel = {x = 0.5; z = 0.5; y = 0.3}; + minsize = 0.3, maxsize = 0.9; + minexptime = 0.5, maxexptime = 1.2; + texture = 'sorcery_spark.png'; + animation = { + glow = i; + length = 1.2; + type = 'vertical_frames'; + aspect_w = 16; + aspect_h = 16; + } + } + meta:set_float('timeleft',meta:get_float('duration') - elapsed) + return true + end; + }) +end Index: wands.lua ================================================================== --- wands.lua +++ wands.lua @@ -75,10 +75,21 @@ if full then if wand.gem ~= nil then name = wand.gem .. ' ' .. name end if wand.wire ~= nil then name = wand.wire .. 'clad ' .. name end end return u.str.capitalize(name) + end; + fullname = function(stack) + local proto = sorcery.wands.util.getproto(stack) + local wm = stack:get_meta() + local spell = wm:get_string('sorcery_wand_spell') + if spell ~= '' then + local sd = sorcery.data.spells[spell] + return u.str.capitalize(sd.name) .. ' wand'; + else + return sorcery.wands.util.basename(proto) + end end; wear = function(item) local meta = item:get_meta() local spell = meta:get_string('sorcery_wand_spell') local proto = sorcery.wands.util.getproto(item) @@ -220,11 +231,11 @@ img.whole = img.gem:blit(img.whole) end return img end -local createstand = function(name, desc, tex, extra) +local createstand = function(name, wood, desc, tex, extra) local hitbox = { type = "fixed"; fixed = { -0.5, -0.5, -0.3; 0.5, -0.1, 0.3; @@ -241,41 +252,63 @@ paramtype2 = 'facedir'; tiles = images; selection_box = hitbox; collision_box = hitbox; use_texture_alpha = true; + _proto = { + wood = wood; + }; groups = { sorcery_wand_stand = 1; choppy = 2; oddly_breakable_by_hand = 2; }; } minetest.register_node(name, u.tbl.merge(auto,extra)) end - +local update_stand_info = function(pos) + local woodname = minetest.registered_nodes[minetest.get_node(pos).name]._proto.wood + local meta = minetest.get_meta(pos) + local inv = meta:get_inventory() + if inv:is_empty('wand') then + meta:set_string('infotext',u.str.capitalize(woodname) .. ' wand stand') + else + local stack = inv:get_stack('wand',1) + local spell = stack:get_meta():get_string('sorcery_wand_spell') + local color = u.color(127,127,127) + if spell ~= '' then + color = u.color(sorcery.data.spells[spell].color):readable() + end + local wand_proto = sorcery.wands.util.getproto(stack) + meta:set_string('infotext',color:fmt(sorcery.wands.util.fullname(stack) .. ' stand')) + end +end for woodname, wood in pairs(sorcery.wands.materials.wood) do - local blank = u.image('doors_blank.png'); -- haaaaack + local blank = u.image('sorcery_transparent.png'); -- haaaaack local name = 'sorcery:wand_stand_' .. woodname - createstand(name , + createstand(name, woodname, u.str.capitalize(woodname .. 'wood wand stand'), { wood.tex; blank; blank; blank; }, { on_construct = function(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() inv:set_size('wand', 1) + update_stand_info(pos) end; on_rightclick = function(pos,node,user,stack) local meta = minetest.get_meta(pos) local stand = meta:get_inventory() + local wand_proto = sorcery.wands.util.getproto(stack) if minetest.get_item_group(stack:get_name(), 'sorcery_wand') ~= 0 then stand:set_stack('wand',1,stack) minetest.swap_node(pos, { - name = sorcery.wands.util.baseid(stack:get_definition()._proto) .. '_stand_' .. woodname; + name = sorcery.wands.util.baseid(wand_proto) .. '_stand_' .. woodname; param2 = node.param2; }) stack = ItemStack(nil) end + update_stand_info(pos) return stack end } ) local plank = wood.plank or 'default:' .. woodname .. '_wood' @@ -296,15 +329,16 @@ then gemimg = gemimg:multiply(u.color(sorcery.wands.materials.gem[kind.gem].tone)) else gemimg = gemimg:fade(1) end createstand( kind.id .. '_stand_' .. woodname, + woodname, u.str.capitalize(woodname) .. 'wood wand stand with ' .. string.lower(sorcery.wands.util.basename(kind,true)), { wood.tex; sorcery.wands.materials.wood[kind.wood].tex; gemimg; - (kind.wire and sorcery.wands.materials.wire[kind.wire].tex) or u.image('doors_blank.png'); -- haaaaack + (kind.wire and sorcery.wands.materials.wire[kind.wire].tex) or u.image('sorcery_transparent.png'); -- haaaaack }, { drop = 'sorcery:wand_stand_' .. woodname; after_dig_node = function(pos,node,meta,digger) local stack = meta.inventory.wand[1] if stack and not stack:is_empty() then @@ -327,11 +361,13 @@ stand:set_stack('wand',1,ItemStack(nil)) minetest.swap_node(pos, { name = 'sorcery:wand_stand_' .. woodname; param2 = node.param2; }) - ::failure:: return stack + ::failure:: + update_stand_info(pos) + return stack end; } ) end end @@ -374,11 +410,11 @@ local wm = stack:get_meta() local spell = wm:get_string('sorcery_wand_spell') if spell ~= "" then local sd = sorcery.data.spells[spell] wm:set_string('description', u.ui.tooltip { - title = u.str.capitalize(sd.name) .. ' wand'; + title = sorcery.wands.util.fullname(stack); desc = sorcery.wands.util.basedesc(proto); props = { { color = u.color(sd.color); desc = sd.desc } }; })