code:
-----------------
-- Fruit Tools --
-----------------

-- Settings:

-- Replace weed pick with pickle pick when set to true - capabilities of both picks have the same capabilities
local say_no_to_cannabis = true

-- Every time the tool is used, it will randomly also drop the fruit the tool is made out of, configure quantities of fruit and chance here:
-- Enable or disable extra drops (default is true)
local extra_drops = true
-- Every one in x drops the fruit the tool is made out of will also drop, lower is more likely (default is 5)
local extra_drop_chance = 20
-- Maximum amount of extra drops (default is 5)
local extra_drop_max = 3
-- Minimum amount of extra drops (default is 1)
local extra_drop_min = 1

-- When a throwing food lands, there is a chance they plant the plant they are made of, adjust this functionality here:
-- Enable or disable planting (default is true)
local plant = true
-- Set to true to plant just a whole fully-grown plant, set to false to enable hoeing of dirt and planting a new plant (default is true)
local plant_whole = false
-- Adjust the plant chance (1 in every x node hits will plant a plant)
local plant_chance = 20

-- Code:

if farming.mod ~= "redo" then
    minetest.log("error", "[fruit_tools]: This mod requires TenPlus1's farming redo, not the default minetest_game farming mod.")
end

fruit_tools = {}
fruit_tools.version = "1.0"
fruit_tools.path = minetest.get_modpath("fruit_tools")

-- Sound effect function (unused as of now):
local function play_sound_effect(player, name)
    if player then
        local pos = player:getpos()
        if pos then
            minetest.sound_play({
                pos = pos,
                name = name,
                max_hear_distance = 10,
                gain = 0.5,
            })
        end
    end
end

-- custom particle effects (from TenPlus1's mobs_redo)
local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)

    radius = radius or 2
    min_size = min_size or 0.5
    max_size = max_size or 1
    gravity = gravity or -10
    glow = glow or 0

    minetest.add_particlespawner({
        amount = amount,
        time = 0.25,
        minpos = pos,
        maxpos = pos,
        minvel = {x = -radius, y = -radius, z = -radius},
        maxvel = {x = radius, y = radius, z = radius},
        minacc = {x = 0, y = gravity, z = 0},
        maxacc = {x = 0, y = gravity, z = 0},
        minexptime = 0.1,
        maxexptime = 1,
        minsize = min_size,
        maxsize = max_size,
        texture = texture,
        glow = glow,
    })
end

-- Fruit tools occasionally drop the fruit they are made of
local tool_item_pairs = {
    {tool = "fruit_tools:hoe_bean", fruit = "farming:beans"},
    {tool = "fruit_tools:sword_chili", fruit = "farming:chili_pepper"},
    {tool = "fruit_tools:sword_carrot", fruit = "farming:carrot"},
    {tool = "fruit_tools:sword_carrot_gold", fruit = "farming:carrot_gold"},
    {tool = "fruit_tools:pick_corn", fruit = "farming:corn"},
    {tool = "fruit_tools:axe_raspberry", fruit = "farming:raspberries"},
    {tool = "fruit_tools:axe_melon", fruit = "farming:melon_slice"},
    {tool = "fruit_tools:shovel_grape", fruit = "farming:grapes"},
    {tool = "fruit_tools:shovel_apple", fruit = "default:apple"},
}

-- checks if user is holding a fruit tool, if they are returns the fruit pair name, else returns false
local function is_holding_fruit_tool(player)
    if player then
        local tool = player:get_wielded_item():get_name()
        local fruit_pair_name = false

    for i, p in ipairs(tool_item_pairs) do
        if tool == p.tool then
            fruit_pair_name = p.fruit
        end
    end

    return fruit_pair_name
    end
end

local function get_random_drop_quantity()
    local drop_quantity = false
    if extra_drops and math.random(1, extra_drop_chance) == 1 then
        drop_quantity = math.random(extra_drop_min, extra_drop_max)
    end
    return drop_quantity
end

local old_handle_node_drops = minetest.handle_node_drops

function minetest.handle_node_drops(pos, drops, digger)
    -- Check if fruit tool, if fruit tool save fruit pair for later, if not fruit tool proceed as normal
    local fruit_drop_name = is_holding_fruit_tool(digger)
    if not fruit_drop_name then
        return old_handle_node_drops(pos, drops, digger)
    end

    local new_drops = drops
    local drop_quantity = get_random_drop_quantity()

    if drop_quantity then
        table.insert(new_drops,
                ItemStack({
                    name = fruit_drop_name,
                    count = drop_quantity,
                })
            )
    end

    return old_handle_node_drops(pos, new_drops, digger)
end

-- Do the same for hoes when they are used
local old_hoe_on_use = farming.hoe_on_use

function farming.hoe_on_use(itemstack, user, pointed_thing, uses)
    local fruit_drop_name = is_holding_fruit_tool(user)
    if not fruit_drop_name then
        return old_hoe_on_use(itemstack, user, pointed_thing, uses)
    end

    local drops = {}
    local drop_quantity = get_random_drop_quantity()

    if drop_quantity then
        table.insert(drops,
                ItemStack({
                    name = fruit_drop_name,
                    count = drop_quantity,
                })
            )
        if pointed_thing.type == "node" then
            old_handle_node_drops(pointed_thing.above, drops, user)
        end
    end

    return old_hoe_on_use(itemstack, user, pointed_thing, uses)
end

-- Register craftitems:
minetest.register_craftitem("fruit_tools:resin", {
    description = "Tool Resin (Use to craft fruit tools)",
    inventory_image = "resin_bottle.png",
})

minetest.register_craft({
    output = "fruit_tools:resin",
    recipe = {
        {"default:diamond", "default:gold_ingot", "default:diamond"},
        {"default:mese_crystal", "vessels:glass_bottle", "default:mese_crystal"},
        {"default:diamond", "mesecraft_bucket:bucket_water", "default:diamond"}
    },
})

-- Register sheilds:
if minetest.get_modpath("3d_armor") then
    armor:register_armor("fruit_tools:shield_orange", {
        description = "Orange Shield",
        inventory_image = "fruit_tools_inv_shield_orange.png",
        groups = {armor_shield=1, armor_heal=16, armor_use=100},
        armor_groups = {fleshy=16},
        damage_groups = {cracky=2, snappy=1, level=3},
        reciprocate_damage = true,
        on_punched = function(player, hitter, time_from_last_punch, tool_capabilities)
            if type(hitter) == "userdata" then
                if extra_drops then
                    local drops = {}
                    local drop_quantity = get_random_drop_quantity()

                    if drop_quantity then
                        table.insert(drops,
                                ItemStack({
                                    name = "ethereal:orange",
                                    count = drop_quantity,
                                })
                            )
                        old_handle_node_drops(player:get_pos(), drops, hitter)
                    end
                end
            end
        end,
    })

    minetest.register_craft({
        output = "fruit_tools:shield_orange",
        recipe = {
            {"ethereal:orange", "fruit_tools:resin", "ethereal:orange"},
            {"fruit_tools:resin", "ethereal:orange", "fruit_tools:resin"},
            {"", "fruit_tools:resin", ""}
        },
    })

    armor:register_armor("fruit_tools:shield_tomato", {
        description = "Tomato Shield",
        inventory_image = "fruit_tools_inv_shield_tomato.png",
        groups = {armor_shield=1, armor_heal=14, armor_use=150},
        armor_groups = {fleshy=15},
        damage_groups = {cracky=2, snappy=1, level=3},
        reciprocate_damage = true,
        on_punched = function(player, hitter, time_from_last_punch, tool_capabilities)
            if type(hitter) == "userdata" then
                if extra_drops then
                    local drops = {}
                    local drop_quantity = get_random_drop_quantity()

                    if drop_quantity then
                        table.insert(drops,
                                ItemStack({
                                    name = "farming:tomato",
                                    count = drop_quantity,
                                })
                            )
                        old_handle_node_drops(player:get_pos(), drops, hitter)
                    end
                end
            end
        end,
    })

    minetest.register_craft({
        output = "fruit_tools:shield_tomato",
        recipe = {
            {"farming:tomato", "fruit_tools:resin", "farming:tomato"},
            {"fruit_tools:resin", "farming:tomato", "fruit_tools:resin"},
            {"", "fruit_tools:resin", ""}
        },
    })
end

-- Register hoe:
farming.register_hoe(":fruit_tools:hoe_bean", {
    description = "Bean Hoe",
    inventory_image = "hoe_bean.png",
    max_uses = 1000,
    recipe = {
        {"farming:beans", "farming:beans", ""},
        {"fruit_tools:resin", "group:stick", "fruit_tools:resin"},
        {"fruit_tools:resin", "group:stick", "fruit_tools:resin"}
    }
})

-- Register swords:
minetest.register_tool("fruit_tools:sword_chili", {
    description = "Chili Sword",
    inventory_image = "sword_chili.png",
    tool_capabilities = {
        full_punch_interval = 0.5,
        max_drop_level=1,
        groupcaps={
            snappy={times={[1]=1.85, [2]=0.80, [3]=0.25}, uses=60, maxlevel=3},
        },
        damage_groups = {fleshy=8},
        },
        sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:sword_chili",
    recipe = {
        {"", "farming:chili_pepper", ""},
        {"fruit_tools:resin", "farming:chili_pepper", "fruit_tools:resin"},
        {"fruit_tools:resin", "farming:chili_pepper", "fruit_tools:resin"}
    },
})

minetest.register_tool("fruit_tools:sword_carrot", {
    description = "Carrot Sword",
    inventory_image = "sword_carrot.png",
    tool_capabilities = {
        full_punch_interval = 0.5,
        max_drop_level=1,
        groupcaps={
            snappy={times={[1]=1.70, [2]=0.70, [3]=0.20}, uses=80, maxlevel=3},
        },
        damage_groups = {fleshy=9},
        },
        sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:sword_carrot",
    recipe = {
        {"", "farming:carrot", ""},
        {"fruit_tools:resin", "farming:carrot", "fruit_tools:resin"},
        {"fruit_tools:resin", "farming:carrot", "fruit_tools:resin"}
    },
})

minetest.register_tool("fruit_tools:sword_carrot_gold", {
    description = "Golden Carrot Sword",
    inventory_image = "sword_carrot_gold.png",
    tool_capabilities = {
        full_punch_interval = 0.4,
        max_drop_level=1,
        groupcaps={
            snappy={times={[1]=1.65, [2]=0.60, [3]=0.15}, uses=100, maxlevel=3},
        },
        damage_groups = {fleshy=10},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:sword_carrot_gold",
    recipe = {
        {"", "farming:carrot_gold", ""},
        {"fruit_tools:resin", "farming:carrot_gold", "fruit_tools:resin"},
        {"fruit_tools:resin", "farming:carrot_gold", "fruit_tools:resin"}
    },
})

-- Register Picks:
local pick_name = "fruit_tools:pick_weed"
local pick_image = "pick_weed.png"
local pick_description = "Weed Pickaxe"
local pick_priv = "dealer"

if say_no_to_cannabis == false then
    minetest.register_alias("fruit_tools:pick_pickle", "fruit_tools:pick_weed")

    minetest.register_privilege("dealer", {
        description = "Players can use the Weed Pick with this priv.",
        give_to_singleplayer = false,
    })
else
    minetest.register_alias("fruit_tools:pick_weed", "fruit_tools:pick_pickle")

    minetest.register_privilege("picklepick", {
        description = "Players can use the Pickle Pick with this priv.",
        give_to_singleplayer = false,
    })

    pick_name = "fruit_tools:pick_pickle"
    pick_image = "pick_pickle.png"
    pick_description = "Pickle Pickaxe"
    pick_priv = "picklepick"
end

minetest.register_tool(pick_name, {
    description = pick_description,
    inventory_image = pick_image,
    range = 20,
    tool_capabilities = {
        full_punch_interval = 0.1,
        max_drop_level = 3,
        groupcaps = {
            unbreakable =   {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            dig_immediate = {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            fleshy =    {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            choppy =    {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            bendy =     {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            cracky =    {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            crumbly =   {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3},
            snappy =    {times={[1] = 0, [2] = 0, [3] = 0}, uses = 0, maxlevel = 3}
        },
        damage_groups = {fleshy = 100}
    },
    after_use = function(itemstack, user, pointed_thing)
        if not minetest.check_player_privs(user:get_player_name(), {[pick_priv] = true}) then
            minetest.log("action", "[fruit_tools] "..user:get_player_name().." just tried to use a "..pick_description.."!")
            minetest.chat_send_player(user:get_player_name(), "You are not authorised to use the mighty "..pick_description.."!")
            itemstack:take_item()
            return itemstack
        end
    end,
})

-- Make cloud removable with admin picks
minetest.register_node(":default:cloud", {
    description = "Cloud",
    tiles = {"default_cloud.png"},
    drop = "",
    groups = {unbreakable = 1},
    is_ground_content = false,
    sounds = default.node_sound_defaults(),
})

minetest.register_tool("fruit_tools:pick_corn", {
    description = "Corn Pickaxe",
    inventory_image = "pick_corn.png",
    tool_capabilities = {
        full_punch_interval = 0.9,
        max_drop_level=3,
        groupcaps={
            cracky = {times={[1]=2.0, [2]=1.0, [3]=0.50}, uses=30, maxlevel=3},
        },
        damage_groups = {fleshy=5},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:pick_corn",
    recipe = {
        {"farming:corn_cob", "farming:corn_cob", "farming:corn_cob"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"}
    },
})

-- Register axes:
minetest.register_tool("fruit_tools:axe_raspberry", {
    description = "Raspberry Axe",
    inventory_image = "axe_raspberry.png",
    tool_capabilities = {
        full_punch_interval = 0.9,
        max_drop_level=1,
        groupcaps={
            choppy={times={[1]=1.90, [2]=0.80, [3]=0.40}, uses=50, maxlevel=2},
        },
        damage_groups = {fleshy=7},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:axe_raspberry",
    recipe = {
        {"fruit_tools:resin", "farming:raspberries", "farming:raspberries"},
        {"fruit_tools:resin", "default:stick", "farming:raspberries"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"}
    },
})

minetest.register_tool("fruit_tools:axe_melon", {
    description = "Melon Axe",
    inventory_image = "axe_melon.png",
    tool_capabilities = {
        full_punch_interval = 0.9,
        max_drop_level=1,
        groupcaps={
            choppy={times={[1]=1.80, [2]=0.70, [3]=0.30}, uses=70, maxlevel=2},
        },
        damage_groups = {fleshy=7},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:axe_melon",
    recipe = {
        {"fruit_tools:resin", "farming:melon_slice", "farming:melon_slice"},
        {"fruit_tools:resin", "default:stick", "farming:melon_slice"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"}
    },
})

-- Register shovels:
minetest.register_tool("fruit_tools:shovel_grape", {
    description = "Grape Shovel",
    inventory_image = "shovel_grape.png",
    tool_capabilities = {
        full_punch_interval = 1.0,
        max_drop_level=1,
        groupcaps={
            crumbly = {times={[1]=1.00, [2]=0.40, [3]=0.20}, uses=50, maxlevel=3},
        },
        damage_groups = {fleshy=4},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:shovel_grape",
    recipe = {
        {"", "farming:grapes", ""},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"}
    },
})

minetest.register_tool("fruit_tools:shovel_apple", {
    description = "Apple Shovel",
    inventory_image = "shovel_apple.png",
    tool_capabilities = {
        full_punch_interval = 1.0,
        max_drop_level=1,
        groupcaps={
            crumbly = {times={[1]=0.9, [2]=0.30, [3]=0.10}, uses=80, maxlevel=3},
        },
        damage_groups = {fleshy=4},
    },
    sound = {breaks = "default_tool_breaks"},
})

minetest.register_craft({
    output = "fruit_tools:shovel_apple",
    recipe = {
        {"", "default:apple", ""},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"},
        {"fruit_tools:resin", "default:stick", "fruit_tools:resin"}
    },
})

if minetest.get_modpath("mobs") then
    function fruit_tools:register_throwing_food(modname, foodname, plantname, final_stage, toplant, plantwhole)
        local food
        local f = string.find(foodname, ":")
        if f then
            food = foodname:sub(f + 1)
        else
            food = foodname
            foodname = foodname
        end

        if not plantname then
            toplant = false
        else
            local p = string.find(plantname, ":")
            if not p then
                plantname = "farming:"..plantname
            end
        end

        if modname ~= "fruit_tools" and modname:sub(1,1) ~= ":" then
            modname = ":"..modname
        end

        local description
        if minetest.registered_craftitems[foodname] then
            description = minetest.registered_craftitems[foodname].description
        else
            -- If failed to get description, do our best to capitalise the food name so it looks right and log a warning
            description = food:sub(1,1):upper()..food:sub(2)
            minetest.log("warning", "[fruit_tools]: failed to get description for food: "..foodname)
        end

        mobs:register_arrow(modname..":"..food.."_entity", {
            visual = "sprite",
            visual_size = {x=.5, y=.5},
            textures = {food..".png"},
            velocity = 6,

            hit_player = function(self, player)
                if player:get_player_name() == self.playername then
                    return
                end

                local pos = player:get_pos()
                pos.y = pos.y + ((-player:get_properties().collisionbox[2] + player:get_properties().collisionbox[5]) * .5) + 0.5
                effect(pos, 5, food.."_splat.png", 1, 3, 2, -10, 0)

                player:punch(minetest.get_player_by_name(self.playername) or self.object, 1.0, {
                    full_punch_interval = 1.0,
                    damage_groups = {fleshy = 2},
                }, nil)
            end,

            hit_mob = function(self, player)
                local pos = player:get_pos()
                pos.y = pos.y + ((-player:get_properties().collisionbox[2] + player:get_properties().collisionbox[5]) * .5) + 0.5
                effect(pos, 5, food.."_splat.png", 1, 3, 2, -10, 0)

                player:punch(minetest.get_player_by_name(self.playername) or self.object, 1.0, {
                    full_punch_interval = 1.0,
                    damage_groups = {fleshy = 2},
                }, nil)
            end,

            hit_node = function(self, pos, node)
                local under = {x=pos.x,y=pos.y-1,z=pos.z}

                local above = {x=pos.x,y=pos.y+1,z=pos.z}

                local landed_thing = {type="node", under=pos, above=above}

                effect(above, 5, food.."_splat.png", 1, 3, 2, -10, 0)

                if not plant
                or not toplant
                or math.random(1, plant_chance) ~= 1
                or minetest.get_node_or_nil(above).name ~= "air"
                or minetest.get_item_group(node, "soil") ~= 1
                then
                    return
                end

                if plantwhole then
                    if minetest.is_protected(above, self.playername)then
                        return
                    end

                    local final_plant
                    if not final_stage then
                        final_plant = plantname
                    else
                        final_plant = plantname.."_"..tostring(final_stage)
                    end

                    minetest.set_node(above, {name = final_plant})
                else
                    if minetest.is_protected(above, self.playername) or minetest.is_protected(pos, self.playername) then
                        return
                    end

                    minetest.set_node(pos, {name = "farming:soil_wet"})

                    farming.place_seed(nil, minetest.get_player_by_name(self.playername), landed_thing, plantname.."_1")
                end
            end,
        })

        local food_GRAVITY = 9
        local food_VELOCITY = 19

        local shoot_food = function (item, player, pointed_thing)
            local playerpos = player:get_pos()

            play_sound_effect(player, "default_place_node_hard")

            local obj = minetest.add_entity({
                x = playerpos.x,
                y = playerpos.y +1.5,
                z = playerpos.z
            }, modname..":"..food.."_entity")

            local ent = obj:get_luaentity()
            local dir = player:get_look_dir()

            ent.velocity = food_VELOCITY -- needed for api internal timing
            ent.switch = 1 -- needed so that food doesn't despawn straight away

            obj:setvelocity({
                x = dir.x * food_VELOCITY,
                y = dir.y * food_VELOCITY,
                z = dir.z * food_VELOCITY
            })

            obj:setacceleration({
                x = dir.x * -3,
                y = -food_GRAVITY,
                z = dir.z * -3
            })

            local ent2 = obj:get_luaentity()
            ent2.playername = player:get_player_name()

            item:take_item()

            return item
        end

        minetest.register_craftitem(":"..modname..":throwing_"..food, {
            description = "Throwing "..description,
            inventory_image = food..".png",
            on_use = shoot_food,
        })

        minetest.register_craft({
            output = modname..":throwing_"..food.." 9",
            recipe = {
                {foodname, foodname, foodname},
                {foodname, "default:stone", foodname},
                {foodname, foodname, foodname}
            },
        })

        minetest.register_craft({
            output = foodname.." 9",
            recipe = {
                {modname..":throwing_"..food, modname..":throwing_"..food, modname..":throwing_"..food},
                {modname..":throwing_"..food, modname..":throwing_"..food, modname..":throwing_"..food},
                {modname..":throwing_"..food, modname..":throwing_"..food, modname..":throwing_"..food},
            },
        })
    end

    -- Register throwing foods:
    fruit_tools:register_throwing_food("fruit_tools", "farming:donut_apple", nil, nil, false, nil)

    fruit_tools:register_throwing_food("fruit_tools", "farming:tomato", "tomato", 8, true, plant_whole)
    fruit_tools:register_throwing_food("fruit_tools", "default:blueberries", "blueberry", 4, true, plant_whole)
    fruit_tools:register_throwing_food("fruit_tools", "farming:grapes", "grapebush", nil, true, true)
    fruit_tools:register_throwing_food("fruit_tools", "farming:raspberries", "raspberry", 4, true, plant_whole)
    fruit_tools:register_throwing_food("fruit_tools", "farming:melon_slice", "melon", 8, true, plant_whole)

	if minetest.get_modpath("etheral") then
		fruit_tools:register_throwing_food("fruit_tools", "ethereal:orange", nil, nil, false, nil)
		fruit_tools:register_throwing_food("fruit_tools", "ethereal:strawberry", nil, nil, false, nil)
	end
end

-- Add toolranks support if found
if minetest.get_modpath("toolranks") then
    for i, p in ipairs(tool_item_pairs) do
        local original_description = minetest.registered_tools[p.tool].description
        minetest.override_item(p.tool, {
            original_description = original_description,
            description = toolranks.create_description(original_description, 0, 1),
            after_use = toolranks.new_afteruse
        })
    end
end


code:
name = fruit_tools
title = Fruit Tools
description = Adds a variety of fruit tools and other fruit-related items to Minetest.
author = ChimneySwift
depends = farming, default, mesecraft_bucket, vessels
optional_depends = mobs, 3d_armor

repo_name: korkut/MeseCraft

license: mit


code:
# gadgets_modpack
gadgets_modpack aims at providing a full progression of magic and technology based tools and consumables that add various player status effects and do other useful functions. Modpack also provides an API to easily make your own "gadgets".
Content packs are split into separate mods, so if you don't like something (for example you only want to leave magic), you can simply disable them.

## Requirements:

**Minetest 5.0.0+**
**Minetest_game 5.0.0+**

Full list of requirements:

- default, vessels, farming, bucket, flowers (parts of minetest_game, installed by default)
- [playereffects](https://repo.or.cz/minetest_playereffects.git) (Main framework for status effect handling)
- [player_monoids](https://github.com/minetest-mods/player_monoids) (Dependency of gadgets_default_effects, library for handling monoidal player values)
- [technic](https://github.com/minetest-mods/technic) (optional, but strongly recommended)
- [mana](https://repo.or.cz/minetest_mana.git) (optional, but strongly recommended)
- [magic_materials](https://github.com/ClockGen/magic_materials) (dependency for gadgets_consumables and gadgets_magic)
- [basic_materials](https://gitlab.com/VanessaE/basic_materials) (dependency of technic)

## Recommended mods

- [bweapons_modpack](https://github.com/ClockGen/bweapons_modpack) (Together with gadgets_modpack forms a single combat, status effect and magic system)
- [sprint_lite](https://github.com/ClockGen/sprint_lite) (If present, gadgets_consumables provide a stamina potion)
- [craftguide](https://github.com/minetest-mods/craftguide) (explore crafting recipes of the new items)

## List of mods in modpack
- ### gadgets_api

    API and main code for defining and handling status effect tools and consumables

    **Requires:** [playereffects](https://repo.or.cz/minetest_playereffects.git), [technic](https://github.com/minetest-mods/technic) (optional), [mana](https://repo.or.cz/minetest_mana.git) (optional)

- ### gadgets_default_effects

    Library providing default gadgets_modpack effects to other parts of gadgets_modpack

    Effects:

    - Increased Speed
    - Increased Jump Height
    - Lowered Gravity
    - Health Regeneration
    - Stamina Regeneration (if sprint_lite is present)
    - Mana Regeneration (if mana is present)
    - Water Breathing
    - Fire Shield (Damages mobs around you)

    Some effects have their second, stronger version (see according potions and crafting recipes)

    **Requires:** gadgets_api, [player_monoids](https://github.com/minetest-mods/player_monoids)

- ### gadgets_consumables

    Provides various magic-related consumables and potions

    Items:

    - Water Bottle
    - Potion Of Speed
    - Potion Of Jump
    - Potion Of Gravity
    - Potion Of Water Breathing
    - Potion Of Fire Shield
    - Potion Of Health Regeneration
    - Potion Of Stamina Regeneration (if sprint_lite is present)
    - Potion Of Mana Regeneration (if mana is present)
    - Dispel potion (removes all active status effects)
    - Teleport potion (teleports to a random location you can fit in, in specified range)

    **Requires:** default, vessels, farming, bucket, flowers (parts of minetest_game, installed by default), gadgets_api, gadgets_default_effects, [mana](https://repo.or.cz/minetest_mana.git) (optional, provides mana potion), [sprint_lite](https://github.com/ClockGen/sprint_lite) (optional, provides stamina potion)

- ### gadgets_magic

    Provides various magic-based spellbooks and staves

    Items:

    - Tome Of Gravity
    - Tome Of Speed
    - Tome Of Jump
    - Tome Of Blink (instantly teleports to the point you're looking, in limited range)
    - Tome Of Magic Bridge (creates a temporary magical bridge in front of you)
    - Staff Of Earth (Technic's drill counterpart, allows for fast mining)
    - Druid's Staff (Allows to perform a chain of transmutations on stone, from stone to dirt with grass)

    **Requires:** gadgets_api, gadgets_default_effects, [magic_materials](https://github.com/ClockGen/magic_materials)

## Tips

All interactions respect area protection

Magic tomes use mana and don't wear down, wear bar of staves represent their "charge", and they can be recharged by combining them with februm crystals on the crafting grid.

## Settingtypes
Modpack provides some settings, accessible via "Settings->All Settings->Mods->bweapons_api
You can also put these settings directly to your minetest.conf

```
enable_gadgets_effects = true, bool, should status effect visuals spawn

effect_interval = 0.5, float, interval at which status effect visuals spawn
```

## Making your own gadgets
To define gadgets in your own mod you need to call `gadgets.register_gadget(def)`
where `def` is a definition table. To see a complete list of possible definition options (with comments)
refer to **[this document](gadgets_api/documentation.txt)**.

To register new effects you need to call `gadgets.register_effect(def)`, where `def`` is an effect definition table. Refer to the document above for details.

## License
All code is GPLv3 [link to the license](https://www.gnu.org/licenses/gpl-3.0.en.html)
All resources not covered in the "credits" section are licensed under CC BY 4.0 [link to the license](https://creativecommons.org/licenses/by/4.0/legalcode)

## Credits
Sounds from following users of freesound.org were mixed, cut, edited and used to produce sounds in this modpack:

- prozaciswack - digging
- alcinov - staircase-metal-rumble
- breviceps - ethereal-teleport
- spookymodem - hitting_wall
- ktinquelal - rayo-laser
- lamamakesmusic - step-ice-break
- northern87 - barbecue-fire-northern87
- midimagician - fire-burning-loop.2
- smararaine - bonfire-being-lit
- steffcaffrey - chimes
- quaker540 - supernatural-explosion
- cyberkineticfilms - strange-teleport-sound
- slugzilla - phoenixscreech1
- qubodup - fire-spell
- zenithinfinitivestudios - fantasy-ui-button-1
- adrimb86 - fairy-sound

All sounds listed above were licensed as CC0. Produced sounds are licensed under CC BY 4.0 [link to the license](https://creativecommons.org/licenses/by/4.0/legalcode)


code: A modpack providing full progression of magic and technology-based items that apply status effects or do other useful functions


code:
gadgets = {}
local gadgets_effect_visuals = {}
local gadgets_current_player_effects = {}

--Getting settingtypes
local enable_gadgets_effects = minetest.settings:get("gadgets_api_enable_effects") ~= false
local effect_interval = minetest.settings:get("gadgets_api_effect_interval") or 0.5

if enable_gadgets_effects then

    minetest.register_on_joinplayer(function(player)
        local name = player:get_player_name()
        gadgets_current_player_effects[name] = {}
        end
    )

    minetest.register_on_leaveplayer(function(player)
        local name = player:get_player_name()
        gadgets_current_player_effects[name] = nil
        end
    )

    local effect_timer = 0

    minetest.register_globalstep(function(dtime)
        effect_timer = effect_timer + dtime
        if effect_timer < effect_interval then return end
        for playername,effectlist in pairs(gadgets_current_player_effects) do
            if #effectlist > 0 then
                for _,v in pairs(effectlist) do
                    if gadgets_effect_visuals[v] then
                        local player = minetest.get_player_by_name(playername)
                        local def = gadgets_effect_visuals[v]
                        minetest.add_particlespawner({
                            amount = def.particle_amount,
                            time = effect_interval,
                            minpos = {x=-def.particle_displacement, y=0.5, z=-def.particle_displacement},
                            maxpos = {x=def.particle_displacement, y=1.5, z=def.particle_displacement},
                            minvel = {x=-def.particle_velocity, y=-def.particle_velocity, z=-def.particle_velocity},
                            maxvel = {x=def.particle_velocity, y=def.particle_velocity, z=def.particle_velocity},
                            minacc = {x=0, y=def.particle_gravity, z=0},
                            maxacc = {x=0, y=def.particle_gravity, z=0},
                            minexptime = 1,
                            maxexptime = 4,
                            minsize = def.particle_min_size,
                            maxsize = def.particle_size,
                            collisiondetection = false,
                            collision_removal = false,
                            attached = player,
                            glow = def.particle_glow,
                            vertical = false,
                            texture = def.particle,
                        })
                    end
                end
            end
        end
        effect_timer = 0
    end)

end

local function gadgets_on_use(itemstack, user, pointed_thing, def)

    if not user then return end

    local playername = user:get_player_name()
    local inv = user:get_inventory()
    local meta = minetest.deserialize(itemstack:get_metadata())

    if def.mana_per_use and mana.get(playername) < def.mana_per_use or
    def.ammo_type and not inv:contains_item("main", {name=def.ammo_type, count=def.ammo_per_use})
    then
        if def.reload_sound then
            minetest.sound_play(def.reload_sound, {object=user, gain=def.reload_sound_gain, max_hear_distance=2*64})
        end
        return
    end

    if not def.consumable and def.has_durability then
        if
        def.requires_technic and not meta or
        def.requires_technic and meta.charge < def.technic_charge_per_use or
        not def.requires_technic and def.custom_charge and (65535 - itemstack:get_wear()) < (65535 / def.uses)
        then
            if def.reload_sound then
                minetest.sound_play(def.reload_sound, {object=user, gain=def.reload_sound_gain, max_hear_distance=2*64})
            end
            return
        end
    end

    local custom_return

    if def.custom_on_use then
        custom_return = def.custom_on_use(itemstack, user, pointed_thing)
    end

    if def.custom_wear and not custom_return then return end

    if not def.consumable and def.has_durability then
        if def.requires_technic then
            meta.charge = meta.charge - def.technic_charge_per_use
            technic.set_RE_wear(itemstack, meta.charge, def.technic_charge)
            itemstack:set_metadata(minetest.serialize(meta))
        else
            local wear = itemstack:get_wear()
            wear = wear + (65535/def.uses)
            if def.custom_charge and wear > 65535 then wear = 65535 end
            itemstack:set_wear(wear)
        end
    elseif def.consumable then
        itemstack:take_item()
    end

    if def.ammo_type then
        inv:remove_item("main", {name=def.ammo_type, count=def.ammo_per_use})
    end

    if def.mana_per_use then
        mana.subtract(playername, def.mana_per_use)
    end

    local playerpos = user:getpos()
    local dir = user:get_look_dir()

    if def.return_item then
        if inv:room_for_item("main", {name = def.return_item}) then
            inv:add_item("main", {name = def.return_item})
        else
            local obj = minetest.add_item({x = playerpos.x, y = playerpos.y + 1.5, z = playerpos.z}, def.return_item)
            obj:setvelocity({x = dir.x * 5, y = dir.y * 5, z = dir.z * 5})
        end
    end

    if def.effect then
        if type(def.effect) == "string" then
            playereffects.apply_effect_type(def.effect, def.duration, user)
        elseif type(def.effect) == "table" then
            for _,v in pairs(def.effect) do
                playereffects.apply_effect_type(v, def.duration, user)
            end
        end
    end

    if def.use_sound then minetest.sound_play(def.use_sound, {object=user, gain=def.use_sound_gain, max_hear_distance=2*64}) end

    if def.use_particle then
        minetest.add_particlespawner({
            amount = def.use_particle_amount,
            time = 0.05,
            minpos = {x=-def.use_particle_displacement, y=0.5, z=-def.use_particle_displacement},
            maxpos = {x=def.use_particle_displacement, y=1.5, z=def.use_particle_displacement},
            minvel = {x=-def.use_particle_velocity, y=-def.use_particle_velocity, z=-def.use_particle_velocity},
            maxvel = {x=def.use_particle_velocity, y=def.use_particle_velocity, z=def.use_particle_velocity},
            minacc = {x=0, y=def.use_particle_gravity, z=0},
            maxacc = {x=0, y=def.use_particle_gravity, z=0},
            minexptime = 1,
            maxexptime = 4,
            minsize = def.use_particle_min_size,
            maxsize = def.use_particle_size,
            collisiondetection = false,
            collision_removal = false,
            attached = user,
            glow = def.use_particle_glow,
            vertical = false,
            texture = def.use_particle,
        })
    end

    return itemstack
end

local function gadgets_repair(itemstack, player, old_craft_grid, craft_inv, name, repair_with, uses, repair_uses)
    if itemstack == nil or itemstack:get_name() ~= name then return end
    local w_count = 0
    local r_count = 0
    local modstack

    for _,v in pairs(old_craft_grid) do
        if v:get_name() == name then
            w_count = w_count + 1
            modstack = v
        elseif v:get_name() == repair_with then
            r_count = r_count + 1
        end
    end

    if w_count == 1 and r_count == 1 then
        local wear = modstack:get_wear()
        if wear > 0 then
            wear = wear - (65535/uses * repair_uses)
            if wear < 0 then wear = 0 end
            modstack:set_wear(wear)
            return modstack
        end
    end

end

function gadgets.register_effect(def)

    local particle_glow = 0
    if def.particle_glow then
        particle_glow = 14
    end

    if def.particle and enable_gadgets_effects then
        local particle_options = {
            particle = def.particle,
            particle_amount = def.particle_amount or 4,
            particle_glow = particle_glow,
            particle_velocity = def.particle_velocity or 0.5,
            particle_gravity = def.particle_gravity or 2,
            particle_size = def.particle_size or 4,
            particle_min_size = math.floor((def.particle_size or 4)/2),
            particle_displacement = def.particle_displacement or 0.5,
        }
        gadgets_effect_visuals[def.id] = particle_options
    end

    playereffects.register_effect_type(
        def.id,
        def.name,
        def.icon,
        def.groups,
        function(player)
            if def.apply then
                def.apply(player)
            end
            if enable_gadgets_effects and def.particle then
                local name = player:get_player_name()
                --Re-initializing player tables right in apply function in case the server was shut down
                if not gadgets_current_player_effects[name] then
                    gadgets_current_player_effects[name] = {}
                end
                for _,v in pairs(gadgets_current_player_effects[name]) do
                    if v == def.id then return end
                end
                table.insert(gadgets_current_player_effects[name], def.id)
            end
        end,
        function(effect, player)
            if def.cancel then
                def.cancel(effect, player)
            end
            if enable_gadgets_effects and def.particle then
                local name = player:get_player_name()
                if not gadgets_current_player_effects[name] then return end
                for k,v in pairs(gadgets_current_player_effects[name]) do
                    if v == def.id then
                        table.remove(gadgets_current_player_effects[name], k)
                    end
                end
            end
        end,
        def.hidden,
        def.cancel_on_death,
        def.repeat_interval)

end

function gadgets.register_gadget(def)

    --Initial checks
    if def.name == nil or def.description == nil or def.texture == nil then
        minetest.log("error", "[gadgets_api] Missing parameters in item definition!")
        return
    end

    if not minetest.get_modpath("technic") and def.requires_technic then
        minetest.log("error", "[gadgets_api] Technic modpack is required for technic-powered gadgets!")
        return
    end

    if not minetest.get_modpath("mana") and def.mana_per_use then
        minetest.log("error", "[gadgets_api] Mana is required for registering mana-consuming gadgets!")
        return
    end

    --Setting values to defaults if not defined
    local stack_max = def.stack_max or 4
    local uses = def.uses or 8
    local technic_charge = def.technic_charge or 100000
    local technic_charge_per_use = technic_charge/uses
    local mana_per_use = def.mana_per_use or 50
    local ammo_per_use = def.ammo_per_use or 1
    local repair_uses = def.repair_uses or 1
    local duration = def.duration or 120
    local use_sound_gain = def.use_sound_gain or 1
    local reload_sound_gain = def.reload_sound_gain or 1
    local craft_amount = def.craft_amount or 1
    local tool_groups = {}

    local use_particle_amount = def.use_particle_amount or 4
    local use_particle_glow = 0
    local use_particle_velocity = def.use_particle_velocity or 0
    local use_particle_gravity = def.use_particle_gravity or 0
    local use_particle_size = def.use_particle_size or 4
    local use_particle_min_size = math.floor(use_particle_size/2)
    local use_particle_displacement = def.use_particle_displacement or 0

    local recipe_type = "shaped"

    if def.shapeless_recipe then
        recipe_type = "shapeless"
    end

    if def.use_particle_glow then
        use_particle_glow = 14
    end

    if not def.consumable and uses > 1 then
        stack_max = 1
    end

    if def.effect == nil then
        duration = 1
    end

    local on_refill = nil
    local wear_represents = "mechanical_wear"

    if def.requires_technic then
        on_refill = technic.refill_RE_charge
        wear_represents = "technic_RE_charge"
    elseif def.custom_charge then
        wear_represents = "gadgets_custom_charge"
    end

    if def.consumable or not def.tool_repair then
        tool_groups["disable_repair"] = 1
    end

    --Creating a temporary setting definition to pass to a on_use function
    local tdef = {
        mana_per_use = def.mana_per_use,
        ammo_type = def.ammo_type,
        ammo_per_use = ammo_per_use,
        reload_sound = def.reload_sound,
        reload_sound_gain = reload_sound_gain,
        consumable = def.consumable,
        has_durability = def.has_durability,
        requires_technic = def.requires_technic,
        technic_charge = technic_charge,
        technic_charge_per_use = technic_charge_per_use,
        custom_charge = def.custom_charge,
        uses = uses,
        return_item = def.return_item,
        effect = def.effect,
        duration = duration,
        use_sound = def.use_sound,
        use_sound_gain = use_sound_gain,
        custom_wear = def.custom_wear,
        custom_on_use = def.custom_on_use,
        use_particle = def.use_particle,
        use_particle_amount = use_particle_amount,
        use_particle_glow = use_particle_glow,
        use_particle_velocity = use_particle_velocity,
        use_particle_gravity = use_particle_gravity,
        use_particle_size = def.use_particle_size,
        use_particle_min_size = use_particle_min_size,
        use_particle_displacement = use_particle_displacement,
    }

    --Register a craftitem if one-time use, register a tool otherwise
    if def.consumable then
            minetest.register_craftitem(def.name, {
            description = def.description,
            inventory_image = def.texture,
            wield_image = def.texture,
            stack_max = stack_max,
            liquids_pointable = false,
            groups = tool_groups,
            on_use = function(itemstack, user, pointed_thing)
                local stack = gadgets_on_use(itemstack, user, pointed_thing, tdef)
                return stack
            end
        })
    else
        minetest.register_tool(def.name, {
            description = def.description,
            inventory_image = def.texture,
            wield_image = def.texture,
            liquids_pointable = false,
            groups = tool_groups,
            on_refill = on_refill,
            wear_represents = wear_represents,
            on_use = function(itemstack, user, pointed_thing)
                local stack = gadgets_on_use(itemstack, user, pointed_thing, tdef)
                return stack
            end
        })
    end

    --Register a crafting recipe
    if def.recipe then
        for _,v in pairs(def.recipe) do
            minetest.register_craft({
            type = recipe_type,
            output = def.name .. " " .. craft_amount,
            recipe = v,
            replacements = def.replacements,
            })
        end
    end

    --Register tool as technic_powered
    if def.requires_technic and not def.consumable then
        technic.register_power_tool(def.name, technic_charge)
    end

    if def.repair_with and not def.consumable then
        minetest.register_craft({
            type = "shapeless",
            output = def.name,
            recipe = {
                def.name,
                def.repair_with,
            },
        })

        minetest.register_on_craft(
            function(itemstack, player, old_craft_grid, craft_inv)
                local stack = gadgets_repair(itemstack, player, old_craft_grid, craft_inv, def.name, def.repair_with, uses, repair_uses)
                return stack
            end)

        minetest.register_craft_predict(
            function(itemstack, player, old_craft_grid, craft_inv)
                local stack = gadgets_repair(itemstack, player, old_craft_grid, craft_inv, def.name, def.repair_with, uses, repair_uses)
                return stack
            end)

    end
end


code:
gadgets.register_gadget() is the main function for registering items. Many options can be skipped and the definition
table doesn't require all of them to be present. Look at other mods in gadgets_modpack for examples of gadget definition.

gadgets.register_gadget({
    --Mandatory
    name = "gadgets_potions:potion_speed_01",                   --Id of the item
    description = "Weak Potion Of Speed",                       --Name displayed in inventory
    texture = "bottle_cyan.png",                                --Texture of the item

    --Ammo or charge
    consumable = true,                                          --If item is consumed on use or has a wear bar
    stack_max = 4,                                              --Maximum stack size for consumables.
    has_durability = true,                                      --Master switch to toggle durability of the item
    uses = 8,                                                   --Amount of uses before the wear bar is depleted
    requires_technic = false,                                   --Instead of mechanical wear, the wear bar represents technic charge
                                                                --in EUs, and can be recharged at technic stations
    technic_charge = 100000,                                    --Total amount of technic EUs needed to charge the item
    custom_charge = false,                                      --Custom charge for item. Instead of breaking it becomes unusuable before
                                                                --Repaired.
    mana_per_use = 50,                                          --If defined, the item requires this amount of mana per use. Requires "mana" by Wuzzy.
    return_item = "vessels:bottle",                             --If defined, returns this item to the inventory if use is successfull.
    ammo_type = "default:stone",                                --If defined requires this item to be present in inventory and takes on each successfull use
    ammo_per_use = "1",                                         --How much ammo is needed per use

    --Repair
    repair_with = "default:stone",                              --Id defined, combining the gadget with this item on the crafting grid will restore
                                                                --Some of its wear
    repair_uses = 8,                                            --How much wear in uses restore per repair item

    --Effects
    conflicting_effects = {"jump_1", "jump_2"},                 --If defined, item can't be used if player has any of the effects from this list active
    effect = {"gadgets_default_effects_speed_1"},               --If defined, applies this effect to the player. See gadgets.register_effect() or
                                                                --playereffects.register_effect_type()
    duration = 120,                                             --Duration of effect

    --Visuals and sounds
    use_sound = "test_drink",                                   --Sound played on item use
    use_sound_gain = 1,                                         --Loudness of use sound
    use_particle = "particle_speed.png",                        --If defined, spawns this particle around the player on use (not effect particle)
    use_particle_amount = 4,                                    --Amount of particles to spawn
    use_particle_glow = true,                                   --Should particles glow
    use_particle_velocity = 0,                                  --Particle velocity in random direction
    use_particle_gravity = 1,                                   --Particle acceleration on Y axis
    use_particle_size = 6,                                      --Particle size. Minimum particle size is this value divided by 2
    use_particle_displacement = 0.5,                            --Displacement of particle origin around the player

    --Custom function
    custom_wear = true,                                         --If enabled, gadget only consumes ammo and performs other functions if
                                                                --custom_on_use() function returned true. Can be used for completely custom gadgets that
                                                                --Don't apply status effects
    custom_on_use = function(itemstack, user, pointed_thing),
        do_something()
    end,                                                        --Custom function called on item use

    --Recipe
    shapeless_recipe = true,                                    --Should the recipe be shapeless
    recipe = {
        {
            {"farming:cotton", "default:obsidian_shard", "default:apple"},
            {"", "magic_materials:magic_root", ""},
            {"", "gadgets_consumables:water_bottle", ""},
        }
    },                                                          --Crafting recipe. The table may contain multiple recipes, all of which will be registered
    replacements = {
        {
            "mesecraft_bucket:bucket_lava",
            "mesecraft_bucket:bucket_empty"
        }
    },                                                          --List of item replacements on crafting grid
    craft_amount = 1,                                           --How much items to craft
})

gadgets.register_effect() is a wrapper around playereffects.register_effect_type() with sole point of adding
visual effects (particles spawning around the player) and managing them (if it's canceled, player disconnected or dead, etc).
If you don't have any visuals on your effect you can safely use playereffects.register_effect_type() instead of this function.
Definition table fields correspond to playereffects.register_effect_type() in https://repo.or.cz/minetest_playereffects.git/blob/HEAD:/README.md

gadgets.register_effect({
    id = "custom_effect_01",                                    --Id of the effect. Must be unique
    name = "Display Name",                                      --Name displayed on the HUD
    icon = "effect.png",                                        --Effect icon, displayed on HUD
    groups = {"custom_effects"},                                --Group for this effect
    apply = function(player)
        do_something()
    end,                                                        --Function called on effect applied
    cancel = function(effect, player)
        do_something()
    end,                                                        --Function called when effect is canceled
    hidden = false,                                             --Effect doesn't appear on HUD
    cancel_on_death = true,                                     --Effect is canceled on player death
    repeat_interval = 1,                                        --If defined, repeats apply() each repeat_interval
    particle = "particle_speed.png",                            --Particle spawned around the player for the duration of the effect
    particle_amount = 4,                                        --Amount of particles spawned per effect_interval
    particle_glow = true,                                       --Should particles glow
    particle_velocity = 0,                                      --Random velocity of each particle
    particle_gravity = 1,                                       --Particle acceleration on Y axis
    particle_size = 6,                                          --Max particle size. Min size is this value/2
    particle_displacement = 0.5,                                --Particle displacement around the user
})


code:
local path = minetest.get_modpath("gadgets_api")
dofile(path .. "/api.lua")


code:
name = gadgets_api
description = An API for registering consumables and other items that add status effects to the player
depends = playereffects
optional_depends = technic, mana
author = ClockGen
title = Gadgets API


code:
enable_gadgets_effects (Enable gadgets_api particle visuals) bool true
effect_interval (Server step interval for spawning particle effects) float 0.5 0.0 60.0


code:
local path = minetest.get_modpath("gadgets_consumables")
dofile(path .. "/potions.lua")
dofile(path .. "/specials.lua")


code:
name = gadgets_consumables
description = Part of gadgets_modpack, providing various potions and consumables
depends = default, vessels, farming, mesecraft_bucket, flowers, gadgets_api, gadgets_default_effects
optional_depends = mana, sprint_lite
author = ClockGen
title = Gadgets Consumables


code:
local effect_potions = {
    {
        desc = "Speed",
        name = "speed",
        recipe = {
            {
                {"default:papyrus", "default:cactus", "farming:seed_wheat"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
    },
    {
        desc = "Jump",
        name = "jump",
        recipe = {
            {
                {"default:junglegrass", "farming:cotton", "farming:seed_cotton"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
    },
    {
        desc = "Gravity",
        name = "gravity",
        recipe = {
            {
                {"farming:cotton", "default:obsidian_shard", "default:apple"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
    },
}

local regen_potions = {
    {
        desc = "Water Breathing",
        name = "water_breath",
        recipe = {
            {
                {"default:papyrus", "flowers:waterlily", "group:leaves"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
    },
    {
        desc = "Fire Shield",
        name = "fire_shield",
        recipe = {
            {
                {"default:obsidian_shard", "mesecraft_bucket:bucket_lava", "default:mese_crystal_fragment"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
        replacements = {{"mesecraft_bucket:bucket_lava", "mesecraft_bucket:bucket_empty"}},
    },
    {
        desc = "Health Regeneration",
        name = "health_regen",
        recipe = {
            {
                {"farming:wheat", "default:gold_ingot", "default:apple"},
                {"", "magic_materials:magic_root", ""},
                {"", "gadgets_consumables:water_bottle", ""},
            }
        },
    },
}

if minetest.get_modpath("mana") then
    table.insert(regen_potions,
        {
            desc = "Mana Regeneration",
            name = "mana_regen",
            recipe = {
                {
                    {"group:sapling", "magic_materials:februm_crystal", "group:flower"},
                    {"", "magic_materials:magic_root", ""},
                    {"", "gadgets_consumables:water_bottle", ""},
                }
            },
        })
end

if minetest.get_modpath("sprint_lite") then
    table.insert(regen_potions,
        {
            desc = "Stamina Regeneration",
            name = "stamina_regen",
            recipe = {
                {
                    {"group:sapling", "", "farming:wheat"},
                    {"", "magic_materials:magic_root", ""},
                    {"", "gadgets_consumables:water_bottle", ""},
                }
            },
        })
end

for _,v in pairs(effect_potions) do
    gadgets.register_gadget({
        name = "gadgets_consumables:potion_" .. v.name .. "_01",
        description = "Weak Potion Of " .. v.desc,
        texture = "gadgets_consumables_potion_" .. v.name .. ".png",
        consumable = true,
        stack_max = 4,
        return_item = "vessels:glass_bottle",
        conflicting_effects = {"gadgets_default_effects_" .. v.name .. "_2"},
        effect = {"gadgets_default_effects_" .. v.name .. "_1"},
        duration = 60,
        use_sound = "gadgets_consumables_potion_drink",
        use_sound_gain = 1,
        recipe = v.recipe,
        replacements = v.replacements,
    })

    gadgets.register_gadget({
        name = "gadgets_consumables:potion_" .. v.name .. "_02",
        description = "Strong Potion Of " .. v.desc,
        texture = "gadgets_consumables_potion_" .. v.name .. ".png",
        consumable = true,
        stack_max = 4,
        return_item = "vessels:glass_bottle",
        conflicting_effects = {"gadgets_default_effects_" .. v.name .. "_1"},
        effect = {"gadgets_default_effects_" .. v.name .. "_2"},
        duration = 60,
        use_sound = "gadgets_consumables_potion_drink",
        use_sound_gain = 1,
        shapeless_recipe = true,
        recipe = {
            {"gadgets_consumables:potion_" .. v.name .. "_01", "magic_materials:magic_mushroom"},
        },
    })

    gadgets.register_gadget({
        name = "gadgets_consumables:potion_" .. v.name .. "_03",
        description = "Long-Lasting Potion Of " .. v.desc,
        texture = "gadgets_consumables_potion_" .. v.name .. ".png",
        consumable = true,
        stack_max = 4,
        return_item = "vessels:glass_bottle",
        conflicting_effects = {"gadgets_default_effects_" .. v.name .. "_2"},
        effect = {"gadgets_default_effects_" .. v.name .. "_1"},
        duration = 120,
        use_sound = "gadgets_consumables_potion_drink",
        use_sound_gain = 1,
        shapeless_recipe = true,
        recipe = {
            {"gadgets_consumables:potion_" .. v.name .. "_01", "magic_materials:magic_flower"},
        },
    })
end

for _,v in pairs(regen_potions) do
    gadgets.register_gadget({
        name = "gadgets_consumables:potion_" .. v.name .. "_01",
        description = "Potion Of " .. v.desc,
        texture = "gadgets_consumables_potion_" .. v.name .. ".png",
        consumable = true,
        stack_max = 4,
        return_item = "vessels:glass_bottle",
        effect = {"gadgets_default_effects_" .. v.name},
        duration = 30,
        use_sound = "gadgets_consumables_potion_drink",
        use_sound_gain = 1,
        recipe = v.recipe,
        replacements = v.replacements,
    })

    gadgets.register_gadget({
        name = "gadgets_consumables:potion_" .. v.name .. "_02",
        description = "Long-Lasting Potion Of " .. v.desc,
        texture = "gadgets_consumables_potion_" .. v.name .. ".png",
        consumable = true,
        stack_max = 4,
        return_item = "vessels:glass_bottle",
        effect = {"gadgets_default_effects_" .. v.name},
        duration = 60,
        use_sound = "gadgets_consumables_potion_drink",
        use_sound_gain = 1,
        shapeless_recipe = true,
        recipe = {
            {"gadgets_consumables:potion_" .. v.name .. "_01", "magic_materials:magic_flower"},
        },
    })
end

gadgets.register_gadget({
    name = "gadgets_consumables:potion_dispel",
    description = "Potion of Dispelling",
    texture = "gadgets_consumables_potion_dispel.png",
    consumable = true,
    stack_max = 4,
    return_item = "vessels:glass_bottle",
    use_sound = "gadgets_consumables_potion_drink",
    use_sound_gain = 1,
    custom_on_use = function(itemstack, user, pointed_thing)
        local name = user:get_player_name()
        local effects = playereffects.get_player_effects(name)
        for k,v in pairs(effects) do
            playereffects.cancel_effect(effects[k].effect_id)
        end
    end,
    recipe = {
        {
            {"", "farming:cotton", ""},
            {"", "magic_materials:magic_root", ""},
            {"", "gadgets_consumables:water_bottle", ""},
        }
    },
})

local function get_random_pos(pos, range)
    return {x = pos.x + math.random(-range,range), y = pos.y + math.random(-range, range), z = pos.z + math.random(-range, range)}
end

gadgets.register_gadget({
    name = "gadgets_consumables:potion_teleport",
    description = "Potion of Teleport",
    texture = "gadgets_consumables_potion_teleport.png",
    consumable = true,
    stack_max = 4,
    return_item = "vessels:glass_bottle",
    use_sound = "gadgets_consumables_potion_drink",
    use_sound_gain = 1,
    custom_on_use = function(itemstack, user, pointed_thing)

        if not user then return end

        local user_pos = user:get_pos()
        local random_range = 250
        local scan_range = 50
        local attempts = 5

        local c_air = minetest.get_content_id("air")
        local c_ignore = minetest.get_content_id("ignore")

        for i = 1, attempts do
            local target_pos = get_random_pos(user_pos, random_range)
            local minpos = {x = target_pos.x - scan_range, y = target_pos.y - scan_range, z = target_pos.z - scan_range}
            local maxpos = {x = target_pos.x + scan_range, y = target_pos.y + scan_range, z = target_pos.z + scan_range}

            local vm = minetest.get_voxel_manip()
            local emin, emax = vm:read_from_map(minpos, maxpos)
            local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
            local data = vm:get_data()

            local teleported = false

            for z = minpos.z, maxpos.z do
                for y = minpos.y, maxpos.y do
                    for x = minpos.x, maxpos.x do
                        local vi = area:index(x, y, z)
                        if not teleported and (data[vi] ~= c_air and data[vi] ~= c_ignore) and
                        data[vi + area.ystride] == c_air and data[vi + area.ystride * 2] == c_air then
                            local tpos = area:position(vi)
                            user:set_pos({x = tpos.x, y = tpos.y + 1, z = tpos.z})
                            teleported = true
                            for _,v in pairs({user_pos, tpos}) do
                                minetest.add_particlespawner({
                                    amount = 32,
                                    time = 0.05,
                                    minpos = {x=v.x-0.5, y=v.y, z=v.z-0.5},
                                    maxpos = {x=v.x+0.5, y=v.y+2, z=v.z+0.5},
                                    minvel = {x=-4, y=-4, z=-4},
                                    maxvel = {x=4, y=4, z=4},
                                    minacc = {x=0, y=2, z=0},
                                    maxacc = {x=0, y=2, z=0},
                                    minexptime = 1,
                                    maxexptime = 4,
                                    minsize = 2,
                                    maxsize = 4,
                                    glow = 14,
                                    texture = "gadgets_consumables_teleport_potion_particle.png",
                                })
                            minetest.sound_play("gadgets_consumables_teleport", {pos=v, gain=1, max_hear_distance=2*64})
                            end
                            break
                        end
                    end
                    if teleported then break end
                end
                if teleported then break end
            end
            if teleported then break end
        end
    end,
    recipe = {
        {
            {"default:obsidian_shard", "default:flint", "default:mese_crystal_fragment"},
            {"", "magic_materials:magic_root", ""},
            {"", "gadgets_consumables:water_bottle", ""},
        }
    },
})


code:
gadgets.register_gadget({
    name = "gadgets_consumables:water_bottle",
    description = "Water Bottle",
    texture = "gadgets_consumables_potion_water.png",
    consumable = true,
    stack_max = 4,
    return_item = "vessels:glass_bottle",
    use_sound = "gadgets_consumables_potion_drink",
    use_sound_gain = 1,
})

minetest.register_craft({
    output = "gadgets_consumables:water_bottle 4",
    recipe = {
		{"vessels:glass_bottle", "vessels:glass_bottle", ""},
		{"vessels:glass_bottle", "vessels:glass_bottle", ""},
		{"", "mesecraft_bucket:bucket_water", ""}
	},
	replacements = {{ "mesecraft_bucket:bucket_water", "mesecraft_bucket:bucket_empty"}},
})


code:
gadgets.register_effect({
    id = "gadgets_default_effects_speed_1",
    name = "Speed 1",
    icon = "gadgets_default_effects_speed_icon.png",
    groups = {"speed"},
    apply = function(player)
        player_monoids.speed:add_change(player, 1.75, "gadgets_default_effects_speed_1")
    end,
    cancel = function(effect, player)
        player_monoids.speed:del_change(player, "gadgets_default_effects_speed_1")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_speed_particle.png",
    particle_amount = 4,
    particle_glow = true,
    particle_velocity = 0,
    particle_gravity = 2,
    particle_size = 3,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_speed_2",
    name = "Speed 2",
    icon = "gadgets_default_effects_speed_icon.png",
    groups = {"speed"},
    apply = function(player)
        player_monoids.speed:add_change(player, 2.5, "gadgets_default_effects_speed_2")
    end,
    cancel = function(effect, player)
        player_monoids.speed:del_change(player, "gadgets_default_effects_speed_2")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_speed_particle.png",
    particle_amount = 4,
    particle_glow = true,
    particle_velocity = 0,
    particle_gravity = 2,
    particle_size = 3,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_jump_1",
    name = "Jump 1",
    icon = "gadgets_default_effects_jump_icon.png",
    groups = {"jump"},
    apply = function(player)
        player_monoids.jump:add_change(player, 1.5, "gadgets_default_effects_jump_1")
    end,
    cancel = function(effect, player)
        player_monoids.jump:del_change(player, "gadgets_default_effects_jump_1")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_jump_particle.png",
    particle_amount = 2,
    particle_glow = true,
    particle_velocity = 0.5,
    particle_gravity = 0.25,
    particle_size = 3,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_jump_2",
    name = "Jump 2",
    icon = "gadgets_default_effects_jump_icon.png",
    groups = {"jump"},
    apply = function(player)
        player_monoids.jump:add_change(player, 2.5, "gadgets_default_effects_jump_2")
    end,
    cancel = function(effect, player)
        player_monoids.jump:del_change(player, "gadgets_default_effects_jump_2")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_jump_particle.png",
    particle_amount = 2,
    particle_glow = true,
    particle_velocity = 0.5,
    particle_gravity = 0.25,
    particle_size = 3,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_gravity_1",
    name = "Gravity 1",
    icon = "gadgets_default_effects_gravity_icon.png",
    groups = {"gravity"},
    apply = function(player)
        player_monoids.gravity:add_change(player, 0.5, "gadgets_default_effects_gravity_1")
    end,
    cancel = function(effect, player)
        player_monoids.gravity:del_change(player, "gadgets_default_effects_gravity_1")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_gravity_particle.png",
    particle_amount = 2,
    particle_glow = true,
    particle_velocity = 0.25,
    particle_gravity = 0.25,
    particle_size = 2,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_gravity_2",
    name = "Gravity 2",
    icon = "gadgets_default_effects_gravity_icon.png",
    groups = {"gravity"},
    apply = function(player)
        player_monoids.gravity:add_change(player, 0.15, "gadgets_default_effects_gravity_2")
    end,
    cancel = function(effect, player)
        player_monoids.gravity:del_change(player, "gadgets_default_effects_gravity_2")
    end,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = nil,
    particle = "gadgets_default_effects_gravity_particle.png",
    particle_amount = 2,
    particle_glow = true,
    particle_velocity = 0.25,
    particle_gravity = 0.25,
    particle_size = 2,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_health_regen",
    name = "Health Regen",
    icon = "gadgets_default_effects_health_regen_icon.png",
    groups = {"health_regen"},
    apply = function(player)
        player:set_hp(player:get_hp()+0.5)
    end,
    nil,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = 1,
    particle = "gadgets_default_effects_health_regen_particle.png",
    particle_amount = 2,
    particle_glow = true,
    particle_velocity = 0.25,
    particle_gravity = 0.5,
    particle_size = 2,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_water_breath",
    name = "Water Breath",
    icon = "gadgets_default_effects_water_breath_icon.png",
    groups = {"water_breath"},
    apply = function(player)
        player:set_breath(player:get_breath()+2)
    end,
    nil,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = 1,
    particle = "gadgets_default_effects_water_breath_particle.png",
    particle_amount = 4,
    particle_glow = false,
    particle_velocity = 0.5,
    particle_gravity = 0.5,
    particle_size = 2,
    particle_displacement = 0.5,
})

gadgets.register_effect({
    id = "gadgets_default_effects_fire_shield",
    name = "Fire Shield",
    icon = "gadgets_default_effects_fire_shield_icon.png",
    groups = {"fire_shield"},
    apply = function(player)
        local pos = player:get_pos()
        local targets = minetest.get_objects_inside_radius(pos, 3)
        for _,v in pairs(targets) do
            if v ~= player then
                v:punch(player, 1.0, {
                    full_punch_interval=1.0,
                    damage_groups={fleshy=5},
                    }, nil)
            end
        end
    end,
    nil,
    hidden = false,
    cancel_on_death = true,
    repeat_interval = 1,
    particle = "gadgets_default_effects_fire_shield_particle.png",
    particle_amount = 6,
    particle_glow = true,
    particle_velocity = 0.5,
    particle_gravity = 1.5,
    particle_size = 3,
    particle_displacement = 0.5,
})

if minetest.get_modpath("sprint_lite") then
    gadgets.register_effect({
        id = "gadgets_default_effects_stamina_regen",
        name = "Stamina Regen",
        icon = "gadgets_default_effects_stamina_regen_icon.png",
        groups = {"stamina_regen"},
        apply = function(player)
            local name = player:get_player_name()
            sprint_lite.set_stamina(name, 1.5, true)
        end,
        nil,
        hidden = false,
        cancel_on_death = true,
        repeat_interval = 1,
        particle = "gadgets_default_effects_stamina_regen_particle.png",
        particle_amount = 2,
        particle_glow = true,
        particle_velocity = 0.25,
        particle_gravity = 0.5,
        particle_size = 2,
        particle_displacement = 0.5,
    })
end

if minetest.get_modpath("mana") then
    gadgets.register_effect({
        id = "gadgets_default_effects_mana_regen",
        name = "Mana Regen",
        icon = "gadgets_default_effects_mana_regen_icon.png",
        groups = {"mana_regen"},
        apply = function(player)
            local name = player:get_player_name()
            mana.add_up_to(name, 15)
        end,
        nil,
        hidden = false,
        cancel_on_death = true,
        repeat_interval = 1,
        particle = "gadgets_default_effects_mana_regen_particle.png",
        particle_amount = 2,
        particle_glow = true,
        particle_velocity = 0.25,
        particle_gravity = 0.5,
        particle_size = 2,
        particle_displacement = 0.5,
    })
end


code:
local path = minetest.get_modpath("gadgets_default_effects")
dofile(path .. "/effects.lua")


code:
name = gadgets_default_effects
description = Part of gadgets_modpack, providing default status effects
depends = gadgets_api, player_monoids
optional_depends =
author = ClockGen
title = Gadgets Default Effects


code:
local path = minetest.get_modpath("gadgets_magic")
dofile(path .. "/spellbooks.lua")
dofile(path .. "/staves.lua")
dofile(path .. "/nodes.lua")


code:
name = gadgets_magic
description = Part of gadgets_modpack that adds magical spellbooks and similar items
depends = gadgets_api, gadgets_default_effects, magic_materials
optional_depends =
author = ClockGen
title = Gadgets Magic


code:
minetest.register_node("gadgets_magic:magic_lantern", {
    description = "Magic Lantern",
    tiles = {"gadgets_magic_magic_lantern.png"},
    drop = "",
    light_source = 12,
    groups = {not_in_creative_inventory = 1, cracky=1},
    sounds = default.node_sound_stone_defaults(),
})

minetest.register_node("gadgets_magic:magic_bridge", {
    description = "Magic Bridge Block",
    tiles = {"gadgets_magic_magic_bridge_node.png"},
    drawtype = "glasslike",
    diggable = false,
    walkable = true,
    pointable = false,
    sunlight_propagades = true,
    drop = "",
    light_source = 10,
    groups = {not_in_creative_inventory = 1},
    sounds = default.node_sound_stone_defaults(),
    on_timer = function(pos, elapsed)
        minetest.add_particlespawner({
            amount = 8,
            time = 0.05,
            minpos = {x=pos.x-0.25, y=pos.y, z=pos.z-0.25},
            maxpos = {x=pos.x+0.25, y=pos.y+0.5, z=pos.z+0.25},
            minvel = {x=-1, y=-1, z=-1},
            maxvel = {x=1, y=1, z=1},
            minacc = {x=0, y=2, z=0},
            maxacc = {x=0, y=2, z=0},
            minexptime = 1,
            maxexptime = 4,
            minsize = 2,
            maxsize = 4,
            glow = 14,
            texture = "gadgets_magic_magic_bridge_particle.png",
        })
        minetest.set_node(pos, {name = "air"})
    end
})


code:
gadgets.register_gadget({
    name = "gadgets_magic:tome_speed",
    description = "Tome Of Speed",
    texture = "gadgets_magic_tome_speed.png",
    mana_per_use = 100,
    conflicting_effects = {"gadgets_default_effects_speed_2"},
    effect = {"gadgets_default_effects_speed_1"},
    duration = 60,
    use_sound = "gadgets_magic_spell_cast",
    use_sound_gain = 1,
    recipe = {
        {
            {"", "magic_materials:light_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:energy_rune", ""}
        },
    },
})

gadgets.register_gadget({
    name = "gadgets_magic:tome_jump",
    description = "Tome Of Jump",
    texture = "gadgets_magic_tome_jump.png",
    mana_per_use = 100,
    conflicting_effects = {"gadgets_default_effects_jump_2"},
    effect = {"gadgets_default_effects_jump_1"},
    duration = 60,
    use_sound = "gadgets_magic_spell_cast",
    use_sound_gain = 1,
    recipe = {
        {
            {"", "magic_materials:energy_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:earth_rune", ""}
        },
    },
})

gadgets.register_gadget({
    name = "gadgets_magic:tome_gravity",
    description = "Tome Of Gravity",
    texture = "gadgets_magic_tome_gravity.png",
    mana_per_use = 100,
    conflicting_effects = {"gadgets_default_effects_gravity_2"},
    effect = {"gadgets_default_effects_gravity_1"},
    duration = 60,
    use_sound = "gadgets_magic_spell_cast",
    use_sound_gain = 1,
    recipe = {
        {
            {"", "magic_materials:void_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:earth_rune", ""}
        },
    },
})

gadgets.register_gadget({
    name = "gadgets_magic:tome_blink",
    description = "Tome Of Blink",
    texture = "gadgets_magic_tome_blink.png",
    mana_per_use = 150,
    use_sound = "gadgets_magic_blink",
    use_sound_gain = 1,

    custom_wear = true,
    custom_on_use = function(itemstack, user, pointed_thing)
        local dist = 50
        local pos = user:get_pos()
        local dir = user:get_look_dir()
        local ray = minetest.raycast(
            {
                x = pos.x + dir.x,
                y = pos.y + 1.625 + dir.y,
                z = pos.z + dir.z,
            },
            {
            x=pos.x + dir.x * dist,
            y=pos.y + 1.625 + dir.y * dist,
            z=pos.z + dir.z * dist,
            },
            true,
            false)
        local target = ray:next()
        if not target then return end
        if target and target.intersection_point then
            local normal = target.intersection_normal
            if normal.x == 0 and normal.y == 0 and normal.z == 0 then return end
            local b_pos = {
                x = target.intersection_point.x + normal.x * 0.5,
                y = target.intersection_point.y + normal.y * 0.5,
                z = target.intersection_point.z + normal.z * 0.5,
            }
            local node = minetest.get_node_or_nil(b_pos)
            local above = minetest.get_node_or_nil({x = b_pos.x, y = b_pos.y + 1, z = b_pos.z})
            local below = minetest.get_node_or_nil({x = b_pos.x, y = b_pos.y - 1, z = b_pos.z})
            local teleported = false
            local new_pos = {}
            if node and above and below then
                if node.name == "air" and above.name == "air" then
                    new_pos = {x = b_pos.x, y = b_pos.y - 0.5, z = b_pos.z}
                    user:set_pos(new_pos)
                    teleported = true
                elseif node.name == "air" and below.name == "air" then
                    new_pos = {x = b_pos.x, y = b_pos.y - 1.5, z = b_pos.z}
                    user:set_pos(new_pos)
                    teleported = true
                end
            end
            if teleported then
                for _,v in pairs({new_pos, pos}) do
                    minetest.add_particlespawner({
                        amount = 32,
                        time = 0.05,
                        minpos = v,
                        maxpos = v,
                        minvel = {x=-4, y=-4, z=-4},
                        maxvel = {x=4, y=4, z=4},
                        minacc = {x=0, y=2, z=0},
                        maxacc = {x=0, y=2, z=0},
                        minexptime = 1,
                        maxexptime = 4,
                        minsize = 2,
                        maxsize = 4,
                        glow = 14,
                        texture = "gadgets_magic_tome_blink_particle.png",
                    })
                    minetest.add_particle({
                        pos = v,
                        expirationtime = 1,
                        size = 35,
                        texture = "gadgets_magic_tome_blink_particle_big.png",
                        glow = 14,
                    })
                end
                return true
            end
        end
    end,
    recipe = {
        {
            {"", "magic_materials:void_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:storm_rune", ""}
        },
    },
})

gadgets.register_gadget({
    name = "gadgets_magic:tome_bridge",
    description = "Tome Of Magical Bridge",
    texture = "gadgets_magic_tome_magic_bridge.png",
    mana_per_use = 100,
    use_sound = "gadgets_magic_spell_cast",
    use_sound_gain = 1,

    custom_on_use = function(itemstack, user, pointed_thing)

        local length = 20
        local width = 3
        local timeout = 30

        local pos = user:get_pos()
        local bpos = {x = pos.x, y = pos.y - 1, z = pos.z}
        local name = user:get_player_name()

        local dir = user:get_look_dir()
        local facedir = minetest.dir_to_facedir(dir)

        local dirvec = {}
        if facedir == 0 then
            dirvec = {x = 0, y = 0, z = 1}
        elseif facedir == 1 then
            dirvec = {x = 1, y = 0, z = 0}
        elseif facedir == 2 then
            dirvec = {x = 0, y = 0, z = -1}
        elseif facedir == 3 then
            dirvec = {x = -1, y = 0, z = 0}
        end

        for i = 0,length - 1 do
            for j = -math.floor(width/2),math.floor(width/2) do
                local temppos = {x = bpos.x + (i * dirvec.x) + (j * dirvec.z), y = bpos.y, z = bpos.z + (i * dirvec.z)  + (j * dirvec.x)}
                if not minetest.is_protected(temppos, name) then
                    local node = minetest.get_node_or_nil(temppos)
                    if node and node.name == "air" then
                        minetest.set_node(temppos, {name = "gadgets_magic:magic_bridge"})
                        minetest.get_node_timer(temppos):start(timeout)
                        minetest.add_particlespawner({
                            amount = 8,
                            time = 0.05,
                            minpos = {x=temppos.x-0.25, y=temppos.y, z=temppos.z-0.25},
                            maxpos = {x=temppos.x+0.25, y=temppos.y+0.5, z=temppos.z+0.25},
                            minvel = {x=-1, y=-1, z=-1},
                            maxvel = {x=1, y=1, z=1},
                            minacc = {x=0, y=2, z=0},
                            maxacc = {x=0, y=2, z=0},
                            minexptime = 1,
                            maxexptime = 4,
                            minsize = 2,
                            maxsize = 4,
                            glow = 14,
                            texture = "gadgets_magic_magic_bridge_particle.png",
                        })
                    end
                end
            end
        end

    end,
    recipe = {
        {
            {"", "magic_materials:energy_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:ice_rune", ""}
        },
    },
})

gadgets.register_gadget({
    name = "gadgets_magic:tome_light",
    description = "Tome Of Magical Light",
    texture = "gadgets_magic_tome_light.png",
    mana_per_use = 100,
    use_sound = "gadgets_magic_spell_cast",
    use_sound_gain = 1,

    custom_wear = true,
    custom_on_use = function(itemstack, user, pointed_thing)

        if not user then return end
        local name = user:get_player_name()

        if pointed_thing and pointed_thing.type == "node" then
            local pos = minetest.get_pointed_thing_position(pointed_thing)
            local node = minetest.get_node_or_nil(pos)
            if node and (node.name == "default:stone" or node.name == "default:desert_stone") then
                if not minetest.is_protected(pos, name) then
                    minetest.set_node(pos, {name = "gadgets_magic:magic_lantern"})
                    --local point = pointed_thing.intersection_point
                    minetest.add_particlespawner({
                        amount = 32,
                        time = 0.05,
                        minpos = {x=pos.x-0.25, y=pos.y-0.25, z=pos.z-0.25},
                        maxpos = {x=pos.x+0.25, y=pos.y+0.25, z=pos.z+0.25},
                        minvel = {x=-3, y=-3, z=-3},
                        maxvel = {x=3, y=3, z=3},
                        minacc = {x=0, y=-8, z=0},
                        maxacc = {x=0, y=-8, z=0},
                        minexptime = 1,
                        maxexptime = 4,
                        minsize = 2,
                        maxsize = 4,
                        glow = 14,
                        texture = "gadgets_magic_tome_light_particle.png",
                    })
                    return true
                end
            end
        end

    end,
    recipe = {
        {
            {"", "magic_materials:light_rune", ""},
            {"", "magic_materials:enchanted_book", ""},
            {"", "magic_materials:fire_rune", ""}
        },
    },
})


code:
local plant_list = {
    "default:junglegrass",
    "default:grass_1",
    "default:grass_2",
    "default:grass_3",
    "default:bush_sapling",
    "default:blueberry_bush_sapling",
    "default:marram_grass_1",
    "default:marram_grass_2",
    "default:marram_grass_3",
    "default:fern_1",
    "default:fern_2",
    "default:fern_3",
    "default:dry_grass_1",
    "default:dry_grass_2",
    "default:dry_grass_3",
    "default:dry_shrub",
}

gadgets.register_gadget({
    name = "gadgets_magic:staff_druid",
    description = "Druid's Staff",
    texture = "gadgets_magic_staff_druid.png",

    has_durability = true,
    uses = 64,
    custom_charge = true,

    repair_with = "magic_materials:februm_crystal",
    repair_uses = 8,

    use_sound = "gadgets_magic_earth_staff_cast",
    use_sound_gain = 1,
    custom_wear = true,
    custom_on_use = function(itemstack, user, pointed_thing)
        local pos = minetest.get_pointed_thing_position(pointed_thing)
	local name = user:get_player_name()
        if not pos then return end
        local node = minetest.get_node_or_nil(pos)
        if not node or node and minetest.is_protected(pos, name) then return end
        local success = false
        if node.name == "default:stone" then
            minetest.set_node(pos, {name = "default:cobble"})
            success = true
        elseif node.name == "default:cobble" then
            minetest.set_node(pos, {name = "default:gravel"})
            success = true
        elseif node.name == "default:gravel" then
            minetest.set_node(pos, {name = "default:sand"})
            success = true
        elseif node.name == "default:sand" then
            minetest.set_node(pos, {name = "default:dirt"})
            success = true
        elseif node.name == "default:dirt" then
            minetest.set_node(pos, {name = "default:dirt_with_grass"})
            success = true
        elseif node.name == "default:dirt_with_grass" then
            local above = {x = pos.x, y = pos. y + 1, z = pos.z}
-- Try to workaround new staff bug with druid
            local name = user:get_player_name()
            if minetest.is_protected(above, name) then return end
            local node_above = minetest.get_node_or_nil(above)
            if node_above and node_above.name == "air" then
                local new_node = plant_list[math.random(#plant_list)]
                minetest.set_node(above, {name = new_node})
            end
        end
        if success then
            local tile = minetest.registered_nodes[node.name].tiles[1] or "tnt_smoke.png"
            minetest.add_particlespawner({
                amount = 32,
                time = 0.05,
                minpos = {x=pos.x-0.25, y=pos.y-0.25, z=pos.z-0.25},
                maxpos = {x=pos.x+0.25, y=pos.y+0.25, z=pos.z+0.25},
                minvel = {x=-1, y=0, z=-1},
                maxvel = {x=1, y=8, z=1},
                minacc = {x=0, y=-8, z=0},
                maxacc = {x=0, y=-8, z=0},
                minexptime = 1,
                maxexptime = 4,
                minsize = 1,
                maxsize = 3,
                texture = tile,
            })
            return true
        end
    end,
    recipe = {
        {
            {"magic_materials:magic_root", "magic_materials:magic_flower", "magic_materials:magic_root"},
            {"", "magic_materials:enchanted_staff", ""},
            {"", "magic_materials:earth_rune", ""}
        },
    },
})

local earth_staff_blacklist = {
    "df_underworld_items:slade_wall",
    "df_underworld_items:slade_brick",
    "df_underworld_items:slade",
    "df_underworld_items:slade_sand",
    "df_underworld_items:slade_seal",
    "df_underworld_items:slade_block",
    "df_underworld_items:slade_capstone",
    "df_underworld_items:inscription_block",
    "df_underworld_items:puzzle_seal",
    "stairs:slab_slade_brick",
    "stairs:stair_outer_slade_brick",
    "stairs:stair_slade_brick",
    "default:obsidian",
    "default:obsidian_block",
    "default:obsidianbrick",
    "towercrane:mast",
    "towercrane:arm",
    "towercrane:arm2",
    "towercrane:balance",
    "towercrane:mast_ctrl_off",
    "towercrane:mast_ctrl_on",
    "wielded_light:1",
    "wielded_light:2",
    "wielded_light:3",
    "wielded_light:4",
    "wielded_light:5",
    "wielded_light:6",
    "wielded_light:7",
    "wielded_light:8",
    "wielded_light:9",
    "wielded_light:10",
    "wielded_light:11",
    "wielded_light:12",
    "wielded_light:13",
    "wielded_light:14",
}

gadgets.register_gadget({
    name = "gadgets_magic:staff_earth",
    description = "Staff Of Earth",
    texture = "gadgets_magic_staff_earth.png",

    has_durability = true,
    uses = 64,
    custom_charge = true,

    repair_with = "magic_materials:februm_crystal",
    repair_uses = 8,

    use_sound = "gadgets_magic_earth_staff_cast",
    use_sound_gain = 1,

    custom_wear = true,
    custom_on_use = function(itemstack, user, pointed_thing)

        if not user then return end

        if not pointed_thing or pointed_thing and pointed_thing.type ~= "node" then
            return
        end

        local radius = 3
        local pos = minetest.get_pointed_thing_position(pointed_thing)
        local name = user:get_player_name()

        for x = -math.floor(radius/2), math.floor(radius/2) do
            for y = -math.floor(radius/2), math.floor(radius/2) do
                for z = -math.floor(radius/2), math.floor(radius/2) do
                    local t_pos = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
                    if not minetest.is_protected(t_pos, name) then
                        local node = minetest.get_node_or_nil(t_pos)
                        if node and minetest.registered_nodes[node.name] and
                        (minetest.registered_nodes[node.name].liquidtype == "none" or
                        not minetest.registered_nodes[node.name].liquidtype) and
                        not earth_staff_blacklist[node.name] and
                        minetest.registered_nodes[node.name].diggable ~= false then
                            local tile = "tnt_smoke.png"
                            local glow = minetest.registered_nodes[node.name].light_source or 0
                            minetest.node_dig(t_pos, node, user)
                            minetest.add_particlespawner({
                                amount = 16,
                                time = 0.05,
                                minpos = {x=t_pos.x-0.25, y=t_pos.y-0.25, z=t_pos.z-0.25},
                                maxpos = {x=t_pos.x+0.25, y=t_pos.y+0.25, z=t_pos.z+0.25},
                                minvel = {x=-1, y=-1, z=-1},
                                maxvel = {x=1, y=1, z=1},
                                minacc = {x=0, y=-8, z=0},
                                maxacc = {x=0, y=-8, z=0},
                                minexptime = 1,
                                maxexptime = 4,
                                minsize = 1,
                                maxsize = 2.5,
                                collision_detection = true,
                                glow = glow,
                                texture = tile,
                            })
                        end
                    end
                end
            end
        end
        return true
    end,
    recipe = {
        {
            {"default:steel_ingot", "magic_materials:earth_rune", "default:steel_ingot"},
            {"", "magic_materials:enchanted_staff", ""},
            {"", "magic_materials:fire_rune", ""},
        },
    }
})