Index: altar.lua ================================================================== --- altar.lua +++ altar.lua @@ -48,19 +48,31 @@ props.wield_item = itemstring object:set_properties(props) object:set_yaw(math.pi*2 - node.param2*(math.pi / 2)) end end + +-- remove unknown gifts +minetest.register_on_mods_loaded(function() + for name, god in pairs(sorcery.data.gods) do + local bad = {} + for g in pairs(god.gifts) do + -- can't mutate table while we're iterating it + if not minetest.registered_nodes[g] then bad[#bad+1] = g end + end + for _, g in ipairs(bad) do god.gifts[g] = nil end + end +end) for name, god in pairs(sorcery.data.gods) do local hitbox = { 0-(god.idol.width / 2.0), 0-(god.idol.height / 2.0), -0.15, god.idol.width / 2.0, god.idol.height / 2.0, 0.15 } -- {xmin, ymin, zmin, -- xmax, ymax, zmax} in nodes from node center. paramtype = "light"; - minetest.register_node('sorcery:idol_' .. name, { + sorcery.lib.node.reg_autopreserve('sorcery:idol_' .. name, { description = god.idol.desc; drawtype = "mesh"; mesh = 'sorcery-idol-' .. name .. '.obj'; paramtype = 'light'; paramtype2 = 'facedir'; @@ -69,35 +81,18 @@ tiles = god.idol.tex; selection_box = { type = "fixed"; fixed = {hitbox}; }; collision_box = { type = "fixed"; fixed = {hitbox}; }; groups = { cracky = 2, sorcery_idol = 1, heavy = 1, sorcery_worship = 1}; - after_place_node = function(pos, placer, stack, pointat) - local meta = minetest.get_meta(pos) - local stackmeta = stack:get_meta() - meta:set_int('favor', stackmeta:get_int('favor')) - meta:set_string('last_sacrifice', stackmeta:get_string('last_sacrifice')) - + on_construct = function(pos) minetest.get_node_timer(pos):start(1) end; - drop = { - -- for some idiot reason this is necessary for - -- preserve_metadata to work right - max_items = 1; - items = { - { items = {'sorcery:idol_' .. name} } - }; - }; - - preserve_metadata = function(pos, node, meta, newstack) - newstack[1]:get_meta():from_table(meta) - end; - on_timer = function(pos, elapsed) local altar = minetest.find_node_near(pos, 3, "sorcery:altar") -- TODO even without an altar, an idol with high favor could still be the source of miracles + -- refills nearby partly empty troughs at cost to favor? if not altar then return true end local altarmeta = minetest.get_meta(altar) local inv = altarmeta:get_inventory() local idolmeta = minetest.get_meta(pos) @@ -236,10 +231,11 @@ -- preserve wear local gift if type(tx) == 'string' then gift = ItemStack(tx) else gift = tx end + if not gift:is_known() then goto skip end local wear = stack:get_wear() if wear > 0 then gift:set_wear(wear) end -- preserve meta @@ -266,11 +262,11 @@ divine_favor = divine_favor - cost log.act(god.name, 'has consecrated', s, 'into', tx, 'for the cost of', cost, 'points of divine favor') goto refresh end end - end + ::skip::end end ::refresh:: idolmeta:set_int('favor', divine_favor) update_altar(altar,nil) Index: astrolabe.lua ================================================================== --- astrolabe.lua +++ astrolabe.lua @@ -79,10 +79,12 @@ groups = { cracky = 2, choppy = 2; dig_immediate = 2; sorcery_tech = 1; }; + sunlight_propagates = true; + paramtype = 'light'; selection_box = albox, collision_box = albox; after_dig_node = sorcery.lib.node.purge_containers; tiles = { 'default_steel_block.png'; 'default_bronze_block.png'; Index: data/elixirs.lua ================================================================== --- data/elixirs.lua +++ data/elixirs.lua @@ -24,19 +24,19 @@ }; Rapidity = { color = {183,28,238}; qual = 'speed'; apply = inc('speed'); describe = function(potion) - return 'good', 'Quickened', 'This potion will take effect more quiclkly and easily' + return 'good', 'quickened', 'This potion will take effect more quickly and easily' end; infusion = 'sorcery:liquid_sap_acacia_bottle'; }; Purity = { color = {244,255,255}; qual = 'purity'; apply = inc('purity'); describe = function(potion) - return 'good', 'purified', 'This potion\'s impurities and undesirable side effects are diminished or eliminated' + return 'good', 'purified', 'This potion\'s impurities and undesirable qualities are diminished or eliminated' end; infusion = 'sorcery:oil_purifying'; }; Beauty = { color = {255,20,226}; qual = 'beauty'; Index: gems.lua ================================================================== --- gems.lua +++ gems.lua @@ -56,10 +56,11 @@ local img = sorcery.lib.image local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)) local img_sparkle = img('sorcery_amulet_sparkle.png') local useamulet = function(stack,user,target) local sp = sorcery.amulet.getspell(stack) + print('got spell',dump(sp)) if not sp or not sp.cast then return nil end local usedamulet if stack:get_count() == 1 then usedamulet = stack else Index: infuser.lua ================================================================== --- infuser.lua +++ infuser.lua @@ -206,14 +206,16 @@ local infusion = inv:get_list('infusion') local potions = inv:get_list('potions') local elixir = infusion[1]:get_definition() local probe = sorcery.spell.probe(pos) local fx = infuser_mods(pos) + local sparkle_color = {sorcery.lib.color(255, 0, 145)}; if probe.disjunction then return true end local potionct = 0 + local cancel = true do local ingredient -- *eyeroll* if infusion[1]:is_empty() then goto cancel end ingredient = infusion[1]:get_name() for i = 1,#potions do @@ -222,22 +224,28 @@ local base = potions[i]:get_name() local potion = potions[i]:get_definition() if elixir_can_apply(elixir._proto,potion) then -- at least one combination makes a valid potion; -- we can start the infuser - goto start + if elixir._proto.color then + sparkle_color[#sparkle_color+1] = sorcery.lib.color(elixir._proto.color) + end + cancel = false end for _,v in pairs(sorcery.register.infusions.db) do if v.infuse == ingredient and v.into == base then -- at least one combination makes a valid -- potion; we can start the infuser - goto start + if v.output.data and v.output.data.color then + sparkle_color[#sparkle_color+1] = sorcery.lib.color(v.output.data.color) + end + cancel = false end end ::skip:: end - ::cancel:: do + ::cancel:: if cancel then infuser_stop(pos) return false end ::start:: @@ -270,17 +278,19 @@ aspect_w = 16; length = 4.1; }; } end - -- for i=0,4 do - spawn('sorcery_spark.png^[multiply:#FF8FDD', 1, 32 * 4) - -- end - -- for i=0,4 do - spawn('sorcery_spark.png^[multiply:#FFB1F6', 0.5, 64 * 4) - -- end - + local spark = sorcery.lib.image('sorcery_spark.png') + for i = 1,4 do + local fac = 1 / i + local _, spc = sorcery.lib.tbl.pick(sparkle_color) + local sp = spark:glow(spc) + + spawn(sp:render(), fac, (32/fac) * 4) + end + local discharge = sorcery.lib.node.discharger(pos) if newtime >= infusion_time then -- finished local ingredient = infusion[1]:get_name() Index: keg.lua ================================================================== --- keg.lua +++ keg.lua @@ -125,14 +125,12 @@ sorcery.liquid.sound_dip(chg,avail,pos) update() -- fancy visuals local color = sorcery.lib.color(liq.color or {255,255,255}) - local spritz = sorcery.lib.image('sorcery_droplet.png') - local drop = sorcery.lib.image('sorcery_drop.png') - spritz = spritz:blit(spritz:multiply(color)) - drop = drop:blit (drop:multiply (color)) + local spritz = sorcery.lib.image('sorcery_droplet.png'):glow(color) + local drop = sorcery.lib.image('sorcery_drop.png'):glow(color) local facing = minetest.facedir_to_dir(minetest.get_node(pos).param2) local noz = vector.add(pos, vector.rotate( vector.new(0.0,0,-0.48), vector.dir_to_rotation(facing) )) @@ -151,11 +149,11 @@ minexptime = 0.5, maxexptime = 0.5; animation = { type = 'sheet_2d'; frames_w = 14; frames_h = 1; - frame_length = 0.5/14; + frame_length = (0.5/14) + 0.02; } } minetest.after(0.2, function() minetest.add_particlespawner { amount = math.random(5,11) * chg, time = 0.13 * chg; @@ -171,11 +169,11 @@ minexptime = 1, maxexptime = 1.5; animation = { type = 'sheet_2d'; frames_w = 10; frames_h = 1; - frame_length = 1.5/10; + frame_length = (1.5/10) + 0.02; } } end) return filled Index: lib/node.lua ================================================================== --- lib/node.lua +++ lib/node.lua @@ -360,6 +360,29 @@ end else return function(i) return i, false end end end; + + autopreserve = function(id, tbl) + tbl.drop = tbl.drop or { + max_items = 1; + items = { + { items = {id} }; + }; + } + local next_apn = tbl.after_place_node + tbl.after_place_node = function(...) local pos, who, stack = ... + minetest.get_meta(pos):from_table(stack:get_meta():to_table()) + if next_apn then return next_apn(...) end + end + local next_pm = tbl.preserve_metadata + tbl.preserve_metadata = function(...) local pos, node, meta, drops = ... + drops[1]:get_meta():from_table({fields = meta}) + if next_pm then return next_pm(...) end + end + return tbl + end; + reg_autopreserve = function(id, tbl) + minetest.register_node(id, sorcery.lib.node.autopreserve(id, tbl)) + end; } Index: liquid.lua ================================================================== --- liquid.lua +++ liquid.lua @@ -196,11 +196,11 @@ -- local img_glass = L.image('vessels_drinking_glass.png'):blit( -- L.image(fmt('sorcery_liquid_glass_%s.png', liq.imgvariant or 'dull')) -- :multiply(L.color(liq.color))) - minetest.register_node(':'..bottle, { + sorcery.lib.node.reg_autopreserve(':'..bottle, { description = liq.desc_bottle or fmt('%s Bottle', L.str.capitalize(liq.name)); inventory_image = img_bottle; drawtype = 'plantlike', tiles = {img_bottle}; is_ground_content = false, walkable = false; sunlight_propagates = true, paramtype = 'light'; @@ -246,10 +246,11 @@ end; sorcery.liquid.sound_dip = function(amt_output, amt_basin, pos) sorcery.liquid.sound_pour(amt_output, amt_basin, pos) end; + -- pre-register basic liquids used in Sorcery and common ones sorcery depends on sorcery.liquid.register{ id = 'default:water'; @@ -292,12 +293,12 @@ } minetest.register_abm { label = 'Rainfall'; nodenames = {'group:sorcery_collect_rainwater'}; - interval = 230; - chance = 40; + interval = 120; + chance = 27; min_y = -400; catch_up = true; action = function(pos, node) -- TODO vary by season and biome? if minetest.get_natural_light(vector.offset(pos,0,1,0), 0.5) >= 15 then Index: potions.lua ================================================================== --- potions.lua +++ potions.lua @@ -26,13 +26,10 @@ inventory_image = image; paramtype = "light"; is_ground_content = false; light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0; drop = 'sorcery:' .. name; - preserve_metadata = function(pos,node,meta,newstack) - newstack[1]:get_meta():from_table(meta) - end; walkable = false; selection_box = { type = "fixed", fixed = {-0.25, -0.5, -0.25, 0.25, 0.3, 0.25} }; @@ -45,11 +42,11 @@ if not node.groups then node.groups = {} end node.groups.dig_immediate = 3; node.groups.attached_node = 1; node.groups.vessel = 1; node.groups.not_in_creative_inventory = 1; - minetest.register_node("sorcery:"..name, node) + sorcery.lib.node.reg_autopreserve("sorcery:"..name, node) end sorcery.register_oil = function(name,label,desc,color,imgvariant,extra) local image = 'xdecor_bowl.png^(sorcery_oil_' .. (imgvariant or 'dull') .. '.png^[colorize:'..tostring(color)..':140)' sorcery.register.residue.link('sorcery:' .. name, 'xdecor:bowl') Index: runeforge.lua ================================================================== --- runeforge.lua +++ runeforge.lua @@ -7,11 +7,11 @@ local constants = { rune_mine_interval = 240; -- how often a powered forge rolls for new runes - rune_cache_max = 4; + rune_cache_max = 6; -- how many runes a runeforge can hold at a time rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'}; -- how many grades of rune quality/power there are @@ -37,17 +37,17 @@ supreme = {grade = 6, name = 'Supreme'; infusion = 'sorcery:powder_levitanium'; dist = { Fragile = 0, Weak = 0, Ordinary = 1, Pristine = 0.7, Sublime = 0.4 }; }; }; } -local calc_phial_props = function(phial) --> mine interval: float, time factor: float +local calc_phial_props = function(phial) --> mine interval: float, power factor: float local m = phial:get_meta() local g = phial:get_definition()._proto.data.grade local i = constants.rune_mine_interval local fac = (g-1) / 5 - fac = fac + 0.4 * m:get_int('speed') - return i - ((i*0.5) * fac), 0.5 * fac + fac = fac + 0.2 * m:get_int('speed') + return math.max(3,i - ((i*0.5) * fac)), 0.5 * fac end sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune) local id = 'sorcery:rune_' .. name rune.image = rune.image or string.format('sorcery_rune_%s.png',name) rune.item = id @@ -91,10 +91,13 @@ } sorcery.register.infusions.link { infuse = p.infusion; into = 'sorcery:potion_subtle'; output = 'sorcery:'..id; + _proto = { + data = { color = color }; + }; } end local register_rune_wrench = function(w) local mp = sorcery.data.metals[w.metal].parts @@ -233,11 +236,11 @@ if proto.frame and spell.frame and spell.frame[proto.frame] then local sp = spell.frame[proto.frame] if not sp.mingrade or rg >= sp.mingrade then title = sp.name or title desc = sp.desc or desc - cast = sp.desc or cast + cast = sp.cast or cast apply = sp.apply or apply remove = sp.remove or remove mingrade = sp.mingrade or mingrade base_spell = false end @@ -320,22 +323,24 @@ end has_phial = has_phial() local spec = string.format([[ formspec_version[3] size[10.25,8] real_coordinates[true] - list[context;cache;%f,0.25;%u,1;] list[context;amulet;3.40,1.50;1,1;] list[context;active;5.90,1.50;1,1;] list[context;wrench;1.25,1.75;1,1;] list[context;phial;7.25,1.75;1,1;] list[context;refuse;8.50,1.75;1,1;] list[current_player;main;0.25,3;8,4;] + style_type[list;size=0.8] + list[context;cache;%f,0.25;%u,1;] + image[0.25,0.50;1,1;sorcery_statlamp_%s.png] - ]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max, + ]], (10.5 - 0.8*(constants.rune_cache_max*1.25))/2, constants.rune_cache_max, ((not (has_phial and pow_min)) and 'off' ) or ( probe.disjunction and 'blue' ) or ((has_phial and pow_max) and 'green') or 'yellow') local ghost = function(slot,x,y,img) @@ -399,11 +404,11 @@ if r.minpower > max then max = r.minpower end if min == nil or r.minpower < min then min = r.minpower end end -- high-quality phials reduce power usage local fac = select(2, calc_phial_props(phial)) - min = min * fac max = max * fac + min = min / fac max = max / fac return min*time,max*time end; }; on_leychange = runeforge_update; recipe = { Index: tap.lua ================================================================== --- tap.lua +++ tap.lua @@ -1,6 +1,12 @@ local log = sorcery.logger('tap') +local sap_interval = 20; + +local function tapdrip(liq, pos) + return sorcery.vfx.drip(liq, vector.offset(pos, 0, -0.3, 0), math.random(5,12), sap_interval, 2) +end + minetest.register_node('sorcery:tap',{ description = 'Tree Tap'; drawtype = 'mesh'; mesh = 'sorcery-tap.obj'; inventory_image = 'sorcery_tap_inv.png'; @@ -18,26 +24,31 @@ collision_box = { type='fixed', fixed = {-0.2,-0.5,-0.35; 0.3,0.1,0.4} }; node_placement_prediction = ''; on_place = function(stack,who,where) if where.type ~= 'node' then return end local bl = minetest.get_node(where.under) - -- FIXME prevent tapping 'dead' non-tree wood blocks local tree = sorcery.tree.get(where.under) - if not tree or tree.sap == false then return end; + if not tree or tree.def.sap == false then return end; - -- disallow vertical attachment + -- disallow vertical attachment, bc that makes no sense if vector.subtract(where.under,where.above).y ~= 0 then return end minetest.set_node(where.above, { name = 'sorcery:tap'; param2 = minetest.dir_to_wallmounted(vector.subtract(where.under,where.above)) }) + + if sorcery.lib.node.tree_is_live(where.under) then + -- start dripping immediately to indicate the tree is alive + tapdrip(tree.def.sapliq, where.above) + end stack:take_item(1) return stack end; + on_screwdriver = function() return false end; _sorcery = { recipe = { note = 'Extract syrups and oils from trees'; }; }; @@ -50,19 +61,18 @@ {'sorcery:pipe','sorcery:valve','sorcery:screw_steel'}; {'','sorcery:pipe',''}; }; } -local sap_interval = 60; local abm_cache local abm_cache_time minetest.register_abm { label = 'Sap drip'; nodenames = {'sorcery:tap'}; neighbors = {'group:tree'}; interval = sap_interval; - chance = 7; + chance = 4; catch_up = true; action = function(pos, node) local now = os.time() if abm_cache_time == nil or now > abm_cache_time + (sap_interval-1) then abm_cache = { treehash = {} } @@ -103,10 +113,12 @@ if (not live) or tree.sap == false or not tree.sapliq then return end if mass_trunk < 12*3 then return end -- too small + + tapdrip(tree.sapliq,pos) local mass = mass_leaves + mass_trunk local max_mass = 400 local ltratio = mass_leaves / mass_trunk local mratio = mass / max_mass Index: vfx.lua ================================================================== --- vfx.lua +++ vfx.lua @@ -150,5 +150,31 @@ aspect_w = 16, aspect_h = 16; }; } end end + +function sorcery.vfx.drip(liquid, noz, amt, time, exp) + if type(liquid) == 'string' then liquid = sorcery.register.liquid.db[liquid] end + local minnoz = vector.offset(noz, -0.03, 0.0, -0.03); + local maxnoz = vector.offset(noz, 0.03, 0.0, 0.03); + local drop = sorcery.lib.image('sorcery_drop.png'):multiply(liquid.color) + return minetest.add_particlespawner { + amount = amt, time = time; + texture = drop:render(); + minpos = minnoz, maxpos = maxnoz; + minvel = vector.new(0,0,0); + maxvel = vector.new(0,-0.2,0); + minacc = vector.new(0,-0.2,0); + maxacc = vector.new(0,-0.23,0); + minsize = 0.4, maxsize = 1; + glow = liquid.glow or 2; + minexptime = exp, maxexptime = exp; + animation = { + type = 'sheet_2d'; + frames_w = 10; + frames_h = 1; + frame_length = (exp/10) + 0.01; + }; + vertical = true; + } +end