2023-11-19 01:48:35 +01:00
|
|
|
-- A ton of functions with approximately zero organization. At least there are comments now.
|
2023-10-31 00:54:59 +01:00
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
local S = minetest.get_translator()
|
|
|
|
|
|
|
|
-- Rounds to the nearest integer
|
|
|
|
function exchangeclone.round(num)
|
|
|
|
if num % 1 < 0.5 then
|
|
|
|
return math.floor(num)
|
|
|
|
else
|
|
|
|
return math.ceil(num)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Don't think this even works correctly.
|
|
|
|
function exchangeclone.get_inventory_drops(pos, inventory, drops)
|
2023-10-31 00:54:59 +01:00
|
|
|
local inv = minetest.get_meta(pos):get_inventory()
|
|
|
|
local n = #drops
|
|
|
|
for i = 1, inv:get_size(inventory) do
|
|
|
|
local stack = inv:get_stack(inventory, i)
|
|
|
|
if stack:get_count() > 0 then
|
|
|
|
drops[n+1] = stack:to_table()
|
|
|
|
n = n + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-23 18:17:53 +01:00
|
|
|
-- Gets the energy value of an itemstring or ItemStack
|
|
|
|
-- Handles "group:foo" syntax (although it goes through every item)
|
2023-11-03 00:53:52 +01:00
|
|
|
function exchangeclone.get_item_energy(item)
|
2023-11-10 23:43:10 +01:00
|
|
|
if (item == "") or not item then return end
|
2023-11-13 02:30:16 +01:00
|
|
|
-- handle groups
|
2023-11-03 00:53:52 +01:00
|
|
|
if type(item) == "string" and item:sub(1,6) == "group:" and exchangeclone.group_values then
|
|
|
|
local item_group = item:sub(7,-1)
|
|
|
|
for _, group in ipairs(exchangeclone.group_values) do
|
|
|
|
if item_group == group[1] then return group[2] end
|
|
|
|
end
|
2023-11-03 00:33:30 +01:00
|
|
|
local group_items = exchangeclone.get_group_items(item_group)
|
|
|
|
local cheapest
|
|
|
|
for _, group_item in ipairs(group_items[item_group]) do
|
2023-11-08 03:00:09 +01:00
|
|
|
if group_item then
|
|
|
|
local energy_value = exchangeclone.get_item_energy(group_item)
|
|
|
|
if energy_value then
|
|
|
|
if energy_value > 0 and ((not cheapest) or energy_value < cheapest) then
|
|
|
|
cheapest = energy_value
|
|
|
|
end
|
|
|
|
end
|
2023-11-03 00:33:30 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return cheapest
|
2023-11-03 00:53:52 +01:00
|
|
|
end
|
2023-11-13 02:30:16 +01:00
|
|
|
-- handle items/itemstacks
|
2023-11-03 00:53:52 +01:00
|
|
|
item = ItemStack(item)
|
2023-11-19 01:48:35 +01:00
|
|
|
if item == ItemStack("") then return end
|
|
|
|
item:set_name(exchangeclone.handle_alias(item))
|
2023-11-13 02:30:16 +01:00
|
|
|
local meta_energy_value = tonumber(item:get_meta():get_string("exchangeclone_energy_value"))
|
2023-11-19 01:48:35 +01:00
|
|
|
if meta_energy_value then
|
|
|
|
if meta_energy_value < 0 then return 0 end
|
|
|
|
if meta_energy_value > 0 then return meta_energy_value end
|
2023-11-10 23:43:10 +01:00
|
|
|
end
|
2023-11-03 00:53:52 +01:00
|
|
|
local def = minetest.registered_items[item:get_name()]
|
2023-11-10 23:43:10 +01:00
|
|
|
if not def then return end
|
2023-11-03 00:53:52 +01:00
|
|
|
if item:get_name() == "exchangeclone:exchange_orb" then
|
2023-11-10 23:43:10 +01:00
|
|
|
if def.energy_value then
|
|
|
|
return def.energy_value + exchangeclone.get_orb_itemstack_energy(item)
|
|
|
|
end
|
2023-11-03 00:53:52 +01:00
|
|
|
end
|
|
|
|
if def.energy_value then
|
|
|
|
return (def.energy_value) * item:get_count()
|
|
|
|
end
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- https://forum.unity.com/threads/re-map-a-number-from-one-range-to-another.119437/
|
2023-11-03 00:53:52 +01:00
|
|
|
-- Remaps a number from one range to another
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.map(input, min1, max1, min2, max2)
|
|
|
|
return (input - min1) / (max1 - min1) * (max2 - min2) + min2
|
|
|
|
end
|
|
|
|
|
2023-11-03 00:53:52 +01:00
|
|
|
-- Gets the energy stored in a specified exchange_orb itemstack.
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.get_orb_itemstack_energy(itemstack)
|
2023-11-03 00:33:30 +01:00
|
|
|
if not itemstack then return end
|
|
|
|
if itemstack:get_name() ~= "exchangeclone:exchange_orb" then return end
|
2023-11-10 23:43:10 +01:00
|
|
|
return math.max(itemstack:get_meta():get_float("stored_energy"), 0)
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
|
2023-11-03 00:53:52 +01:00
|
|
|
-- Gets the amount of energy stored in an orb in a specific inventory slot
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.get_orb_energy(inventory, listname, index)
|
2023-11-10 03:35:23 +01:00
|
|
|
if not inventory then return end
|
2023-10-31 00:54:59 +01:00
|
|
|
if not listname then listname = "main" end
|
|
|
|
if not index then index = 1 end
|
|
|
|
local itemstack = inventory:get_stack(listname, index)
|
|
|
|
return exchangeclone.get_orb_itemstack_energy(itemstack)
|
|
|
|
end
|
|
|
|
|
2023-11-13 02:30:16 +01:00
|
|
|
function exchangeclone.set_orb_itemstack_energy(itemstack, amount)
|
2023-11-19 01:48:35 +01:00
|
|
|
if not itemstack or not amount then return end
|
|
|
|
if itemstack:get_name() ~= "exchangeclone:exchange_orb" then return end
|
2023-10-31 00:54:59 +01:00
|
|
|
local old_energy = exchangeclone.get_orb_itemstack_energy(itemstack)
|
2023-11-19 01:48:35 +01:00
|
|
|
if amount > old_energy and old_energy > exchangeclone.orb_max then return end -- don't allow more energy to be put into an over-filled orb
|
2023-10-31 00:54:59 +01:00
|
|
|
|
|
|
|
-- Square roots will hopefully make it less linear
|
|
|
|
-- And if they don't, I don't really care and I don't want to think about math anymore.
|
|
|
|
local sqrt_amount = math.sqrt(amount)
|
|
|
|
local sqrt_max = math.sqrt(exchangeclone.orb_max)
|
|
|
|
|
|
|
|
local r, g, b = 0, 0, 0
|
2023-11-13 02:30:16 +01:00
|
|
|
if amount <= 0 then
|
2023-10-31 00:54:59 +01:00
|
|
|
-- do nothing
|
|
|
|
elseif sqrt_amount < (sqrt_max/4) then
|
|
|
|
r = exchangeclone.map(sqrt_amount, 0, sqrt_max/4, 0, 255)
|
|
|
|
elseif sqrt_amount < (sqrt_max/2) then
|
|
|
|
g = exchangeclone.map(sqrt_amount, sqrt_max/4, sqrt_max/2, 0, 255)
|
|
|
|
r = 255 - g
|
|
|
|
elseif sqrt_amount < (3*sqrt_max/4) then
|
|
|
|
b = exchangeclone.map(sqrt_amount, sqrt_max/2, 3*sqrt_max/4, 0, 255)
|
|
|
|
g = 255 - b
|
|
|
|
else
|
|
|
|
r = math.min(exchangeclone.map(sqrt_amount, 3*sqrt_max/4, sqrt_max, 0, 255), 255)
|
|
|
|
b = 255
|
|
|
|
end
|
|
|
|
|
|
|
|
local colorstring = minetest.rgba(r,g,b)
|
|
|
|
local meta = itemstack:get_meta()
|
|
|
|
meta:set_float("stored_energy", amount)
|
2023-11-19 01:48:35 +01:00
|
|
|
meta:set_string("description", S("Exchange Orb").."\n"..S("Current Charge: @1", exchangeclone.format_number(amount)))
|
2023-10-31 00:54:59 +01:00
|
|
|
meta:set_string("color", colorstring)
|
2023-11-13 02:30:16 +01:00
|
|
|
return itemstack
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Sets the amount of energy in an orb in a specific inventory slot
|
|
|
|
function exchangeclone.set_orb_energy(inventory, listname, index, amount)
|
|
|
|
if not inventory or not amount or amount < 0 then return end
|
|
|
|
if not listname then listname = "main" end
|
|
|
|
if not index then index = 1 end
|
|
|
|
local itemstack = inventory:get_stack(listname, index)
|
|
|
|
local new_stack = exchangeclone.set_orb_itemstack_energy(itemstack, amount)
|
2023-11-19 01:48:35 +01:00
|
|
|
if not new_stack then return end
|
2023-11-13 02:30:16 +01:00
|
|
|
inventory:set_stack(listname, index, new_stack)
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- HUD stuff (show energy value in bottom right)
|
2023-10-31 00:54:59 +01:00
|
|
|
local hud_elements = {}
|
|
|
|
|
|
|
|
function exchangeclone.update_hud(player)
|
|
|
|
local hud_text = hud_elements[player:get_player_name()]
|
2023-11-19 01:48:35 +01:00
|
|
|
player:hud_change(hud_text, "text", S("Personal Energy: @1", exchangeclone.format_number(exchangeclone.get_player_energy(player))))
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
minetest.register_on_joinplayer(function(player, last_login)
|
|
|
|
hud_elements[player:get_player_name()] = player:hud_add({
|
|
|
|
hud_elem_type = "text",
|
|
|
|
position = {x = 1, y = 1},
|
|
|
|
offset = {x = 0, y = 0},
|
2023-11-19 01:48:35 +01:00
|
|
|
text = S("Personal Energy: @1", 0),
|
2023-10-31 00:54:59 +01:00
|
|
|
alignment = {x = -1, y = -1},
|
|
|
|
scale = {x = 100, y = 100},
|
|
|
|
number = 0xDDDDDD
|
|
|
|
})
|
|
|
|
exchangeclone.update_hud(player)
|
|
|
|
end)
|
|
|
|
|
|
|
|
minetest.register_on_leaveplayer(function(player, timed_out)
|
|
|
|
hud_elements[player:get_player_name()] = nil
|
|
|
|
end)
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Get a player's personal energy
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.get_player_energy(player)
|
|
|
|
return tonumber(player:get_meta():get_string("exchangeclone_stored_energy")) or 0
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Set a player's personal energy
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.set_player_energy(player, amount)
|
2023-11-20 01:01:56 +01:00
|
|
|
amount = tonumber(amount)
|
2023-11-10 03:35:23 +01:00
|
|
|
if not (player and amount) then return end
|
2023-11-13 02:30:16 +01:00
|
|
|
if amount < 0 or amount > exchangeclone.limit then return end
|
2023-11-03 01:29:17 +01:00
|
|
|
player:get_meta():set_string("exchangeclone_stored_energy", tonumber(amount))
|
2023-10-31 00:54:59 +01:00
|
|
|
exchangeclone.update_hud(player)
|
|
|
|
end
|
|
|
|
|
2023-11-13 02:30:16 +01:00
|
|
|
-- Add to a player's personal energy (amount can be negative)
|
|
|
|
function exchangeclone.add_player_energy(player, amount)
|
|
|
|
if not (player and amount) then return end
|
2023-11-20 01:01:56 +01:00
|
|
|
exchangeclone.set_player_energy(player, (exchangeclone.get_player_energy(player) or 0) + amount)
|
2023-11-13 02:30:16 +01:00
|
|
|
end
|
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
-- Through trial and error, I have found that this number (1 trillion) works the best.
|
|
|
|
-- When a player has any more energy (as in ANY more), precision-based exploits such as creating infinite glass panes are possible.
|
2023-11-23 18:17:53 +01:00
|
|
|
-- I temporarily considered finding some Lua library that allowed for arbitrary precision (and therefore infinite maximum energy)
|
2023-11-19 01:48:35 +01:00
|
|
|
-- but I decided not to.
|
|
|
|
exchangeclone.limit = 1000000000000
|
|
|
|
|
|
|
|
-- From https://stackoverflow.com/questions/10989788/format-integer-in-lua
|
|
|
|
-- Formats an integer with commas, accounting for decimal points.
|
|
|
|
function exchangeclone.format_number(number)
|
|
|
|
-- Quit if not a number
|
|
|
|
if not tonumber(tostring(number)) then return tostring(number) end
|
|
|
|
|
|
|
|
local _, _, minus, int, fraction = tostring(number):find('([-]?)(%d+)([.]?%d*)')
|
2023-11-23 18:17:53 +01:00
|
|
|
if not int then return tostring(number) end
|
2023-11-19 01:48:35 +01:00
|
|
|
-- reverse the int-string and append a comma to all blocks of 3 digits
|
|
|
|
int = int:reverse():gsub("(%d%d%d)", "%1,")
|
|
|
|
-- reverse the int-string back remove an optional comma and put the
|
|
|
|
-- optional minus and fractional part back
|
|
|
|
return minus .. int:reverse():gsub("^,", "") .. fraction
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Splits a string into a table using a delimiter (copied from somewhere, I don't remember)
|
2023-11-13 02:30:16 +01:00
|
|
|
function exchangeclone.split (input, sep)
|
|
|
|
if sep == nil then
|
|
|
|
sep = "%s"
|
|
|
|
end
|
|
|
|
local result={}
|
|
|
|
for str in string.gmatch(input, "([^"..sep.."]+)") do
|
|
|
|
table.insert(result, str)
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Returns a table of all items in the specified group(s).
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.get_group_items(groups, allow_duplicates, include_no_group)
|
|
|
|
if type(groups) ~= "table" then
|
2023-11-03 01:24:26 +01:00
|
|
|
if type(groups) == "string" then
|
|
|
|
groups = {groups}
|
|
|
|
else
|
|
|
|
return
|
|
|
|
end
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
allow_duplicates = allow_duplicates or false
|
|
|
|
include_no_group = include_no_group or false
|
|
|
|
|
2023-11-13 02:30:16 +01:00
|
|
|
local num_groups = #groups
|
2023-10-31 00:54:59 +01:00
|
|
|
|
|
|
|
local result = {}
|
2023-11-13 02:30:16 +01:00
|
|
|
for i = 1, num_groups do
|
2023-10-31 00:54:59 +01:00
|
|
|
result[groups[i]] = {}
|
|
|
|
end
|
|
|
|
if include_no_group then
|
|
|
|
result["NO_GROUP"] = {}
|
|
|
|
end
|
|
|
|
local in_group
|
|
|
|
|
|
|
|
for name, def in pairs(minetest.registered_items) do
|
|
|
|
in_group = false
|
2023-11-13 02:30:16 +01:00
|
|
|
for i = 1, num_groups do
|
2023-10-31 00:54:59 +01:00
|
|
|
local grp = groups[i]
|
2023-11-13 02:30:16 +01:00
|
|
|
local subgroups = exchangeclone.split(grp, ",")
|
|
|
|
local success = true
|
|
|
|
for _, subgroup in ipairs(subgroups) do
|
|
|
|
if not def.groups[subgroup] then
|
|
|
|
success = false
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if success then
|
2023-10-31 00:54:59 +01:00
|
|
|
result[grp][#result[grp]+1] = name
|
|
|
|
in_group = true
|
|
|
|
if allow_duplicates == false then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if include_no_group and in_group == false then
|
|
|
|
result["NO_GROUP"][#result["NO_GROUP"]+1] = name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Plays the sound caused by ExchangeClone abilities
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.play_ability_sound(player, base_pitch)
|
|
|
|
if not player then return end
|
|
|
|
if not base_pitch then base_pitch = 1 end
|
|
|
|
local new_pitch = base_pitch + (math.random(-100, 100) / 500)
|
|
|
|
minetest.sound_play("exchangeclone_ability", {pitch = new_pitch, pos = player:get_pos(), max_hear_distance = 20, })
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Check the clicked node for a right-click function.
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.check_on_rightclick(itemstack, player, pointed_thing)
|
|
|
|
if pointed_thing.type ~= "node" then return false end
|
|
|
|
if player:get_player_control().sneak then return false end
|
|
|
|
local node = minetest.get_node(pointed_thing.under)
|
|
|
|
if player and not player:get_player_control().sneak then
|
|
|
|
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
|
|
|
|
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, player, itemstack) or itemstack
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Update the range of an ExchangeClone tool
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.range_update(itemstack, player, max)
|
|
|
|
if not max then max = 4 end
|
|
|
|
local range = tonumber(itemstack:get_meta():get_int("exchangeclone_item_range"))
|
|
|
|
if player:get_player_control().sneak then
|
|
|
|
if range == 0 then
|
|
|
|
range = max
|
|
|
|
else
|
|
|
|
range = range - 1
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if range == max then
|
|
|
|
range = 0
|
|
|
|
else
|
|
|
|
range = range + 1
|
|
|
|
end
|
|
|
|
end
|
2023-11-19 01:48:35 +01:00
|
|
|
minetest.chat_send_player(player:get_player_name(), S("Current Range: @1", range))
|
2023-10-31 00:54:59 +01:00
|
|
|
itemstack:get_meta():set_int("exchangeclone_item_range", range)
|
|
|
|
return itemstack
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Make ExchangeClone tools slightly larger...
|
2023-12-15 00:09:59 +01:00
|
|
|
exchangeclone.wield_scale = {x=1.3,y=1.3,z=1.3}
|
2023-10-31 00:54:59 +01:00
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
-- Itemstrings for various items used in crafting recipes.
|
2023-11-08 01:02:24 +01:00
|
|
|
exchangeclone.itemstrings = {
|
2023-12-15 00:09:59 +01:00
|
|
|
cobble = exchangeclone.mcl and "mcl_core:cobble" or "default:cobble",
|
|
|
|
redstoneworth = exchangeclone.mcl and "mesecons:redstone" or "default:obsidian",
|
|
|
|
glowstoneworth = exchangeclone.mcl and "mcl_nether:glowstone_dust" or "default:tin_ingot",
|
|
|
|
coal = exchangeclone.mcl and "mcl_core:coal_lump" or "default:coal_lump",
|
|
|
|
iron = exchangeclone.mcl and "mcl_core:iron_ingot" or "default:steel_ingot",
|
|
|
|
copper = exchangeclone.mcl and "mcl_copper:copper_ingot" or "default:copper_ingot",
|
|
|
|
gold = exchangeclone.mcl and "mcl_core:gold_ingot" or "default:gold_ingot",
|
|
|
|
emeraldworth = exchangeclone.mcl and "mcl_core:emerald" or "default:mese_crystal",
|
|
|
|
diamond = exchangeclone.mcl and "mcl_core:diamond" or "default:diamond",
|
2023-11-08 01:02:24 +01:00
|
|
|
}
|
2023-10-31 00:54:59 +01:00
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
exchangeclone.energy_aliases = {}
|
|
|
|
|
|
|
|
-- <itemstring> will be treated as <alias> in Deconstructors, Constructors, Transmutation Table(t)s, etc.
|
|
|
|
-- When you put <itemstring> into a TT, you will learn <alias> instead.
|
|
|
|
function exchangeclone.register_energy_alias_force(alias, itemstring)
|
2023-11-20 21:35:10 +01:00
|
|
|
if alias == itemstring then return end
|
2023-11-19 01:48:35 +01:00
|
|
|
exchangeclone.energy_aliases[itemstring] = alias
|
|
|
|
end
|
|
|
|
|
|
|
|
function exchangeclone.register_energy_alias(alias, itemstring)
|
|
|
|
if not exchangeclone.energy_aliases[alias] then
|
|
|
|
exchangeclone.register_energy_alias_force(alias, itemstring)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-20 21:35:10 +01:00
|
|
|
-- Returns the correct itemstring, handling both Minetest and Exchangeclone aliases.
|
2023-11-19 01:48:35 +01:00
|
|
|
function exchangeclone.handle_alias(item)
|
|
|
|
item = ItemStack(item)
|
|
|
|
if not item:is_empty() then
|
2023-11-20 21:35:10 +01:00
|
|
|
local de_aliased = exchangeclone.energy_aliases[item:get_name()] or item:get_name() -- Resolve ExchangeClone aliases
|
2023-11-26 16:24:45 +01:00
|
|
|
return ItemStack(de_aliased):get_name() or item:get_name() -- Resolve MT aliases
|
2023-11-19 01:48:35 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Returns a player's inventory formspec with the correct width and hotbar position for the current game
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.inventory_formspec(x,y)
|
|
|
|
local formspec
|
|
|
|
if exchangeclone.mcl then
|
2023-11-19 01:48:35 +01:00
|
|
|
formspec = "list[current_player;main;"..x..","..y..";9,3;9]"..
|
2023-10-31 00:54:59 +01:00
|
|
|
mcl_formspec.get_itemslot_bg(x,y,9,3)..
|
2023-11-19 01:48:35 +01:00
|
|
|
"list[current_player;main;"..x..","..(y+3.25)..";9,1]"..
|
2023-10-31 00:54:59 +01:00
|
|
|
mcl_formspec.get_itemslot_bg(x,y+3.25,9,1)
|
|
|
|
else
|
2023-11-19 01:48:35 +01:00
|
|
|
formspec = "list[current_player;main;"..x..","..y..";8,1]"..
|
|
|
|
"list[current_player;main;"..x..","..(y+1.25)..";8,3;8]"
|
2023-10-31 00:54:59 +01:00
|
|
|
end
|
|
|
|
return formspec
|
|
|
|
end
|
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
-- Modified from MineClone2, basically helps drop items on the player {
|
2023-11-03 01:24:26 +01:00
|
|
|
local doTileDrops = minetest.settings:get_bool("mcl_doTileDrops", true)
|
|
|
|
|
|
|
|
local function get_fortune_drops(fortune_drops, fortune_level)
|
|
|
|
local drop
|
|
|
|
local i = fortune_level
|
|
|
|
repeat
|
|
|
|
drop = fortune_drops[i]
|
|
|
|
i = i - 1
|
|
|
|
until drop or i < 1
|
|
|
|
return drop or {}
|
|
|
|
end
|
|
|
|
|
2023-10-31 00:54:59 +01:00
|
|
|
local function discrete_uniform_distribution(drops, min_count, max_count, cap)
|
|
|
|
local new_drops = table.copy(drops)
|
|
|
|
for i, item in ipairs(drops) do
|
|
|
|
local new_item = ItemStack(item)
|
|
|
|
local multiplier = math.random(min_count, max_count)
|
|
|
|
if cap then
|
|
|
|
multiplier = math.min(cap, multiplier)
|
|
|
|
end
|
|
|
|
new_item:set_count(multiplier * new_item:get_count())
|
|
|
|
new_drops[i] = new_item
|
|
|
|
end
|
|
|
|
return new_drops
|
|
|
|
end
|
|
|
|
|
|
|
|
local tmp_id = 0
|
|
|
|
|
|
|
|
local function get_drops(drop, toolname, param2, paramtype2)
|
|
|
|
tmp_id = tmp_id + 1
|
|
|
|
local tmp_node_name = "mcl_item_entity:" .. tmp_id
|
|
|
|
minetest.registered_nodes[tmp_node_name] = {
|
|
|
|
name = tmp_node_name,
|
|
|
|
drop = drop,
|
|
|
|
paramtype2 = paramtype2
|
|
|
|
}
|
|
|
|
local drops = minetest.get_node_drops({ name = tmp_node_name, param2 = param2 }, toolname)
|
|
|
|
minetest.registered_nodes[tmp_node_name] = nil
|
|
|
|
return drops
|
|
|
|
end
|
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
-- This function gets the drops from a node and drops them at the player's position
|
|
|
|
function exchangeclone.drop_items_on_player(pos, drops, player) -- modified from MineClone's code
|
2023-10-31 00:54:59 +01:00
|
|
|
if not exchangeclone.mcl then
|
|
|
|
return minetest.handle_node_drops(pos, drops, player)
|
|
|
|
end
|
|
|
|
-- NOTE: This function override allows player to be nil.
|
|
|
|
-- This means there is no player. This is a special case which allows this function to be called
|
|
|
|
-- by hand. Creative Mode is intentionally ignored in this case.
|
|
|
|
if player and player:is_player() and minetest.is_creative_enabled(player:get_player_name()) then
|
|
|
|
local inv = player:get_inventory()
|
|
|
|
if inv then
|
|
|
|
for _, item in ipairs(drops) do
|
|
|
|
if not inv:contains_item("main", item, true) then
|
|
|
|
inv:add_item("main", item)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return
|
|
|
|
elseif not doTileDrops then return end
|
|
|
|
|
|
|
|
-- Check if node will yield its useful drop by the player's tool
|
|
|
|
local dug_node = minetest.get_node(pos)
|
|
|
|
local tooldef
|
|
|
|
local tool
|
|
|
|
if player then
|
|
|
|
tool = player:get_wielded_item()
|
|
|
|
tooldef = minetest.registered_items[tool:get_name()]
|
|
|
|
|
|
|
|
if not mcl_autogroup.can_harvest(dug_node.name, tool:get_name(), player) then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local diggroups = tooldef and tooldef._mcl_diggroups
|
|
|
|
local shearsy_level = diggroups and diggroups.shearsy and diggroups.shearsy.level
|
|
|
|
|
|
|
|
--[[ Special node drops when dug by shears by reading _mcl_shears_drop or with a silk touch tool reading _mcl_silk_touch_drop
|
|
|
|
from the node definition.
|
|
|
|
Definition of _mcl_shears_drop / _mcl_silk_touch_drop:
|
|
|
|
* true: Drop itself when dug by shears / silk touch tool
|
|
|
|
* table: Drop every itemstring in this table when dug by shears _mcl_silk_touch_drop
|
|
|
|
]]
|
|
|
|
|
2023-11-19 01:48:35 +01:00
|
|
|
local enchantments = tool and mcl_enchanting.get_enchantments(tool)
|
2023-10-31 00:54:59 +01:00
|
|
|
|
|
|
|
local silk_touch_drop = false
|
|
|
|
local nodedef = minetest.registered_nodes[dug_node.name]
|
|
|
|
if not nodedef then return end
|
|
|
|
|
|
|
|
if shearsy_level and shearsy_level > 0 and nodedef._mcl_shears_drop then
|
|
|
|
if nodedef._mcl_shears_drop == true then
|
|
|
|
drops = { dug_node.name }
|
|
|
|
else
|
|
|
|
drops = nodedef._mcl_shears_drop
|
|
|
|
end
|
|
|
|
elseif tool and enchantments.silk_touch and nodedef._mcl_silk_touch_drop then
|
|
|
|
silk_touch_drop = true
|
|
|
|
if nodedef._mcl_silk_touch_drop == true then
|
|
|
|
drops = { dug_node.name }
|
|
|
|
else
|
|
|
|
drops = nodedef._mcl_silk_touch_drop
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
if tool and nodedef._mcl_fortune_drop and enchantments.fortune then
|
2023-10-31 00:54:59 +01:00
|
|
|
local fortune_level = enchantments.fortune
|
|
|
|
local fortune_drop = nodedef._mcl_fortune_drop
|
|
|
|
if fortune_drop.discrete_uniform_distribution then
|
|
|
|
local min_count = fortune_drop.min_count
|
|
|
|
local max_count = fortune_drop.max_count + fortune_level * (fortune_drop.factor or 1)
|
|
|
|
local chance = fortune_drop.chance or fortune_drop.get_chance and fortune_drop.get_chance(fortune_level)
|
|
|
|
if not chance or math.random() < chance then
|
|
|
|
drops = discrete_uniform_distribution(fortune_drop.multiply and drops or fortune_drop.items, min_count, max_count,
|
|
|
|
fortune_drop.cap)
|
|
|
|
elseif fortune_drop.override then
|
|
|
|
drops = {}
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Fixed Behavior
|
|
|
|
local drop = get_fortune_drops(fortune_drop, fortune_level)
|
|
|
|
drops = get_drops(drop, tool:get_name(), dug_node.param2, nodedef.paramtype2)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if player and mcl_experience.throw_xp and not silk_touch_drop then
|
|
|
|
local experience_amount = minetest.get_item_group(dug_node.name, "xp")
|
|
|
|
if experience_amount > 0 then
|
|
|
|
mcl_experience.throw_xp(player:get_pos(), experience_amount)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, item in ipairs(drops) do
|
|
|
|
local count
|
|
|
|
if type(item) == "string" then
|
|
|
|
count = ItemStack(item):get_count()
|
|
|
|
else
|
|
|
|
count = item:get_count()
|
|
|
|
end
|
|
|
|
local drop_item = ItemStack(item)
|
|
|
|
drop_item:set_count(1)
|
|
|
|
for i = 1, count do
|
|
|
|
-- Spawn item
|
|
|
|
local obj = minetest.add_item(player:get_pos(), drop_item)
|
|
|
|
if obj then
|
|
|
|
-- set the velocity multiplier to the stored amount or if the game dug this node, apply a bigger velocity
|
|
|
|
obj:get_luaentity().age = 0.65
|
|
|
|
obj:get_luaentity()._insta_collect = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-08 01:02:24 +01:00
|
|
|
-- }
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Get the direction a player is facing (rounded to -1, 0, and 1 for each axis)
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.get_face_direction(player)
|
|
|
|
local h_look = player:get_look_horizontal()
|
|
|
|
local v_look = player:get_look_vertical()
|
|
|
|
|
|
|
|
local result = {x = 0, y = 0, z = 0}
|
|
|
|
|
|
|
|
if h_look <= math.pi / 4 or h_look >= (7*math.pi)/4 then
|
|
|
|
result.z = 1
|
|
|
|
elseif h_look > math.pi / 4 and h_look <= (3*math.pi)/4 then
|
|
|
|
result.x = -1
|
|
|
|
elseif h_look > (3*math.pi)/4 and h_look <= (5*math.pi)/4 then
|
|
|
|
result.z = -1
|
|
|
|
else
|
|
|
|
result.x = 1
|
|
|
|
end
|
|
|
|
|
|
|
|
if v_look < -1 then
|
|
|
|
result.y = 1
|
|
|
|
elseif v_look > 1 then
|
|
|
|
result.y = -1
|
|
|
|
end
|
|
|
|
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Execute an action for every node within a cubic radius
|
2023-11-19 01:48:35 +01:00
|
|
|
-- extra_info is usually used for the tool's itemstack.
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.node_radius_action(player, center, range, functions, extra_info)
|
|
|
|
if not functions.action then return end
|
|
|
|
local data
|
|
|
|
local stop = false
|
|
|
|
if functions.start_action then
|
|
|
|
data = functions.start_action(player, center, range, extra_info)
|
|
|
|
if not data then return end
|
|
|
|
end
|
|
|
|
if not center then center = player:get_pos() end
|
|
|
|
center.x = exchangeclone.round(center.x)
|
|
|
|
center.y = math.floor(center.y) --make sure y is node BELOW player's feet
|
|
|
|
center.z = exchangeclone.round(center.z)
|
|
|
|
for x = center.x-range,center.x+range do
|
|
|
|
for y = center.y-range,center.y+range do
|
|
|
|
for z = center.z-range,center.z+range do
|
|
|
|
local pos = {x=x,y=y,z=z}
|
|
|
|
local node = minetest.get_node(pos)
|
|
|
|
local result = functions.action(player, pos, node, data)
|
|
|
|
if not result then stop = true break end
|
|
|
|
if result ~= true then data = result end
|
|
|
|
end
|
|
|
|
if stop then break end
|
|
|
|
end
|
|
|
|
if stop then break end
|
|
|
|
end
|
|
|
|
if functions.end_action then
|
|
|
|
data = functions.end_action(player, center, range, data)
|
|
|
|
end
|
|
|
|
return data
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Cooldowns
|
2023-10-31 00:54:59 +01:00
|
|
|
exchangeclone.cooldowns = {}
|
|
|
|
|
|
|
|
minetest.register_on_joinplayer(function(player, last_login)
|
|
|
|
exchangeclone.cooldowns[player:get_player_name()] = {}
|
|
|
|
end)
|
|
|
|
|
|
|
|
minetest.register_on_leaveplayer(function(player, timed_out)
|
|
|
|
exchangeclone.cooldowns[player:get_player_name()] = nil
|
|
|
|
end)
|
|
|
|
|
2023-11-10 23:43:10 +01:00
|
|
|
-- Start a <time>-second cooldown called <name> for <player>
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.start_cooldown(player, name, time)
|
|
|
|
local player_name = player:get_player_name()
|
|
|
|
exchangeclone.cooldowns[player_name][name] = time
|
|
|
|
minetest.after(time, function()
|
|
|
|
if exchangeclone.cooldowns[player_name] then
|
|
|
|
exchangeclone.cooldowns[player_name][name] = nil
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
2023-11-03 01:24:26 +01:00
|
|
|
-- Returns the TOTAL time of a cooldown called <name> for <player> or nil if no matching cooldown is running.
|
2023-10-31 00:54:59 +01:00
|
|
|
function exchangeclone.check_cooldown(player, name)
|
|
|
|
local player_name = player:get_player_name()
|
|
|
|
if exchangeclone.cooldowns[player_name] then
|
|
|
|
return exchangeclone.cooldowns[player_name][name]
|
|
|
|
end
|
2023-11-20 01:01:56 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Chat commands:
|
2023-12-15 00:09:59 +01:00
|
|
|
minetest.register_chatcommand("add_player_energy", {
|
2023-11-20 01:01:56 +01:00
|
|
|
params = "[player] <value>",
|
|
|
|
description = "Add to a player's personal energy (player is self if not included, value can be negative)",
|
|
|
|
privs = {privs = true},
|
|
|
|
func = function(name, param)
|
|
|
|
local split_param = exchangeclone.split(param, " ")
|
|
|
|
local player
|
|
|
|
local value
|
|
|
|
if #split_param == 1 then
|
|
|
|
player = minetest.get_player_by_name(name)
|
|
|
|
value = split_param[1]
|
|
|
|
end
|
|
|
|
if #split_param == 2 then
|
|
|
|
player = minetest.get_player_by_name(split_param[1])
|
|
|
|
value = split_param[2]
|
|
|
|
end
|
|
|
|
if not (player and value) then
|
|
|
|
minetest.chat_send_player(name, "Bad command. Use /add_energy [player] [value] or /add_energy [value]")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
exchangeclone.add_player_energy(player, tonumber(value))
|
2023-12-15 00:09:59 +01:00
|
|
|
minetest.chat_send_player(name, "Added "..value.." to "..name.."'s personal energy.")
|
2023-11-20 01:01:56 +01:00
|
|
|
end
|
|
|
|
})
|
|
|
|
|
2023-12-15 00:09:59 +01:00
|
|
|
minetest.register_chatcommand("set_player_energy", {
|
2023-11-20 01:01:56 +01:00
|
|
|
params = "[player] <value>",
|
|
|
|
description = "Set a player's personal energy (player is self if not included; use 'limit' as value to set it to maximum)",
|
|
|
|
privs = {privs = true},
|
|
|
|
func = function(name, param)
|
|
|
|
local split_param = exchangeclone.split(param, " ")
|
|
|
|
local player
|
|
|
|
local value
|
|
|
|
if #split_param == 1 then
|
|
|
|
player = minetest.get_player_by_name(name)
|
|
|
|
value = split_param[1]
|
|
|
|
end
|
|
|
|
if #split_param == 2 then
|
|
|
|
player = minetest.get_player_by_name(split_param[1])
|
|
|
|
value = split_param[2]
|
|
|
|
end
|
|
|
|
if not (player and value) then
|
|
|
|
minetest.chat_send_player(name, "Bad command. Use '/set_energy player value' or '/set_energy value'")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if value == "limit" then value = exchangeclone.limit end
|
|
|
|
exchangeclone.set_player_energy(player, tonumber(value))
|
2023-12-15 00:09:59 +01:00
|
|
|
minetest.chat_send_player(name, "Personal energy of "..name.." set to "..value)
|
|
|
|
end
|
|
|
|
})
|
|
|
|
|
|
|
|
exchangeclone.neighbors = {
|
|
|
|
{x=-1, y=0, z=0},
|
|
|
|
{x=1, y=0, z=0},
|
|
|
|
{x=0, y=-1, z=0},
|
|
|
|
{x=0, y=1, z=0},
|
|
|
|
{x=0, y=0, z=-1},
|
|
|
|
{x=0, y=0, z=1},
|
|
|
|
}
|
|
|
|
|
|
|
|
function exchangeclone.check_nearby_falling(pos)
|
|
|
|
for i=1, #exchangeclone.neighbors do
|
|
|
|
local new_pos = vector.add(pos, exchangeclone.neighbors[i])
|
|
|
|
if exchangeclone.mcl then
|
|
|
|
local node = minetest.get_node(new_pos)
|
|
|
|
if node.name == "mcl_core:vine" then
|
|
|
|
mcl_core.check_vines_supported(new_pos, node)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
minetest.check_single_for_falling(new_pos)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function exchangeclone.remove_nodes(positions)
|
|
|
|
for _, pos in ipairs(positions) do
|
|
|
|
exchangeclone.check_nearby_falling(pos)
|
|
|
|
end
|
|
|
|
minetest.bulk_set_node(positions, {name = "air"})
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
|
|
Recipes are registered with the same format that they are in minetest.register_craft:
|
|
|
|
{
|
|
|
|
type = <type>
|
|
|
|
recipe = <recipe>
|
|
|
|
output = <itemstring>
|
|
|
|
replacements = {{<itemstring>, <replace_itemstring>}, {<itemstring>, <replace_itemstring>}}
|
|
|
|
}
|
|
|
|
|
|
|
|
You do NOT have to call exchangeclone.register_craft if you use minetest.register_craft.
|
|
|
|
]]
|
|
|
|
|
|
|
|
--[[
|
|
|
|
name (string): The name of the crafting type.
|
|
|
|
recipe_type (string): One of the following:
|
|
|
|
shaped (default): Recipe is given in an array of arrays of ingredients.
|
|
|
|
shapeless: Recipe is given as a array of ingredients
|
|
|
|
cooking: Recipe is a single item
|
|
|
|
reverse (bool): Only applies for "cooking" recipe_type. If set to true, all recipes of this
|
|
|
|
type will be registered twice: once normally, and once with the recipe and output swapped.
|
|
|
|
]]
|
|
|
|
|
|
|
|
exchangeclone.craft_types = {}
|
|
|
|
|
|
|
|
function exchangeclone.register_craft_type(name, recipe_type, reverse)
|
|
|
|
exchangeclone.craft_types[name] = {type = recipe_type, reverse = true}
|
|
|
|
end
|
|
|
|
|
|
|
|
function exchangeclone.register_craft(data)
|
|
|
|
if not data.output then return end
|
|
|
|
local itemstring = ItemStack(data.output):get_name()
|
|
|
|
exchangeclone.recipes[itemstring] = exchangeclone.recipes[itemstring] or {}
|
|
|
|
table.insert(exchangeclone.recipes[itemstring], table.copy(data))
|
|
|
|
if data.type then
|
|
|
|
local type_data = exchangeclone.craft_types[data.type]
|
|
|
|
if type_data.type == "cooking" and type_data.reverse then
|
|
|
|
local flipped_data = table.copy(data)
|
|
|
|
flipped_data.output, flipped_data.recipe = flipped_data.recipe, flipped_data.output
|
|
|
|
local itemstring = ItemStack(flipped_data.output):get_name()
|
|
|
|
exchangeclone.recipes[itemstring] = exchangeclone.recipes[itemstring] or {}
|
|
|
|
table.insert(exchangeclone.recipes[itemstring], table.copy(flipped_data))
|
|
|
|
end
|
2023-11-20 01:01:56 +01:00
|
|
|
end
|
2023-12-15 00:09:59 +01:00
|
|
|
end
|