508 lines
14 KiB
Lua
508 lines
14 KiB
Lua
-- Magicalities Wands
|
|
|
|
magicalities.wands = {}
|
|
|
|
-- This is a method of picking up crystals without breaking them into shards and preserving all of their elements.
|
|
-- Surround a crystal in a 3x3 thing of glass, except for the top layer, which has to be wood slabs.
|
|
-- When the structure is complete, hit any node of glass with the wand.
|
|
-- Views from the side:
|
|
-- (middle)
|
|
-- sss sss sss
|
|
-- ggg gxg ggg
|
|
-- ggg ggg ggg
|
|
-- s: stairs:slab_wood, g: default:glass, x: group:crystal_cluster
|
|
local function pickup_jarred(itemstack, user, glassp)
|
|
local node = minetest.get_node_or_nil(glassp)
|
|
if not node or node.name ~= "default:glass" then return nil end
|
|
local closest = minetest.find_node_near(glassp, 1, "group:crystal_cluster")
|
|
if not closest then return nil end
|
|
if minetest.is_protected(closest, user:get_player_name()) then return nil end
|
|
local cap = minetest.find_nodes_in_area(
|
|
vector.add(closest, {x = -1, y = 1, z = -1}),
|
|
vector.add(closest, {x = 1, y = 1, z = 1}), {"stairs:slab_wood"})
|
|
if #cap ~= 9 then return nil end
|
|
local glass = minetest.find_nodes_in_area(
|
|
vector.add(closest, {x = -1, y = 0, z = -1}),
|
|
vector.add(closest, {x = 1, y = -1, z = 1}), {"default:glass", "group:crystal_cluster"})
|
|
if #glass ~= 18 then return nil end
|
|
local node = minetest.get_node(closest)
|
|
local item = ItemStack(node.name)
|
|
local nmeta = minetest.get_meta(closest)
|
|
local imeta = item:get_meta()
|
|
|
|
local contents = nmeta:get_string("contents")
|
|
if contents ~= "" then
|
|
local def = minetest.registered_items[node.name]
|
|
imeta:set_string("description", def.description .. "\n" ..
|
|
minetest.colorize("#a070e0", "Contains elements!"))
|
|
imeta:set_string("contents", contents)
|
|
end
|
|
|
|
for _,p in pairs(cap) do
|
|
minetest.set_node(p, { name = "air" })
|
|
end
|
|
|
|
for _,p in pairs(glass) do
|
|
minetest.set_node(p, { name = "air" })
|
|
end
|
|
|
|
minetest.add_item(closest, item)
|
|
|
|
return itemstack
|
|
end
|
|
|
|
magicalities.wands.transform_recipes = {
|
|
["group:enchanted_table"] = {result = "magicalities:arcane_table", requirements = nil},
|
|
["default:bookshelf"] = {result = "magicalities:book", requirements = nil, drop = true},
|
|
["default:glass"] = {result = pickup_jarred, requirements = nil, learn = "magicalities:pickup_jarred"},
|
|
["group:tree"] = {result = "magicalities:tree_enchanted", requirements = nil},
|
|
}
|
|
|
|
local wandcaps = {
|
|
full_punch_interval = 1.0,
|
|
max_drop_level = 0,
|
|
groupcaps = {},
|
|
damage_groups = {fleshy = 2},
|
|
}
|
|
|
|
local function align(len)
|
|
local str = ""
|
|
for i = 1, len do
|
|
str = str.."\t"
|
|
end
|
|
return str
|
|
end
|
|
|
|
function magicalities.wands.get_wand_focus(stack)
|
|
local meta = stack:get_meta()
|
|
if meta:get_string("focus") == "" then
|
|
return nil
|
|
end
|
|
|
|
local focus = meta:get_string("focus")
|
|
local itemdef = minetest.registered_items[focus]
|
|
if not itemdef then return nil end
|
|
|
|
return focus, itemdef
|
|
end
|
|
|
|
function magicalities.wands.get_wand_owner(stack)
|
|
local meta = stack:get_meta()
|
|
return meta:get_string("player")
|
|
end
|
|
|
|
local function focus_requirements(stack, fdef)
|
|
if fdef["_wand_requirements"] then
|
|
return magicalities.wands.wand_has_contents(stack, fdef["_wand_requirements"])
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function focuses_formspec(available, focusname)
|
|
local x = 0
|
|
local fsp = ""
|
|
for focus in pairs(available) do
|
|
if x < 5 then
|
|
fsp = fsp .. "item_image_button["..x..",2.8;1,1;"..focus..";"..focus..";]"
|
|
x = x + 1
|
|
end
|
|
end
|
|
|
|
local current = ""
|
|
if not focusname then
|
|
current = "label[2,1;No Focus]"
|
|
else
|
|
current = "item_image_button[2,0.5;1,1;"..focusname..";remove;Remove]"..
|
|
"label[0,1.5;Current: "..minetest.registered_items[focusname].description.."]"
|
|
end
|
|
|
|
return "size[5,3.5]"..
|
|
default.gui_bg..
|
|
default.gui_bg_img..
|
|
"label[0,0;Wand Focuses]"..
|
|
current..
|
|
"label[0,2.4;Available]"..
|
|
fsp
|
|
end
|
|
|
|
-- Update wand's description
|
|
function magicalities.wands.update_wand_desc(stack)
|
|
local meta = stack:get_meta()
|
|
local data_table = minetest.deserialize(meta:get_string("contents"))
|
|
if not data_table then data_table = {} end
|
|
|
|
local focus, fdef = magicalities.wands.get_wand_focus(stack)
|
|
|
|
local wanddata = minetest.registered_items[stack:get_name()]
|
|
local description = wanddata.description
|
|
local capcontents = wanddata["_cap_max"] or 15
|
|
local strbld = description.."\n\n"
|
|
|
|
local elems = {}
|
|
for elem, amount in pairs(data_table) do
|
|
local dataelem = magicalities.elements[elem]
|
|
local visual = amount
|
|
if amount < 10 then visual = "0" .. amount end
|
|
if amount == 0 then visual = minetest.colorize("#ff0505", visual) end
|
|
local str = "["..visual.."/"..capcontents.."] "
|
|
str = str .. minetest.colorize(dataelem.color, dataelem.description)
|
|
if focus and fdef and fdef['_wand_requirements'] and fdef['_wand_requirements'][elem] ~= nil then
|
|
elems[#elems + 1] = str .. minetest.colorize("#a070e0", " ("..fdef['_wand_requirements'][elem]..") ")
|
|
elseif amount ~= 0 then
|
|
elems[#elems + 1] = str
|
|
end
|
|
end
|
|
|
|
local focusstr = "No Wand Focus"
|
|
if focus then
|
|
focusstr = fdef.description
|
|
end
|
|
|
|
strbld = strbld .. minetest.colorize("#a070e0", focusstr) .. "\n"
|
|
if #elems > 0 then
|
|
table.sort(elems)
|
|
strbld = strbld .. "\n" .. table.concat(elems, "\n")
|
|
end
|
|
|
|
local owner = meta:get_string("player")
|
|
if owner ~= "" then
|
|
strbld = strbld .. "\n" .. minetest.colorize("#d33b57", string.format("Soulbound to %s", owner))
|
|
end
|
|
|
|
meta:set_string("description", strbld)
|
|
end
|
|
|
|
-- Ensure wand has contents
|
|
function magicalities.wands.wand_has_contents(stack, requirements)
|
|
local meta = stack:get_meta()
|
|
local data_table = minetest.deserialize(meta:get_string("contents"))
|
|
|
|
if not data_table then return false end
|
|
|
|
for name, count in pairs(requirements) do
|
|
if not data_table[name] or data_table[name] < count then
|
|
return false
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Take wand contents
|
|
function magicalities.wands.wand_take_contents(stack, to_take)
|
|
local meta = stack:get_meta()
|
|
local data_table = minetest.deserialize(meta:get_string("contents"))
|
|
|
|
for name, count in pairs(to_take) do
|
|
if not data_table[name] or data_table[name] - count < 0 then
|
|
return nil
|
|
end
|
|
|
|
data_table[name] = data_table[name] - count
|
|
end
|
|
|
|
local data_res = minetest.serialize(data_table)
|
|
meta:set_string("contents", data_res)
|
|
|
|
return stack
|
|
end
|
|
|
|
-- Add wand contents
|
|
function magicalities.wands.wand_insert_contents(stack, to_put)
|
|
local meta = stack:get_meta()
|
|
local data_table = minetest.deserialize(meta:get_string("contents"))
|
|
local cap = minetest.registered_items[stack:get_name()]["_cap_max"]
|
|
local leftover = {}
|
|
|
|
for name, count in pairs(to_put) do
|
|
if data_table[name] then
|
|
if data_table[name] + count > cap then
|
|
data_table[name] = cap
|
|
leftover[name] = (data_table[name] + count) - cap
|
|
else
|
|
data_table[name] = data_table[name] + count
|
|
end
|
|
end
|
|
end
|
|
|
|
local data_res = minetest.serialize(data_table)
|
|
meta:set_string("contents", data_res)
|
|
|
|
return stack, leftover
|
|
end
|
|
|
|
-- Can add wand contents
|
|
function magicalities.wands.wand_insertable_contents(stack, to_put)
|
|
local meta = stack:get_meta()
|
|
local data_table = minetest.deserialize(meta:get_string("contents"))
|
|
local cap = minetest.registered_items[stack:get_name()]["_cap_max"]
|
|
local insertable = {}
|
|
|
|
for name, count in pairs(to_put) do
|
|
if data_table[name] then
|
|
if data_table[name] + count <= cap then
|
|
insertable[name] = count
|
|
elseif cap - data_table[name] > 0 then
|
|
insertable[name] = cap - data_table[name]
|
|
end
|
|
end
|
|
end
|
|
|
|
return insertable
|
|
end
|
|
|
|
-- Initialize wand metadata
|
|
local function initialize_wand(stack, player)
|
|
local data_table = {}
|
|
|
|
for name, data in pairs(magicalities.elements) do
|
|
if not data.inheritance then
|
|
data_table[name] = 0
|
|
end
|
|
end
|
|
|
|
local meta = stack:get_meta()
|
|
meta:set_string("player", player)
|
|
meta:set_string("contents", minetest.serialize(data_table))
|
|
end
|
|
|
|
local function wand_action(itemstack, placer, pointed_thing)
|
|
if not pointed_thing.type == "node" then return itemstack end
|
|
local pos = pointed_thing.under
|
|
local node = minetest.get_node(pointed_thing.under)
|
|
local imeta = itemstack:get_meta()
|
|
|
|
-- Initialize wand metadata
|
|
if imeta:get_string("contents") == nil or imeta:get_string("contents") == "" then
|
|
initialize_wand(itemstack, placer:get_player_name())
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
end
|
|
|
|
-- Call rightclick on the wand focus
|
|
local focus, fdef = magicalities.wands.get_wand_focus(itemstack)
|
|
if focus then
|
|
if fdef["_wand_node"] and focus_requirements(itemstack, fdef) then
|
|
itemstack = fdef["_wand_node"](pos, node, placer, itemstack, pointed_thing)
|
|
|
|
return itemstack
|
|
end
|
|
end
|
|
|
|
-- Call on_rightclick on the node
|
|
local nodedef = minetest.registered_nodes[node.name]
|
|
if nodedef.on_rightclick then
|
|
itemstack = nodedef.on_rightclick(pos, node, placer, itemstack, pointed_thing)
|
|
end
|
|
|
|
return itemstack
|
|
end
|
|
|
|
local function use_wand(itemstack, user, pointed_thing)
|
|
local imeta = itemstack:get_meta()
|
|
|
|
-- Initialize wand metadata
|
|
if imeta:get_string("contents") == "" then
|
|
initialize_wand(itemstack, user:get_player_name())
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
end
|
|
|
|
-- Call use on the wand focus
|
|
local focus, fdef = magicalities.wands.get_wand_focus(itemstack)
|
|
if focus then
|
|
if fdef["_wand_use"] and focus_requirements(itemstack, fdef) then
|
|
itemstack = fdef["_wand_use"](itemstack, user, pointed_thing)
|
|
|
|
return itemstack
|
|
end
|
|
end
|
|
|
|
if pointed_thing.type ~= "node" then
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
return itemstack
|
|
end
|
|
|
|
local pos = pointed_thing.under
|
|
local node = minetest.get_node_or_nil(pos)
|
|
|
|
if not node or node.name == "air" or minetest.is_protected(pos, user:get_player_name()) then
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
return itemstack
|
|
end
|
|
|
|
-- Replacement
|
|
local to_replace = nil
|
|
for name, result in pairs(magicalities.wands.transform_recipes) do
|
|
if name:match("group:") ~= nil and
|
|
minetest.get_item_group(node.name, string.gsub(name, "group:", "")) > 0 then
|
|
to_replace = result
|
|
break
|
|
elseif name == node.name then
|
|
to_replace = result
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Make sure player has this replacement ability
|
|
if to_replace and to_replace.learn and not magicalities.player_has_ability(magicalities.wands.get_wand_owner(itemstack), to_replace.learn) then
|
|
to_replace = nil
|
|
end
|
|
|
|
-- Make sure we can "dig" the node before we, potentially, remove it from the world
|
|
if to_replace then
|
|
local ndef = minetest.registered_items[node.name]
|
|
if ndef.can_dig and not ndef.can_dig(pos, user) then
|
|
to_replace = nil
|
|
end
|
|
end
|
|
|
|
-- Commit action
|
|
if to_replace then
|
|
local take_req = true
|
|
|
|
if type(to_replace.result) == "function" then
|
|
local t = to_replace.result(itemstack, user, pos)
|
|
if not t then
|
|
take_req = false
|
|
else
|
|
itemstack = t
|
|
end
|
|
elseif to_replace.drop then
|
|
local istack = ItemStack(to_replace.result)
|
|
local istackdef = minetest.registered_items[to_replace.result]
|
|
if istackdef._wand_created then
|
|
istack = istackdef._wand_created(istack, itemstack, user, pos)
|
|
end
|
|
minetest.add_item(pos, istack)
|
|
minetest.set_node(pos, {name = "air"})
|
|
else
|
|
minetest.set_node(pos, {name = to_replace.result, param1 = node.param1, param2 = node.param2})
|
|
local spec = minetest.registered_nodes[to_replace.result]
|
|
if spec.on_construct then
|
|
spec.on_construct(pos)
|
|
end
|
|
end
|
|
|
|
if take_req and to_replace.requirements then
|
|
if not magicalities.wands.wand_has_contents(itemstack, to_replace.requirements) then
|
|
return itemstack
|
|
end
|
|
itemstack = magicalities.wands.wand_take_contents(itemstack, to_replace.requirements)
|
|
end
|
|
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
return itemstack
|
|
end
|
|
|
|
-- Call _wand_use on the node, if it has the callback registered
|
|
local ndef = minetest.registered_nodes[node.name]
|
|
if ndef['_wand_use'] then
|
|
return ndef['_wand_use'](pos, node, itemstack, user, pointed_thing)
|
|
end
|
|
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
return itemstack
|
|
end
|
|
|
|
local function wand_focuses(itemstack, user, pointed_thing)
|
|
local focuses_found = {}
|
|
local inv = user:get_inventory()
|
|
local list = inv:get_list("main")
|
|
|
|
local focusname, focusdef = magicalities.wands.get_wand_focus(itemstack)
|
|
local meta = itemstack:get_meta()
|
|
|
|
for _, stack in pairs(list) do
|
|
if minetest.get_item_group(stack:get_name(), "wand_focus") > 0 then
|
|
focuses_found[stack:get_name()] = true
|
|
end
|
|
end
|
|
|
|
minetest.show_formspec(user:get_player_name(), "magicalities:wand_focuses", focuses_formspec(focuses_found, focusname))
|
|
minetest.register_on_player_receive_fields(function (player, formname, fields)
|
|
if formname ~= "magicalities:wand_focuses" then
|
|
return false
|
|
end
|
|
|
|
-- Make sure field is a valid item
|
|
local f = ""
|
|
if not fields["quit"] then
|
|
if fields["remove"] then
|
|
f = nil
|
|
else
|
|
for v in pairs(fields) do
|
|
if minetest.registered_items[v] then
|
|
f = v
|
|
break
|
|
end
|
|
end
|
|
end
|
|
else
|
|
return true
|
|
end
|
|
|
|
local was
|
|
|
|
was = meta:get_string("focus")
|
|
if was == "" and not f then
|
|
return true
|
|
elseif was ~= "" then
|
|
was = ItemStack(was)
|
|
if not inv:room_for_item("main", was) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
minetest.close_formspec(player:get_player_name(), "magicalities:wand_focuses")
|
|
|
|
local removed_focus = false
|
|
local set = false
|
|
|
|
-- Update itemstack
|
|
for i, stack in pairs(list) do
|
|
if set and (removed_focus or not f) then break end
|
|
if not removed_focus and stack:get_name() == f then
|
|
inv:set_stack("main", i, ItemStack(nil))
|
|
removed_focus = true -- Make sure to only remove one
|
|
end
|
|
|
|
if stack:get_name() == itemstack:get_name() and stack:get_meta() == itemstack:get_meta() and not set then
|
|
if not f then
|
|
meta:set_string("focus", "")
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
elseif f ~= "" then
|
|
meta:set_string("focus", f)
|
|
magicalities.wands.update_wand_desc(itemstack)
|
|
end
|
|
|
|
inv:set_stack("main", i, itemstack)
|
|
set = true
|
|
end
|
|
end
|
|
|
|
-- Give the removed focus back
|
|
if was then
|
|
inv:add_item("main", was)
|
|
end
|
|
|
|
return true
|
|
end)
|
|
|
|
return itemstack
|
|
end
|
|
|
|
function magicalities.wands.register_wand(name, data)
|
|
local mod = minetest.get_current_modname()
|
|
minetest.register_tool(mod..":wand_"..name, {
|
|
description = data.description,
|
|
inventory_image = data.image,
|
|
tool_capabilities = wandcaps,
|
|
stack_max = 1,
|
|
_cap_max = data.wand_cap,
|
|
on_use = use_wand,
|
|
on_place = wand_action,
|
|
on_secondary_use = wand_focuses,
|
|
groups = {wand = 1}
|
|
})
|
|
end
|