Comment: | balance amulets better, add sound effects, add debugging privilege for runes, swat various glitches and bugs |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA3-256: |
83426a2748e5b3d65016273423dd93ac |
User & Date: | lexi on 2020-10-22 15:51:39 |
Other Links: | manifest | tags |
2020-10-23
| ||
00:08 | fix some showstopping bugs, more amulet spells, add sound effects, improve teleportation visuals check-in: 90e64c483c user: lexi tags: trunk | |
2020-10-22
| ||
15:51 | balance amulets better, add sound effects, add debugging privilege for runes, swat various glitches and bugs check-in: 83426a2748 user: lexi tags: trunk | |
2020-10-21
| ||
03:35 | add rune forges, runes, amulet frames, write sacrifice spell, touch up amulet graphics, enable enchantment of amulets (though spells cannot yet be cast), defuckulate syncresis core icon, unfuckitize sneaky leycalc bug that's probably been the cause of some long-standing wackiness, add item classes, add some more textures, disbungle various other asstastrophes, remove sneaky old debug code, improve library code, add utility for uploading merge requests check-in: 96c5289a2a user: lexi tags: trunk | |
Modified coins.lua from [7e4d0ae5aa] to [d6a069e16f].
179 179 local inv = meta:get_inventory() 180 180 local reduce_slot = function(slot) 181 181 local i = inv:get_stack(slot,1) 182 182 i:take_item(items_used) inv:set_stack(slot,1,i) 183 183 end 184 184 reduce_slot('ingot') 185 185 if not inv:is_empty('gem') then reduce_slot('gem') end 186 + minetest.sound_play('sorcery_coins', { pos = pos, gain = 0.7 }) 186 187 end 187 188 update_press_output(meta) 188 189 end; 189 190 }) end 190 191 191 192 minetest.register_craft { 192 193 output = 'sorcery:coin_press'; 193 194 recipe = { 194 195 {'group:wood','group:wood','group:wood'}; 195 196 {'basic_materials:steel_bar','default:steel_ingot','basic_materials:steel_bar'}; 196 197 {'default:copper_ingot','default:stone','default:copper_ingot'}; 197 198 }; 198 199 }
Modified data/metals.lua from [127702c66e] to [3560b06dd9].
218 218 slots = { 219 219 { 220 220 affinity = {'counterpraxic'}; 221 221 confluence = 0.65; 222 222 interference = {speed = 1}; 223 223 }; 224 224 }; 225 - amulet = {}; 225 + amulet = { power = 1.5 }; 226 226 }; 227 227 lithium = { 228 228 tone = {255,252,93}, alpha = 80; 229 229 dye = 'yellow'; 230 230 rarity = 13; 231 231 hardness = 2; 232 232 fuel = 80; ................................................................................ 278 278 image = { 279 279 block = 'sorcery_metal_iridium_shiny.png'; 280 280 }; 281 281 slots = { 282 282 {affinity={'counterpraxic','syncretic'}, confluence = 1.1}; 283 283 {affinity={'cognic','entropic'}, confluence = 0.8}; 284 284 }; 285 - amulet = {}; 285 + amulet = { power = 1.7 }; 286 286 }; 287 287 duridium = { 288 288 tone = {255,64,175}, alpha = 70; 289 289 cooktime = 120; 290 290 artificial = true; 291 291 durability = 3400; 292 292 speed = 3.1;
Modified data/oils.lua from [f8f2204848] to [fb12becfb1].
95 95 'sorcery:extract_raspberry'; 96 96 'sorcery:extract_raspberry'; 97 97 'sorcery:extract_onion'; 98 98 'farming:peas'; 99 99 'farming:peas'; 100 100 'farming:peas'; 101 101 }; 102 + }; 103 + luscious = { 104 + color = {10,255,10}; 105 + mix = { 106 + 'sorcery:extract_marram'; 107 + 'sorcery:extract_grape'; 108 + 'farming:cocoa_beans'; 109 + 'farming:sugar'; 110 + 'farming:sugar'; 111 + }; 102 112 }; 103 113 }
Modified data/potions.lua from [eb3ff7cdbe] to [018fbedf8a].
31 31 color = {119,51,111}; 32 32 infusion = 'sorcery:oil_bleak'; 33 33 }; 34 34 Isolating = { 35 35 color = {188,78,225}; 36 36 infusion = 'sorcery:extract_fern'; 37 37 }; 38 + Subtle = { 39 + color = {230,253,150}, glow = 6; 40 + infusion = 'sorcery:oil_luscious'; 41 + }; 38 42 }
Modified data/runes.lua from [bd35ca52e8] to [720c3d8102].
9 9 name = 'Translocate'; 10 10 tone = {0,235,233}; 11 11 minpower = 3; 12 12 rarity = 15; 13 13 amulets = { 14 14 amethyst = { 15 15 name = 'Joining'; 16 - desc = 'Give this amulet to another and they can arrive at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter'; 16 + desc = 'Give this amulet to another and they can arrive safely at your side in a flash from anywhere in the world — though returning whence they came may be a more difficult matter'; 17 + apply = function(ctx) 18 + local maker = ctx.user:get_player_name() 19 + ctx.meta:set_string('rune_join_target',maker) 20 + end; 21 + remove = function(ctx) ctx.meta:set_string('rune_join_target','') end; 17 22 frame = { 18 23 gold = { 19 24 name = 'Exchange'; 20 25 desc = 'Give this amulet to another and they will be able to trade places with you no matter where in the world each of you might be.'; 21 26 }; 22 27 cobalt = { 23 28 name = 'Sending'; ................................................................................ 27 32 name = 'Arrival'; 28 33 desc = "Give this amulet to another and they will be able to arrive at your side in a flash from anywhere in the world, carrying others with them in the spell's grip"; 29 34 }; 30 35 }; 31 36 }; 32 37 sapphire = { 33 38 name = 'Return'; 34 - desc = 'Use this amulet once to bind it to a particular point in the world, then use it again to return instantly to that point.'; 39 + desc = 'Use this amulet once to bind it to a particular point in the world, then discharge its spell to return instantly to that point.'; 40 + remove = function(ctx) 41 + ctx.meta:set_string('rune_return_dest','') 42 + end; 43 + cast = function(ctx) 44 + if not ctx.meta:contains('rune_return_dest') then 45 + local pos = ctx.caster:get_pos() 46 + ctx.meta:set_string('rune_return_dest',minetest.pos_to_string(pos)) 47 + return true -- play effects but do not break spell 48 + else 49 + local pos = minetest.string_to_pos(ctx.meta:get_string('rune_return_dest')) 50 + ctx.meta:set_string('rune_return_dest','') 51 + local subjects = { ctx.caster } 52 + local center = ctx.caster:get_pos() 53 + ctx.sparkle = false 54 + for _,s in pairs(subjects) do 55 + local offset = vector.subtract(s:get_pos(), center) 56 + local pt = sorcery.lib.node.get_arrival_point(vector.add(pos,offset)) 57 + if pt then 58 + sorcery.vfx.body_sparkle(s,sorcery.lib.color(20,120,255),2) 59 + sorcery.vfx.body_sparkle(nil,sorcery.lib.color(20,255,120),2,pt) 60 + s:set_pos(pt) 61 + end 62 + end 63 + end 64 + end; 35 65 frame = { 36 66 iridium = { 37 67 name = 'Mass Return'; 38 68 desc = 'Use this amulet once to bind it to a particular point in the world, then carry yourself and everyone around you back to that point in a flash simply by using it again'; 39 69 }; 40 70 }; 41 71 }; ................................................................................ 114 144 ruby = { 115 145 name = 'Shattering'; 116 146 desc = 'Tear a violent wound in the earth with the destructive force of this amulet'; 117 147 }; 118 148 diamond = { 119 149 name = 'Killing'; 120 150 desc = 'Wield this amulet against a foe to instantly snuff the life out of their mortal form, regardless of their physical protections.'; 151 + cast = function(ctx) 152 + if not (ctx.target and ctx.target.type == 'object') then return false end 153 + local tgt = ctx.target.ref 154 + if not minetest.is_player(obj) then return false end 155 + local tgth = tgt:get_properties().eye_height 156 + sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),20) 157 + minetest.sound_play('sorcery_bloody_burst', { pos = pos, gain = 1.5 }) 158 + tgt:set_hp(0) 159 + end; 121 160 frame = { 122 161 iridium = { 123 162 name = 'Massacre'; 124 163 desc = "Unleash the dark and wicked force that lurks within this fell amulet to instantaneously slay all those who surround you, friend and foe alike"; 125 164 }; 126 165 }; 127 166 }; ................................................................................ 179 218 }; 180 219 dominate = { 181 220 name = 'Dominate'; 182 221 tone = {235,0,228}; 183 222 minpower = 4; 184 223 rarity = 40; 185 224 amulets = { 225 + amethyst = { 226 + name = 'Suffocation'; 227 + desc = 'Wrap this spell tightly around your victim\'s throat, cutting off their oxygen until you release them.'; 228 + }; 186 229 ruby = { 187 230 name = 'Exsanguination'; 188 231 desc = 'Rip the life force out of another, leaving them on the brink of death, and use it to mend your own wounds and invigorate your own being'; 232 + cast = function(ctx) 233 + if not (ctx.target and ctx.target.type == 'object') then return false end 234 + local tgt = ctx.target.ref 235 + local takefac = math.min(99,50 + (ctx.stats.power * 5)) / 100 236 + local dmg = tgt:get_hp() * takefac 237 + print("!!! dmg calc",takefac,dmg,tgt:get_hp()) 238 + 239 + local numhits = math.random(6,10+ctx.stats.power/2) 240 + local function dohit(hitsleft) 241 + if tgt == nil or tgt:get_properties() == nil then return end 242 + tgt:punch(ctx.caster, 1, { 243 + full_punch_interval = 1; 244 + damage_groups = { fleshy = dmg / numhits } 245 + }) 246 + local tgth = tgt:get_properties().eye_height 247 + sorcery.vfx.bloodburst(vector.add(tgt:get_pos(),{x=0,y=tgth/2,z=0}),math.random(10 * takefac, 40 * takefac)) 248 + ctx.caster:set_hp(ctx.caster:get_hp() + math.max(1,(dmg/numhits)*takefac)) 249 + 250 + local sound = {'sorcery_bloody_hit','sorcery_crunch',false} 251 + sound = sound[math.random(#sound)] 252 + if sound ~= false then 253 + minetest.sound_play(sound, { pos = pos, gain = math.random(5,15)*0.1 }) 254 + end 255 + 256 + local nexthit = math.random() * 0.4 + 0.1 257 + local dir = vector.subtract(ctx.caster:get_pos(), tgt:get_pos()) 258 + local spark = sorcery.lib.image('sorcery_spark.png') 259 + minetest.add_particlespawner { 260 + amount = math.random(80*takefac,150*takefac); 261 + texture = spark:blit(spark:multiply(sorcery.lib.color(255,20,10))):render(); 262 + time = nexthit; 263 + attached = tgt; 264 + minpos = {x = -0.3, y = -0.5, z = -0.3}; 265 + maxpos = {x = 0.3, y = tgth, z = 0.3}; 266 + minvel = vector.multiply(dir,0.5); 267 + maxvel = vector.multiply(dir,0.9); 268 + minacc = vector.multiply(dir,0.1); 269 + maxacc = vector.multiply(dir,0.2); 270 + minexptime = nexthit * 1.5; 271 + maxexptime = nexthit * 2; 272 + minsize = 0.5; 273 + maxsize = 5 * takefac; 274 + glow = 14; 275 + animation = { 276 + type = 'vertical_frames'; 277 + aspect_w = 16, aspect_h = 16; 278 + length = nexthit*2 + 0.1; 279 + }; 280 + } 281 + 282 + if hitsleft > 0 then 283 + minetest.after(nexthit, function() dohit(hitsleft-1) end) 284 + end 285 + end 286 + dohit(numhits) 287 + end; 189 288 }; 190 289 amethyst = { 191 290 name = 'Disarming'; 192 291 desc = 'Wield this amulet against a foe to rip all the weapons in their possession out of their grasp'; 193 292 frame = { 194 293 iridium = { 195 294 name = 'Peacemaking';
Modified enchanter.lua from [bac74a37c3] to [b41a4d81d6].
311 311 buildable_to = true; 312 312 sunlight_propagates = true; 313 313 light_source = i + 4; 314 314 groups = { 315 315 air = 1, sorcery_air = 1; 316 316 not_in_creative_inventory = 1; 317 317 }; 318 + drop = {max_items = 0, items = {}}; 319 + on_blast = function() end; -- not affected by explosions 318 320 on_construct = function(pos) 319 321 minetest.get_node_timer(pos):start(0.05) 320 322 end; 321 323 on_timer = function(pos) 322 324 if i <= 2 then minetest.remove_node(pos) else 323 325 minetest.set_node(pos, {name='sorcery:air_flash_1'}) 324 326 return true
Modified forcefield.lua from [72bf5182e0] to [a330742505].
45 45 minetest.register_node('sorcery:air_barrier_' .. tostring(i), { 46 46 drawtype = 'glasslike'; 47 47 walkable = true; 48 48 pointable = false; 49 49 sunlight_propagates = true; 50 50 paramtype = 'light'; 51 51 light_source = i; 52 + drop = {max_items = 0, items = {}}; 53 + on_blast = function() end; -- not affected by explosions 52 54 tiles = {'sorcery_transparent.png'}; 53 55 groups = { 54 56 air = 1; 55 57 sorcery_air = 1; 56 58 sorcery_force_barrier = i; 57 59 }; 58 60 -- _proto = {
Modified gems.lua from [e866e2741b] to [f5bc90eb64].
52 52 }; 53 53 }) 54 54 end 55 55 if not gem.foreign_amulet then 56 56 local img = sorcery.lib.image 57 57 local img_stone = img('sorcery_amulet.png'):multiply(sorcery.lib.color(gem.tone)) 58 58 local img_sparkle = img('sorcery_amulet_sparkle.png') 59 + local useamulet = function(stack,user,target) 60 + local sp = sorcery.amulet.getspell(stack) 61 + if not sp or not sp.cast then return nil end 62 + local stats = sorcery.amulet.stats(stack) 63 + 64 + local ctx = { 65 + caster = user; 66 + target = target; 67 + stats = stats; 68 + sound = "xdecor_enchanting"; --FIXME make own sounds 69 + sparkle = true; 70 + amulet = stack; 71 + meta = stack:get_meta(); -- avoid spell boilerplate 72 + color = sorcery.lib.color(sp.tone); 73 + } 74 + print('casting') 75 + local res = sp.cast(ctx) 76 + 77 + if res == nil or res == true then 78 + minetest.sound_play(ctx.sound, { 79 + pos = user:get_pos(); 80 + gain = 1; 81 + }) 82 + end 83 + if ctx.sparkle then 84 + sorcery.vfx.cast_sparkle(user, ctx.color, stats.power,0.5) 85 + end 86 + if res == nil then 87 + if not minetest.check_player_privs(user, 'sorcery:infinirune') then 88 + sorcery.amulet.setrune(stack) 89 + end 90 + end 91 + 92 + return ctx.amulet 93 + end; 59 94 minetest.register_craftitem(amuletname, { 60 95 description = sorcery.lib.str.capitalize(name) .. ' amulet'; 61 96 inventory_image = img_sparkle:blit(img_stone):render(); 62 97 wield_scale = { x = 0.6, y = 0.6, z = 0.6 }; 63 98 groups = { sorcery_amulet = 1 }; 99 + on_use = useamulet; 64 100 _sorcery = { 65 101 material = { 66 102 gem = true, id = name, data = gem; 67 103 value = (5 * shards_per_gem) + 4; 68 104 }; 69 105 amulet = { base = name }; 70 106 }; ................................................................................ 74 110 local framedid = string.format("%s_frame_%s", amuletname, metalid) 75 111 local img_frame = img(string.format('sorcery_amulet_frame_%s.png',metalid)) 76 112 minetest.register_craftitem(framedid, { 77 113 description = string.format("%s-framed %s amulet",sorcery.lib.str.capitalize(metalid), name); 78 114 inventory_image = img_sparkle:blit(img_frame):blit(img_stone):render(); 79 115 wield_scale = { x = 0.6, y = 0.6, z = 0.6 }; 80 116 groups = { sorcery_amulet = 1 }; 117 + on_use = useamulet; 81 118 _sorcery = { 82 119 amulet = { base = name, frame = metalid }; 83 120 }; 84 121 }) 85 122 local frag = metal.parts.fragment 86 123 minetest.register_craft { 87 124 output = framedid;
Modified init.lua from [f9d7281393] to [e9275c1995].
128 128 'harvester'; 'metallurgy-hot', 'metallurgy-cold'; 129 129 'entities'; 'recipes'; 'coins'; 'interop'; 130 130 'tnodes'; 'forcefield'; 'farcaster'; 'portal'; 131 131 'cookbook', 'writing'; 'disassembly'; 'displacer'; 132 132 'gravitator'; 'precipitator'; 'calendar', 'astrolabe'; 133 133 'keypunch'; 'runeforge'; 134 134 135 - 'admin'; 135 + 'privs', 'admin'; 136 136 } do sorcery.load(u) end 137 137 sorcery.stage('finalize') 138 138 139 139 sorcery.registry.defercheck()
Modified lib/node.lua from [b1f018643d] to [5b83bf6142].
63 63 offsets = ofs; 64 64 purge_container = function(...) return purge_container(nil, ...) end; 65 65 purge_only = function(lst) 66 66 return function(...) 67 67 return purge_container(lst, ...) 68 68 end 69 69 end; 70 + 71 + is_air = function(pos) 72 + local n = sorcery.lib.node.force(pos) 73 + if n.name == 'air' then return true end 74 + local d = minetest.registered_nodes[n.name] 75 + if not d then return false end 76 + return not d.walkable 77 + end; 78 + 79 + get_arrival_point = function(pos) 80 + local air = sorcery.lib.node.is_air 81 + if air(pos) then 82 + local n = {x=0,y=1,z=0} 83 + if air(vector.add(pos,n)) then return pos end 84 + local down = vector.subtract(pos,n) 85 + if air(down) then return down end 86 + else return nil end 87 + end; 70 88 71 89 amass = function(startpoint,names,directions) 72 90 if not directions then directions = ofs.neighbors end 73 91 local nodes, positions, checked = {},{},{} 74 92 local checkedp = function(pos) 75 93 for _,v in pairs(checked) do 76 94 if vector.equals(pos,v) then return true end
Modified potions.lua from [32e1f8328e] to [f87e1ae861].
1 1 local u = sorcery.lib 2 2 sorcery.registry.mk('infusions',false) 3 3 sorcery.registry.mk('residue',false) 4 4 5 +sorcery.register_potion_tbl = function(tbl) -- :/ 6 + return sorcery.register_potion(tbl.name,tbl.label,tbl.desc,tbl.color,tbl.imgvariant,tbl.glow,tbl.extra) 7 +end 5 8 sorcery.register_potion = function(name,label,desc,color,imgvariant,glow,extra) 6 9 local image = 'sorcery_liquid_'..(imgvariant or 'dull')..'.png' .. 7 10 '^[multiply:'..tostring(color).. 8 11 '^vessels_glass_bottle.png' 9 12 10 13 sorcery.register.residue.link('sorcery:' .. name, 'vessels:glass_bottle') 11 14 local node = { ................................................................................ 19 22 ); 20 23 short_description = label; 21 24 drawtype = "plantlike"; 22 25 tiles = {image}; 23 26 inventory_image = image; 24 27 paramtype = "light"; 25 28 is_ground_content = false; 26 - light_source = glow or 0; 29 + light_source = glow and math.min(minetest.LIGHT_MAX,glow) or 0; 27 30 drop = 'sorcery:' .. name; 28 31 preserve_metadata = function(pos,node,meta,newstack) 29 32 newstack[1]:get_meta():from_table(meta) 30 33 end; 31 34 walkable = false; 32 35 selection_box = { 33 36 type = "fixed",
Added privs.lua version [62e9e10513].
1 +minetest.register_privilege('sorcery:infinirune', { 2 + description = "runes don't discharge upon use, for debugging use only"; 3 + give_to_singleplayer = false; 4 + give_to_admin = false; 5 +})
Modified runeforge.lua from [b4ccf2cc5a] to [e97e283f3c].
1 +-- TODO make some kind of disposable "filter" tool that runeforges require 2 +-- to generate runes and that wears down over time, to make amulets more 3 +-- expensive than they currently are? the existing system is neat but 4 +-- i think amulets are a little overpowered for something that just 5 +-- passively consumes ley-current 6 + 1 7 local constants = { 2 - rune_mine_interval = 90; 8 + rune_mine_interval = 250; 3 9 -- how often a powered forge rolls for new runes 4 10 5 11 rune_cache_max = 4; 6 12 -- how many runes a runeforge can hold at a time 7 13 8 - rune_grades = {'Fragile', 'Shoddy', 'Ordinary', 'Pristine'}; 14 + rune_grades = {'Fragile', 'Weak', 'Ordinary', 'Pristine', 'Sublime'}; 9 15 -- how many grades of rune quality/power there are 16 + 17 + amulet_grades = {'Slight', 'Minor', 'Major', 'Grand', 'Ultimate' }; 18 + -- what kind of amulet each rune grade translates to 19 + 20 + phial_kinds = { 21 + lesser = {grade = 1; name = 'Lesser'; infusion = 'sorcery:powder_brass'}; 22 + simple = {grade = 2; name = 'Simple'; infusion = 'sorcery:powder_silver'}; 23 + great = {grade = 3; name = 'Great'; infusion = 'sorcery:powder_gold'}; 24 + splendid = {grade = 4; name = 'Splendid'; infusion = 'sorcery:powder_electrum'}; 25 + exalted = {grade = 5; name = 'Exalted'; infusion = 'sorcery:powder_levitanium'}; 26 + supreme = {grade = 6; name = 'Supreme'; infusion = 'sorcery:essence_force'}; 27 + }; 10 28 } 29 +local calc_phial_props = function(phial) --> mine interval: float, time factor: float 30 + local g = phial:get_definition()._proto.grade 31 + local i = constants.rune_mine_interval 32 + local fac = (g-1) / 5 33 + return i - ((i*0.5) * fac), 0.5 * fac 34 +end 11 35 sorcery.register.runes.foreach('sorcery:generate',{},function(name,rune) 12 36 local id = 'sorcery:rune_' .. name 13 37 rune.image = rune.image or string.format('sorcery_rune_%s.png',name) 14 38 rune.item = id 15 39 minetest.register_craftitem(id, { 16 40 description = sorcery.lib.color(rune.tone):readable():fmt(rune.name .. ' Rune'); 17 41 short_description = rune.name .. ' Rune'; ................................................................................ 20 44 groups = { 21 45 sorcery_rune = 1; 22 46 not_in_creative_inventory = 1; 23 47 }; 24 48 _proto = { id = name, data = rune; }; 25 49 }) 26 50 end) 51 + 52 +for name,p in pairs(constants.phial_kinds) do 53 + local f = string.format 54 + local color = sorcery.lib.color(255,27,188) 55 + local fac = p.grade / 6 56 + local id = f('phial_%s', name); 57 + sorcery.register_potion_tbl { 58 + name = id; 59 + label = f('%s Phial',p.name); 60 + desc = "A powerful liquid consumed in the operation of a rune forge. Its quality determines how fast new runes can be constructed."; 61 + color = color:brighten(1 + fac*0.5); 62 + imgvariant = (fac >= 5) and 'sparkle' or 'dull'; 63 + glow = 5+p.grade; 64 + extra = { 65 + groups = { sorcery_phial = p.grade }; 66 + _proto = { id = name, data = p }; 67 + }; 68 + } 69 + sorcery.register.infusions.link { 70 + infuse = p.infusion; 71 + into = 'sorcery:potion_subtle'; 72 + output = id; 73 + } 74 +end 75 + 76 +local register_rune_wrench = function(w) 77 + local mp = sorcery.data.metals[w.metal].parts 78 + minetest.register_tool(w.name, { 79 + description = w.desc; 80 + inventory_image = w.img; 81 + groups = { 82 + sorcery_magitech = 1; 83 + sorcery_rune_wrench = 1; 84 + crafttool = 50; 85 + }; 86 + _proto = w; 87 + _sorcery = { 88 + recipe = { note = w.note }; 89 + }; 90 + }) 91 + minetest.register_craft { 92 + output = w.name; 93 + recipe = { 94 + {'', mp.fragment,''}; 95 + {'', mp.ingot, mp.fragment}; 96 + {'sorcery:vidrium_fragment','', ''}; 97 + }; 98 + } 99 +end 100 + 101 +register_rune_wrench { 102 + name = 'sorcery:rune_wrench', desc = 'Rune Wrench'; 103 + img = 'sorcery_rune_wrench.png', metal = 'brass'; 104 + powers = { imbue = 30 }; 105 + note = 'A runeworking tool used to imbue amulets with enchantments'; 106 +} 107 + 108 +register_rune_wrench { 109 + name = 'sorcery:rune_wrench_iridium', desc = 'Iridium Rune Wrench'; 110 + img = 'sorcery_rune_wrench_iridium.png', metal = 'iridium'; 111 + powers = { imbue = 80, extract = 40 }; 112 + note = 'A rare and powerful runeworking tool used to imbue amulets with enchantments, or extract runes intact from enchanted amulets'; 113 +} 27 114 28 115 local rune_set = function(stack,r) 29 116 local m = stack:get_meta() 30 117 local def = stack:get_definition()._proto.data 31 118 local grade 32 119 if r.grade then grade = r.grade 33 120 elseif m:contains('rune_grade') then grade = m:get_int('rune_grade') end ................................................................................ 36 123 local title = sorcery.lib.color(def.tone):readable():fmt(string.format('%s %s Rune',qpfx,def.name)) 37 124 38 125 m:set_int('rune_grade',grade) 39 126 m:set_string('description',title) 40 127 end 41 128 42 129 sorcery.amulet = {} 43 -sorcery.amulet.setrune = function(stack,rune) 130 +sorcery.amulet.setrune = function(stack,rune,user) 44 131 local m = stack:get_meta() 45 132 if rune then 46 133 local rp = rune:get_definition()._proto 47 134 local rg = rune:get_meta():get_int('rune_grade') 48 135 m:set_string('amulet_rune', rp.id) 49 136 m:set_int('amulet_rune_grade', rg) 50 137 local spell = sorcery.amulet.getspell(stack) 51 138 if not spell then return nil end 52 139 53 - local name = string.format('Amulet of %s', spell.name) 54 - 140 + local name = string.format('Amulet of %s %s', constants.amulet_grades[rg], spell.name) 55 141 m:set_string('description', sorcery.lib.ui.tooltip { 56 142 title = name; 57 143 color = spell.tone; 58 144 desc = spell.desc; 59 145 }) 146 + 147 + if spell.apply then spell.apply { 148 + stack = stack; 149 + meta = m; 150 + user = user; 151 + self = spell; 152 + } end 60 153 else 154 + local spell = sorcery.amulet.getspell(stack) 61 155 m:set_string('description','') 62 156 m:set_string('amulet_rune','') 63 157 m:set_string('amulet_rune_grade','') 158 + if spell and spell.remove then spell.remove { 159 + stack = stack; 160 + meta = m; 161 + user = user; 162 + self = spell; 163 + } end 64 164 end 65 165 return stack 66 166 end 167 + 168 +sorcery.amulet.stats = function(stack) 169 + local spell = sorcery.amulet.getspell(stack) 170 + if not spell then return nil end 171 + local power = spell.grade 172 + 173 + if spell.base_spell then 174 + -- only consider the default effect of the frame metal 175 + -- if the frame doesn't totally override the spell 176 + power = power * (spell.framestats and spell.framestats.power or 1) 177 + end 178 + 179 + return { 180 + power = power; 181 + } 182 +end 67 183 68 184 sorcery.amulet.getrune = function(stack) 69 185 local m = stack:get_meta() 70 186 if not m:contains('amulet_rune') then return nil end 71 187 local rune = m:get_string('amulet_rune') 72 188 local grade = m:get_int('amulet_rune_grade') 73 189 local rs = ItemStack(sorcery.data.runes[rune].item) ................................................................................ 74 190 rune_set(rs, {grade = grade}) 75 191 return rs 76 192 end 77 193 78 194 sorcery.amulet.getspell = function(stack) 79 195 local m = stack:get_meta() 80 196 local proto = stack:get_definition()._sorcery.amulet 197 + if not m:contains('amulet_rune') then return nil end 81 198 local rune = m:get_string('amulet_rune') 199 + local rg = m:get_string('amulet_rune_grade') 82 200 local rd = sorcery.data.runes[rune] 83 201 local spell = rd.amulets[proto.base] 84 202 if not spell then return nil end 85 - local title,desc,cast = spell.name, spell.desc, spell.cast 203 + local title,desc,cast,apply,remove = spell.name, spell.desc, spell.cast, spell.apply, spell.remove -- FIXME in serious need of refactoring 204 + local base_spell = true 86 205 87 206 if proto.frame and spell.frame and spell.frame[proto.frame] then 88 207 local sp = spell.frame[proto.frame] 89 208 title = sp.name or title 90 209 desc = sp.desc or desc 91 210 cast = sp.desc or cast 211 + apply = sp.apply or apply 212 + remove = sp.remove or remove 213 + base_spell = false 92 214 end 93 215 94 216 return { 95 217 rune = rune; 218 + grade = rg; 96 219 spell = spell; 97 - name = title; 98 - desc = desc; 99 - cast = cast; 220 + name = title, desc = desc; 221 + cast = cast, apply = apply, remove = remove; 222 + frame = proto.frame; 223 + framestats = proto.frame and sorcery.data.metals[proto.frame].amulet; 100 224 tone = sorcery.lib.color(rd.tone); 225 + base_spell = base_spell; 101 226 } 102 227 end 103 228 104 229 105 230 local runeforge_update = function(pos,time) 106 231 local m = minetest.get_meta(pos) 107 232 local i = m:get_inventory() 108 233 local l = sorcery.ley.netcaps(pos,time or 1) 109 234 110 235 local pow_min = l.self.powerdraw >= l.self.minpower 111 236 local pow_max = l.self.powerdraw >= l.self.maxpower 237 + local has_phial = function() return not i:is_empty('phial') end 112 238 113 - if time and pow_min then -- roll for runes 114 - local rolls = math.floor(time/constants.rune_mine_interval) 239 + if time and has_phial() and pow_min then -- roll for runes 240 + local rolls = math.floor(time/calc_phial_props(i:get_stack('phial',1))) 115 241 local newrunes = {} 116 242 for _=1,rolls do 117 243 local choices = {} 118 244 for name,rune in pairs(sorcery.data.runes) do 119 245 if rune.minpower*time <= l.self.powerdraw and math.random(rune.rarity) == 1 then 120 246 local n = ItemStack(rune.item) 121 247 choices[#choices + 1] = n 122 248 end 123 249 end 124 250 if #choices > 0 then newrunes[#newrunes + 1] = choices[math.random(#choices)] end 125 251 end 126 252 127 - print('rolled for runes, got', dump(newrunes)) 128 253 for _,r in pairs(newrunes) do 129 - if i:room_for_item('cache',r) then 254 + if i:room_for_item('cache',r) and has_phial() then 130 255 local qual = math.random(#constants.rune_grades) 131 256 rune_set(r,{grade = qual}) 132 257 i:add_item('cache',r) 133 - end 258 + -- consume a phial 259 + local ph = i:get_stack('phial',1) 260 + local n = ph:get_name() 261 + ph:take_item(1) i:set_stack('phial',1,ph) 262 + minetest.add_item(pos,i:add_item('refuse',ItemStack(sorcery.register.residue.db[n]))) 263 + else break end 134 264 end 135 265 end 136 266 267 + has_phial = has_phial() 137 268 local spec = string.format([[ 138 269 formspec_version[3] size[10.25,8] real_coordinates[true] 139 270 list[context;cache;%f,0.25;%u,1;] 140 271 list[context;amulet;3.40,1.50;1,1;] 141 272 list[context;active;5.90,1.50;1,1;] 273 + 274 + list[context;wrench;1.25,1.75;1,1;] 275 + list[context;phial;7.25,1.75;1,1;] 276 + list[context;refuse;8.50,1.75;1,1;] 277 + 142 278 list[current_player;main;0.25,3;8,4;] 143 279 144 280 image[0.25,0.50;1,1;sorcery_statlamp_%s.png] 145 281 ]], (10.5 - constants.rune_cache_max*1.25)/2, constants.rune_cache_max, 146 - pow_max and 'green' or (pow_min and 'yellow') or 'off') 282 + ((has_phial and pow_max) and 'green' ) or 283 + ((has_phial and pow_min) and 'yellow') or 'off') 284 + 285 + local ghost = function(slot,x,y,img) 286 + if i:is_empty(slot) then spec = spec .. string.format([[ 287 + image[%f,%f;1,1;%s.png] 288 + ]], x,y,img) end 289 + end 290 + 291 + ghost('active',5.90,1.50,'sorcery_ui_ghost_rune') 292 + ghost('amulet',3.40,1.50,'sorcery_ui_ghost_amulet') 293 + ghost('wrench',1.25,1.75,'sorcery_ui_ghost_rune_wrench') 294 + ghost('phial',7.25,1.75,'vessels_shelf_slot') 147 295 148 296 m:set_string('formspec',spec) 297 + 298 + if i:is_empty('phial') then return false end 149 299 return true 150 300 end 151 301 152 302 local rfbox = { 153 303 type = 'fixed'; 154 304 fixed = { 155 305 -0.5, -0.5, -0.5; ................................................................................ 180 330 'default_copper_block.png'; 181 331 }; 182 332 _sorcery = { 183 333 ley = { 184 334 mode = 'consume'; 185 335 affinity = {'praxic'}; 186 336 power = function(pos,time) 337 + local i = minetest.get_meta(pos):get_inventory() 338 + if i:is_empty('phial') then return 0 end 339 + local phial = i:get_stack('phial',1) 340 + 187 341 local max,min = 0 188 342 for _,r in pairs(sorcery.data.runes) do 189 343 if r.minpower > max then max = r.minpower end 190 344 if min == nil or r.minpower < min then min = r.minpower end 191 345 end 346 + -- high-quality phials reduce power usage 347 + local fac = select(2, calc_phial_props(phial)) 348 + min = min * fac max = max * fac 192 349 return min*time,max*time 193 350 end; 194 351 }; 195 352 on_leychange = runeforge_update; 196 353 recipe = { 197 354 note = 'Periodically creates runes when sufficiently powered and can be used to imbue them into an amulet, giving it a powerful magical effect'; 198 355 }; 199 356 }; 200 357 on_construct = function(pos) 201 358 local m = minetest.get_meta(pos) 202 359 local i = m:get_inventory() 203 360 i:set_size('cache',constants.rune_cache_max) 204 - i:set_size('amulet',1) 205 - i:set_size('active',1) 361 + i:set_size('wrench',1) i:set_size('phial',1) i:set_size('refuse',1) 362 + i:set_size('amulet',1) i:set_size('active',1) 206 363 m:set_string('infotext','Rune Forge') 207 364 runeforge_update(pos) 208 - minetest.get_node_timer(pos):start(constants.rune_mine_interval) 209 365 end; 210 366 after_dig_node = sorcery.lib.node.purge_only {'amulet'}; 211 367 on_timer = runeforge_update; 212 368 on_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user) 213 369 local inv = minetest.get_meta(pos):get_inventory() 370 + local wrench if not inv:is_empty('wrench') then 371 + wrench = inv:get_stack('wrench',1):get_definition()._proto 372 + end 373 + local wwear = function(cap) 374 + local s = inv:get_stack('wrench',1) 375 + local wear = 65535 / wrench.powers[cap] 376 + s:add_wear(wear) 377 + inv:set_stack('wrench',1,s) 378 + end 214 379 if fl == 'active' then 215 - inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1))) 216 - elseif tl == 'active' then 217 - inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti))) 380 + inv:set_stack('amulet',1,sorcery.amulet.setrune(inv:get_stack('amulet',1),nil,user)) 381 + -- only special wrenches can extract runes intact 382 + if wrench.powers.extract then wwear('extract') 383 + minetest.sound_play('sorcery_chime', { pos = pos, gain = 0.5 }) 384 + elseif wrench.powers.purge then wwear('purge') 385 + inv:set_stack(tl,ti,ItemStack(nil)) 386 + minetest.sound_play('sorcery_disjoin', { pos = pos, gain = 0.5 }) 387 + end 388 + elseif tl == 'active' and wrench.powers.imbue then 389 + local amulet = sorcery.amulet.setrune(inv:get_stack('amulet',1), inv:get_stack(tl,ti), user) 390 + local spell = sorcery.amulet.getspell(amulet) 391 + sorcery.vfx.enchantment_sparkle({ 392 + under = pos; 393 + above = vector.add(pos,{x=0,y=1,z=0}); 394 + }, spell.tone:brighten(1.2):hex()) 395 + minetest.sound_play('xdecor_enchanting', { pos = pos, gain = 0.5 }) 396 + inv:set_stack('amulet',1,amulet) 397 + wwear('imbue') 218 398 end 399 + -- trigger the update early to clean up the ghost image :/ 400 + -- minetest needs a cleaner way to handle these 401 + runeforge_update(pos) 219 402 end; 220 403 on_metadata_inventory_put = function(pos, list, idx, stack, user) 404 + local inv = minetest.get_meta(pos):get_inventory() 221 405 if list == 'amulet' then 222 - local inv = minetest.get_meta(pos):get_inventory() 223 406 inv:set_stack('active',1,ItemStack(sorcery.amulet.getrune(stack))) 224 407 end 408 + runeforge_update(pos) 409 + if not inv:is_empty('phial') then 410 + minetest.get_node_timer(pos):start(calc_phial_props(inv:get_stack('phial',1))) 411 + end 225 412 end; 226 413 on_metadata_inventory_take = function(pos, list, idx, stack, user) 227 414 if list == 'amulet' then 228 415 minetest.get_meta(pos):get_inventory():set_stack('active',1,ItemStack()) 229 416 end 417 + runeforge_update(pos) 230 418 end; 231 419 allow_metadata_inventory_put = function(pos,list,idx,stack,user) 232 420 if list == 'amulet' then 233 421 if minetest.get_item_group(stack:get_name(), 'sorcery_amulet') ~= 0 then 234 422 return 1 235 423 end 424 + end 425 + if list == 'phial' then 426 + if minetest.get_item_group(stack:get_name(), 'sorcery_phial') ~= 0 then 427 + return stack:get_count() 428 + end 429 + end 430 + if list == 'wrench' then 431 + if minetest.get_item_group(stack:get_name(), 'sorcery_rune_wrench') ~= 0 then 432 + return 1 433 + end 236 434 end 237 435 return 0 238 436 end; 239 437 allow_metadata_inventory_take = function(pos,list,idx,stack,user) 240 - if list == 'amulet' then return 1 end 438 + if list == 'amulet' or list == 'wrench' then return 1 end 439 + if list == 'phial' or list == 'refuse' then return stack:get_count() end 241 440 return 0 242 441 end; 243 442 allow_metadata_inventory_move = function(pos, fl,fi, tl,ti, count, user) 443 + local inv = minetest.get_meta(pos):get_inventory() 444 + local wrench if not inv:is_empty('wrench') then 445 + wrench = inv:get_stack('wrench',1):get_definition()._proto 446 + end 244 447 if fl == 'cache' then 245 448 if tl == 'cache' then return 1 end 246 449 if tl == 'active' then 247 - local inv = minetest.get_meta(pos):get_inventory() 248 - if not inv:is_empty('amulet') then 450 + print(dump(wrench)) 451 + if wrench and wrench.powers.imbue and not inv:is_empty('amulet') then 249 452 local amulet = inv:get_stack('amulet',1) 250 453 local rune = inv:get_stack(fl,fi) 251 454 if sorcery.data.runes[rune:get_definition()._proto.id].amulets[amulet:get_definition()._sorcery.amulet.base] then 252 455 return 1 253 456 end 254 457 end 255 458 end 256 459 end 257 460 if fl == 'active' then 258 - if tl == 'cache' then return 1 end 461 + if tl == 'cache' and wrench and (wrench.powers.extract or wrench.powers.purge) then return 1 end 259 462 end 260 463 return 0 261 464 end; 262 465 }) 263 466 264 467 do local m = sorcery.data.metals 265 468 -- temporary recipe until a fancier multi-part crafting path can be come up with
Added sounds/sorcery_bloody_burst.ogg version [86beb5bbfe].
cannot compute difference between binary files
Added sounds/sorcery_bloody_hit.1.ogg version [4a47968931].
cannot compute difference between binary files
Added sounds/sorcery_bloody_hit.2.ogg version [6bea57cc69].
cannot compute difference between binary files
Added sounds/sorcery_bloody_hit.3.ogg version [3d0dd5fb33].
cannot compute difference between binary files
Added sounds/sorcery_chime.1.ogg version [f5f3fa1f0b].
cannot compute difference between binary files
Added sounds/sorcery_chime.2.ogg version [e2c798dcb3].
cannot compute difference between binary files
Added sounds/sorcery_coins.ogg version [24495c2ec0].
cannot compute difference between binary files
Added sounds/sorcery_crunch.1.ogg version [bd7af2ce27].
cannot compute difference between binary files
Added sounds/sorcery_crunch.2.ogg version [436bf86ffa].
cannot compute difference between binary files
Added sounds/sorcery_crunch.3.ogg version [b858a7a30d].
cannot compute difference between binary files
Added sounds/sorcery_crunch.4.ogg version [5a749d38d7].
cannot compute difference between binary files
Added sounds/sorcery_disjoin.1.ogg version [bafe4e15de].
cannot compute difference between binary files
Added sounds/sorcery_disjoin.2.ogg version [72809eab09].
cannot compute difference between binary files
Added textures/sorcery_rune_wrench.png version [c39260a038].
cannot compute difference between binary files
Added textures/sorcery_rune_wrench_iridium.png version [076b434e79].
cannot compute difference between binary files
Added textures/sorcery_ui_ghost_amulet.png version [1e1e56f3c1].
cannot compute difference between binary files
Added textures/sorcery_ui_ghost_rune.png version [8e47e791d3].
cannot compute difference between binary files
Added textures/sorcery_ui_ghost_rune_wrench.png version [1bd60d3986].
cannot compute difference between binary files
Modified tnodes.lua from [1f5f95dd4b] to [4064266f0d].
3 3 drawtype = 'airlike'; 4 4 light_source = 5 + math.ceil(i * (11/minetest.LIGHT_MAX)); 5 5 sunlight_propagates = true; 6 6 buildable_to = true; 7 7 pointable = false; 8 8 walkable = false; 9 9 floodable = true; 10 + drop = {max_items = 0, items = {}}; 11 + on_blast = function() end; -- not affected by explosions 10 12 groups = { air = 1; sorcery_air = 1; not_in_creative_inventory = 1; }; 11 13 on_construct = function(pos) 12 14 local meta = minetest.get_meta(pos) 13 15 meta:set_float('duration',10) 14 16 meta:set_float('timeleft',10) 15 17 meta:set_int('power',minetest.LIGHT_MAX) 16 18 minetest.get_node_timer(pos):start(1)
Modified vfx.lua from [291a2c52f8] to [343a5ccf55].
1 1 sorcery.vfx = {} 2 2 3 -sorcery.vfx.cast_sparkle = function(caster,color,strength,duration) 3 +sorcery.vfx.cast_sparkle = function(caster,color,strength,duration,pos) 4 + local ofs = pos 5 + and function(x) return vector.add(pos,x) end 6 + or function(x) return x end 7 + local height = caster:get_properties().eye_height 4 8 minetest.add_particlespawner { 5 9 amount = 70 * strength; 6 10 time = duration or 1.5; 7 11 attached = caster; 8 12 texture = sorcery.lib.image('sorcery_spark.png'):multiply(color):render(); 9 - minpos = { x = -0.1, z = 0.5, y = 1.2}; 10 - maxpos = { x = 0.1, z = 0.3, y = 1.6}; 13 + minpos = ofs({ x = 0.0, z = 0.6, y = height*0.7}); 14 + maxpos = ofs({ x = 0.4, z = 0.2, y = height*1.1}); 11 15 minvel = { x = -0.5, z = -0.5, y = -0.5}; 12 16 maxvel = { x = 0.5, z = 0.5, y = 0.5}; 13 17 minacc = { x = 0.0, z = 0.0, y = 0.5}; 14 18 maxacc = { x = 0.0, z = 0.0, y = 0.5}; 15 19 minsize = 0.4, maxsize = 0.8; 16 20 minexptime = 1, maxexptime = 1; 17 21 glow = 14; ................................................................................ 19 23 type = 'vertical_frames'; 20 24 aspect_w = 16; 21 25 aspect_h = 16; 22 26 length = 1.1; 23 27 }; 24 28 } 25 29 end 30 + 31 +sorcery.vfx.body_sparkle = function(body,color,str,pos) 32 + local img = sorcery.lib.image 33 + local tex = img('sorcery_spark.png') 34 + local pi = tex:blit(tex:multiply(color)):render() 35 + local ofs = pos 36 + and function(x) return vector.add(pos,x) end 37 + or function(x) return x end 38 + return minetest.add_particlespawner { 39 + amount = 25 * str; 40 + time = 0.5; 41 + attached = body; 42 + minpos = ofs{x = -0.5, y = -0.5, z = -0.5}; 43 + maxpos = ofs{x = 0.5, y = 1.5, z = 0.5}; 44 + minacc = {x = -0.3, y = 0.0, z = 0.3}; 45 + maxacc = {x = -0.3, y = 0.0, z = 0.3}; 46 + minvel = {x = -0.6, y = -0.2, z = 0.6}; 47 + maxvel = {x = -0.6, y = 0.2, z = 0.6}; 48 + minexptime = 1.0; 49 + maxexptime = 1.5; 50 + texture = pi; 51 + glow = 14; 52 + animation = { 53 + type = 'vertical_frames'; 54 + aspect_w = 16, aspect_h = 16; 55 + length = 1.6; 56 + }; 57 + } 58 +end 26 59 27 60 sorcery.vfx.enchantment_sparkle = function(tgt,color) 28 61 local minvel, maxvel 29 62 if minetest.get_node(vector.add(tgt.under,{y=1,z=0,x=0})).name == 'air' then 30 63 minvel = {x=0,z=0,y= 0.3} maxvel = {x=0,z=0,y= 1.5}; 31 64 else 32 65 local dir = vector.subtract(tgt.under,tgt.above)
Modified wands.lua from [e3e1a760a8] to [a021a4fd8f].
91 91 -- but power levels are unpredictable 92 92 tone = u.color(255,117,40); 93 93 tex = u.image('default_copper_block.png'); 94 94 wandprops = { flux = 0.7, chargetime = 0.5 }; 95 95 }; 96 96 silver = { 97 97 tone = u.color(215,238,241); 98 - tex = u.image('default_gold_block'):colorize(u.color(255,238,241), 255); 98 + tex = u.image('default_gold_block.png'):colorize(u.color(255,238,241), 255); 99 99 wandprops = {}; 100 100 }; 101 101 steel = { 102 102 tone = u.color(255,255,255); 103 - tex = u.image('default_steel_block'); 103 + tex = u.image('default_steel_block.png'); 104 104 wandprops = {}; 105 105 }; 106 106 }; 107 107 gem = sorcery.data.gems; 108 108 }; 109 109 util = { 110 110 baseid = function(wand)