--[[ Everness. Never ending discovery in Everness mapgen. Copyright (C) 2024 SaKeL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. --]] local S = minetest.get_translator(minetest.get_current_modname()) local rand_global = PcgRandom(tonumber(tostring(os.time()):reverse():sub(1, 9))) --- Base class ---@class Everness Everness = { bamboo = { -- based on height growth_stages = { --height [1] = { -- next plant { name = 'everness:bamboo_1' }, { name = 'everness:bamboo_2' }, }, [2] = { { name = 'everness:bamboo_1' }, { name = 'everness:bamboo_2' }, { name = 'everness:bamboo_2' }, }, [3] = { { name = 'everness:bamboo_3' }, { name = 'everness:bamboo_4' }, { name = 'everness:bamboo_4' }, { name = 'everness:bamboo_5' }, }, [4] = { { name = 'everness:bamboo_3' }, { name = 'everness:bamboo_3' }, { name = 'everness:bamboo_4' }, { name = 'everness:bamboo_5' }, { name = 'everness:bamboo_5' }, }, }, top_leaves_schem = { { name = 'everness:bamboo_4' }, { name = 'everness:bamboo_5' }, { name = 'everness:bamboo_5' }, } }, loot_chest = { default = {}, }, settings = { biomes = { everness_coral_forest = { enabled = minetest.settings:get_bool('everness_coral_forest', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_coral_forest_y_min')) or 6, }, everness_coral_forest_dunes = { enabled = minetest.settings:get_bool('everness_coral_forest_dunes', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_dunes_y_max')) or 5, y_min = tonumber(minetest.settings:get('everness_coral_forest_dunes_y_min')) or 4, }, everness_coral_forest_ocean = { enabled = minetest.settings:get_bool('everness_coral_forest_ocean', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_ocean_y_max')) or 3, y_min = tonumber(minetest.settings:get('everness_coral_forest_ocean_y_min')) or -10, }, everness_coral_forest_deep_ocean = { enabled = minetest.settings:get_bool('everness_coral_forest_deep_ocean', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_deep_ocean_y_max')) or -11, y_min = tonumber(minetest.settings:get('everness_coral_forest_deep_ocean_y_min')) or -255, }, everness_coral_forest_under = { enabled = minetest.settings:get_bool('everness_coral_forest_under', true), y_max = tonumber(minetest.settings:get('everness_coral_forest_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_coral_forest_under_y_min')) or -31000, }, everness_frosted_icesheet = { enabled = minetest.settings:get_bool('everness_frosted_icesheet', true), y_max = tonumber(minetest.settings:get('everness_frosted_icesheet_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_frosted_icesheet_y_min')) or -8, }, everness_frosted_icesheet_ocean = { enabled = minetest.settings:get_bool('everness_frosted_icesheet_ocean', true), y_max = tonumber(minetest.settings:get('everness_frosted_icesheet_ocean_y_max')) or -9, y_min = tonumber(minetest.settings:get('everness_frosted_icesheet_ocean_y_min')) or -255, }, everness_frosted_icesheet_under = { enabled = minetest.settings:get_bool('everness_frosted_icesheet_under', true), y_max = tonumber(minetest.settings:get('everness_frosted_icesheet_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_frosted_icesheet_under_y_min')) or -31000, }, everness_cursed_lands = { enabled = minetest.settings:get_bool('everness_cursed_lands', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_cursed_lands_y_min')) or 6, }, everness_cursed_lands_dunes = { enabled = minetest.settings:get_bool('everness_cursed_lands_dunes', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_dunes_y_max')) or 5, y_min = tonumber(minetest.settings:get('everness_cursed_lands_dunes_y_min')) or 1, }, everness_cursed_lands_swamp = { enabled = minetest.settings:get_bool('everness_cursed_lands_swamp', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_swamp_y_max')) or 0, y_min = tonumber(minetest.settings:get('everness_cursed_lands_swamp_y_min')) or -1, }, everness_cursed_lands_ocean = { enabled = minetest.settings:get_bool('everness_cursed_lands_ocean', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_ocean_y_max')) or -2, y_min = tonumber(minetest.settings:get('everness_cursed_lands_ocean_y_min')) or -10, }, everness_cursed_lands_deep_ocean = { enabled = minetest.settings:get_bool('everness_cursed_lands_ocean', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_ocean_y_max')) or -11, y_min = tonumber(minetest.settings:get('everness_cursed_lands_ocean_y_min')) or -255, }, everness_cursed_lands_under = { enabled = minetest.settings:get_bool('everness_cursed_lands_under', true), y_max = tonumber(minetest.settings:get('everness_cursed_lands_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_cursed_lands_under_y_min')) or -31000, }, everness_crystal_forest = { enabled = minetest.settings:get_bool('everness_crystal_forest', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_crystal_forest_y_min')) or 6, }, everness_crystal_forest_dunes = { enabled = minetest.settings:get_bool('everness_crystal_forest_dunes', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_dunes_y_max')) or 5, y_min = tonumber(minetest.settings:get('everness_crystal_forest_dunes_y_min')) or 1, }, everness_crystal_forest_shore = { enabled = minetest.settings:get_bool('everness_crystal_forest_shore', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_shore_y_max')) or 0, y_min = tonumber(minetest.settings:get('everness_crystal_forest_shore_y_min')) or -1, }, everness_crystal_forest_ocean = { enabled = minetest.settings:get_bool('everness_crystal_forest_ocean', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_ocean_y_max')) or -2, y_min = tonumber(minetest.settings:get('everness_crystal_forest_ocean_y_min')) or -10, }, everness_crystal_forest_deep_ocean = { enabled = minetest.settings:get_bool('everness_crystal_forest_deep_ocean', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_deep_ocean_y_max')) or -11, y_min = tonumber(minetest.settings:get('everness_crystal_forest_deep_ocean_y_min')) or -255, }, everness_crystal_forest_under = { enabled = minetest.settings:get_bool('everness_crystal_forest_under', true), y_max = tonumber(minetest.settings:get('everness_crystal_forest_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_crystal_forest_under_y_min')) or -31000, }, everness_bamboo_forest = { enabled = minetest.settings:get_bool('everness_bamboo_forest', true), y_max = tonumber(minetest.settings:get('everness_bamboo_forest_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_bamboo_forest_y_min')) or 1, }, everness_bamboo_forest_under = { enabled = minetest.settings:get_bool('everness_bamboo_forest_under', true), y_max = tonumber(minetest.settings:get('everness_bamboo_forest_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_bamboo_forest_under_y_min')) or -31000, }, everness_forsaken_desert = { enabled = minetest.settings:get_bool('everness_forsaken_desert', true), y_max = tonumber(minetest.settings:get('everness_forsaken_desert_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_forsaken_desert_y_min')) or 4, }, everness_forsaken_desert_ocean = { enabled = minetest.settings:get_bool('everness_forsaken_desert_ocean', true), y_max = tonumber(minetest.settings:get('everness_forsaken_desert_ocean_y_max')) or 3, y_min = tonumber(minetest.settings:get('everness_forsaken_desert_ocean_y_min')) or -8, }, everness_forsaken_desert_under = { enabled = minetest.settings:get_bool('everness_forsaken_desert_under', true), y_max = tonumber(minetest.settings:get('everness_forsaken_desert_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_forsaken_desert_under_y_min')) or -31000, }, everness_baobab_savanna = { enabled = minetest.settings:get_bool('everness_baobab_savanna', true), y_max = tonumber(minetest.settings:get('everness_baobab_savanna_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_baobab_savanna_y_min')) or 1, }, everness_forsaken_tundra = { enabled = minetest.settings:get_bool('everness_forsaken_tundra', true), y_max = tonumber(minetest.settings:get('everness_forsaken_tundra_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_y_min')) or 2, }, everness_forsaken_tundra_beach = { enabled = minetest.settings:get_bool('everness_forsaken_tundra_beach', true), y_max = tonumber(minetest.settings:get('everness_forsaken_tundra_beach_y_max')) or 1, y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_beach_y_min')) or -3, }, everness_forsaken_tundra_ocean = { enabled = minetest.settings:get_bool('everness_forsaken_tundra_ocean', true), y_max = tonumber(minetest.settings:get('everness_forsaken_tundra_ocean_y_max')) or -4, y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_ocean_y_min')) or -255, }, everness_forsaken_tundra_under = { enabled = minetest.settings:get_bool('everness_forsaken_tundra_under', true), y_max = tonumber(minetest.settings:get('everness_forsaken_tundra_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_forsaken_tundra_under_y_min')) or -31000, }, everness_mineral_waters = { enabled = minetest.settings:get_bool('everness_mineral_waters', true), y_max = tonumber(minetest.settings:get('everness_mineral_waters_y_max')) or 31000, y_min = tonumber(minetest.settings:get('everness_mineral_waters_y_min')) or 1, }, everness_mineral_waters_under = { enabled = minetest.settings:get_bool('everness_mineral_waters_under', true), y_max = tonumber(minetest.settings:get('everness_mineral_waters_under_y_max')) or -256, y_min = tonumber(minetest.settings:get('everness_mineral_waters_under_y_min')) or -31000, }, }, features = { everness_feature_sneak_pickup = minetest.settings:get_bool('everness_feature_sneak_pickup', true), everness_feature_skybox = minetest.settings:get_bool('everness_feature_skybox', true), } }, hammer_cid_data = {}, colors = { brown = '#DEB887', }, registered_nodes = {}, registered_tools = {}, registered_abms = {}, registered_lbms = {}, registered_craftitems = {}, registered_biomes = {}, registered_decorations = {}, registered_ores = {}, on_generated_queue = {} } function Everness.grow_cactus(self, pos, node, params) local node_copy = table.copy(node) if node.param2 >= 4 then return end pos.y = pos.y - 1 if minetest.get_item_group(minetest.get_node(pos).name, 'sand') == 0 and minetest.get_item_group(minetest.get_node(pos).name, 'everness_sand') == 0 then return end pos.y = pos.y + 1 local height = 0 while (node.name == 'everness:cactus_orange' or node.name == 'everness:cactus_blue') and height < 5 do height = height + 1 pos.y = pos.y + 1 node = minetest.get_node(pos) end if height == 5 or node.name ~= 'air' then return end if minetest.get_node_light(pos) < 13 then return end minetest.set_node(pos, { name = node_copy.name }) return true end function Everness.emerge_area(self, blockpos, action, calls_remaining, param) if not param.total then param.total = calls_remaining + 1 param.current = 0 end param.current = param.current + 1 if param.total == param.current then param.callback(param.data) end end -- how often node timers for plants will tick, +/- some random value function Everness.tick_vine(self, pos) minetest.get_node_timer(pos):start(math.random(5, 10)) end -- how often a growth failure tick is retried (e.g. too dark) function Everness.tick_vine_again(self, pos) minetest.get_node_timer(pos):start(math.random(40, 80)) end -- how often node timers for plants will tick, +/- some random value function Everness.tick_sulfur_stone(self, pos) minetest.get_node_timer(pos):start(math.random(5, 10)) end -- how often a growth failure tick is retried (e.g. too dark) function Everness.tick_sulfur_stone_again(self, pos) minetest.get_node_timer(pos):start(math.random(40, 80)) end -- Grows vines -- @param pos {vector} function Everness.grow_vine(self, pos, elapsed, params) local node = minetest.get_node(pos) local pos_under = vector.new(pos.x, pos.y - 1, pos.z) local node_under = minetest.get_node(pos_under) local node_names = params.node_names local end_node_name = params.end_node_name local end_node_param2 = params.end_node_param2 -- get length local length = 0 local temp_node = node while minetest.get_item_group(temp_node.name, 'vine') > 0 and length < 16 do length = length + 1 temp_node = minetest.get_node(vector.new(pos.x, pos.y + length, pos.z)) end -- stop growing - random height between 12 - 16 nodes if length > 11 and length < 16 then if math.random(1, 3) == 2 then return end end if length >= 16 then return end if minetest.get_item_group(node_under.name, 'vine') > 0 then -- stop timer for gown vine return end if node_under.name ~= 'air' then Everness:tick_vine_again(pos) return end local new_node_name = node_names[math.random(1, #node_names)] minetest.set_node(pos, { name = new_node_name, param2 = new_node_name.param2 or 0 }) -- last hanging vine minetest.set_node(pos_under, { name = end_node_name, param2 = end_node_param2 and end_node_param2 or node.param2 }) Everness:tick_vine(pos_under) end -- -- Sounds -- function Everness.node_sound_defaults(table) table = table or {} table.footstep = table.footstep or { name = '', gain = 1.0 } table.dug = table.dug or { name = 'everness_stone_hit', gain = 1.0 } table.place = table.place or { name = 'everness_stone_dug', gain = 0.6 } return table end function Everness.node_sound_frosted_snow_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_frosted_snow_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_frosted_snow_hit', gain = 0.2 } table.dug = table.dug or { name = 'everness_frosted_snow_footstep', gain = 0.3 } table.place = table.place or { name = 'everness_frosted_snow_place', gain = 0.25 } return table end function Everness.node_sound_crystal_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_crystal_chime', gain = 0.2 } table.dig = table.dig or { name = 'everness_crystal_chime', gain = 0.3 } table.dug = table.dug or { name = 'everness_stone_footstep', gain = 0.3 } table.place = table.place or { name = 'everness_crystal_chime', gain = 1.0 } return table end function Everness.node_sound_bamboo_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_bamboo_hit', gain = 0.2 } table.dig = table.dig or { name = 'everness_bamboo_hit', gain = 0.3 } table.dug = table.dug or { name = 'everness_bamboo_dug', gain = 0.1 } table.place = table.place or { name = 'everness_bamboo_hit', gain = 1.0 } return table end function Everness.node_sound_mud_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_mud_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_mud_footstep', gain = 0.3 } table.dug = table.dug or { name = 'everness_mud_footstep', gain = 0.1 } table.place = table.place or { name = 'everness_mud_footstep', gain = 1.0 } return table end function Everness.node_sound_grass_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_grass_footstep', gain = 0.4 } table.dig = table.dig or { name = 'everness_grass_hit', gain = 1.2 } table.dug = table.dug or { name = 'everness_dirt_hit', gain = 1.0 } table.place = table.place or { name = 'everness_dirt_hit', gain = 1.0 } return table end function Everness.node_sound_dirt_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_dirt_footstep', gain = 0.15 } table.dig = table.dig or { name = 'everness_dirt_hit', gain = 0.4 } table.dug = table.dug or { name = 'everness_dirt_hit', gain = 1.0 } table.place = table.place or { name = 'everness_dirt_hit', gain = 1.0 } return table end function Everness.node_sound_ice_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_ice_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_ice_hit', gain = 0.4 } table.dug = table.dug or { name = 'everness_ice_hit', gain = 1.0 } table.place = table.place or { name = 'everness_ice_hit', gain = 1.0 } return table end function Everness.node_sound_stone_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_stone_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_stone_hit', gain = 1.0 } table.dug = table.dug or { name = 'everness_stone_dug', gain = 0.6 } table.place = table.place or { name = 'everness_stone_place', gain = 1.0 } return table end function Everness.node_sound_leaves_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_leaves_footstep', gain = 0.1 } table.dig = table.dig or { name = 'everness_leaves_hit', gain = 0.25 } table.dug = table.dug or { name = 'everness_leaves_dug', gain = 0.5 } table.place = table.place or { name = 'everness_leaves_place', gain = 0.4 } return table end function Everness.node_sound_wood_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_wood_footstep', gain = 0.15 } table.dig = table.dig or { name = 'everness_wood_hit', gain = 0.8 } table.dug = table.dug or { name = 'everness_wood_place', gain = 0.1 } table.place = table.place or { name = 'everness_wood_place', gain = 0.15 } return table end function Everness.node_sound_sand_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_sand_footstep', gain = 0.1 } table.dig = table.dig or { name = 'everness_sand_hit', gain = 0.5 } table.dug = table.dug or { name = 'everness_sand_dug', gain = 0.1 } table.place = table.place or { name = 'everness_sand_place', gain = 0.15 } return table end function Everness.node_sound_metal_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_metal_footstep', gain = 0.1 } table.dig = table.dig or { name = 'everness_metal_hit', gain = 0.5 } table.dug = table.dug or { name = 'everness_metal_dug', gain = 0.1 } table.place = table.place or { name = 'everness_metal_place', gain = 0.15 } return table end function Everness.node_sound_glass_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_glass_footstep', gain = 0.02 } table.dig = table.dig or { name = 'everness_glass_footstep', gain = 0.05 } table.dug = table.dug or { name = 'everness_glass_dug', gain = 0.4 } table.place = table.place or { name = 'everness_glass_place', gain = 0.2 } return table end function Everness.node_sound_thin_glass_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_thin_glass_footstep', gain = 0.3 } table.dig = table.dig or { name = 'everness_thin_glass_footstep', gain = 0.5 } table.dug = table.dug or { name = 'everness_break_thin_glass', gain = 1.0 } table.place = table.place or { name = 'everness_glass_place', gain = 0.2 } return table end function Everness.node_sound_snow_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_snow_footstep', gain = 0.1 } table.dig = table.dig or { name = 'everness_snow_hit', gain = 0.2 } table.dug = table.dug or { name = 'everness_snow_footstep', gain = 0.2 } table.place = table.place or { name = 'everness_snow_place', gain = 0.3 } return table end function Everness.node_sound_gravel_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_gravel_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_gravel_hit', gain = 1.0 } table.dug = table.dug or { name = 'everness_gravel_dug', gain = 0.6 } table.place = table.place or { name = 'everness_gravel_place', gain = 1.0 } return table end function Everness.node_sound_ceramic_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_ceramic_footstep', gain = 0.2 } table.dig = table.dig or { name = 'everness_ceramic_hit', gain = 1.0 } table.dug = table.dug or { name = 'everness_ceramic_dug', gain = 1.0 } table.place = table.place or { name = 'everness_ceramic_place', gain = 1.0 } return table end function Everness.node_sound_water_defaults(table) table = table or {} table.footstep = table.footstep or { name = 'everness_water_footstep', gain = 0.05 } Everness.node_sound_defaults(table) return table end -- -- Forsted Cave Icicles -- function Everness.stack_icicle_recursive(node, pos_node, incrementer, pos_marker, direction) local nb = node local pos = pos_node local inc = incrementer local m_pos = pos_marker while nb.name == 'air' or nb.name == 'ignore' do if nb.name == 'ignore' then Everness.emerge_icicle_area_recursive(pos, inc, m_pos, direction) break else minetest.set_node(pos, { name = 'everness:frosted_cave_ice_illuminating' }) -- Shift 1 down inc = inc + 1 local y_offset = (direction == 'down') and (m_pos.y - inc) or (m_pos.y + inc) pos = vector.new(m_pos.x, y_offset, m_pos.z) nb = minetest.get_node(pos) end end end function Everness.emerge_icicle_area_recursive(pos_node, incrementer, pos_marker, direction) local y_offset = (direction == 'down') and (pos_node.y - 16) or (pos_node.y + 16) minetest.emerge_area( vector.new(pos_node.x - 1, pos_node.y, pos_node.z - 1), vector.new(pos_node.x + 1, y_offset, pos_node.z + 1), function(blockpos, action, calls_remaining, param) Everness:emerge_area(blockpos, action, calls_remaining, param) end, { callback = function(data) local incrementer_cllbck = data.incrementer local pos_node_cllbck = data.pos_node local node_cllbck = minetest.get_node(pos_node_cllbck) Everness.stack_icicle_recursive(node_cllbck, pos_node_cllbck, incrementer_cllbck, pos_marker, direction) end, data = { incrementer = incrementer, pos_node = pos_node } } ) end function Everness.use_shell_of_underwater_breathing(self, itemstack, user, pointed_thing) if not user then return end local pos_player = user:get_pos() if pointed_thing.type == 'node' then local pos_pt = minetest.get_pointed_thing_position(pointed_thing) if not pos_pt then return itemstack end local pointed_node = minetest.get_node(pos_pt) local pointed_node_def = minetest.registered_nodes[pointed_node.name] if not pointed_node or not pointed_node_def then return itemstack end if pointed_node_def.on_rightclick then return pointed_node_def.on_rightclick(pos_pt, pointed_node, user, itemstack, pointed_thing) end end local node_head = minetest.get_node( vector.new( math.floor(pos_player.x + 0.5), math.ceil(pos_player.y + 1), math.floor(pos_player.z + 0.5) ) ) local breath = user:get_breath() if minetest.get_item_group(node_head.name, 'water') > 0 and breath < 9 then -- Under water user:set_breath(9) if not minetest.settings:get_bool('creative_mode') or not minetest.check_player_privs(user:get_player_name(), { creative = true }) then local wear_to_add = 65535 / 20 if itemstack:get_wear() + wear_to_add > 65535 then local itemstack_def = itemstack:get_definition() -- Break tool minetest.sound_play(itemstack_def.sound.breaks, { pos = pos_player, gain = 0.5 }, true) end itemstack:add_wear(wear_to_add) end minetest.sound_play('everness_underwater_bubbles', { object = user, gain = 1.0, max_hear_distance = 16 }) minetest.add_particlespawner({ amount = 40, time = 0.1, pos = { min = vector.new(pos_player.x - 0.25, pos_player.y + 1.25, pos_player.z - 0.25), max = vector.new(pos_player.x + 0.25, pos_player.y + 1.5, pos_player.z + 0.25) }, vel = { min = vector.new(-0.5, 0, -0.5), max = vector.new(0.5, 0, 0.5) }, acc = { min = vector.new(-0.5, 4, -0.5), max = vector.new(0.5, 1, 0.5), }, exptime = { min = 1, max = 2 }, size = { min = 0.5, max = 2 }, texture = { name = 'everness_bubble.png', alpha_tween = { 1, 0, start = 0.75 } } }) end return itemstack end -- -- Sapling 'on place' function to check protection of node and resulting tree volume -- copy from MTG -- function Everness.sapling_on_place(self, itemstack, placer, pointed_thing, props) if minetest.get_modpath('mcl_util') and minetest.global_exists('mcl_util') then local on_place_func = mcl_util.generate_on_place_plant_function(function(pos, node) local node_below = minetest.get_node_or_nil({ x = pos.x, y = pos.y - 1, z = pos.z }) if not node_below then return false end local nn = node_below.name return minetest.get_item_group(nn, 'grass_block') == 1 or (nn == 'everness:mineral_sand' and itemstack:get_name() == 'everness:palm_tree_sapling') or nn == 'mcl_core:podzol' or nn == 'mcl_core:podzol_snow' or nn == 'mcl_core:dirt' or nn == 'mcl_core:mycelium' or nn == 'mcl_core:coarse_dirt' end) return on_place_func(itemstack, placer, pointed_thing) else local _props = props or {} local sapling_name = _props.sapling_name -- minp, maxp to be checked, relative to sapling pos -- minp_relative.y = 1 because sapling pos has been checked local minp_relative = _props.minp_relative local maxp_relative = _props.maxp_relative -- maximum interval of interior volume check local interval = _props.interval -- Position of sapling local pos = pointed_thing.under local node = minetest.get_node_or_nil(pos) local pdef = node and minetest.registered_nodes[node.name] if pdef and node and pdef.on_rightclick and not (placer and placer:is_player() and placer:get_player_control().sneak) then return pdef.on_rightclick(pos, node, placer, itemstack, pointed_thing) end if not pdef or not pdef.buildable_to then pos = pointed_thing.above node = minetest.get_node_or_nil(pos) pdef = node and minetest.registered_nodes[node.name] if not pdef or not pdef.buildable_to then return itemstack end end local player_name = placer and placer:get_player_name() or '' -- Check sapling position for protection if minetest.is_protected(pos, player_name) then minetest.record_protection_violation(pos, player_name) return itemstack end -- Check tree volume for protection if minetest.is_area_protected( vector.add(pos, minp_relative), vector.add(pos, maxp_relative), player_name, interval ) then minetest.record_protection_violation(pos, player_name) minetest.chat_send_player( player_name, S('@1 will intersect protection on growth.', itemstack:get_definition().description) ) return itemstack end Everness.log_player_action(placer, 'places node', sapling_name, 'at', pos) local take_item = not minetest.is_creative_enabled(player_name) local newnode = { name = sapling_name } local ndef = minetest.registered_nodes[sapling_name] minetest.set_node(pos, newnode) -- Run callback if ndef and ndef.after_place_node then -- Deepcopy place_to and pointed_thing because callback can modify it if ndef.after_place_node(table.copy(pos), placer, itemstack, table.copy(pointed_thing)) then take_item = false end end -- Run script hook for _, callback in ipairs(minetest.registered_on_placenodes or {}) do -- Deepcopy pos, node and pointed_thing because callback can modify them if callback(table.copy(pos), table.copy(newnode), placer, table.copy(node or {}), itemstack, table.copy(pointed_thing)) then take_item = false end end if take_item then itemstack:take_item() end return itemstack end end -- -- Leafdecay - taken from MTG -- -- Prevent decay of placed leaves Everness.after_place_leaves = function(self, pos, placer, itemstack, pointed_thing) if placer and placer:is_player() then local node = minetest.get_node(pos) node.param2 = 1 minetest.set_node(pos, node) end end -- Leafdecay local function leafdecay_after_destruct(pos, oldnode, def) for _, v in pairs(minetest.find_nodes_in_area(vector.subtract(pos, def.radius), vector.add(pos, def.radius), def.leaves)) do local node = minetest.get_node(v) local timer = minetest.get_node_timer(v) if node.param2 ~= 1 and minetest.get_meta(v):get_int('everness_prevent_leafdecay') ~= 1 and not timer:is_started() then timer:start(math.random(20, 120) / 10) end end end local movement_gravity = tonumber(minetest.settings:get('movement_gravity')) or 9.81 local function leafdecay_on_timer(pos, def) if minetest.find_node_near(pos, def.radius, def.trunks) then return false end local node = minetest.get_node(pos) local drops = minetest.get_node_drops(node.name) for _, item in ipairs(drops) do local is_leaf for _, v in pairs(def.leaves) do if v == item then is_leaf = true end end if minetest.get_item_group(item, 'leafdecay_drop') ~= 0 or not is_leaf then minetest.add_item({ x = pos.x - 0.5 + math.random(), y = pos.y - 0.5 + math.random(), z = pos.z - 0.5 + math.random(), }, item) end end minetest.remove_node(pos) minetest.check_for_falling(pos) -- spawn a few particles for the removed node minetest.add_particlespawner({ amount = 8, time = 0.001, minpos = vector.subtract(pos, { x = 0.5, y = 0.5, z = 0.5 }), maxpos = vector.add(pos, { x = 0.5, y = 0.5, z = 0.5 }), minvel = vector.new(-0.5, -1, -0.5), maxvel = vector.new(0.5, 0, 0.5), minacc = vector.new(0, -movement_gravity, 0), maxacc = vector.new(0, -movement_gravity, 0), minsize = 0, maxsize = 0, node = node, }) end function Everness.register_leafdecay(self, def) assert(def.leaves) assert(def.trunks) assert(def.radius) for _, v in pairs(def.trunks) do minetest.override_item(v, { after_destruct = function(pos, oldnode) leafdecay_after_destruct(pos, oldnode, def) end, }) end for _, v in pairs(def.leaves) do minetest.override_item(v, { on_timer = function(pos) leafdecay_on_timer(pos, def) end, }) end end function Everness.register_node(self, name, def) local _def = table.copy(def) local _name = name _def.mod_origin = 'everness' -- X Farming composter description if minetest.get_modpath('x_farming') and minetest.global_exists('x_farming') then -- X Farming if _def.groups and (_def.groups.compost or 0) > 0 then _def.description = _def.description .. '\n' .. S('Compost chance: @1%', def.groups.compost) end end self.registered_nodes[_name] = _def minetest.register_node(_name, _def) end function Everness.register_tool(self, name, def) local _def = table.copy(def) local _name = name _def.mod_origin = 'everness' self.registered_tools[_name] = _def minetest.register_tool(_name, _def) end function Everness.register_abm(self, def) local _def = table.copy(def) local _name = _def.label self.registered_abms[_name] = _def minetest.register_abm(_def) end function Everness.register_lbm(self, def) local _def = table.copy(def) local _name = _def.name self.registered_lbms[_name] = _def minetest.register_lbm(_def) end function Everness.register_craftitem(self, name, def) local _def = table.copy(def) local _name = name _def.mod_origin = 'everness' self.registered_craftitems[_name] = _def minetest.register_craftitem(_name, _def) end function Everness.register_biome(self, def) local _def = table.copy(def) local _name = _def.name self.registered_biomes[_name] = _def minetest.register_biome(_def) end function Everness.register_decoration(self, def) local _def = table.copy(def) local _name = _def.name self.registered_decorations[_name] = _def minetest.register_decoration(_def) end function Everness.register_ore(self, def) local _def = table.copy(def) -- @TOTO using `ore` as name here will override the entry when there are multiple ore registrations for the same ore (different noise) -- using indexed table would be more appropriate here local _name = _def.ore self.registered_ores[_name] = _def minetest.register_ore(_def) end -- -- Log API / helpers - copy from MTG -- local log_non_player_actions = minetest.settings:get_bool('log_non_player_actions', false) local is_pos = function(v) return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number' and type(v.z) == 'number' end function Everness.log_player_action(player, ...) local msg = player:get_player_name() if player.is_fake_player or not player:is_player() then if not log_non_player_actions then return end msg = msg .. '(' .. (type(player.is_fake_player) == 'string' and player.is_fake_player or '*') .. ')' end for _, v in ipairs({ ... }) do -- translate pos local part = is_pos(v) and minetest.pos_to_string(v) or v -- no leading spaces before punctuation marks msg = msg .. (string.match(part, '^[;,.]') and '' or ' ') .. part end minetest.log('action', msg) end function Everness.set_inventory_action_loggers(def, name) def.on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player) Everness.log_player_action(player, 'moves stuff in', name, 'at', pos) end def.on_metadata_inventory_put = function(pos, listname, index, stack, player) Everness.log_player_action(player, 'moves', stack:get_name(), 'to', name, 'at', pos) end def.on_metadata_inventory_take = function(pos, listname, index, stack, player) Everness.log_player_action(player, 'takes', stack:get_name(), 'from', name, 'at', pos) end end -- 'can grow' function - copy from MTG function Everness.can_grow(pos, groups_under) local node_under = minetest.get_node_or_nil({ x = pos.x, y = pos.y - 1, z = pos.z }) if not node_under then return false end local _groups_under = groups_under if not groups_under then _groups_under = { 'soil' } end local has_fertile_under = false -- Check is one of the `groups_under` are under the sapling for i, v in ipairs(_groups_under) do if minetest.get_item_group(node_under.name, v) > 0 then has_fertile_under = true break end end if not has_fertile_under then return false end local light_level = minetest.get_node_light(pos) if not light_level or light_level < 13 then return false end return true end -- -- This method may change in future. -- Copy from MTG -- function Everness.can_interact_with_node(player, pos) if player and player:is_player() then if minetest.check_player_privs(player, 'protection_bypass') then return true end else return false end local meta = minetest.get_meta(pos) local owner = meta:get_string('owner') if not owner or owner == '' or owner == player:get_player_name() then return true end -- Is player wielding the right key? local item = player:get_wielded_item() if minetest.get_item_group(item:get_name(), 'key') == 1 then local key_meta = item:get_meta() if key_meta:get_string('secret') == '' then local key_oldmeta = item:get_metadata() if key_oldmeta == '' or not minetest.parse_json(key_oldmeta) then return false end key_meta:set_string('secret', minetest.parse_json(key_oldmeta).secret) item:set_metadata('') end return meta:get_string('key_lock_secret') == key_meta:get_string('secret') end return false end -- -- Optimized helper to put all items in an inventory into a drops list -- copy from MTG -- function Everness.get_inventory_drops(pos, inventory, drops) 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 function Everness.set_loot_chest_items() local loot_items = {} for name, def in pairs(minetest.registered_items) do local craft_recipe = minetest.get_craft_recipe(name) local mod_name = name:split(':')[1] if def.groups and next(def.groups) and (not def.groups.not_in_creative_inventory or def.groups.not_in_creative_inventory == 0) and (craft_recipe.items or mod_name == 'default') then table.insert(loot_items, { name = name, max_count = 10, chance = 25 }) end end Everness.loot_chest.default = table.copy(loot_items) end function Everness.populate_loot_chests(self, positions, params) local _params = params or {} local _loot_chest_items_group = _params.loot_chest_items_group or 'default' -- Get inventories local string_positions = ''; local inventories = {} for i, pos in ipairs(positions) do local inv = minetest.get_inventory({ type = 'node', pos = pos }) if not inv then local chest_def = minetest.registered_nodes['everness:chest'] chest_def.on_construct(pos) inv = minetest.get_inventory({ type = 'node', pos = pos }) end if inv then table.insert(inventories, inv) string_positions = string_positions .. ' ' .. pos:to_string() else minetest.log('warning', '[Everness] FAILED to populate loot chests inventory at ' .. pos:to_string()) end end if #inventories > 0 then for index, value in ipairs(inventories[1]:get_list('main')) do local rand_idx = rand_global:next(1, #self.loot_chest[_loot_chest_items_group]) local item_def = self.loot_chest[_loot_chest_items_group][rand_idx] if not minetest.registered_items[item_def.name] then return end if rand_global:next(0, 100) <= item_def.chance then local stack = ItemStack(item_def.name) if minetest.registered_tools[item_def.name] then stack:set_wear(rand_global:next(1, 65535)) else stack:set_count(rand_global:next(1, math.min(item_def.max_count, stack:get_stack_max()))) end local rand_inv = inventories[rand_global:next(1, #inventories)] rand_inv:set_stack('main', index, stack) end end minetest.log('action', '[Everness] Loot chests inventory populated at ' .. string_positions) end end -- -- Hammer -- Modified version of default:tnt from MT -- @license MIT local function rand_pos(center, pos, radius) local def local reg_nodes = minetest.registered_nodes local i = 0 repeat -- Give up and use the center if this takes too long if i > 4 then pos.x, pos.z = center.x, center.z break end pos.x = center.x + math.random(-radius, radius) pos.z = center.z + math.random(-radius, radius) def = reg_nodes[minetest.get_node(pos).name] i = i + 1 until def and not def.walkable end local function eject_drops(drops, pos, radius) local drop_pos = vector.new(pos) for _, item in pairs(drops) do local count = math.min(item:get_count(), item:get_stack_max()) while count > 0 do local take = math.max( 1, math.min( radius * radius, count, item:get_stack_max() ) ) -- `drop_pos` is being randomized here rand_pos(pos, drop_pos, radius) local dropitem = ItemStack(item) dropitem:set_count(take) local obj = minetest.add_item(drop_pos, dropitem) if obj then obj:get_luaentity().collect = true obj:set_acceleration({ x = 0, y = -10, z = 0 }) obj:set_velocity({ x = math.random(-2, 2), y = math.random(0, 2), z = math.random(-2, 2) }) end count = count - take end end end -- Populate `drops` table local function add_drop(drops, item) item = ItemStack(item) local name = item:get_name() local drop = drops[name] if drop == nil then drops[name] = item else drop:set_count(drop:get_count() + item:get_count()) end end local function destroy(drops, npos, cid, c_air, can_dig, owner) if minetest.is_protected(npos, owner) then return cid end local def = Everness.hammer_cid_data[cid] if not def then return cid elseif def.can_dig and not def.can_dig(npos, minetest.get_player_by_name(owner)) then return cid else local node_drops = minetest.get_node_drops(def.name, '') for _, item in pairs(node_drops) do add_drop(drops, item) end return c_air end end -- Draw wear bar texture overlay function Everness.draw_wear_bar(itemstack, wear) local itemstack_meta = itemstack:get_meta() local px_width = 14 local px_one = 65535 / px_width local px_color = px_width - math.floor(wear / px_one) local inventory_overlay = '[combine:16x16' for i = 1, px_width do if i > px_color then inventory_overlay = inventory_overlay .. ':' .. i .. ',14=[combine\\:1x1\\^[noalpha\\^[colorize\\:#000000\\:255' else local color if px_color < px_width / 4 then -- below 25% color = '#FF0000' elseif px_color < (px_width / 4) * 2 then -- below 50% color = '#FFA500' elseif px_color < (px_width / 4) * 3 then -- below 75% color = '#FFFF00' else -- above 75% color = '#00FF00' end inventory_overlay = inventory_overlay .. ':' .. i .. ',14=[combine\\:1x1\\^[noalpha\\^[colorize\\:' .. color .. '\\:255' end end itemstack_meta:set_string('inventory_overlay', inventory_overlay) end function Everness.hammer_after_dig_node(pos, node, metadata, digger, can_dig) if not digger then return end local wielditem = digger:get_wielded_item() if not (wielditem:get_name() == 'everness:hammer' or wielditem:get_name() == 'everness:hammer_sharp') then return end local radius = 1 local look_dir = vector.round(digger:get_look_dir()) local look_dir_multi = vector.round(vector.multiply(look_dir, radius / 2)) pos = vector.round(vector.add(pos, look_dir_multi)) local c_air = minetest.CONTENT_AIR local c_ignore = minetest.CONTENT_IGNORE local vm = VoxelManip() local pr = PseudoRandom(os.time()) local p1 = vector.subtract(pos, radius) local p2 = vector.add(pos, radius) local minp, maxp = vm:read_from_map(p1, p2) local voxel_area = VoxelArea:new({MinEdge = minp, MaxEdge = maxp}) local data = vm:get_data() -- `drops` are being populated in `add_drop` method, called from `destroy` method local drops = {} local p_name = digger:get_player_name() if not minetest.settings:get_bool('creative_mode') then local wielditem_meta = wielditem:get_meta() local wielditem_wear = wielditem_meta:get_int('everness_wear') local node_def = minetest.registered_nodes[node.name] local wielditem_def = wielditem:get_definition() local dig_params = minetest.get_dig_params(node_def and node_def.groups, wielditem_def and wielditem_def.tool_capabilities, wielditem:get_wear()) local new_wear = wielditem_wear + dig_params.wear -- Add wear wielditem_meta:set_int('everness_wear', new_wear) -- Draw wear bar texture overlay Everness.draw_wear_bar(wielditem, new_wear) if wielditem_wear > 65535 then -- Break tool minetest.sound_play(wielditem_def.sound.breaks, { pos = pos, gain = 0.5 }, true) if wielditem:get_name() == 'everness:hammer' or wielditem:get_name() == 'everness:hammer_sharp' then digger:set_wielded_item(ItemStack('')) end elseif wielditem:get_name() == 'everness:hammer' or wielditem:get_name() == 'everness:hammer_sharp' then -- Save wear digger:set_wielded_item(wielditem) end end -- Digging blast for z = -radius, radius do for y = -radius, radius do local vi = voxel_area:index(pos.x + (-radius), pos.y + y, pos.z + z) for x = -radius, radius do local r = vector.length(vector.new(x, y, z)) if wielditem:get_name() == 'everness:hammer' then if (radius * radius) / (r * r) >= (pr:next(30, 60) / 100) then local cid = data[vi] local p = vector.new(pos.x + x, pos.y + y, pos.z + z) if cid ~= c_air and cid ~= c_ignore then data[vi] = destroy(drops, p, cid, c_air, can_dig, p_name) end end else local cid = data[vi] local p = vector.new(pos.x + x, pos.y + y, pos.z + z) if cid ~= c_air and cid ~= c_ignore then data[vi] = destroy(drops, p, cid, c_air, can_dig, p_name) end end vi = vi + 1 end end end vm:set_data(data) vm:write_to_map() vm:update_map() vm:update_liquids() -- Call `check_single_for_falling` for everything within 1.5x blast radius for y = math.round(-radius * 1.5), math.round(radius * 1.5) do for z = math.round(-radius * 1.5), math.round(radius * 1.5) do for x = math.round(-radius * 1.5), math.round(radius * 1.5) do local rad = vector.new(x, y, z) local s = vector.add(pos, rad) minetest.check_single_for_falling(s) end end end eject_drops(drops, pos, radius) -- Get texture for particles from majority of the dug nodes local node_for_particles local most = 0 for name, stack in pairs(drops) do local count = stack:get_count() if count > most then most = count local def = minetest.registered_nodes[name] if def then node_for_particles = { name = name } end -- Play sounds if def and def.sounds and def.sounds.dug then for i = 1, math.random(2, 5) do local drop_pos = vector.new(pos) -- `drop_pos` is being randomized here rand_pos(pos, drop_pos, radius) minetest.after( math.random(2, 8) / 10, function() minetest.sound_play(def.sounds.dug, { pos = drop_pos, pitch = math.random(1, 10) / 10 }, true) end ) end end end end if node_for_particles then minetest.add_particlespawner({ amount = 64, time = 0.1, minpos = vector.subtract(pos, radius / 2), maxpos = vector.add(pos, radius / 2), minvel = {x = -2, y = 0, z = -2}, maxvel = {x = 2, y = 5, z = 2}, minacc = {x = 0, y = -10, z = 0}, maxacc = {x = 0, y = -10, z = 0}, minexptime = 0.8, maxexptime = 2.0, minsize = radius * 0.33, maxsize = radius, node = node_for_particles, collisiondetection = true, }) end end -- Function triggered for each qualifying node. -- `dtime_s` is the in-game time (in seconds) elapsed since the block -- was last active function Everness.cool_lava(pos, node, dtime_s, prev_cool_lava_action) -- Variant Obsidian if node.name == 'default:lava_source' or node.name == 'mcl_core:lava_source' or node.name == 'everness:lava_source' then if math.random(1, 10) == 1 then local obi_nodes = { { name = 'everness:blue_crying_obsidian', color = '#2978A6'}, { name = 'everness:blue_weeping_obsidian', color = '#25B8FF'}, { name = 'everness:weeping_obsidian', color = '#C90FFF'}, } local rand_node = obi_nodes[math.random(1, #obi_nodes)] minetest.set_node(pos, { name = rand_node.name }) minetest.sound_play('everness_cool_lava', { pos = pos, max_hear_distance = 16, gain = 0.2 }, true ) if minetest.has_feature({ dynamic_add_media_table = true, particlespawner_tweenable = true }) then -- new syntax, after v5.6.0 minetest.add_particlespawner({ amount = 80, time = 1, size = { min = 0.5, max = 1, }, exptime = 1, pos = pos, glow = 7, attract = { kind = 'point', strength = 0.5, origin = pos, die_on_contact = true }, radius = 3, texture = { name = 'everness_particle.png^[colorize:' .. rand_node.color .. ':255', alpha_tween = { 0, 1, style = 'fwd', reps = 1 }, scale_tween = { 0.25, 1, style = 'fwd', reps = 1 } } }) end elseif node.name == 'everness:lava_source' then -- Lava flowing minetest.set_node(pos, {name = 'default:obsidian'}) elseif node.name == 'everness:lava_flowing' then -- Lava flowing minetest.set_node(pos, {name = 'default:stone'}) else prev_cool_lava_action(pos, node, dtime_s) end else prev_cool_lava_action(pos, node, dtime_s) end end function Everness.get_pot_formspec(pos, label, model_texture) local spos = pos.x .. ',' .. pos.y .. ',' .. pos.z local hotbar_bg = '' local list_bg = '' for i = 0, 7, 1 do hotbar_bg = hotbar_bg .. 'image[' .. 0 + i .. ', ' .. 4.85 .. ';1,1;everness_chest_ui_bg_hb_slot.png]' end for row = 0, 2, 1 do for i = 0, 7, 1 do list_bg = list_bg .. 'image[' .. 0 + i .. ',' .. 6.08 + row .. ';1,1;everness_chest_ui_bg_slot.png]' end end local model = 'model[0,0.5;2.5,2.5;everness_ceramic_pot;everness_ceramic_pot.obj;' .. model_texture .. ';0,0;true;false;]' local formspec = { 'size[8,9]', 'listcolors[#FFFFFF00;#FFFFFF1A;#5E5957]', 'background[5,5;1,1;everness_chest_ui_bg.png;true]', 'list[nodemeta:' .. spos .. ';main;0.5,3;1,1;]', 'list[current_player;main;0,4.85;8,1;]', 'list[current_player;main;0,6.08;8,3;8]', 'listring[nodemeta:' .. spos .. ';main]', 'listring[current_player;main]', list_bg, hotbar_bg, 'image[0.5,3;1,1;everness_chest_ui_bg_hb_slot.png]', 'label[2.5,0.5;' .. minetest.formspec_escape(label) .. ']', model } formspec = table.concat(formspec, '') return formspec end -- -- Encyclopedia -- local ency_data = { nodes = {}, tools = {}, abms = {}, lbms = {}, craftitems = {}, biomes = {}, decorations = {}, ores = {}, } -- -- Encyclopedia Helpers -- local function tchelper(first, rest) return first:upper()..rest:lower() end local function capitalize(str) -- Add extra characters to the pattern if you need to. _ and ' are -- found in the middle of identifiers and English words. -- We must also put %w_' into [%w_'] to make it handle normal stuff -- and extra stuff the same. -- This also turns hex numbers into, eg. 0Xa7d4 return str:gsub("(%a)([%w_']*)", tchelper) end local function tech_name_to_pretty_name(tech_name) local short_name = tech_name:split(':')[2] short_name = short_name:gsub('_', ' ') short_name = capitalize(short_name) return short_name end local function get_model_texture_from_tile_definition(tile_def) -- Assumptions its a string local texture = '' for i, v in ipairs(tile_def) do local temp = tile_def[i] if type(temp) == 'table' then if temp.name then temp = temp.name else temp = temp[i] end end texture = texture .. (i == 1 and '' or ',') .. temp end return texture end local function get_unordered_list(tbl, pos, formspec, lvl) local _formspec = formspec or {} local _lvl = lvl or 1 for k, v in pairs(tbl) do if type(v) == 'table' then -- Label pos.y = pos.y + 0.25 _formspec[#_formspec + 1] = ('label[%f,%f;%s]'):format(pos.x + 0.25 * _lvl, pos.y, k .. ':') get_unordered_list(v, pos, _formspec, _lvl + 1) else if minetest.registered_items[v] then pos.y = pos.y + 0.25 -- Label _formspec[#_formspec + 1] = ('label[%f,%f;%s]'):format(pos.x + 0.25 * _lvl, pos.y, k .. ':') pos.y = pos.y + 0.25 -- Item image _formspec[#_formspec + 1] = ('item_image[%f,%f;1,1;%s]'):format(pos.x + 0.25 * _lvl, pos.y, v) -- Tooltip for description _formspec[#_formspec + 1] = ('tooltip[%f,%f;1,1;%s]'):format(pos.x + 0.25 * _lvl, pos.y, minetest.formspec_escape(v)) pos.y = pos.y + 1 else pos.y = pos.y + 0.25 -- List "bullet" _formspec[#_formspec + 1] = ('label[%f,%f;%s]'):format(pos.x + 0.25 * _lvl, pos.y, k .. ': ' .. v) end end end _formspec = table.concat(_formspec, '') return _formspec end -- -- Encyclopedia API -- function Everness.encyclopedia_init(self) -- Nodes for name, def in pairs(self.registered_nodes) do if def.groups and (not def.groups.not_in_creative_inventory or def.groups.not_in_creative_inventory == 0) then table.insert(ency_data.nodes, name) end end -- Tools for name, def in pairs(self.registered_tools) do if def.groups and (not def.groups.not_in_creative_inventory or def.groups.not_in_creative_inventory == 0) then table.insert(ency_data.tools, name) end end -- ABMs for name, def in pairs(self.registered_abms) do table.insert(ency_data.abms, name) end -- LBMs for name, def in pairs(self.registered_lbms) do table.insert(ency_data.lbms, name) end -- Craftitems for name, def in pairs(self.registered_craftitems) do if def.groups and (not def.groups.not_in_creative_inventory or def.groups.not_in_creative_inventory == 0) then table.insert(ency_data.craftitems, name) end end -- Biomes for name, def in pairs(self.registered_biomes) do table.insert(ency_data.biomes, name) end -- Decorations for name, def in pairs(self.registered_decorations) do table.insert(ency_data.decorations, name) end -- Ores for name, def in pairs(self.registered_ores) do table.insert(ency_data.ores, name) end -- Sort alphabetically table.sort(ency_data.nodes, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.tools, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.abms, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.lbms, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.craftitems, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.biomes, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.decorations, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) table.sort(ency_data.ores, function(a ,b) return tech_name_to_pretty_name(a) < tech_name_to_pretty_name(b) end) if minetest.get_modpath('unified_inventory') then self:encyclopedia_ui_register_page() elseif minetest.get_modpath('i3') then self:encyclopedia_i3_register_page() elseif minetest.get_modpath('sfinv') and sfinv.enabled then self:encyclopedia_sfinv_register_page() end end function Everness.encyclopedia_get_formspec(self, context) local pos_primary = vector.new() local pos_secondary = vector.new() local pos_secondary_container = vector.new() local primary_selected_idx = context.everness_ency_primary_selected_idx or 1 local dropdown_idx = context.everness_ency_dropdown_idx or 1 context.everness_ency_primary_selected_idx = primary_selected_idx context.everness_ency_dropdown_idx = dropdown_idx -- Get dropdown items local ency_dropdown_items = {} for k, v in pairs(ency_data) do table.insert(ency_dropdown_items, k) end table.sort(ency_dropdown_items) -- Dropdown string value (main category) local dropdown_value = ency_dropdown_items[dropdown_idx] -- Data to show in secondary container (corresponding to selected index in primary item list) local primary_list_data = ency_data[dropdown_value] -- Get primary list items (list on the left) local primary_list_items = {} for k, v in ipairs(primary_list_data) do table.insert(primary_list_items, tech_name_to_pretty_name(v)) end -- Primary list selected item value (item technical name, e.g. 'everness:palm_tree_wood') local primary_list_selected_value = primary_list_data[primary_selected_idx] local def = self['registered_' .. dropdown_value][primary_list_selected_value] pos_primary.x = pos_primary.x + 0.25 pos_primary.y = pos_primary.y + 0.5 local formspec = { -- Title 'real_coordinates[true]', ('label[%f,%f;%s]'):format(pos_primary.x, pos_primary.y, minetest.formspec_escape(S('Everness Encyclopedia'))), } -- Dropdown (main categories) pos_primary.y = pos_primary.y + 0.4 formspec[#formspec + 1] = ('dropdown[%f,%f;4,0.4;everness_ency_dropdown;%s;%d;true]'):format(pos_primary.x, pos_primary.y, table.concat(ency_dropdown_items, ','), dropdown_idx) -- Primary list pos_primary.y = pos_primary.y + 0.6 formspec[#formspec + 1] = ('textlist[%f,%f;4,9;everness_ency_main_list;%s;%d;false]'):format(pos_primary.x, pos_primary.y, table.concat(primary_list_items, ','), primary_selected_idx) -- Secondary (details on the right) pos_secondary.x = pos_secondary.x + 4.5 pos_secondary.y = pos_secondary.y + 1.4 formspec[#formspec + 1] = ('scroll_container[%f,%f;5.5,9;everness_ency_detail_view_scrollbar;vertical;0.1]'):format(pos_secondary.x, pos_secondary.y) -- Secondary title pos_secondary_container.y = pos_secondary_container.y + 0.25 formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, primary_list_selected_value) -- Margin pos_secondary_container.y = pos_secondary_container.y + 0.5 if minetest['registered_' .. dropdown_value][primary_list_selected_value] and dropdown_value ~= 'biomes' and dropdown_value ~= 'decorations' and dropdown_value ~= 'ores' then if def.mesh then -- Item model formspec[#formspec + 1] = ('model[%f,%f;2,2;%s;%s;%s;-30,0;true;true;]'):format(pos_secondary_container.x, pos_secondary_container.y, primary_list_selected_value, def.mesh, get_model_texture_from_tile_definition(def.tiles)) else -- Item image formspec[#formspec + 1] = ('item_image[%f,%f;2,2;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, primary_list_selected_value) end if def.description then -- Tooltip for description formspec[#formspec + 1] = ('tooltip[%f,%f;2,2;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, minetest.formspec_escape(def.description)) end pos_secondary_container.y = pos_secondary_container.y + 2 elseif def and def.description then -- Label description formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, minetest.formspec_escape(def.description)) end if def and def.label then pos_secondary_container.y = pos_secondary_container.y + 0.25 -- Label description formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, minetest.formspec_escape(def.label)) end -- Groups if def and def.groups and next(def.groups) then pos_secondary_container.y = pos_secondary_container.y + 0.5 -- Title formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, 'Groups:') -- Unordered list formspec[#formspec + 1] = get_unordered_list((def.groups or {}), pos_secondary_container) end -- Tool capabilities if def and def.tool_capabilities and next(def.tool_capabilities) then pos_secondary_container.y = pos_secondary_container.y + 0.5 -- Title formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, 'Tool Capabilities:') -- Unordered list formspec[#formspec + 1] = get_unordered_list((def.tool_capabilities or {}), pos_secondary_container) end -- ABM/LBM nodenames if def and def.nodenames then pos_secondary_container.y = pos_secondary_container.y + 0.5 -- Title formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, 'Apply "action" function to these nodes:') -- Unordered list formspec[#formspec + 1] = get_unordered_list((def.nodenames or {}), pos_secondary_container) end -- ABM neighbors if def and def.neighbors and next(def.neighbors) then pos_secondary_container.y = pos_secondary_container.y + 0.5 -- Title formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, 'Only apply "action" to nodes that have one of, or any combination of, these neighbors:') -- Unordered list formspec[#formspec + 1] = get_unordered_list((def.neighbors or {}), pos_secondary_container) end if def and def.run_at_every_load then pos_secondary_container.y = pos_secondary_container.y + 0.5 formspec[#formspec + 1] = ('label[%f,%f;%s]'):format(pos_secondary_container.x, pos_secondary_container.y, 'Run at every load: ' .. (def.run_at_every_load and 'yes' or 'no')) end -- Biomes if def and dropdown_value == 'biomes' then -- Unordered list formspec[#formspec + 1] = get_unordered_list(def, pos_secondary_container) end -- Decorations if def and dropdown_value == 'decorations' then -- Unordered list formspec[#formspec + 1] = get_unordered_list(def, pos_secondary_container) end -- Ores if def and dropdown_value == 'ores' then -- Unordered list formspec[#formspec + 1] = get_unordered_list(def, pos_secondary_container) end -- Close secondary container formspec[#formspec + 1] = 'scroll_container_end[]' -- Add scrollbar to secondary container formspec[#formspec + 1] = ('scrollbaroptions[min=0;max=%d;smallstep=10;largestep=100;thumbsize=10;arrows=default]'):format(math.ceil(pos_secondary_container.y * 10)) formspec[#formspec + 1] = ('scrollbar[%f,%f;0.15,9;vertical;everness_ency_detail_view_scrollbar;]'):format(pos_secondary.x + 5.5 + 0.15, pos_secondary.y) return formspec end function Everness.encyclopedia_i3_register_page(self) i3.new_tab('everness_encyclopedia', { description = 'Everness', image = 'everness_logo.png', slots = false, formspec = function(player, data, fs) local context = data or {} local formspec = self:encyclopedia_get_formspec(context) formspec = table.concat(formspec, '') fs(formspec) end, fields = function(player, data, fields) if fields.everness_ency_main_list then local main_list_event = minetest.explode_textlist_event(fields.everness_ency_main_list) -- Set context data if main_list_event.type == 'CHG' then data.everness_ency_primary_selected_idx = main_list_event.index end elseif fields.everness_ency_dropdown then local prev_everness_ency_dropdown_idx = data.everness_ency_dropdown_idx local new_everness_ency_dropdown_idx = tonumber(fields.everness_ency_dropdown) data.everness_ency_dropdown_idx = new_everness_ency_dropdown_idx -- Change to 1st primary list item index only when changing dropdown if prev_everness_ency_dropdown_idx ~= new_everness_ency_dropdown_idx then data.everness_ency_primary_selected_idx = 1 end end end, access = function(player, data) return minetest.check_player_privs(player:get_player_name(), 'everness_encyclopedia') end, }) end function Everness.encyclopedia_ui_register_page(self) unified_inventory.register_page('everness:encyclopedia', { get_formspec = function(player) local context = unified_inventory.everness_context[player:get_player_name()] local formspec = self:encyclopedia_get_formspec(context) return { formspec = table.concat(formspec, ''), draw_inventory = false, draw_item_list = false } end }) minetest.register_on_joinplayer(function(player) local pname = player:get_player_name() unified_inventory.everness_context = {} unified_inventory.everness_context[pname] = { everness_ency_dropdown_idx = 1, everness_ency_primary_selected_idx = 1, } end) minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= '' then return end local pname = player:get_player_name() if fields.everness_ency_main_list then local main_list_event = minetest.explode_textlist_event(fields.everness_ency_main_list) -- Set context data if main_list_event.type == 'CHG' then unified_inventory.everness_context[pname].everness_ency_primary_selected_idx = main_list_event.index unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[pname]) end elseif fields.everness_ency_dropdown then local prev_everness_ency_dropdown_idx = unified_inventory.everness_context[pname].everness_ency_dropdown_idx local new_everness_ency_dropdown_idx = tonumber(fields.everness_ency_dropdown) unified_inventory.everness_context[pname].everness_ency_dropdown_idx = new_everness_ency_dropdown_idx -- Change to 1st primary list item index only when changing dropdown if prev_everness_ency_dropdown_idx ~= new_everness_ency_dropdown_idx then unified_inventory.everness_context[pname].everness_ency_primary_selected_idx = 1 end unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[pname]) end end) unified_inventory.register_button('everness:encyclopedia', { type = 'image', image = 'everness_logo.png', tooltip = 'Everness Encyclopedia', condition = function(player) return minetest.check_player_privs(player:get_player_name(), 'everness_encyclopedia') end, action = function(player) local pname = player:get_player_name() if not minetest.check_player_privs(pname, 'everness_encyclopedia') then minetest.chat_send_player(pname, S('You need "everness_encyclopedia" privilige to access this button.')) unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[pname]) return end unified_inventory.current_page[pname] = 'everness:encyclopedia' unified_inventory.set_inventory_formspec(player, unified_inventory.current_page[pname]) end, }) end function Everness.encyclopedia_sfinv_register_page(self) sfinv.register_page('everness:encyclopedia', { title = 'Everness', is_in_nav = function(_self, player, context) return minetest.check_player_privs(player:get_player_name(), 'everness_encyclopedia') end, get = function(_self, player, context) local formspec = self:encyclopedia_get_formspec(context) return sfinv.make_formspec(player, context, table.concat(formspec, '')) end, on_player_receive_fields = function (_self, player, context, fields) if fields.everness_ency_main_list then local main_list_event = minetest.explode_textlist_event(fields.everness_ency_main_list) -- Set context data if main_list_event.type == 'CHG' then context.everness_ency_primary_selected_idx = main_list_event.index sfinv.set_player_inventory_formspec(player) end end if fields.everness_ency_dropdown then local prev_everness_ency_dropdown_idx = context.everness_ency_dropdown_idx local new_everness_ency_dropdown_idx = tonumber(fields.everness_ency_dropdown) context.everness_ency_dropdown_idx = new_everness_ency_dropdown_idx -- Change to 1st primary list item index only when changing dropdown if prev_everness_ency_dropdown_idx ~= new_everness_ency_dropdown_idx then context.everness_ency_primary_selected_idx = 1 end sfinv.set_player_inventory_formspec(player) end end }) end function Everness.find_content_in_vm_area(minp, maxp, contentIds, data, area) local indexes = {} local id_count = {} for y = minp.y, maxp.y do for z = minp.z, maxp.z do for x = minp.x, maxp.x do local ai = area:index(x, y, z) if table.indexof(contentIds, data[ai]) ~= -1 then id_count[data[ai]] = (id_count[data[ai]] or 0) + 1 table.insert(indexes, ai) end end end end return indexes, id_count end function Everness.find_content_under_air_in_vm_area(minp, maxp, contentIds, data, area) local indexes = {} local id_count = {} for y = minp.y, maxp.y do for z = minp.z, maxp.z do for x = minp.x, maxp.x do local ai = area:index(x, y, z) if table.indexof(contentIds, data[ai]) ~= -1 and data[ai + area.ystride] == minetest.CONTENT_AIR then id_count[data[ai]] = (id_count[data[ai]] or 0) + 1 table.insert(indexes, ai) end end end end return indexes, id_count end function Everness.add_to_queue_on_generated(self, def) if type(def) ~= 'table' then minetest.log('warning', '[add_to_queue_on_generated] Callback definition is not a table, not adding to queue! It was type of ' .. type(def)) return end table.insert(self.on_generated_queue, def) end ---Merge two tables with key/value pair ---@param t1 table ---@param t2 table ---@return table function Everness.mergeTables(t1, t2) for k, v in pairs(t2) do t1[k] = v end return t1 end