everness/api.lua
2024-01-01 10:29:50 -05:00

1392 lines
50 KiB
Lua

--[[
Everness. Never ending discovery in Everness mapgen.
Copyright (C) 2024 SaKeL <juraj.vajda@gmail.com>
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to juraj.vajda@gmail.com
--]]
local S = minetest.get_translator(minetest.get_current_modname())
--- Base class
---@class Everness
---@field bamboo {['growth_stages']: table<number, table>, ['top_leaves_schem']: table}
---@field loot_chest {['default']: table[]}
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,
},
},
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 = {}
}
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
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 = node.param2 })
-- 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
function Everness.dig_vine(self, pos, node_name, digger)
--only dig give the vine if shears are used
if not digger then
return
end
local wielded_item = digger:get_wielded_item()
if 'everness:vine_shears' == wielded_item:get_name() then
local inv = digger:get_inventory()
if inv then
inv:add_item('main', ItemStack(node_name))
end
end
end
--
-- Sounds
--
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
--
-- 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 == '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, props)
local _def = table.copy(def)
local _name = name
minetest.register_node(_name, _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)
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
if minetest.get_item_group(node_under.name, 'soil') == 0 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
--
-- 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)
if node.name == 'default:lava_source' or node.name == 'mcl_core: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
else
prev_cool_lava_action(pos, node, dtime_s)
end
else
prev_cool_lava_action(pos, node, dtime_s)
end
end