From d8e6a6aca3a3d4a79d30c70933f10e4fc97f89cd Mon Sep 17 00:00:00 2001 From: Treer Date: Sat, 27 Nov 2021 15:40:00 +1100 Subject: [PATCH] Treasures (#50) * Add Netherrack slabs and walls Also adds deep netherrack stairs, and deep nether brick stairs, slabs, inner stairs and outer stairs. Adds 9 new nodes in total. * Treasure: Nether pickaxe takes 10x less wear damage when mining netherrack cracky-3 mining time of Nether pickaxe reduced from 0.4 to 0.3 to mine netherrack faster. maxlevel of nether pickaxe dropped from 3 to 2 to increase wear damage with non-netherrack nodes. * Treasure: Nether staff of light Adds "Nether staff of Light" and "Nether staff of Eternal Light" One is limited to 60 uses, and the other has unlimited uses but the glowstone it creates will only last for 40 seconds. There are no crafting recipes as I hope these to eventually be treasure that can be found in the nether. See the pull request (#50) for more discussion --- README.md | 8 +- mod.conf | 2 +- nodes.lua | 190 ++++++++++++++++++++++++--- sounds/nether_lightstaff.ogg | Bin 0 -> 10856 bytes sounds/nether_rack_destroy.ogg | Bin 0 -> 12238 bytes textures/nether_lightstaff.png | Bin 0 -> 1669 bytes tools.lua | 230 ++++++++++++++++++++++++++++++++- 7 files changed, 409 insertions(+), 21 deletions(-) create mode 100644 sounds/nether_lightstaff.ogg create mode 100644 sounds/nether_rack_destroy.ogg create mode 100644 textures/nether_lightstaff.png diff --git a/README.md b/README.md index d72df3f..f938389 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ right-click/used on the frame to activate it. See portal_api.txt for how to create custom portals to your own realms. +Nether Portals can allow surface fast-travel. + This mod provides Nether basalts (natural, hewn, and chiseled) as nodes which require a player to journey to the magma ocean to obtain, so these can be used for gating progression through a game. For example, a portal to another realm @@ -24,8 +26,7 @@ the nether first, or basalt might be a crafting ingredient required to reach a particular branch of the tech-tree. Netherbrick tools are provided (pick, shovel, axe, & sword), see tools.lua - -Nether Portals can allow surface fast-travel. +The Nether pickaxe has a 10x bonus again wear when mining netherrack. ## License of source code: @@ -50,9 +51,11 @@ SOFTWARE. ### [Public Domain Dedication (CC0 1.0)](https://creativecommons.org/publicdomain/zero/1.0/) * `nether_portal_teleport.ogg` is a timing adjusted version of "teleport" by [outroelison](https://freesound.org/people/outroelison), used under CC0 1.0 + * `nether_rack_destroy.ogg` is from "Rock destroy" by [Bertsz](https://freesound.org/people/Bertsz/), used under CC0 1.0 ### [Attribution 3.0 Unported (CC BY 3.0)](https://creativecommons.org/licenses/by/3.0/) + * `nether_lightstaff.ogg` is "Fire Burst" by [SilverIllusionist](https://freesound.org/people/SilverIllusionist/), 2019 * `nether_portal_ambient.ogg` & `nether_portal_ambient.0.ogg` are extractions from "Deep Cinematic Rumble Stereo" by [Patrick Lieberkind](http://www.lieberkindvisuals.dk), used under CC BY 3.0 * `nether_portal_extinguish.ogg` is an extraction from "Tight Laser Weapon Hit Scifi" by [damjancd](https://freesound.org/people/damjancd), used under CC BY 3.0 * `nether_portal_ignite.ogg` is a derivative of "Flame Ignition" by [hykenfreak](https://freesound.org/people/hykenfreak), used under CC BY 3.0. "Nether Portal ignite" is licensed under CC BY 3.0 by Treer. @@ -64,6 +67,7 @@ SOFTWARE. * `nether_fumarole.ogg`: Treer, 2020 * `nether_lava_bubble`* (files starting with "nether_lava_bubble"): Treer, 2020 * `nether_lava_crust_animated.png`: Treer, 2019-2020 + * `nether_lightstaff.png`: Treer, 2021 * `nether_particle_anim`* (files starting with "nether_particle_anim"): Treer, 2019 * `nether_portal_ignition_failure.ogg`: Treer, 2019 * `nether_smoke_puff.png`: Treer, 2020 diff --git a/mod.conf b/mod.conf index 6a7e193..76467cb 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,4 @@ name = nether description = Adds a deep underground realm with different mapgen that you can reach with obsidian portals. depends = stairs, default -optional_depends = moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal +optional_depends = moreblocks, mesecons, loot, dungeon_loot, doc_basics, fire, climate_api, ethereal, walls diff --git a/nodes.lua b/nodes.lua index e07cce5..a484278 100644 --- a/nodes.lua +++ b/nodes.lua @@ -65,13 +65,118 @@ nether.register_wormhole_node("nether:portal_alt", { }) +--== Transmogrification functions ==-- +-- Functions enabling selected nodes to be temporarily transformed into other nodes. +-- (so the light staff can temporarily turn netherrack into glowstone) + +-- Swaps the node at `nodePos` with `newNode`, unless `newNode` is nil in which +-- case the node is swapped back to its original type. +-- `monoSimpleSoundSpec` is optional. +-- returns true if a node was transmogrified +nether.magicallyTransmogrify_node = function(nodePos, playerName, newNode, monoSimpleSoundSpec, isPermanent) + + local meta = minetest.get_meta(nodePos) + local playerEyePos = nodePos -- fallback value in case the player no longer exists + local player = minetest.get_player_by_name(playerName) + if player ~= nil then + local playerPos = player:get_pos() + playerEyePos = vector.add(playerPos, {x = 0, y = 1.5, z = 0}) -- not always the cameraPos, e.g. 3rd person mode. + end + + local oldNode = minetest.get_node(nodePos) + if oldNode.name == "air" then + -- the node has been mined or otherwise destroyed, abort the operation + return false + end + local oldNodeDef = minetest.registered_nodes[oldNode.name] or minetest.registered_nodes["air"] + + local specialFXSize = 1 -- a specialFXSize of 1 is for full SFX, 0.5 is half-sized + local returningToNormal = newNode == nil + if returningToNormal then + -- This is the transmogrified node returning back to normal - a more subdued animation + specialFXSize = 0.5 + -- read what the node used to be from the metadata + newNode = { + name = meta:get_string("transmogrified_name"), + param1 = meta:get_string("transmogrified_param1"), + param2 = meta:get_string("transmogrified_param2") + } + if newNode.name == "" then + minetest.log("warning", "nether.magicallyTransmogrify_node() invoked to restore node which wasn't transmogrified") + return false + end + end + + local soundSpec = monoSimpleSoundSpec + if soundSpec == nil and oldNodeDef.sounds ~= nil then + soundSpec = oldNodeDef.sounds.dug or oldNodeDef.sounds.dig + if soundSpec == "__group" then soundSpec = "default_dig_cracky" end + end + if soundSpec ~= nil then + minetest.sound_play(soundSpec, {pos = nodePos, max_hear_distance = 50}) + end + + -- Start the particlespawner nearer the player's side of the node to create + -- more initial occlusion for an illusion of the old node breaking apart / falling away. + local dirToPlayer = vector.normalize(vector.subtract(playerEyePos, nodePos)) + local impactPos = vector.add(nodePos, vector.multiply(dirToPlayer, 0.5)) + local velocity = 1 + specialFXSize + minetest.add_particlespawner({ + amount = 50 * specialFXSize, + time = 0.1, + minpos = vector.add(impactPos, -0.3), + maxpos = vector.add(impactPos, 0.3), + minvel = {x = -velocity, y = -velocity, z = -velocity}, + maxvel = {x = velocity, y = 3 * velocity, z = velocity}, -- biased upward to counter gravity in the initial stages + minacc = {x=0, y=-10, z=0}, + maxacc = {x=0, y=-10, z=0}, + minexptime = 1.5 * specialFXSize, + maxexptime = 3 * specialFXSize, + minsize = 0.5, + maxsize = 5, + node = {name = oldNodeDef.name}, + glow = oldNodeDef.light_source + }) + + if returningToNormal or isPermanent then + -- clear the metadata that indicates the node is transformed + meta:set_string("transmogrified_name", "") + meta:set_int("transmogrified_param1", 0) + meta:set_int("transmogrified_param2", 0) + else + -- save the original node so it can be restored + meta:set_string("transmogrified_name", oldNode.name) + meta:set_int("transmogrified_param1", oldNode.param1) + meta:set_int("transmogrified_param2", oldNode.param2) + end + + minetest.swap_node(nodePos, newNode) + return true +end + + +local function transmogrified_can_dig (pos, player) + if minetest.get_meta(pos):get_string("transmogrified_name") ~= "" then + -- This node was temporarily transformed into its current form + -- revert it back, rather than allow the player to mine transmogrified nodes. + local playerName = "" + if player ~= nil then playerName = player:get_player_name() end + nether.magicallyTransmogrify_node(pos, playerName) + return false + end + return true +end + + + -- Nether nodes minetest.register_node("nether:rack", { description = S("Netherrack"), tiles = {"nether_rack.png"}, is_ground_content = true, - groups = {cracky = 3, level = 2}, + -- setting workable_with_nether_tools reduces the wear on nether:pick_nether when mining this node + groups = {cracky = 3, level = 2, workable_with_nether_tools = 3}, sounds = default.node_sound_stone_defaults(), }) @@ -81,7 +186,8 @@ minetest.register_node("nether:rack_deep", { _doc_items_longdesc = S("Netherrack from deep in the mantle"), tiles = {"nether_rack_deep.png"}, is_ground_content = true, - groups = {cracky = 3, level = 2}, + -- setting workable_with_nether_tools reduces the wear on nether:pick_nether when mining this node + groups = {cracky = 3, level = 2, workable_with_nether_tools = 3}, sounds = default.node_sound_stone_defaults(), }) @@ -103,6 +209,7 @@ minetest.register_node("nether:glowstone", { paramtype = "light", groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults(), + can_dig = transmogrified_can_dig, -- to ensure glowstone temporarily created by the lightstaff can't be kept }) -- Deep glowstone, found in the mantle / central magma layers @@ -114,6 +221,7 @@ minetest.register_node("nether:glowstone_deep", { paramtype = "light", groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults(), + can_dig = transmogrified_can_dig, -- to ensure glowstone temporarily created by the lightstaff can't be kept }) minetest.register_node("nether:brick", { @@ -175,37 +283,89 @@ minetest.register_node("nether:brick_deep", { -- Register stair and slab -stairs.register_stair_and_slab( - "nether_brick", - "nether:brick", - {cracky = 2, level = 2}, - {"nether_brick.png"}, - S("Nether Stair"), - S("Nether Slab"), - default.node_sound_stone_defaults(), - nil, - S("Inner Nether Stair"), - S("Outer Nether Stair") +-- Nether bricks can be made into stairs, slabs, inner stairs, and outer stairs + +stairs.register_stair_and_slab( -- this function also registers inner and outer stairs + "nether_brick", -- subname + "nether:brick", -- recipeitem + {cracky = 2, level = 2}, -- groups + {"nether_brick.png"}, -- images + S("Nether Stair"), -- desc_stair + S("Nether Slab"), -- desc_slab + minetest.registered_nodes["nether:brick"].sounds, -- sounds + false, -- worldaligntex + S("Inner Nether Stair"), -- desc_stair_inner + S("Outer Nether Stair") -- desc_stair_outer ) +stairs.register_stair_and_slab( -- this function also registers inner and outer stairs + "nether_brick_deep", -- subname + "nether:brick_deep", -- recipeitem + {cracky = 2, level = 2}, -- groups + {"nether_brick_deep.png"}, -- images + S("Deep Nether Stair"), -- desc_stair + S("Deep Nether Slab"), -- desc_slab + minetest.registered_nodes["nether:brick_deep"].sounds, -- sounds + false, -- worldaligntex + S("Inner Deep Nether Stair"), -- desc_stair_inner + S("Outer Deep Nether Stair") -- desc_stair_outer +) + +-- Netherrack can be shaped into stairs, slabs and walls + stairs.register_stair( "netherrack", "nether:rack", {cracky = 2, level = 2}, {"nether_rack.png"}, S("Netherrack stair"), - default.node_sound_stone_defaults() + minetest.registered_nodes["nether:rack"].sounds ) +stairs.register_slab( -- register a slab without adding inner and outer stairs + "netherrack", + "nether:rack", + {cracky = 2, level = 2}, + {"nether_rack.png"}, + S("Deep Netherrack slab"), + minetest.registered_nodes["nether:rack"].sounds +) + +stairs.register_stair( + "netherrack_deep", + "nether:rack_deep", + {cracky = 2, level = 2}, + {"nether_rack_deep.png"}, + S("Deep Netherrack stair"), + minetest.registered_nodes["nether:rack_deep"].sounds +) +stairs.register_slab( -- register a slab without adding inner and outer stairs + "netherrack_deep", + "nether:rack_deep", + {cracky = 2, level = 2}, + {"nether_rack_deep.png"}, + S("Deep Netherrack slab"), + minetest.registered_nodes["nether:rack_deep"].sounds +) + +-- Connecting walls +if minetest.get_modpath("walls") and minetest.global_exists("walls") and walls.register ~= nil then + walls.register("nether:rack_wall", "A Netherrack wall", "nether_rack.png", "nether:rack", minetest.registered_nodes["nether:rack"].sounds) + walls.register("nether:rack_deep_wall", "A Deep Netherrack wall", "nether_rack_deep.png", "nether:rack_deep", minetest.registered_nodes["nether:rack_deep"].sounds) +end -- StairsPlus if minetest.get_modpath("moreblocks") then + -- Registers about 49 different shapes of nether brick, replacing the stairs & slabs registered above. + -- (This could also be done for deep nether brick, but I've left that out to avoid a precedent of 49 new + -- nodes every time the nether gets a new material. Nether structures won't be able to use them because + -- they can't depend on moreblocks) stairsplus:register_all( "nether", "brick", "nether:brick", { description = S("Nether Brick"), groups = {cracky = 2, level = 2}, tiles = {"nether_brick.png"}, - sounds = default.node_sound_stone_defaults(), + sounds = minetest.registered_nodes["nether:brick"].sounds, }) end diff --git a/sounds/nether_lightstaff.ogg b/sounds/nether_lightstaff.ogg new file mode 100644 index 0000000000000000000000000000000000000000..c1260e3c7404a2cfb2be3e7aba8f1694dc8db09b GIT binary patch literal 10856 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 D2Rm2B literal 0 HcmV?d00001 diff --git a/textures/nether_lightstaff.png b/textures/nether_lightstaff.png new file mode 100644 index 0000000000000000000000000000000000000000..915a7062b362e0ac399658562769c9821ccefda8 GIT binary patch literal 1669 zcmah}PiP}m7=K|~sa*<-y08=v!>fW@$;>3()@C--HJijH*r_3nY$^yloq3ZyI+-`l zylf^tEvq8xK|CsY_f%B8c~B4!T38PvyMn!RVHZJB+>1hC{oW)^nr*9x{Q2JR_x;}Y zzJG65R+pzHrza6YQ{_^j3cqK>XY3Sw|Gv8?4#pZeBZtsu@15Q^$H7j7)#b}*`{t9s zg7K(dS`QIA{kZrf(1#zLLumX(TC4NAv7%Y5C7U+eAad05fsIi1QskS~CgIWsX;4qc ze|-5BmZ+`cEAxhG_<7Q#rLBOhZLQXI{7U8B8wOlXMA|1!N8vGZ> z3YKCD-_&v4s7QGh5NTeXmsPwpDP;rO(W-?P`p#hKxXHP%DN3i)kvnrT3mQr)lgTJ* zT1lsqz(|H&kDF1_3ulBxPoqFWE1*87%#(zsxxw08#~5&_FK$}{ZNDA3v9fI`#3e1_ z@lZ+0DdjMqM06O97xvQyiBkjubajNzsdEBdAV=talW`U{nLh|>k$TLD-yatKu7hxo(gA2l1*C$GqgsdG{q<9UoHxnmLl(j95Q|^-3 z>PSsT=9*qZ$5GNIj@foOUJAMCI9Wx^j7$6Ov31~|RYt_K5S(FXB^q)T zbPwIPMqV4eFCINfiW^EZEm030`vJ4t7O~?DLgYO(WDf6`0m(I>mvo$S914}|N)<6A z5)7u=&*Q+O($ix^jCXTRHK_in!b=kBKu?%#gp{CV@jg$Muq_V@0`-+V%EeLHgp z_z7NJT|>{`z3qT8UoPZo6Su6Z*Aw!+sWXr4e1Rrj&-{4m`-#+BvwP1xcK!2nXU*w1 zOJ{FP{~$Stj9vWt)dbrAYkUX))X9Lr)4W*cxqw4SP%R)Nki_(cutOV^H`^Ps*jX{~ o^BeE(gToWTA8qOzyHh5OMs(taka30`Ra{-0gJK(O#lD@ literal 0 HcmV?d00001 diff --git a/tools.lua b/tools.lua index 26e1425..1591e47 100644 --- a/tools.lua +++ b/tools.lua @@ -20,18 +20,33 @@ local S = nether.get_translator minetest.register_tool("nether:pick_nether", { - description = S("Nether Pickaxe"), + description = S("Nether Pickaxe\nWell suited for mining netherrack"), + _doc_items_longdesc = S("Uniquely suited for mining netherrack, with minimal wear when doing so. Blunts quickly on other materials."), inventory_image = "nether_tool_netherpick.png", tool_capabilities = { full_punch_interval = 0.8, max_drop_level=3, groupcaps={ - cracky = {times={[1]=1.90, [2]=0.9, [3]=0.4}, uses=35, maxlevel=3}, + cracky = {times={[1]=1.90, [2]=0.9, [3]=0.3}, uses=35, maxlevel=2}, }, damage_groups = {fleshy=4}, }, sound = {breaks = "default_tool_breaks"}, - groups = {pickaxe = 1} + groups = {pickaxe = 1}, + + after_use = function(itemstack, user, node, digparams) + local wearDivisor = 1 + local nodeDef = minetest.registered_nodes[node.name] + if nodeDef ~= nil and nodeDef.groups ~= nil then + -- The nether pick hardly wears out when mining netherrack + local workable = nodeDef.groups.workable_with_nether_tools or 0 + wearDivisor = 1 + (3 * workable) -- 10 for netherrack, 1 otherwise. Making it able to mine 350 netherrack nodes, instead of 35. + end + + local wear = math.floor(digparams.wear / wearDivisor) + itemstack:add_wear(wear) -- apply the adjusted wear as usual + return itemstack + end }) minetest.register_tool("nether:shovel_nether", { @@ -136,3 +151,212 @@ minetest.register_craft({ {"group:stick"} } }) + + + + +--===========================-- +--== Nether Staff of Light ==-- +--===========================-- + +nether.lightstaff_recipes = { + ["nether:rack"] = "nether:glowstone", + ["nether:brick"] = "nether:glowstone", + ["nether:brick_cracked"] = "nether:glowstone", + ["nether:brick_compressed"] = "nether:glowstone", + ["stairs:slab_netherrack"] = "nether:glowstone", + ["nether:rack_deep"] = "nether:glowstone_deep", + ["nether:brick_deep"] = "nether:glowstone_deep", + ["stairs:slab_netherrack_deep"] = "nether:glowstone_deep" +} +nether.lightstaff_range = 100 +nether.lightstaff_velocity = 60 +nether.lightstaff_gravity = 0 -- using 0 instead of 10 because projectile arcs look less magical - magic isn't affected by gravity ;) (but set this to 10 if you're making a crossbow etc.) +nether.lightstaff_uses = 60 -- number of times the Eternal Lightstaff can be used before wearing out +nether.lightstaff_duration = 40 -- lifespan of glowstone created by the termporay Lightstaff + +-- 'serverLag' is a rough amount to reduce the projected impact-time the server must wait before initiating the +-- impact events (i.e. node changing to glowstone with explosion particle effect). +-- In tests using https://github.com/jagt/clumsy to simulate network lag I've found this value to not noticeably +-- matter. A large network lag is noticeable in the time between clicking fire and when the shooting-particleEffect +-- begins, as well as the time between when the impact sound/particleEffect start and when the netherrack turns +-- into glowstone. The synchronization that 'serverLag' adjusts seems to already tolerate network lag well enough (at +-- least when lag is consistent, as I have not simulated random lag) +local serverLag = 0.05 -- in seconds. Larger values makes impact events more premature/early. + +-- returns a pointed_thing, or nil if no solid node intersected the ray +local function raycastForSolidNode(rayStartPos, rayEndPos) + + local raycast = minetest.raycast( + rayStartPos, + rayEndPos, + false, -- objects - if false, only nodes will be returned. Default is `true` + true -- liquids - if false, liquid nodes won't be returned. Default is `false` + ) + local next_pointed = raycast:next() + while next_pointed do + local under_node = minetest.get_node(next_pointed.under) + local under_def = minetest.registered_nodes[under_node.name] + + if (under_def and not under_def.buildable_to) or not under_def then + return next_pointed + end + + next_pointed = raycast:next(next_pointed) + end + return nil +end + +-- Turns a node into a light source +-- `lightDuration` 0 is considered permanent, lightDuration is in seconds +-- returns true if a node is transmogrified into a glowstone +local function light_node(pos, playerName, lightDuration) + + local result = false + if minetest.is_protected(pos, playerName) then + minetest.record_protection_violation(pos, playerName) + return false + end + + local oldNode = minetest.get_node(pos) + local litNodeName = nether.lightstaff_recipes[oldNode.name] + + if litNodeName ~= nil then + result = nether.magicallyTransmogrify_node( + pos, + playerName, + {name=litNodeName}, + {name = "nether_rack_destroy", gain = 0.8}, + lightDuration == 0 -- isPermanent + ) + + if lightDuration > 0 then + minetest.after(lightDuration, + function() + -- Restore the node to its original type. + -- + -- If the server crashes or shuts down before this is invoked, the node + -- will remain in its transmogrified state. These could be cleaned up + -- with an LBM, but I don't think that's necessary: if this functionality + -- is only being used for the Nether Lightstaff then I don't think it + -- matters if there's occasionally an extra glowstone left in the + -- netherrack. + nether.magicallyTransmogrify_node(pos, playerName) + end + ) + end + end + return result +end + +-- a lightDuration of 0 is considered permanent, lightDuration is in seconds +-- returns true if a node is transmogrified into a glowstone +local function lightstaff_on_use(user, boltColorString, lightDuration) + + if not user then return false end + local playerName = user:get_player_name() + local playerlookDir = user:get_look_dir() + local playerPos = user:get_pos() + local playerEyePos = vector.add(playerPos, {x = 0, y = 1.5, z = 0}) -- not always the cameraPos, e.g. 3rd person mode. + local target = vector.add(playerEyePos, vector.multiply(playerlookDir, nether.lightstaff_range)) + + local targetHitPos = nil + local targetNodePos = nil + local target_pointed = raycastForSolidNode(playerEyePos, target) + if target_pointed then + targetNodePos = target_pointed.under + targetHitPos = vector.divide(vector.add(target_pointed.under, target_pointed.above), 2) + end + + local wieldOffset = {x= 0.5, y = -0.2, z= 0.8} + local lookRotation = ({x = -user:get_look_vertical(), y = user:get_look_horizontal(), z = 0}) + local wieldPos = vector.add(playerEyePos, vector.rotate(wieldOffset, lookRotation)) + local aimPos = targetHitPos or target + local distance = math.abs(vector.length(vector.subtract(aimPos, wieldPos))) + local flightTime = distance / nether.lightstaff_velocity + local dropDistance = nether.lightstaff_gravity * 0.5 * (flightTime * flightTime) + aimPos.y = aimPos.y + dropDistance + local boltDir = vector.normalize(vector.subtract(aimPos, wieldPos)) + + minetest.sound_play("nether_lightstaff", {to_player = playerName, gain = 0.8}, true) + + -- animate a "magic bolt" from wieldPos to aimPos + local particleSpawnDef = { + amount = 20, + time = 0.4, + minpos = vector.add(wieldPos, -0.13), + maxpos = vector.add(wieldPos, 0.13), + minvel = vector.multiply(boltDir, nether.lightstaff_velocity - 0.3), + maxvel = vector.multiply(boltDir, nether.lightstaff_velocity + 0.3), + minacc = {x=0, y=-nether.lightstaff_gravity, z=0}, + maxacc = {x=0, y=-nether.lightstaff_gravity, z=0}, + minexptime = 1, + maxexptime = 2, + minsize = 4, + maxsize = 5, + collisiondetection = true, + collision_removal = true, + texture = "nether_particle_anim3.png", + animation = { type = "vertical_frames", aspect_w = 7, aspect_h = 7, length = 0.8 }, + glow = 15 + } + minetest.add_particlespawner(particleSpawnDef) + particleSpawnDef.texture = "nether_particle_anim3.png^[colorize:" .. boltColorString .. ":alpha" + particleSpawnDef.amount = 12 + particleSpawnDef.time = 0.2 + particleSpawnDef.minsize = 6 + particleSpawnDef.maxsize = 7 + particleSpawnDef.minpos = vector.add(wieldPos, -0.35) + particleSpawnDef.maxpos = vector.add(wieldPos, 0.35) + minetest.add_particlespawner(particleSpawnDef) + + local result = false + if targetNodePos then + -- delay the impact until roughly when the particle effects will have reached the target + minetest.after( + math.max(0, (distance / nether.lightstaff_velocity) - serverLag), + function() + light_node(targetNodePos, playerName, lightDuration) + end + ) + + if lightDuration ~= 0 then + -- we don't need to care whether the transmogrify will be successful + result = true + else + -- check whether the transmogrify will be successful + local targetNode = minetest.get_node(targetNodePos) + result = nether.lightstaff_recipes[targetNode.name] ~= nil + end + end + return result +end + +-- Inspired by FaceDeer's torch crossbow and Xanthin's Staff of Light +minetest.register_tool("nether:lightstaff", { + description = S("Nether staff of Light\nTemporarily transforms the netherrack into glowstone"), + inventory_image = "nether_lightstaff.png", + wield_image = "nether_lightstaff.png", + light_source = 11, -- used by wielded_light mod etc. + stack_max = 1, + on_use = function(itemstack, user, pointed_thing) + lightstaff_on_use(user, "#F70", nether.lightstaff_duration) + end +}) + +minetest.register_tool("nether:lightstaff_eternal", { + description = S("Nether staff of Eternal Light\nCreates glowstone from netherrack"), + inventory_image = "nether_lightstaff.png^[colorize:#55F:90", + wield_image = "nether_lightstaff.png^[colorize:#55F:90", + light_source = 11, -- used by wielded_light mod etc. + sound = {breaks = "default_tool_breaks"}, + stack_max = 1, + on_use = function(itemstack, user, pointed_thing) + if lightstaff_on_use(user, "#23F", 0) then -- was "#8088FF" or "#13F" + -- The staff of Eternal Light wears out, to limit how much + -- a player can alter the nether with it. + itemstack:add_wear(65535 / (nether.lightstaff_uses - 1)) + end + return itemstack + end +})