Index: altar.lua ================================================================== --- altar.lua +++ altar.lua @@ -1,9 +1,9 @@ local altar_item_offset = { x = 0, y = -0.3, z = 0 } -local log = function(...) sorcery.log('altar',...) end +local log = sorcery.logger('altar') local range = function(min, max) local span = max - min local val = math.random() * span return val + min @@ -75,11 +75,11 @@ 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')) - minetest.get_node_timer(pos):start(60) + minetest.get_node_timer(pos):start(1) end; drop = { -- for some idiot reason this is necessary for -- preserve_metadata to work right @@ -169,11 +169,11 @@ local gift = sorcery.lib.tbl.pick(god.gifts) local data = god.gifts[gift] local value, rarity = data[1], data[2] if value <= divine_favor and math.random(rarity) == 1 then bestow(gift) - log(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift') + log.act(god.name .. ' has produced ' .. gift .. ' upon an altar as a gift') if math.random(god.generosity) == 1 then -- unappreciated gifts may incur divine -- irritation divine_favor = divine_favor - 1 end @@ -217,37 +217,56 @@ end -- loop through the list of things this god will consecrate and -- check whether the item on the altar is any of them for s, cons in pairs(god.consecrate) do - local cost, tx = cons[1], cons[2] - if type(tx) == "table" then - tx = tx[math.random(#tx)] - end - -- preserve wear - local gift = ItemStack(tx) - local wear = stack:get_wear() - if wear > 0 then - gift:set_wear(wear) - end - -- preserve meta - gift:get_meta():from_table(stack:get_meta():to_table()) - -- reflash enchantments to ensure label is accurate - local ench = sorcery.enchant.get(gift) - if #ench.spells > 0 then - -- add a bit of energy as a gift? - if math.random(math.ceil(god.stinginess * 0.5)) == 1 then - local max = 0.05 * god.generosity - ench.energy = ench.energy * range(0.7*max,max) + if itemname == s then + local cost, tx + if type(cons) == "table" then + cost, tx = cons[1], cons[2] + tx = tx[math.random(#tx)] + elseif type(cons) == 'function' then + cost, tx = cons { + favor = divine_favor; + pos = pos; + altar = altarmeta; + idol = idolmeta; + god = god; + } + end + -- preserve wear + local gift + if type(tx) == 'string' then + gift = ItemStack(tx) + else gift = tx end + local wear = stack:get_wear() + if wear > 0 then + gift:set_wear(wear) + end + -- preserve meta + local gm = gift:get_meta() + gm:from_table( + sorcery.lib.tbl.merge( + stack:get_meta():to_table(), + gm:to_table() + ) + ) -- oof + -- reflash enchantments to ensure label is accurate + local ench = sorcery.enchant.get(gift) + if #ench.spells > 0 then + -- add a bit of energy as a gift? + if math.random(math.ceil(god.stinginess * 0.5)) == 1 then + local max = 0.05 * god.generosity + ench.energy = ench.energy * range(0.7*max,max) + end + sorcery.enchant.set(gift,ench) end - sorcery.enchant.set(gift,ench) - end - if itemname == s then + if divine_favor >= cost then bestow(gift) divine_favor = divine_favor - cost - print(god.name..'has consecrated ' ..s.. ' into ' ..tx.. ', for the cost of ' ..cost.. ' points of divine favor') + log.act(god.name, 'has consecrated', s, 'into', tx, 'for the cost of', cost, 'points of divine favor') goto refresh end end end end Index: astrolabe.lua ================================================================== --- astrolabe.lua +++ astrolabe.lua @@ -6,11 +6,10 @@ r[i] = { id = k[i]; name = sorcery.data.calendar.styles[v].name; } end - print(dump(r)) return r end local astrolabe_formspec = function(pos) local m = minetest.get_meta(pos) local i = m:get_inventory() Index: cookbook.lua ================================================================== --- cookbook.lua +++ cookbook.lua @@ -2,10 +2,12 @@ -- recipe and enscribe it on a sheet of paper. these sheets of -- paper can then bound together into books, combining like -- recipes sorcery.cookbook = {} +local log = sorcery.logger('cookbook') + local constants = { -- do not show recipes for items in these groups exclude_groups = { }; exclude_names = { @@ -53,34 +55,44 @@ local modofname = function(id) local sep = string.find(id,':') if sep == nil then return nil end -- uh oh return string.sub(id, 1, sep - 1) end +local item_restrict_eval = function(name, restrict) + for _,n in pairs(constants.exclude_names) do + if string.find(name,n) ~= nil then + return false + end + end + for _,g in pairs(constants.exclude_groups) do + if minetest.get_item_group(name, g) > 0 then + return false + end + end + + local props = minetest.registered_items[name]._sorcery + local module = modofname(name) + + return not (excluded + or sorcery.lib.tbl.has(constants.blacklist_mods,module) + or (props and props.recipe and props.recipe.secret) + or (restrict and ( + (restrict.pred and restrict.pred { + mod = module, item = name, props = props + } ~= true) + or (restrict.mod and module ~= restrict.mod) + or (restrict.group and (minetest.get_item_group(name, restrict.group) == 0)) + ))) +end + local pick_builtin = function(kind) return function(restrict) -- ow ow ow ow ow ow ow local names = {} for k in pairs(minetest.registered_items) do local rec = minetest.get_craft_recipe(k) if rec.items ~= nil and (rec.method == kind or (rec.method == 'shapeless' and kind == 'normal')) then -- is this last bit necessary? - local excluded = false - for _,n in pairs(constants.exclude_names) do - if string.find(k,n) ~= nil then - excluded = true break end - end - if not excluded then for _,g in pairs(constants.exclude_groups) do - if minetest.get_item_group(k, g) > 0 then - excluded = true break end - end end - local props = minetest.registered_items[k]._sorcery - local module = modofname(k) - if not (excluded - or sorcery.lib.tbl.has(constants.blacklist_mods,module) - or (props and props.recipe and props.recipe.secret) - or (restrict and ( - (restrict.mod and module ~= restrict.mod) - or (restrict.group and (minetest.get_item_group(k, restrict.group) == 0)) - ))) then names[#names + 1] = k end + if item_restrict_eval(k, restrict) then names[#names + 1] = k end end end return names[math.random(#names)] end end local find_builtin = function(method,kind) @@ -221,11 +233,26 @@ {0,0}; {0,1}; }; pick = function(restrict) -- TODO make sure affinity restrictions match - return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output + if restrict then + local t = {} + for _, i in pairs(sorcery.register.infusions.db) do + if item_restrict_eval(i.output, restrict) and not ( + -- conditions which cause failure of restriction test + (restrict.ipred and restrict.ipred { + mod = module; + infusion = i; + output = i.output; + } ~= true) + ) then t[#t+1] = i.output end + end + return select(2, sorcery.lib.tbl.pick(t)) + else + return sorcery.register.infusions.db[math.random(#sorcery.register.infusions.db)].output + end end; title = function(output) for _,i in pairs(sorcery.register.infusions.db) do if i.output == output then if i._proto and i._proto.name @@ -256,13 +283,24 @@ booksuf = 'Manual'; chance = 1; w = 1, h = 2; pick = function(restrict) cache:populate_grindables() - local i = cache.grindables[math.random(#cache.grindables)] - local pd = sorcery.itemclass.get(i, 'grindable') - return pd.powder + if restrict then + local t = {} + for _, i in pairs(cache.grindables) do + local pd = sorcery.itemclass.get(i, 'grindable') + if item_restrict_eval(pd.powder, restrict) then + t[#t+1] = pd.powder + end + end + return select(2, sorcery.lib.tbl.pick(t)) + else + local gd = cache.grindables[math.random(#cache.grindables)] + local pd = sorcery.itemclass.get(gd, 'grindable') + return pd.powder + end end; props = props_builtin; slots = { {0,1}, {0,0}; @@ -402,10 +440,13 @@ local rks = sorcery.lib.tbl.keys(recipe_kinds) kind = rks[math.random(#rks)] end end + if not recipe_kinds[kind] then + log.fatalf('attempted to pick recipe of unknown kind "%s"', kind) + end return recipe_kinds[kind].pick(restrict), kind end local render_recipe = function(kind,ingredients,result,notes_right) local k = recipe_kinds[kind] @@ -466,10 +507,11 @@ end sorcery.cookbook.setrecipe = function(stack,k,r,restrict) local meta = stack:get_meta() if not r then r,k = sorcery.cookbook.pickrecipe(k,restrict) end + if not r then return false end local t = recipe_kinds[k] meta:set_string('recipe_kind', k) meta:set_string('recipe_name', r) meta:set_string('description', (t.title and t.title(r) or desc_builtin(r)) .. ' ' .. t.name) Index: data/draughts.lua ================================================================== --- data/draughts.lua +++ data/draughts.lua @@ -8,10 +8,11 @@ infusion = 'sorcery:blood'; basis = 'sorcery:potion_luminous'; duration = function(self,meta) return 10 + meta:get_int('duration')*2 end; + quals = { force = true, duration = true }; effect = function(self, user, proto) local meta = self:get_meta() local force = 1 + meta:get_int('force') late.new_effect(user, { duration = proto:duration(meta); @@ -29,10 +30,11 @@ desc = "Conserve your precious supply of oxygen when diving down into the ocean's depths"; infusion = 'sorcery:extract_kelp'; duration = function(self,meta) return 20 + meta:get_int('duration')*30 end; + quals = { force = true, duration = true }; effect = function(self,user,proto) local meta = self:get_meta() local force = 1 + 2 * (meta:get_int('force')) late.new_effect(user, { duration = proto:duration(meta); @@ -45,11 +47,11 @@ }; heal = { name = 'Healing'; color = {243,44,58}; style = 'sparkle'; - no_duration = true; + quals = { force = true }; desc = 'This blood-red liquid glitters with an enchantment that rapidly knits torn flesh and broken bones'; infusion = 'sorcery:oil_sanguine'; basis = 'sorcery:potion_luminous'; effect = function(self, user) local meta = self:get_meta() @@ -63,10 +65,11 @@ name = 'Stealth'; color = {184,106,224}; style = 'sparkle'; infusion = 'default:coal_lump'; basis = 'sorcery:potion_soft'; desc = 'Drinking this dark, swirling draught will shelter you from the power of mortal perception for a time, even rendering you entirely invisible at full strength.'; + quals = { force = true, duration = true }; duration = function(self,meta) return 30 + meta:get_int('duration')*30 end; effect = function(self,user,proto) local meta = self:get_meta() @@ -87,10 +90,11 @@ color = {91,0,200}; style = 'sparkle'; desc = 'While this potion flows through your veins, your vision will be strengthened against the darkness of the night'; maxforce = 3; infusion = 'sorcery:oil_dawn'; basis = 'sorcery:potion_soft'; + quals = { force = true, duration = true }; duration = function(self,meta) return 50 + meta:get_int('duration')*70 end; effect = function(self,user,proto) --TODO ensure it can only be drunk at night @@ -110,10 +114,11 @@ name = 'Antigravity'; color = {240,59,255}; style = 'sparkle'; desc = 'Loosen the crushing grip of the earth upon your tender mortal form with a few sips from this glittering phial'; infusion = 'sorcery:oil_stone'; basis = 'sorcery:potion_soft'; + quals = { force = true, duration = true }; duration = function(self,meta) return 20 + meta:get_int('duration')*25 end; effect = function(self,user,proto) local meta = self:get_meta() @@ -131,10 +136,11 @@ name = 'Gale'; color = {187,176,203}; desc = 'Move and strike with the speed of a hurricane as this enchanted fluid courses through your veins'; infusion = 'sorcery:grease_storm'; basis = 'sorcery:potion_soft'; + quals = { force = true, duration = true }; duration = function(self,meta) return 10 + meta:get_int('duration')*15 end; effect = function(self,user,proto) local meta = self:get_meta() @@ -153,20 +159,21 @@ infusion = 'default:obsidian_shard'; color = {76,0,121}; style = 'sparkle'; desc = 'Walk untroubled through volleys of arrows and maelstroms of swinging blades, for all will batter uselessly against skin protected by spellwork mightier than the doughtiest armor'; infusion = 'default:obsidian_shard'; basis = 'sorcery:potion_luminous'; - no_force = true; + quals = { duration = true }; duration = function(self,meta) return 5 + meta:get_int('duration')*7 end; }; lavabreathing = { name = 'Lavabreathing'; color = {243,118,79}; style = 'sparkle'; glow = 12; basis = 'sorcery:potion_soft'; desc = "Wade through seas of roiling lava as easily as though it were but a babbling brook"; + quals = { duration = true }; }; -- mighty = { -- name = 'Mighty'; -- color = {255,0,119}; style = 'sparkle'; glow = 5; -- infusion = 'sorcery:grease_war'; @@ -176,24 +183,26 @@ resilient = { name = 'Resilient'; color = {124,124,124}; style = 'dull'; basis = 'sorcery:potion_soft'; desc = 'Withstand greater damage and hold your ground even in face of tremendous force'; + quals = { force = true, duration = true }; }; hover = { name = 'Hover'; color = {164,252,55}; style = 'sparkle'; desc = 'Rise into the air for a time and stay there until the potion wears off'; basis = 'sorcery:potion_soft'; + quals = { force = true, duration = true }; }; flight = { name = 'Flight'; color = {143,35,255}; style = 'sparkle'; desc = 'Free yourself totally from the shackles of gravity and soar through the air however you should will'; basis = 'sorcery:potion_soft'; infusion = 'sorcery:grease_lift'; - no_force = true; + quals = { duration = true }; duration = function(self,meta) return 40 + meta:get_int('duration')*55 end; effect = function(self,user,proto) late.new_effect(user, { @@ -208,10 +217,11 @@ name = 'Leap'; color = {164,252,55}; desc = 'Soar high into the air each time you jump (but may risk damage if used without a Feather Potion)'; infusion = 'sorcery:oil_wind'; basis = 'sorcery:potion_soft'; + quals = { force = true, duration = true }; duration = function(self,meta) return 5 + meta:get_int('duration')*7 end; effect = function(self,user,proto) local meta = self:get_meta() Index: data/elixirs.lua ================================================================== --- data/elixirs.lua +++ data/elixirs.lua @@ -1,24 +1,51 @@ +local inc = function(prop, val) + return function(potion, kind) + local meta = potion:get_meta() + meta:set_int(prop, meta:get_int(prop) + (val or 1)) + end +end + return { Force = { - color = {255,165,85}; flag = 'force'; - apply = function(potion, kind) - local meta = potion:get_meta() - meta:set_int('force', meta:get_int('force') + 1) - end; + color = {255,165,85}; qual = 'force'; + apply = inc('force'); describe = function(potion) return 'good', 'empowered', "The strength of this potion's effect has been alchemically amplified" end; infusion = 'sorcery:grease_thunder'; }; Longevity = { - color = {255,85,216}; flag = 'duration'; - apply = function(potion, kind) - local meta = potion:get_meta() - meta:set_int('duration', meta:get_int('duration') + 1) - end; + color = {255,85,216}; qual = 'duration'; + apply = inc('duration'); describe = function(potion) return 'good', 'prolonged', 'The effects of this potion will last longer than normal' end; infusion = 'sorcery:grease_pine'; }; + 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' + 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' + end; + infusion = 'sorcery:oil_purifying'; + }; + Beauty = { + color = {255,20,226}; qual = 'beauty'; + apply = inc('beauty'); + describe = function(potion) + return 'good', 'beautified', 'The effects of this potion will be more vivid and spectacular than normal' + end; + infusion = 'sorcery:liquid_sap_apple_bottle'; + }; + -- Glory? + -- Clarity? } Index: data/gods.lua ================================================================== --- data/gods.lua +++ data/gods.lua @@ -1,11 +1,15 @@ +local L = sorcery.lib return { harvest = { name = "Irix Irimentari" --[[ an old Elerian harvest goddess, Irix Irimentari has watched vigilantly over the fields of her worshippers since before the Second Age. she favors alcoholic beverages as tribute, and has been known to perform blessings for sorcerers when sufficiently inebriated. she harbors a particular hatred of daemons and those who spill the blood of farmers. legend says that a barbarian lord high on opium once wandered into a temple of Irix and left the severed head of a local shepherd on the her altar. this desecration so enraged the goddess that the barbarian's entire tribe soon starved horribly to death, their crops refusing to take root, and their stolen breads turning to ash in their mouths. + + in the mystic arts, she is the patron of Alchemy. it is said that Irix + Irimentari herself invented alchemy when she brewed the first mead. ]]; laziness = 5; stinginess = 3; generosity = 10; color = {214, 255, 146}; @@ -35,10 +39,30 @@ "flowerpot:flowers_dandelion_yellow"; }}; ["sorcery:dagger"] = {17, "sorcery:dagger_consecrated"}; ["sorcery:oil_mystic"] = {9, "sorcery:oil_purifying"}; ["sorcery:potion_water"] = {4, "sorcery:holy_water"}; + ["default:paper"] = function(ctx) + local stack = ItemStack('sorcery:recipe') + local mode = select(2,L.tbl.pick{'cook','craft','infuse','grind'}) + sorcery.cookbook.setrecipe(stack, mode, nil, { + pred = function(c) + local me = ctx.god + if c.mod == 'farming' or + minetest.get_item_group(c.item, 'sorcery_potion') ~= 0 or + minetest.get_item_group(c.item, 'sorcery_oil') ~= 0 or + minetest.get_item_group(c.item, 'sorcery_grease') ~= 0 or + minetest.get_item_group(c.item, 'sorcery_extract') ~= 0 or + me.sacrifice [c.item] or + me.consecrate[c.item] then + print(' !! accepted') + return true end + print(' -- rejected') + end; + }) + return 1, stack + end; -- ["default:steel_ingot"] = {15, "sorcery:holy_token_harvest"}; }; sacrifice = { -- beattitudes ["farming:straw" ] = 1; @@ -132,10 +156,11 @@ }; tools = { rend = {favor=80, cost=15, chance=20}; }; }; + laziness = 2; generosity = 4; stinginess = 9; idol = { desc = "Blood Idol"; width = 0.7; Index: data/spells.lua ================================================================== --- data/spells.lua +++ data/spells.lua @@ -468,11 +468,11 @@ end local dst local bonus = math.floor(ctx.stats.power or 1) if div.mode == 'any' then - local lst = sorcery.lib.tbl.cshuf(div.give) + local lst = sorcery.lib.tbl.scramble(div.give) dst = function(i) return lst[i] end elseif div.mode == 'random' then dst = function() return tblroll(bonus,div.give) end elseif div.mode == 'set' then dst = function(i) return div.give[i] end Index: entities.lua ================================================================== --- entities.lua +++ entities.lua @@ -80,11 +80,10 @@ -- local nname = minetest.get_node(pos).name -- if nname == 'air' or minetest.registered_nodes[nname].walkable ~= true then return -- elseif nname == 'ignore' then goto destroy end -- else fall through to explode if collision then -- since 5.3 only!! - print('collision detected!',dump(collision)) if collision.collides == false then return end if #collision.collisions > 0 then local col = collision.collisions[1] if col.node_pos then pos = col.node_pos Index: infuser.lua ================================================================== --- infuser.lua +++ infuser.lua @@ -103,15 +103,14 @@ local elixir_can_apply = function(elixir, potion) -- accepts an elixir def and potion def if elixir == nil or potion == nil then return false end - if elixir.apply and potion.on_use then - -- the ingredient is an elixir and at least one potion - -- is a fully enchanted, usable potion - if elixir.flag and potion._proto and - potion._proto['no_' .. elixir.flag] == true then + if elixir.apply and potion._proto and potion._proto.quals then + -- the ingredient is an elixir and at least one potion has a + -- quality that can be enhanced + if elixir.qual and potion._proto and not potion._proto.quals[elixir.qual] then -- does the elixir have a property used to denote -- compatibility? if so, check the potion to see if it's -- marked as incompatible return false else @@ -124,12 +123,12 @@ local effects_table = function(potion) local meta = potion:get_meta() local tbl = {} for k,v in pairs(sorcery.data.elixirs) do - if not v.flag then goto skip end - local val = meta:get_int(v.flag) + if not v.qual then goto skip end + local val = meta:get_int(v.qual) if val > 0 then local aff, title, desc = v.describe(potion) if val > 3 then title = title .. ' x' .. val elseif val == 3 then title = 'thrice-' .. title elseif val == 2 then title = 'twice-' .. title @@ -148,11 +147,11 @@ if not potion then return end local pdef = potion:get_definition() if elixir_can_apply(elixir, pdef) then elixir.apply(potion, pdef._proto) potion:get_meta():set_string('description', sorcery.lib.ui.tooltip { - title = pdef._proto.name .. ' Draught'; + title = string.format('%s %s', pdef._proto.name, pdef._proto.kind.label); desc = pdef._proto.desc; color = sorcery.lib.color(pdef._proto.color):readable(); props = effects_table(potion); }); end @@ -303,11 +302,10 @@ pos = pos; stack = out; potion = r.proto; elixir = r.elixir; } end - log.act(dump(r)) log.act(string.format('an infuser at %s has enhanced a %s potion with a %s elixir', minetest.pos_to_string(pos), out:get_name(), infusion[1]:get_name())) end inv:set_stack('potions',i,discharge(out)) end Index: init.lua ================================================================== --- init.lua +++ init.lua @@ -21,25 +21,30 @@ if not arg then return "(nil)" end return tostring(arg) .. ' ' .. argjoin(nxt, ...) end local logger = function(module) - local emit = function(lvl) - return function(...) - if module then - minetest.log(lvl,string.format('[%s :: %s] %s',selfname,module,argjoin(...))) - else - minetest.log(lvl,string.format('[%s] %s',selfname,argjoin(...))) + local lg = {} + local setup = function(fn, lvl) + lvl = lvl or fn + local function emit(...) + local call = (fn == 'fatal') and error + or function(str) minetest.log(lvl, str) end + if module + then call(string.format('[%s :: %s] %s',selfname,module,argjoin(...))) + else call(string.format('[%s] %s',selfname,argjoin(...))) end end + lg[fn ] = function(...) emit(...) end + lg[fn .. 'f'] = function(...) emit(string.format(...)) end -- convenience fn end - return { - info = emit('info'); - warn = emit('warning'); - err = emit('error'); - act = emit('action'); - } + setup('info') + setup('warn','warning') + setup('err','error') + setup('act','action') + setup('fatal') + return lg end; local stage = function(s,...) logger().info('entering stage',s) local f = sorcery.cfg(s .. '.lua') Index: keg.lua ================================================================== --- keg.lua +++ keg.lua @@ -187,11 +187,11 @@ minetest.register_craft { output = "sorcery:keg"; recipe = { {'','screwdriver:screwdriver',''}; {'sorcery:screw_bronze', 'sorcery:tap', 'sorcery:screw_bronze'}; - {'', 'xdecor:barrel', ''}; + {'sorcery:screw_bronze', 'xdecor:barrel', 'sorcery:screw_bronze'}; }; replacements = { {'screwdriver:screwdriver', 'screwdriver:screwdriver'}; }; } Index: lib/node.lua ================================================================== --- lib/node.lua +++ lib/node.lua @@ -317,10 +317,12 @@ n = minetest.get_node(sum) end fn(sum, n) end end; + + amass = amass; force = force; -- when items have already been removed; notify cannot be relied on -- to reach the entire network; this function accounts for the gap Index: lib/tbl.lua ================================================================== --- lib/tbl.lua +++ lib/tbl.lua @@ -6,11 +6,11 @@ list[i], list[j] = list[j], list[i] end return list end -fn.cshuf = function(list) +fn.scramble = function(list) return fn.shuffle(table.copy(list)) end fn.urnd = function(min,max) local r = {} @@ -29,16 +29,10 @@ end end return new end -fn.scramble = function(list) - local new = table.copy(list) - fn.shuffle(new) - return new -end - fn.copy = function(t) local new = {} for i,v in pairs(t) do new[i] = v end setmetatable(new,getmetatable(t)) return new Index: liquid.lua ================================================================== --- liquid.lua +++ liquid.lua @@ -52,11 +52,11 @@ -- troughs are used for collecting liquid from the environment, -- like rainwater and tree sap. they hold twice as much as a bucket local Q = constants.glasses_per_bottle local trough_mkid = function(l,i) if type(l) == 'string' then l = sorcery.register.liquid.db[l] end - if not l or not i then return 'sorcery:trough' end + if (not l) or (not i) or i < 1 then return 'sorcery:trough' end return string.format('%s:trough_%s_%u', l.mod,l.sid,i) end local lid = function(l) return trough_mkid(liq, l) end local M = constants.bottles_per_trough @@ -163,10 +163,20 @@ sorcery.liquid.mktrough() sorcery.liquid.measure_default = function(amt) return string.format('%s drams', amt*constants.drams_per_glass) end + +sorcery.liquid.container = function(liq, ctr) + return liq.containers[({ + bottle = 'vessels:glass_bottle'; + glass = 'vessels:drinking_glass'; + keg = 'sorcery:keg'; + trough = 'sorcery:trough'; + })[ctr] or ctr] +end + sorcery.liquid.register = function(liq) local fmt = string.format local Q = constants.glasses_per_bottle liq.sid = liq.sid or liq.id:gsub('^[^:]+:','') liq.mod = liq.mod or liq.id:gsub('^([^:]+):.*','%1') Index: potions.lua ================================================================== --- potions.lua +++ potions.lua @@ -54,10 +54,11 @@ local image = 'xdecor_bowl.png^(sorcery_oil_' .. (imgvariant or 'dull') .. '.png^[colorize:'..tostring(color)..':140)' sorcery.register.residue.link('sorcery:' .. name, 'xdecor:bowl') extra.description = label; extra.inventory_image = image; if not extra.groups then extra.groups = {} end + extra.groups.sorcery_oil = 1 minetest.register_craftitem('sorcery:' .. name, extra) end sorcery.register_potion('blood', 'Blood', 'A bottle of sacrificial blood, imbued with stolen (or perhaps donated) life force', u.color(219,19,14), nil, nil, { _sorcery = { @@ -104,10 +105,14 @@ } end end -- for n,v in pairs(sorcery.data.potions) do +local kind_potion = { + label = 'Potion'; + kind = 'A mystical liquid crucial to the art of alchemy'; +} sorcery.register.potions.foreach('sorcery:mknodes',{},function(n,v) local color = u.color(v.color) local kind = v.style local glow = v.glow local id = 'potion_' .. string.lower(n) @@ -115,19 +120,20 @@ 'bottle of ' .. string.lower(n) .. ((kind == 'sparkle' and ', fiercely bubbling') or '') .. ' liquid' local fullname = n .. ' Potion' sorcery.register.liquid.link('sorcery:'..id, { - name = 'Serene Potion'; + name = fullname; color = v.color; proto = v; kind = 'sorcery:potion'; measure = function(amt) return string.format('%s draughts', amt / 3) end; containers = { ['vessels:glass_bottle'] = 'sorcery:' .. id; }; }) + v.kind = kind_potion; sorcery.register_potion(id, fullname, desc, color, kind, glow, { groups = { sorcery_potion = 1; sorcery_magical = 1; }; @@ -144,12 +150,17 @@ }) create_infusion_recipe(id,v,'sorcery:potion_serene',{data=v,name=fullname}) end) -- for n,potion in pairs(sorcery.data.draughts) do +local kind_draught = { + label = 'Draught'; + desc = 'A drink that will suffuse your body and spirit with mystic energies'; +} sorcery.register.draughts.foreach('sorcery:mknodes',{},function(n,potion) local name = 'draught_' .. n + potion.kind = kind_draught local behavior = { _proto = potion; groups = { sorcery_potion = 2; sorcery_draught = 1; @@ -203,14 +214,19 @@ behavior) create_infusion_recipe(name,potion,'sorcery:potion_luminous',{data=potion,name=fullname}) end) -- for n,elixir in pairs(sorcery.data.elixirs) do +local kind_elixir = { + label = 'Elixir'; + desc = 'A special kind of potion that enhances the particular qualities of other alchemical brews'; +} sorcery.register.elixirs.foreach('sorcery:mknodes',{},function(n,elixir) local color = u.color(elixir.color) local id = 'elixir_' .. string.lower(n) local fullname = 'Elixir of ' .. n + elixir.kind = kind_elixir; sorcery.register_potion(id, fullname, nil, color, 'dull', false, { _proto = elixir; groups = { sorcery_elixir = 1; sorcery_magical = 1; @@ -238,23 +254,30 @@ groups = { sorcery_grease = 1 } }) end) -- for n,v in pairs(sorcery.data.philters) do +local kind_philter = { + label = 'Philter'; + desc = 'A special kind of potion that wooden rods can be soaked in to imbue them with special powers and transform them into wands'; +} sorcery.register.philters.foreach('sorcery:mknodes',{},function(n,v) local color = u.color(v.color) local id = 'philter_' .. n local name = v.name or u.str.capitalize(n) + if not v.name then v.name = name end local fullname = name .. ' Philter' + v.kind = kind_philter sorcery.register_potion(id, fullname, v.desc, color, 'sparkle',v.glow or 4, { _proto = v; _protoname = n; groups = { sorcery_magical = 1; sorcery_philter = 1; }; }) + v.quals = {force = true}; create_infusion_recipe(id,v,'sorcery:potion_viscous',{data=v,name=fullname}) end) -- for n,v in pairs(sorcery.data.extracts) do sorcery.register.extracts.foreach('sorcery:mknodes',{},function(n,v) @@ -304,7 +327,9 @@ } end -- need a relatively pure alcohol for this, tho other alcohols can be used -- for potionmaking in other ways add_alcohol('farming:bottle_ethanol') - add_alcohol('wine:glass_vodka') + if minetest.get_modpath('wine') then + add_alcohol('wine:glass_vodka') + end end) Index: runeforge.lua ================================================================== --- runeforge.lua +++ runeforge.lua @@ -38,13 +38,15 @@ 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 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 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) @@ -57,29 +59,36 @@ stack_max = 1; groups = { sorcery_rune = 1; not_in_creative_inventory = 1; }; - _proto = { id = name, data = rune; }; + _proto = { id = name, data = rune }; }) end) + +local phkind = { + label = 'Phial'; + desc = 'An alchemical substance which rune forges consume while coalescing new runes'; +} for name,p in pairs(constants.phial_kinds) do local f = string.format local color = sorcery.lib.color(142,232,0) local fac = p.grade / 6 local id = f('phial_%s', name); + local fname = f('%s Phial',p.name); + local desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed and how much energy is required by the process, and affects your odds of getting a high-quality rune." sorcery.register_potion_tbl { name = id; - label = f('%s Phial',p.name); - desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed and how much energy is required by the process, and affects your odds of getting a high-quality rune."; + label = fname; + desc = desc; color = color:brighten(1 + fac*0.5); imgvariant = (fac >= 5) and 'sparkle' or 'dull'; glow = 5+p.grade; extra = { groups = { sorcery_phial = p.grade }; - _proto = { id = name, data = p }; + _proto = { id = name, desc = desc, name = p.name, kind = phkind, data = p, quals = {force = true, speed = true}, color = color }; }; } sorcery.register.infusions.link { infuse = p.infusion; into = 'sorcery:potion_subtle'; @@ -257,18 +266,19 @@ local has_phial = function() return not i:is_empty('phial') end if time and has_phial() and pow_min and not probe.disjunction then -- roll for runes local phial = i:get_stack('phial',1) local int, powerfac = calc_phial_props(phial) + local pf = phial:get_meta():get_int('force') local rolls = math.floor(time/int) local newrunes = {} for _=1,rolls do local choices = {} for name,rune in pairs(sorcery.data.runes) do -- print('considering',name) -- print('-- power',rune.minpower,(rune.minpower*powerfac)*time,'//',l.self.powerdraw,l.self.powerdraw/time,'free',l.freepower,'max',l.maxpower) - if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then + if (rune.minpower*powerfac)*time <= l.self.powerdraw and math.random(rune.rarity - pf) == 1 then choices[#choices + 1] = rune end end if #choices > 0 then -- if multiple runes were rolled up, be nice to the player @@ -493,11 +503,10 @@ end if fl == 'cache' then if probe.disjunction then return 0 end if tl == 'cache' then return 1 end if tl == 'active' and inv:is_empty('active') then - print(dump(wrench)) if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then local amulet = inv:get_stack('amulet',1) local rune = inv:get_stack(fl,fi) local runeid = rune:get_definition()._proto.id local runegrade = rune:get_meta():get_int('rune_grade') Index: sorcery.md ================================================================== --- sorcery.md +++ sorcery.md @@ -59,11 +59,11 @@ * stop your foes in their tracks by flipping a switch to turn on your **Force Field Emitters**, generating an impenetrable barrier wherever they aim. * who needs elevators when you have **Gravitators**? float gently up a vast borehole, bring attackers crashing down to earth, or slow a fatal plunge to a soft and easy landing. * build graven **Idols** to your gods and set sacrifices to them upon an altar. if they're feeling generous, they might start sending you presents of their own, or consecrating your offerings. but beware: different gods have different tastes (and different powers), and get bored quickly with repetitive offerings. * to bend the ultimate arcane forces of the cosmos to your will, you'll need a **Rune Forge**. with a strong ley-current and a steady supply of **Phials** from an Infuser, a Rune Forge will crystallize thaumic impurities into Runes that can you can imbue into a gemstone **Amulet** with a **Rune Wrench**. each amulet can only be used once before it loses its charge, and it may be a long time before the same kind of rune happens to coalesce in your forge again, but the spells they unleash are unique and priceless boons — or weapons. teleport without a teleporter! purge your environs of all spellcraft no matter how fearsomely potent! surround yourself with a shimmering Disjunction Field that, for a short while, will snuff out any spell it touches and, rendering enemy mages utterly helpless and piercing otherwise impenetrable defenses! stride through solid stone like open air! carve huge tunnels deep into the rock with only a snap of your fingers! rain obliteration down upon a target of your choosing! send forth a titanic bolt of flame with the power to blast open mountainsides! tear the very life force straight from an enemy's body and use it to fortify your being! all this and more can be done with the power of rune magic. -there's more as well. i have yet to figure out how i want to go about introducing users to the lore, but for now there's some information on the wiki and some things you can glean from creative mode; otherwise you'll have to read the source code. +there's more as well. i have yet to figure out how i want to go about introducing users to the lore (although you can occasionally find random recipes in dungeon chests, and gods can be coaxed to bestow recipes and cookbooks), but for now there's some information on the wiki and some things you can glean from creative mode; otherwise you'll have to read the source code. # lore `sorcery` supplies a default system of lore (that is, the arbitrary objects that the basic principles of the setting operate over) but this can be augmented or replaced on a per-world basis. for instance, you can substitute your own gods for the Harvest Goddess, Blood God, change their names, and so on, or simply make your own additions to the pantheon. since lore overrides are stored outside the minetest tree, it can be updated without destroying your changes. lore is stored separately from the rest of the game logic, in the 'data' directory of the `sorcery` mod. it is arranged in a hierarchy of thematically-organized tables. for instance, the table of gods can be found in `data/gods.lua`. ideally, lore tables should contain plain data, though they can also contain lambdas if necessary. lore files are evaluated in the second stage of the init process, after library code has been loaded but before any game logic has been instantiated. lore can thus depend on libraries where reasonable (though e.g. colors should be stored as 3-tuples rather than `sorcery.lib.color` objects, and images as texture strings, unless there is a very good reason to do otherwise). Index: spell.lua ================================================================== --- spell.lua +++ spell.lua @@ -30,11 +30,11 @@ -- also contain any other data useful to the spell. if a subject has -- a 'disjoin' field it must be a function called when they are removed -- from the list of spell targets. -- * caster is the individual who cast the spell, if any. a disjunction -- against their person will totally disrupt the spell. -local log = function(...) sorcery.log('spell',...) end +local log = sorcery.logger 'spell' -- FIXME saving object refs is iffy, find a better alternative sorcery.spell = { active = {} } @@ -226,21 +226,21 @@ else t = (s.duration * (when.whence or 0)) + when.secs end if t then return math.min(s.duration,math.max(0,t)) end - log('invalid timespec ' .. dump(when)) + log.err('invalid timespec ' .. dump(when)) return 0 end s.queue = function(when,fn) local elapsed = s.starttime and minetest.get_server_uptime() - s.starttime or 0 local timepast = interpret_timespec(when) if not timepast then timepast = 0 end local timeleft = s.duration - timepast local howlong = (s.delay + timepast) - elapsed if howlong < 0 then - log('cannot time-travel! queue() called with `when` specifying timepoint that has already passed') + log.err('cannot time-travel! queue() called with `when` specifying timepoint that has already passed') howlong = 0 end s.jobs[#s.jobs+1] = minetest.after(howlong, function() -- this is somewhat awkward. since we're using a non-polling approach, we -- need to find a way to account for a caster or subject walking into an @@ -336,11 +336,11 @@ what(s,...) if s.terminate then s:terminate() end sorcery.spell.active[myid] = nil end) else - log('multiple final timeline events not possible, ignoring') + log.warn('multiple final timeline events not possible, ignoring') end elseif when == 0 and s.disjunction then startqueued = true s.queue(when_raw,function(...) perform_disjunction_calls() Index: tap.lua ================================================================== --- tap.lua +++ tap.lua @@ -1,10 +1,11 @@ local log = sorcery.logger('tap') minetest.register_node('sorcery:tap',{ description = 'Tree Tap'; drawtype = 'mesh'; mesh = 'sorcery-tap.obj'; + inventory_image = 'sorcery_tap_inv.png'; tiles = { 'default_copper_block.png'; 'default_steel_block.png'; }; groups = { ADDED textures/sorcery_tap_inv.png Index: textures/sorcery_tap_inv.png ================================================================== --- textures/sorcery_tap_inv.png +++ textures/sorcery_tap_inv.png cannot compute difference between binary files