-- A ton of functions with approximately zero organization. At least there are comments now. local S = minetest.get_translator() local exchangeclone = exchangeclone --- 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 exchangeclone.width = exchangeclone.mtg and 8 or 9 --- Adds all items in a certain list to a table. function exchangeclone.get_inventory_drops(pos, listname, drops) local inv = minetest.get_meta(pos):get_inventory() local n = #drops for i = 1, inv:get_size(listname) do local stack = inv:get_stack(listname, i) if stack:get_count() > 0 then drops[n+1] = stack:to_table() n = n + 1 end end end function exchangeclone.on_blast(lists) return function(pos) local drops = {} for _, list in pairs(lists) do exchangeclone.get_inventory_drops(pos, list, drops) end table.insert(drops, minetest.get_node(pos).name) minetest.remove_node(pos) if exchangeclone.mcl then for _, drop in pairs(drops) do local p = {x=pos.x+math.random(0, 10)/10-0.5, y=pos.y, z=pos.z+math.random(0, 10)/10-0.5} minetest.add_item(p, drop) end end return drops end end --- Gets the EMC value of an itemstring or ItemStack --- Handles "group:group_name" syntax (although it goes through every item), returns cheapest item in group function exchangeclone.get_item_emc(item) if (item == "") or not item then return end -- handle groups 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 pairs(exchangeclone.group_values) do if item_group == group[1] then return group[2] end end local group_items = exchangeclone.get_group_items(item_group) local cheapest for _, group_item in pairs(group_items[item_group]) do if group_item then local emc_value = exchangeclone.get_item_emc(group_item) if emc_value then if emc_value > 0 and ((not cheapest) or emc_value < cheapest) then cheapest = emc_value end end end end return cheapest end -- Only check metadata for ItemStacks if type(item) == "userdata" then local meta_emc_value = item:get_meta():get_string("exchangeclone_emc_value") if meta_emc_value == "none" then return 0 elseif tonumber(meta_emc_value) then return math.max(0, tonumber(meta_emc_value)) end end -- handle items/itemstacks item = ItemStack(item) if item == ItemStack("") then return end item:set_name(exchangeclone.handle_alias(item)) local def = minetest.registered_items[item:get_name()] if not def then return end if minetest.get_item_group(item:get_name(), "klein_star") > 0 then if def.emc_value then return def.emc_value + exchangeclone.get_star_itemstack_emc(item) end end if def.emc_value then return (def.emc_value) * item:get_count() end end -- https://forum.unity.com/threads/re-map-a-number-from-one-range-to-another.119437/ -- Remaps a number from one range to another function exchangeclone.map(input, min1, max1, min2, max2) return (input - min1) / (max1 - min1) * (max2 - min2) + min2 end -- Gets the EMC stored in a specified Klein/Magnum Star itemstack. function exchangeclone.get_star_itemstack_emc(itemstack) if not itemstack then return end if minetest.get_item_group(itemstack:get_name(), "klein_star") < 1 then return end return math.max(itemstack:get_meta():get_float("stored_energy"), 0) end -- Gets the amount of EMC stored in a star in a specific inventory slot function exchangeclone.get_star_emc(inventory, listname, index) if not inventory then return end if not listname then listname = "main" end if not index then index = 1 end local itemstack = inventory:get_stack(listname, index) return exchangeclone.get_star_itemstack_emc(itemstack) end function exchangeclone.set_star_itemstack_emc(itemstack, amount) if not itemstack or not amount then return end if minetest.get_item_group(itemstack:get_name(), "klein_star") < 1 then return end local old_emc = exchangeclone.get_star_itemstack_emc(itemstack) local max = exchangeclone.get_star_max(itemstack) if amount > old_emc and old_emc > max then return end -- don't allow more EMC to be put into an over-filled star local meta = itemstack:get_meta() meta:set_float("stored_energy", amount) -- Unfortunately, this is still "energy" not EMC meta:set_string("description", itemstack:get_definition()._mcl_generate_description(itemstack)) local wear = math.max(1, math.min(65535, 65535 - 65535*amount/max)) itemstack:set_wear(wear) return itemstack end -- Sets the amount of EMC in a star in a specific inventory slot function exchangeclone.set_star_emc(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_star_itemstack_emc(itemstack, amount) if not new_stack then return end inventory:set_stack(listname, index, new_stack) end function exchangeclone.add_star_itemstack_emc(itemstack, amount) if itemstack and amount then local emc = exchangeclone.get_star_itemstack_emc(itemstack) + amount if not emc or emc < 0 or emc > exchangeclone.get_star_max(itemstack) then return end return exchangeclone.set_star_itemstack_emc(itemstack, emc) end end -- Adds to the amount of EMC in a star in a specific inventory slot function exchangeclone.add_star_emc(inventory, listname, index, amount) if not (inventory and listname and index and amount) 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.add_star_itemstack_emc(itemstack, amount) if not new_stack then return end inventory:set_stack(listname, index, new_stack) end function exchangeclone.get_star_max(item) item = ItemStack(item) return item:get_definition().max_capacity or 0 end -- HUD stuff (show EMC value in bottom right) local hud_elements = {} function exchangeclone.update_hud(player) local hud_text = hud_elements[player:get_player_name()] player:hud_change(hud_text, "text", S("Personal EMC: @1", exchangeclone.format_number(exchangeclone.get_player_emc(player)))) 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}, text = S("Personal EMC: @1", 0), 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) -- Get a player's personal EMC function exchangeclone.get_player_emc(player) -- Can't really change it to "EMC" without everyone losing everything return tonumber(player:get_meta():get_string("exchangeclone_stored_energy")) or 0 end -- Set a player's personal EMC function exchangeclone.set_player_emc(player, amount) amount = tonumber(amount) if not (player and amount) then return end if amount < 0 or amount > exchangeclone.limit then return end player:get_meta():set_string("exchangeclone_stored_energy", tonumber(amount)) exchangeclone.update_hud(player) end -- Add to a player's personal EMC (amount can be negative) function exchangeclone.add_player_emc(player, amount) if not (player and amount) then return end exchangeclone.set_player_emc(player, (exchangeclone.get_player_emc(player) or 0) + amount) end -- Through trial and error, I have found that this number (1 trillion) works the best. -- When a player has any more EMC (as in ANY more), precision-based exploits such as creating infinite glass panes are possible. -- I temporarily considered finding some Lua library that allowed for arbitrary precision (and therefore infinite maximum EMC) -- 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*)') if not int then return tostring(number) end -- 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) 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 -- Returns a table of all items in the specified group(s). function exchangeclone.get_group_items(groups, allow_duplicates, include_no_group) if type(groups) ~= "table" then if type(groups) == "string" then groups = {groups} else return end end allow_duplicates = allow_duplicates or false include_no_group = include_no_group or false local num_groups = #groups local result = {} for i = 1, num_groups do result[groups[i]] = {} end if include_no_group then result["NO_GROUP"] = {} end local in_group -- copied from... somewhere for name, def in pairs(minetest.registered_items) do in_group = false for i = 1, num_groups do local grp = groups[i] local subgroups = exchangeclone.split(grp, ",") local success = true for _, subgroup in pairs(subgroups) do local group_info = exchangeclone.split(subgroup, "=") if #group_info == 1 then if minetest.get_item_group(name, subgroup) <= 0 then success = false break end elseif #group_info == 2 then if minetest.get_item_group(name, group_info[1]) ~= tonumber(group_info[2]) then success = false break end else success = false break end end if success then 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 -- Plays the sound caused by ExchangeClone abilities function exchangeclone.play_sound(player, sound, pitch) if not player then return end minetest.sound_play(sound, {pitch = pitch or 1, pos = player:get_pos(), max_hear_distance = 20, }) end -- Check the clicked node for a right-click function. 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 -- Update the charge level of an ExchangeClone tool function exchangeclone.charge_update(itemstack, player) itemstack = ItemStack(itemstack) -- don't affect original local charge_type = exchangeclone.charge_types[itemstack:get_name()] local max_charge = exchangeclone.tool_levels.count[charge_type] if not max_charge then return itemstack end local charge = math.max(itemstack:get_meta():get_int("exchangeclone_tool_charge"), 1) local new_pitch = 0.5 + ((0.5 / (max_charge - 1)) * (charge-1)) if player:get_player_control().sneak then if charge > 1 then exchangeclone.play_sound(player, "exchangeclone_charge_down", new_pitch) charge = charge - 1 end elseif charge < max_charge then exchangeclone.play_sound(player, "exchangeclone_charge_up", new_pitch) charge = charge + 1 end itemstack:get_meta():set_int("exchangeclone_tool_charge", charge) itemstack:set_wear(math.max(1, math.min(65535, 65535-(65535/(max_charge-1))*(charge-1)))) itemstack = exchangeclone.update_tool_capabilities(itemstack) return itemstack end -- Itemstrings for various items used in crafting recipes. exchangeclone.itemstrings = { cobble = exchangeclone.mcl and "mcl_core:cobble" or "default:cobble", redstoneworth = exchangeclone.mcl and "mesecons:redstone" or "default:obsidian", obsidian = exchangeclone.mcl and "mcl_core:obsidian" or "default:obsidian", glowstoneworth = exchangeclone.mcl and "mcl_nether:glowstone_dust" or "default:tin_ingot", lapisworth = exchangeclone.mcl and "mcl_core:lapis" or "bucket:bucket_lava", 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", gravel = exchangeclone.mcl and "mcl_core:gravel" or "default:gravel", dirt = exchangeclone.mcl and "mcl_core:dirt" or "default:dirt", clay = exchangeclone.mcl and "mcl_core:clay" or "default:clay", sand = exchangeclone.mcl and "mcl_core:sand" or "default:sand", torch = exchangeclone.mcl and "mcl_torches:torch" or "default:torch" } exchangeclone.emc_aliases = {} -- will be treated as in Deconstructors, Constructors, Transmutation Table(t)s, etc. -- When you put into a TT, you will learn instead. function exchangeclone.register_alias_force(alias, itemstring) if alias == itemstring then return end exchangeclone.emc_aliases[itemstring] = alias end function exchangeclone.register_alias(alias, itemstring) if not exchangeclone.emc_aliases[alias] then exchangeclone.register_alias_force(alias, itemstring) end end -- Returns the correct itemstring, handling both Minetest and Exchangeclone aliases. function exchangeclone.handle_alias(item) item = ItemStack(item) if not item:is_empty() then local de_aliased = exchangeclone.emc_aliases[item:get_name()] or item:get_name() -- Resolve ExchangeClone aliases return ItemStack(de_aliased):get_name() or item:get_name() -- Resolve MT aliases end end -- Returns a player's inventory formspec with the correct width and hotbar position for the current game function exchangeclone.inventory_formspec(x,y) local formspec if exchangeclone.mcl then formspec = "list[current_player;main;"..x..","..y..";9,3;9]".. mcl_formspec.get_itemslot_bg(x,y,9,3).. "list[current_player;main;"..x..","..(y+3.25)..";9,1]".. mcl_formspec.get_itemslot_bg(x,y+3.25,9,1) else formspec = "list[current_player;main;"..x..","..y..";8,1]".. "list[current_player;main;"..x..","..(y+1.25)..";8,3;8]" end return formspec end -- Modified from MineClone2, basically helps drop items on the player { 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 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 -- 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 if exchangeclone.mtg 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 pairs(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 ]] local enchantments = tool and mcl_enchanting.get_enchantments(tool) 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 if tool and nodedef._mcl_fortune_drop and enchantments.fortune then 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 pairs(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 -- } -- Get the direction a player is facing (rounded to -1, 0, and 1 for each axis) 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 -- Cooldowns 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) -- Start a