EinsDreiDreiSieben/mods/x_farming/api.lua
2025-05-04 16:01:41 +02:00

2940 lines
96 KiB
Lua

--[[
X Farming. Extends Minetest farming mod with new plants, crops and ice fishing.
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.
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())
-- main class
x_farming = {
hunger_ng = minetest.get_modpath('hunger_ng'),
hbhunger = minetest.get_modpath('hbhunger'),
vessels = minetest.get_modpath('vessels'),
bucket = minetest.get_modpath('bucket'),
colors = {
brown = '#DEB887',
red = '#FF8080',
green = '#98E698'
},
x_bonemeal = {
tree_defs = {
['x_farming:christmas_tree_sapling'] = {
-- christmas tree
name = 'x_farming:christmas_tree_sapling',
chance = 2,
grow_tree = function(pos)
if not x_farming.x_bonemeal.is_on_soil(pos) then
return false
end
x_farming.grow_christmas_tree(pos)
return true
end
},
['x_farming:kiwi_sapling'] = {
-- Kiwi Tree
name = 'x_farming:kiwi_sapling',
chance = 2,
grow_tree = function(pos)
if not x_farming.x_bonemeal.is_on_soil(pos) then
return false
end
x_farming.grow_kiwi_tree(pos)
return true
end
},
['x_farming:large_cactus_with_fruit_seedling'] = {
-- Cactus Seedling
name = 'x_farming:large_cactus_with_fruit_seedling',
chance = 2,
grow_tree = function(pos)
if not x_farming.x_bonemeal.is_on_sand(pos) then
return false
end
x_farming.grow_large_cactus(pos)
return true
end
},
['x_farming:jungle_with_cocoa_sapling'] = {
-- Jungle Tree with Cocoa
name = 'x_farming:jungle_with_cocoa_sapling',
chance = 2,
grow_tree = function(pos)
if not x_farming.x_bonemeal.is_on_soil(pos) then
return false
end
x_farming.grow_jungle_tree(pos)
return true
end
},
['x_farming:pine_nut_sapling'] = {
-- Pine Nut Tree
name = 'x_farming:pine_nut_sapling',
chance = 2,
grow_tree = function(pos)
if not x_farming.x_bonemeal.is_on_soil(pos) then
return false
end
x_farming.grow_pine_nut_tree(pos)
return true
end
},
}
},
allowed_crate_items = {},
allowed_bag_items = {},
registered_crates = {},
lbm_nodenames_crates = {},
registered_plants = {},
mcl = {},
candle_colors = {
black = {
name = S('Black'),
hex = '#2B2B2B',
mcl_groups = { basecolor_black = 1, excolor_black = 1, unicolor_black = 1 }
},
dark_grey = {
name = S('Dark Grey'),
hex = '#4E4E4E',
mcl_groups = { basecolor_grey = 1, excolor_darkgrey = 1, unicolor_darkgrey = 1 }
},
grey = {
name = S('Grey'),
hex = '#A5A5A5',
mcl_groups = { basecolor_grey = 1, excolor_grey = 1, unicolor_grey = 1 }
},
red = {
name = S('Red'),
hex = '#AB5C4A',
mcl_groups = { basecolor_red = 1, excolor_red = 1, unicolor_red = 1 }
},
violet = {
name = S('Violet'),
hex = '#595287',
mcl_groups = { basecolor_magenta = 1, excolor_violet = 1, unicolor_violet = 1 }
},
magenta = {
name = S('Magenta'),
hex = '#A25B5D',
mcl_groups = { basecolor_magenta = 1, excolor_red_violet = 1, unicolor_red_violet = 1 }
},
pink = {
name = S('Pink'),
hex = '#FFA6A6',
mcl_groups = { basecolor_red = 1, excolor_red = 1, unicolor_light_red = 1 }
},
dark_green = {
name = S('Dark Green'),
hex = '#556E48',
mcl_groups = { basecolor_green = 1, excolor_green = 1, unicolor_dark_green = 1 }
},
green = {
name = S('Green'),
hex = '#779154',
mcl_groups = { basecolor_green = 1, excolor_green = 1, unicolor_green = 1 }
},
cyan = {
name = S('Cyan'),
hex = '#4E7683',
mcl_groups = { basecolor_cyan = 1, excolor_cyan = 1, unicolor_cyan = 1 }
},
blue = {
name = S('Blue'),
hex = '#4B6696',
mcl_groups = { basecolor_blue = 1, excolor_blue = 1, unicolor_blue = 1 }
},
light_blue = {
name = S('Light Blue'),
hex = '#648CB4',
craft_dye = 'group:dye,color_light_blue',
mcl_groups = { basecolor_blue = 1, excolor_blue = 1, unicolor_light_blue = 1 }
},
orange = {
name = S('Orange'),
hex = '#A86A4D',
mcl_groups = { basecolor_orange = 1, excolor_orange = 1, unicolor_orange = 1 }
},
yellow = {
name = S('Yellow'),
hex = '#BD8D39',
mcl_groups = { basecolor_yellow = 1, excolor_yellow = 1, unicolor_yellow = 1 }
},
brown = {
name = S('Brown'),
hex = '#684E45',
mcl_groups = { basecolor_brown = 1, excolor_orange = 1, unicolor_dark_orange = 1 }
}
},
}
function x_farming.node_sound_grass_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_grass_footstep', gain = 0.4 }
table.dig = table.dig or { name = 'x_farming_grass_hit', gain = 1.2 }
table.dug = table.dug or { name = 'x_farming_dirt_hit', gain = 1.0 }
table.place = table.place or { name = 'x_farming_dirt_hit', gain = 1.0 }
return table
end
function x_farming.node_sound_leaves_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_leaves_footstep', gain = 0.1 }
table.dig = table.dig or { name = 'x_farming_leaves_hit', gain = 0.25 }
table.dug = table.dug or { name = 'x_farming_leaves_dug', gain = 0.5 }
table.place = table.place or { name = 'x_farming_leaves_place', gain = 0.4 }
return table
end
function x_farming.node_sound_wood_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_wood_footstep', gain = 0.15 }
table.dig = table.dig or { name = 'x_farming_wood_hit', gain = 0.5 }
table.dug = table.dug or { name = 'x_farming_wood_place', gain = 0.1 }
table.place = table.place or { name = 'x_farming_wood_place', gain = 0.15 }
return table
end
function x_farming.node_sound_sand_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_sand_footstep', gain = 0.1 }
table.dig = table.dig or { name = 'x_farming_sand_hit', gain = 0.5 }
table.dug = table.dug or { name = 'x_farming_sand_dug', gain = 0.1 }
table.place = table.place or { name = 'x_farming_sand_place', gain = 0.15 }
return table
end
function x_farming.node_sound_thin_glass_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_thin_glass_footstep', gain = 0.3 }
table.dig = table.dig or { name = 'x_farming_thin_glass_footstep', gain = 0.5 }
table.dug = table.dug or { name = 'x_farming_break_thin_glass', gain = 1.0 }
table.place = table.place or { name = 'x_farming_glass_place', gain = 0.2 }
return table
end
function x_farming.node_sound_dirt_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_dirt_footstep', gain = 0.15 }
table.dig = table.dig or { name = 'x_farming_dirt_hit', gain = 0.4 }
table.dug = table.dug or { name = 'x_farming_dirt_hit', gain = 1.0 }
table.place = table.place or { name = 'x_farming_dirt_hit', gain = 1.0 }
return table
end
function x_farming.node_sound_ice_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_ice_footstep', gain = 0.2 }
table.dig = table.dig or { name = 'x_farming_ice_hit', gain = 0.4 }
table.dug = table.dug or { name = 'x_farming_ice_hit', gain = 1.0 }
table.place = table.place or { name = 'x_farming_ice_hit', gain = 1.0 }
return table
end
function x_farming.node_sound_stone_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_stone_footstep', gain = 0.2 }
table.dig = table.dig or { name = 'x_farming_stone_hit', gain = 1.0 }
table.dug = table.dug or { name = 'x_farming_stone_dug', gain = 0.6 }
table.place = table.place or { name = 'x_farming_stone_place', gain = 1.0 }
return table
end
function x_farming.node_sound_slime_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_slime_footstep', gain = 0.2 }
table.dig = table.dig or { name = 'x_farming_slime_dig', gain = 1.0 }
table.dug = table.dug or { name = 'x_farming_slime_dug', gain = 0.3 }
table.place = table.place or { name = 'x_farming_slime_footstep', gain = 1.0 }
return table
end
function x_farming.node_sound_rope_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_rope_footstep', gain = 0.05 }
table.dig = table.dig or { name = 'x_farming_rope_hit', gain = 0.7 }
table.dug = table.dug or { name = 'x_farming_rope_dug', gain = 0.2 }
table.place = table.place or { name = 'x_farming_rope_hit', gain = 0.8 }
return table
end
function x_farming.node_sound_pillow_defaults(table)
table = table or {}
table.footstep = table.footstep or { name = 'x_farming_pillow_footstep', gain = 0.1 }
table.dig = table.dig or { name = 'x_farming_pillow_hit', gain = 0.15 }
table.dug = table.dug or { name = 'x_farming_pillow_dug', gain = 0.2 }
table.place = table.place or { name = 'x_farming_pillow_footstep', gain = 0.25 }
return table
end
---how often node timers for plants will tick, +/- some random value
function x_farming.tick_block(pos)
minetest.get_node_timer(pos):start(math.random(498, 1287))
end
---how often a growth failure tick is retried (e.g. too dark)
function x_farming.tick_block_short(pos)
minetest.get_node_timer(pos):start(math.random(332, 858))
end
-- how often node timers for plants will tick, +/- some random value
function x_farming.tick(pos)
minetest.get_node_timer(pos):start(math.random(166, 286))
end
-- how often a growth failure tick is retried (e.g. too dark)
function x_farming.tick_again(pos)
minetest.get_node_timer(pos):start(math.random(40, 80))
end
---just shorthand for minetest metadata handling
function x_farming.meta_get_str(key, pos)
local meta = minetest.get_meta(pos)
return meta:get_string(key)
end
---just shorthand for minetest metadata handling
function x_farming.meta_set_str(key, value, pos)
local meta = minetest.get_meta(pos)
meta:set_string(key, value)
end
---merge two indexed tables
function x_farming.mergeTables(t1, t2)
local _t1 = { unpack(t1) }
local _t2 = { unpack(t2) }
for k, v in ipairs(_t2) do
table.insert(_t1, v)
end
return _t1
end
---Change an entire string to Title Case (i.e. capitalise the first letter of each word)
function x_farming.tchelper(first, rest)
return first:upper() .. rest:lower()
end
--- Hoes - copy from MTG
x_farming.hoe_on_use = function(itemstack, user, pointed_thing, uses)
local pt = pointed_thing
-- check if pointing at a node
if not pt then
return
end
if pt.type ~= 'node' then
return
end
local under = minetest.get_node(pt.under)
local p = { x = pt.under.x, y = pt.under.y + 1, z = pt.under.z }
local above = minetest.get_node(p)
-- return if any of the nodes is not registered
if not minetest.registered_nodes[under.name] then
return
end
if not minetest.registered_nodes[above.name] then
return
end
-- check if the node above the pointed thing is air
if above.name ~= 'air' then
return
end
-- check if pointing at soil
if minetest.get_item_group(under.name, 'soil') ~= 1 then
return
end
-- check if (wet) soil defined
local regN = minetest.registered_nodes
if regN[under.name].soil == nil or regN[under.name].soil.wet == nil or regN[under.name].soil.dry == nil then
return
end
local player_name = user and user:get_player_name() or ''
if minetest.is_protected(pt.under, player_name) then
minetest.record_protection_violation(pt.under, player_name)
return
end
if minetest.is_protected(pt.above, player_name) then
minetest.record_protection_violation(pt.above, player_name)
return
end
-- turn the node into soil and play sound
minetest.set_node(pt.under, { name = regN[under.name].soil.dry })
minetest.sound_play('x_farming_dirt_hit', {
pos = pt.under,
gain = 0.3,
}, true)
if not minetest.is_creative_enabled(player_name) then
-- wear tool
local wdef = itemstack:get_definition()
itemstack:add_wear_by_uses(uses)
-- tool break sound
if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
minetest.sound_play(wdef.sound.breaks, {pos = pt.above,
gain = 0.5}, true)
end
end
return itemstack
end
-- Register new hoes
x_farming.register_hoe = function(name, def)
local _def = table.copy(def)
-- Check for : prefix (register new hoes in your mod's namespace)
if name:sub(1, 1) ~= ':' then
name = ':' .. name
end
-- Check def table
if _def.description == nil then
_def.description = S('Hoe')
end
if _def.inventory_image == nil then
_def.inventory_image = 'x_farming_unknown_item.png'
end
if _def.max_uses == nil then
_def.max_uses = 30
end
if minetest.get_modpath('farming') then
_def.on_use = function(itemstack, user, pointed_thing)
return x_farming.hoe_on_use(itemstack, user, pointed_thing, _def.max_uses)
end
end
if minetest.get_modpath('mcl_farming') then
_def.on_place = x_farming.mcl.hoe_on_place_function(_def.max_uses)
end
-- MCL
_def.groups = _def.groups
_def.sound = { breaks = 'x_farming_tool_breaks' }
_def.wield_scale = _def.wield_scale or { x = 1, y = 1, z = 1 }
-- Register the tool
minetest.register_tool(name, _def)
-- Register its recipe
if _def.recipe then
minetest.register_craft({
output = name:sub(2),
recipe = _def.recipe
})
elseif _def.material then
minetest.register_craft({
output = name:sub(2),
recipe = {
{ _def.material, 'group:stick' },
{ _def.material, 'group:stick' },
{ '', 'group:stick' }
}
})
end
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 x_farming.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 x_farming.set_inventory_action_loggers(def, name)
def.on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
x_farming.log_player_action(player, 'moves stuff in', name, 'at', pos)
end
def.on_metadata_inventory_put = function(pos, listname, index, stack, player)
x_farming.log_player_action(player, 'moves', stack:get_name(), 'to', name, 'at', pos)
end
def.on_metadata_inventory_take = function(pos, listname, index, stack, player)
x_farming.log_player_action(player, 'takes', stack:get_name(), 'from', name, 'at', pos)
end
end
--
-- Sapling 'on place' function to check protection of node and resulting tree volume
--
function x_farming.sapling_on_place(itemstack, placer, pointed_thing, sapling_name, minp_relative, maxp_relative, 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 not node then
return itemstack
end
if pdef 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)
-- Print extra information to explain
-- minetest.chat_send_player(player_name,
-- itemstack:get_definition().description .. ' will intersect protection ' ..
-- 'on growth')
minetest.chat_send_player(
player_name,
S('@1 will intersect protection on growth.', itemstack:get_definition().description)
)
return itemstack
end
x_farming.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) 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
--
-- Leafdecay
--
-- Prevent decay of placed leaves
function x_farming.after_place_leaves(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 or minetest.get_item_group('cocoa') == 0) 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 x_farming.register_leafdecay(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
-- Seed placement - copy from MTG
function x_farming.place_seed(itemstack, placer, pointed_thing, plantname)
local pt = pointed_thing
-- check if pointing at a node
if not pt then
return itemstack
end
if pt.type ~= 'node' then
return itemstack
end
local under = minetest.get_node(pt.under)
local above = minetest.get_node(pt.above)
local player_name = placer and placer:get_player_name() or ''
if minetest.is_protected(pt.under, player_name) then
minetest.record_protection_violation(pt.under, player_name)
return itemstack
end
if minetest.is_protected(pt.above, player_name) then
minetest.record_protection_violation(pt.above, player_name)
return itemstack
end
-- return if any of the nodes is not registered
if not minetest.registered_nodes[under.name] then
return itemstack
end
if not minetest.registered_nodes[above.name] then
return itemstack
end
-- check if pointing at the top of the node
if pt.above.y ~= pt.under.y + 1 then
return itemstack
end
-- check if you can replace the node above the pointed node
if not minetest.registered_nodes[above.name].buildable_to then
return itemstack
end
-- check if pointing at soil
if minetest.get_item_group(under.name, 'soil') < 2 then
return itemstack
end
-- add the node and remove 1 item from the itemstack
x_farming.log_player_action(placer, 'places node', plantname, 'at', pt.above)
minetest.add_node(pt.above, { name = plantname, param2 = 1 })
x_farming.tick(pt.above)
if not minetest.is_creative_enabled(player_name) then
itemstack:take_item()
end
return itemstack
end
x_farming.grow_plant = function(pos, elapsed)
local node = minetest.get_node(pos)
local name = node.name
local def = minetest.registered_nodes[name]
if not def.next_plant then
-- disable timer for fully grown plant
return
end
-- grow seed
if minetest.get_item_group(node.name, 'seed') and def.fertility then
local soil_node = minetest.get_node_or_nil({ x = pos.x, y = pos.y - 1, z = pos.z })
if not soil_node then
x_farming.tick_again(pos)
return
end
-- omitted is a check for light, we assume seeds can germinate in the dark.
for _, v in pairs(def.fertility) do
if minetest.get_item_group(soil_node.name, v) ~= 0 or string.find(soil_node.name, 'mcl_farming:soil') then
local placenode = { name = def.next_plant }
if def.place_param2 then
placenode.param2 = def.place_param2
end
minetest.swap_node(pos, placenode)
if minetest.registered_nodes[def.next_plant].next_plant then
x_farming.tick(pos)
return
end
end
end
return
end
-- check if on wet soil
local below = minetest.get_node({ x = pos.x, y = pos.y - 1, z = pos.z })
if minetest.get_item_group(below.name, 'soil') < 3 then
x_farming.tick_again(pos)
return
end
-- check light
local light = minetest.get_node_light(pos)
if not light or light < def.minlight or light > def.maxlight then
x_farming.tick_again(pos)
return
end
-- grow
local placenode = { name = def.next_plant }
if def.place_param2 then
placenode.param2 = def.place_param2
end
minetest.swap_node(pos, placenode)
-- new timer needed?
if minetest.registered_nodes[def.next_plant].next_plant then
x_farming.tick(pos)
end
return
end
-- Register plants
x_farming.register_plant = function(name, def)
local mname = name:split(':')[1]
local pname = name:split(':')[2]
-- Check def table
if not def.description then
def.description = S('Seed')
end
if not def.harvest_description then
def.harvest_description = pname:gsub('^%l', string.upper)
end
if not def.inventory_image then
def.inventory_image = 'unknown_item.png'
end
if not def.steps then
return nil
end
if not def.minlight then
def.minlight = 1
end
if not def.maxlight then
def.maxlight = 14
end
if not def.fertility then
def.fertility = {}
end
x_farming.registered_plants[pname] = def
-- Register seed
local lbm_nodes = { mname .. ':seed_' .. pname }
local g = {
-- MTG
seed = 1,
snappy = 3,
attached_node = 1,
flammable = 2,
-- MCL
handy = 1,
shearsy = 1,
deco_block = 1,
dig_by_water = 1,
destroy_by_lava_flow = 1,
dig_by_piston = 1
}
for k, v in pairs(def.fertility) do
g[v] = 1
end
if def.groups then
for group, value in pairs(def.groups) do
g[group] = value
end
end
minetest.register_node(':' .. mname .. ':seed_' .. pname, {
description = def.description,
tiles = def.tiles or { def.inventory_image },
inventory_image = def.inventory_image,
wield_image = def.inventory_image,
drawtype = def.drawtype or 'signlike',
groups = g,
paramtype = 'light',
paramtype2 = def.paramtype2 or 'wallmounted',
place_param2 = def.place_param2 or nil, -- this isn't actually used for placement
walkable = false,
sunlight_propagates = true,
selection_box = def.selection_box or {
type = 'fixed',
fixed = { -0.5, -0.5, -0.5, 0.5, -5 / 16, 0.5 },
},
fertility = def.fertility,
sounds = x_farming.node_sound_grass_defaults(),
special_tiles = def.special_tiles and { { name = def.special_tiles, tileable_vertical = true } } or nil,
visual_scale = def.visual_scale or 1,
node_dig_prediction = def.node_dig_prediction or '',
node_placement_prediction = def.node_placement_prediction or nil,
on_place = def.on_place or function(itemstack, placer, pointed_thing)
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak)
then
return udef.on_rightclick(under, node, placer, itemstack, pointed_thing) or itemstack
end
return x_farming.place_seed(itemstack, placer, pointed_thing, mname .. ':seed_' .. pname)
end,
after_destruct = def.after_destruct or nil,
next_plant = mname .. ':' .. pname .. '_1',
on_timer = def.on_timer or x_farming.grow_plant,
minlight = def.minlight,
maxlight = def.maxlight,
_mcl_blast_resistance = 0,
_mcl_hardness = 0,
})
-- Register harvest
minetest.register_craftitem(':' .. mname .. ':' .. pname, {
description = def.harvest_description,
inventory_image = mname .. '_' .. pname .. '.png',
groups = def.groups or { flammable = 2 },
})
-- Register growing steps
for i = 1, def.steps do
local base_rarity = 1
if def.steps ~= 1 then
base_rarity = 8 - (i - 1) * 7 / (def.steps - 1)
end
local drop = {
items = {
{items = { mname .. ':' .. pname }, rarity = base_rarity },
{items = { mname .. ':' .. pname }, rarity = base_rarity * 2 },
{items = { mname .. ':seed_' .. pname }, rarity = base_rarity },
{items = { mname .. ':seed_' .. pname }, rarity = base_rarity * 2 },
}
}
local nodegroups = {
-- MTG
snappy = 3,
flammable = 2,
plant = 1,
not_in_creative_inventory = 1,
attached_node = 1,
-- MCL
handy = 1,
shearsy = 1,
deco_block = 1,
dig_by_water = 1,
destroy_by_lava_flow = 1,
dig_by_piston = 1
}
nodegroups[pname] = i
if def.groups then
for group, value in pairs(def.groups) do
nodegroups[group] = value
end
end
local next_plant = nil
if i < def.steps then
next_plant = mname .. ':' .. pname .. '_' .. (i + 1)
lbm_nodes[#lbm_nodes + 1] = mname .. ':' .. pname .. '_' .. i
end
local _buildable_to = true
if def.buildable_to ~= nil then
_buildable_to = def.buildable_to
end
minetest.register_node(':' .. mname .. ':' .. pname .. '_' .. i, {
drawtype = def.drawtype or 'plantlike',
waving = 1,
tiles = def.tiles or { mname .. '_' .. pname .. '_' .. i .. '.png' },
special_tiles = def.special_tiles and { { name = mname .. '_' .. pname .. '_' .. i .. '.png', tileable_vertical = true } } or nil,
paramtype = 'light',
paramtype2 = def.paramtype2 or nil,
place_param2 = def.place_param2 or nil,
visual_scale = def.visual_scale or 1,
node_dig_prediction = def.node_dig_prediction or '',
node_placement_prediction = def.node_placement_prediction or nil,
walkable = false,
buildable_to = _buildable_to,
drop = drop,
selection_box = def['selection_box_' .. i] and def['selection_box_' .. i] or {
type = 'fixed',
fixed = { -0.5, -0.5, -0.5, 0.5, -5 / 16, 0.5 },
},
groups = nodegroups,
sounds = x_farming.node_sound_leaves_defaults(),
next_plant = next_plant,
on_timer = def.on_timer or x_farming.grow_plant,
minlight = def.minlight,
maxlight = def.maxlight,
_mcl_blast_resistance = 0,
_mcl_hardness = 0,
after_destruct = def.after_destruct or nil,
})
end
-- replacement LBM for pre-nodetimer plants
minetest.register_lbm({
name = ':' .. mname .. ':start_nodetimer_' .. pname,
nodenames = lbm_nodes,
action = function(pos, node)
x_farming.tick_again(pos)
end,
})
-- Return
local r = {
seed = mname .. ':seed_' .. pname,
harvest = mname .. ':' .. pname
}
return r
end
---grow blocks next to the plant
function x_farming.grow_block(pos, elapsed)
local node = minetest.get_node(pos)
local random_pos = false
local spawn_positions = {}
local right_pos = { x = pos.x + 1, y = pos.y, z = pos.z }
local front_pos = { x = pos.x, y = pos.y, z = pos.z + 1 }
local left_pos = { x = pos.x - 1, y = pos.y, z = pos.z }
local back_pos = { x = pos.x, y = pos.y, z = pos.z - 1 }
local right = minetest.get_node(right_pos)
local front = minetest.get_node(front_pos)
local left = minetest.get_node(left_pos)
local back = minetest.get_node(back_pos)
local def = minetest.registered_nodes[node.name]
local children = {}
---look for fruits around the stem
if (right.name == def.next_plant) then
children.right = right_pos
end
if (front.name == def.next_plant) then
children.front = front_pos
end
if (left.name == def.next_plant) then
children.left = left_pos
end
if (back.name == def.next_plant) then
children.back = back_pos
end
---check if the fruit belongs to this stem
for side, child_pos in pairs(children) do
local parent_pos_from_child = x_farming.meta_get_str('parent', child_pos)
---disable timer for fully grown plant - fruit for this stem already exists
if minetest.pos_to_string(pos) == parent_pos_from_child then
return
end
end
---make sure that at least one side of the plant has space to put fruit
if right.name == 'air' then
table.insert(spawn_positions, right_pos)
end
if front.name == 'air' then
table.insert(spawn_positions, front_pos)
end
if left.name == 'air' then
table.insert(spawn_positions, left_pos)
end
if back.name == 'air' then
table.insert(spawn_positions, back_pos)
end
---plant is closed from all sides
if #spawn_positions < 1 then
x_farming.tick_block_short(pos)
return
else
---pick random from the open sides
local pick_random
if #spawn_positions == 1 then
pick_random = #spawn_positions
else
pick_random = math.random(1, #spawn_positions)
end
for k, v in pairs(spawn_positions) do
if k == pick_random then
random_pos = v
end
end
end
---check light
local light = minetest.get_node_light(pos)
if not light or light < 13 or light > 14 then
x_farming.tick_block_short(pos)
return
end
---spawn block
if random_pos then
minetest.set_node(random_pos, { name = def.next_plant })
x_farming.meta_set_str('parent', minetest.pos_to_string(pos), random_pos)
end
return
end
function x_farming.grow_kiwi_tree(pos)
local path = minetest.get_modpath('x_farming') ..
'/schematics/x_farming_kiwi_tree_from_sapling.mts'
minetest.place_schematic({ x = pos.x - 2, y = pos.y, z = pos.z - 2 },
path, 'random', nil, false)
end
-- 'can grow' function - copy from MTG
function x_farming.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
---Grow sapling
function x_farming.grow_sapling(pos)
if not x_farming.can_grow(pos) then
---try again 5 min later
minetest.get_node_timer(pos):start(300)
return
end
local node = minetest.get_node(pos)
if node.name == 'x_farming:kiwi_sapling' then
minetest.log('action', 'A sapling grows into a tree at ' ..
minetest.pos_to_string(pos))
x_farming.grow_kiwi_tree(pos)
end
end
---Grow Large Cactus
function x_farming.grow_large_cactus(pos)
local path = minetest.get_modpath('x_farming') ..
'/schematics/x_farming_large_cactus_from_seedling.mts'
minetest.place_schematic({ x = pos.x, y = pos.y, z = pos.z },
path, 'random', nil, false, 'place_center_x, place_center_z')
end
---Grow Jungle Tree
function x_farming.grow_jungle_tree(pos)
local path = minetest.get_modpath('x_farming') ..
'/schematics/x_farming_jungle_tree_with_cocoa_from_sapling.mts'
minetest.place_schematic({ x = pos.x - 2, y = pos.y - 1, z = pos.z - 2 },
path, nil, nil, false)
end
---Pine Nut Tree
function x_farming.grow_pine_nut_tree(pos)
local path = minetest.get_modpath('x_farming') ..
'/schematics/x_farming_pine_nut_tree_from_sapling.mts'
minetest.place_schematic({ x = pos.x - 2, y = pos.y, z = pos.z - 2 },
path, '0', nil, false)
end
---Christmas Tree
function x_farming.grow_christmas_tree(pos)
local path
if math.random() > 0.5 then
path = minetest.get_modpath('x_farming') .. '/schematics/x_farming_christmas_tree_large.mts'
minetest.place_schematic({ x = pos.x - 2, y = pos.y, z = pos.z - 2 }, path, '0', nil, false)
else
path = minetest.get_modpath('x_farming') .. '/schematics/x_farming_christmas_tree.mts'
minetest.place_schematic({ x = pos.x - 1, y = pos.y, z = pos.z - 1 }, path, '0', nil, false)
end
end
----
--- Crates and Bags
----
function x_farming.tick_crates(pos)
minetest.get_node_timer(pos):start(math.random(332, 572))
end
function x_farming.tick_again_crates(pos)
minetest.get_node_timer(pos):start(math.random(80, 160))
end
function x_farming.get_crate_or_bag_formspec(pos, label_copy)
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;x_farming_crate_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;x_farming_crate_ui_bg_slot.png]'
end
end
local formspec = {
'size[8,9]',
'style_type[label;textcolor=#FFFFFF]',
'background[5,5;1,1;x_farming_crate_ui_bg.png;true]',
'list[nodemeta:', spos, ';main;0,0.3;8,4;]',
'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]',
'label[2,0;' .. minetest.formspec_escape(label_copy) .. ']',
list_bg,
hotbar_bg,
'image[0,0.3;1,1;x_farming_crate_ui_bg_hb_slot.png]'
}
formspec = table.concat(formspec, '')
return formspec
end
---Crate
function x_farming.register_crate(name, def)
local _def = table.copy(def) or {}
_def._custom = _def._custom or {}
local mod = "x_farming"
if name:find(":") then
local n = name:split(":")
mod = n[1]
name = n[2]
end
_def.name = 'x_farming:' .. name
_def.description = def.description or name
_def.short_description = def.short_description or def.description
_def.drawtype = 'mesh'
_def.paramtype = 'light'
_def.paramtype2 = 'facedir'
_def.mesh = 'x_farming_crate.obj'
_def.tiles = def.tiles
_def.use_texture_alpha = 'clip'
_def.sounds = def.sounds or x_farming.node_sound_wood_defaults()
_def.is_ground_content = false
_def.groups = def.groups or {
-- MTG
choppy = 2,
oddly_breakable_by_hand = 2,
-- MCL
handy = 1,
material_wood = 1,
deco_block = 1,
fire_encouragement = 3,
fire_flammability = 4,
-- ALL
not_in_creative_inventory = 1,
flammable = 2
}
_def.stack_max = def.stack_max or 1
_def.mod_origin = mod
-- MCL
_def._mcl_hardness = 0.6
_def._mcl_blast_resistance = 0.6
if _def._custom.crate_item then
x_farming.allowed_crate_items[_def._custom.crate_item] = true
end
_def.on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
meta:set_string('infotext', _def.short_description)
meta:set_string('owner', '')
inv:set_size('main', 1)
end
_def.after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local meta_st = itemstack:get_meta()
local crate_inv = minetest.deserialize(meta_st:get_string('crate_inv'))
local inv = meta:get_inventory()
if crate_inv then
inv:add_item('main', ItemStack(crate_inv))
end
local node = minetest.get_node(pos)
meta:set_string('owner', placer:get_player_name() or '')
if not inv:is_empty('main') then
local inv_stack = inv:get_stack('main', 1)
meta:set_string('infotext', _def.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description() .. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
else
local swap_node = minetest.registered_nodes['x_farming:crate_empty']
if swap_node and inv:is_empty('main') and node.name ~= swap_node.name then
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
meta:set_string('infotext', swap_node.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')')
end
end
end
_def.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local p_name = clicker:get_player_name()
if minetest.is_protected(pos, p_name) then
return itemstack
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local inv_stack = inv:get_stack('main', 1)
local label_copy = _def.short_description .. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner')
.. ')\n' .. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
minetest.sound_play('x_farming_wood_hit', { gain = 0.3, pos = pos, max_hear_distance = 10 }, true)
end
_def.on_blast = function(pos, intensity)
if minetest.is_protected(pos, '') then
return
end
local drops = {}
local inv = minetest.get_meta(pos):get_inventory()
local n = #drops
for i = 1, inv:get_size('main') do
local stack = inv:get_stack('main', i)
if stack:get_count() > 0 then
drops[n + 1] = stack:to_table()
n = n + 1
end
end
drops[#drops + 1] = name
minetest.remove_node(pos)
return drops
end
_def.can_dig = function(pos, player)
return not minetest.is_protected(pos, player:get_player_name())
end
_def.preserve_metadata = function(pos, oldnode, oldmeta, drops)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = drops[1]
local meta_drop = stack:get_meta()
if not inv:is_empty('main') then
local inv_stack = inv:get_stack('main', 1)
meta_drop:set_string('crate_inv', minetest.serialize(inv_stack:to_table()))
meta_drop:set_string('description', stack:get_description() .. '\n' .. inv_stack:get_description()
.. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
return
end
end
_def.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
local st_name = stack:get_name()
if minetest.is_protected(pos, player:get_player_name())
or (
not x_farming.allowed_crate_items[st_name]
and minetest.get_item_group(st_name, 'fish') == 0
)
then
return 0
end
return stack:get_count()
end
_def.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
local st_name = stack:get_name()
if minetest.is_protected(pos, player:get_player_name())
or (
not x_farming.allowed_crate_items[st_name]
and minetest.get_item_group(st_name, 'fish') == 0
)
then
return 0
end
return stack:get_count()
end
_def.on_metadata_inventory_put = function(pos, listname, index, stack, player)
local stack_name = stack:get_name()
if not stack_name or stack_name == '' then
return
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local node = minetest.get_node(pos)
local inv_stack = inv:get_stack('main', 1)
local split_name = stack_name:split(':')
if minetest.get_item_group(stack_name, 'fish') ~= 0 then
split_name = { 'x_farming', 'fish' }
end
if stack_name == 'x_farming:cotton' then
split_name = { 'x_farming', 'cotton2' }
end
local swap_node = minetest.registered_nodes['x_farming:crate_' .. split_name[2] .. '_3']
if not swap_node then
return
end
if not inv:is_empty(listname) and node.name ~= swap_node.name then
local p_name = player:get_player_name()
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
local label_copy = swap_node.short_description .. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
end
meta:set_string('infotext', swap_node.short_description .. ' (' .. S('owned by')
.. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description() .. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
end
_def.on_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local inv_stack = inv:get_stack('main', 1)
local node = minetest.get_node(pos)
if inv:is_empty(listname) then
local p_name = player:get_player_name()
local swap_node = minetest.registered_nodes['x_farming:crate_empty']
if swap_node then
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
meta:set_string('infotext', swap_node.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')')
local label_copy = swap_node.short_description .. ' (' .. S('owned by') .. ' '
.. meta:get_string('owner') .. ')\n' .. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
end
else
local node_def = minetest.registered_nodes[node.name]
if node_def then
meta:set_string('infotext', node_def.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description() .. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
end
end
end
_def.on_timer = function(pos, elapsed)
local pos_above = { x = pos.x, y = pos.y + 1, z = pos.z }
local node_above = minetest.get_node(pos_above)
if not node_above then
x_farming.tick_again_crates(pos)
return
end
if node_above.name ~= 'air' then
x_farming.tick_again_crates(pos)
return
end
local rand1 = math.random(1, 2) / 10
minetest.add_particlespawner({
amount = 60,
time = 15,
minpos = { x = pos_above.x - 0.1, y = pos_above.y - 0.3, z = pos_above.z - 0.1 },
maxpos = { x = pos_above.x + 0.1, y = pos_above.y + 0.4, z = pos_above.z + 0.1 },
minvel = { x = rand1 * -1, y = rand1 * -1, z = rand1 * -1 },
maxvel = { x = rand1, y = rand1, z = rand1 },
minacc = { x = rand1 * -1, y = rand1 * -1, z = rand1 * -1 },
maxacc = { x = rand1, y = rand1, z = rand1 },
minexptime = 1,
maxexptime = 1.5,
minsize = 0.1,
maxsize = 0.3,
texture = 'x_farming_fly.png',
collisiondetection = true,
object_collision = true
})
x_farming.tick_crates(pos)
end
x_farming.registered_crates[_def.name] = _def
if _def.name ~= 'x_farming:crate_empty' then
table.insert(x_farming.lbm_nodenames_crates, _def.name)
end
minetest.register_node(_def.name, _def)
end
---Bag
function x_farming.register_bag(name, def)
local _def = table.copy(def) or {}
_def._custom = _def._custom or {}
_def.name = 'x_farming:' .. name
_def.description = def.description or name
_def.short_description = def.short_description or def.description
_def.drawtype = 'mesh'
_def.paramtype = 'light'
_def.paramtype2 = 'facedir'
_def.mesh = 'x_farming_bag.obj'
_def.tiles = def.tiles
_def.use_texture_alpha = 'clip'
_def.sounds = def.sounds or x_farming.node_sound_sand_defaults()
_def.is_ground_content = false
_def.groups = def.groups or {
-- MTG
choppy = 2,
oddly_breakable_by_hand = 2,
-- MCL
handy = 1,
building_block = 1,
deco_block = 1,
-- ALL
not_in_creative_inventory = 1,
flammable = 2
}
-- MCL
_def._mcl_hardness = 0.6
_def._mcl_blast_resistance = 0.6
_def.stack_max = def.stack_max or 1
_def.mod_origin = 'x_farming'
if _def._custom.bag_item then
x_farming.allowed_bag_items[_def._custom.bag_item] = true
end
_def.on_construct = function(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
meta:set_string('infotext', _def.short_description)
meta:set_string('owner', '')
inv:set_size('main', 1)
end
_def.after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local meta_st = itemstack:get_meta()
local bag_inv = minetest.deserialize(meta_st:get_string('bag_inv'))
local inv = meta:get_inventory()
if bag_inv then
inv:add_item('main', ItemStack(bag_inv))
end
local node = minetest.get_node(pos)
meta:set_string('owner', placer:get_player_name() or '')
if not inv:is_empty('main') then
local inv_stack = inv:get_stack('main', 1)
meta:set_string('infotext', _def.short_description .. ' (' .. S('owned by') .. ' '
.. meta:get_string('owner') .. ')\n' .. inv_stack:get_description()
.. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
else
local swap_node = minetest.registered_nodes['x_farming:bag_empty']
if swap_node and inv:is_empty('main') and node.name ~= swap_node.name then
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
meta:set_string('infotext', swap_node.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')')
end
end
end
_def.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local p_name = clicker:get_player_name()
if minetest.is_protected(pos, p_name) then
return itemstack
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local inv_stack = inv:get_stack('main', 1)
local label_copy = _def.short_description .. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
minetest.sound_play('x_farming_sand_footstep', { gain = 0.3, pos = pos, max_hear_distance = 10 }, true)
end
_def.on_blast = function(pos, intensity)
if minetest.is_protected(pos, '') then
return
end
local drops = {}
local inv = minetest.get_meta(pos):get_inventory()
local n = #drops
for i = 1, inv:get_size('main') do
local stack = inv:get_stack('main', i)
if stack:get_count() > 0 then
drops[n + 1] = stack:to_table()
n = n + 1
end
end
drops[#drops + 1] = name
minetest.remove_node(pos)
return drops
end
_def.can_dig = function(pos, player)
return not minetest.is_protected(pos, player:get_player_name())
end
_def.preserve_metadata = function(pos, oldnode, oldmeta, drops)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local stack = drops[1]
local meta_drop = stack:get_meta()
if not inv:is_empty('main') then
local inv_stack = inv:get_stack('main', 1)
meta_drop:set_string('bag_inv', minetest.serialize(inv_stack:to_table()))
meta_drop:set_string('description', stack:get_description() .. '\n'
.. inv_stack:get_description() .. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
return
end
end
_def.allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) or not x_farming.allowed_bag_items[stack:get_name()] then
return 0
end
return stack:get_count()
end
_def.allow_metadata_inventory_take = function(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) or not x_farming.allowed_bag_items[stack:get_name()] then
return 0
end
return stack:get_count()
end
_def.on_metadata_inventory_put = function(pos, listname, index, stack, player)
local stack_name = stack:get_name()
if not stack_name or stack_name == '' then
return
end
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local split_name = stack_name:split(':')
local node = minetest.get_node(pos)
local swap_node = minetest.registered_nodes['x_farming:bag_' .. split_name[2]]
local inv_stack = inv:get_stack('main', 1)
if not swap_node then
return
end
if not inv:is_empty(listname) and node.name ~= swap_node.name then
local p_name = player:get_player_name()
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
local label_copy = swap_node.short_description .. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
end
meta:set_string('infotext', swap_node.short_description .. ' (' .. S('owned by')
.. ' ' .. meta:get_string('owner') .. ')\n'
.. inv_stack:get_description() .. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
end
_def.on_metadata_inventory_take = function(pos, listname, index, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local inv_stack = inv:get_stack('main', 1)
local node = minetest.get_node(pos)
if inv:is_empty(listname) then
local p_name = player:get_player_name()
local swap_node = minetest.registered_nodes['x_farming:bag_empty']
if swap_node then
minetest.swap_node(pos, { name = swap_node.name, param2 = node.param2 })
meta:set_string('infotext', swap_node.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')')
local label_copy = swap_node.short_description
.. ' (' .. S('owned by') .. ' ' .. meta:get_string('owner') .. ')\n' .. inv_stack:get_description()
minetest.show_formspec(p_name, _def.name, x_farming.get_crate_or_bag_formspec(pos, label_copy))
end
else
local node_def = minetest.registered_nodes[node.name]
if node_def then
meta:set_string('infotext', node_def.short_description .. ' (' .. S('owned by') .. ' '
.. meta:get_string('owner') .. ')\n' .. inv_stack:get_description()
.. '\n' .. S('Quantity') .. ': ' .. inv_stack:get_count())
end
end
end
minetest.register_node(_def.name, _def)
end
--
---Bonemeal
--
------------
---Main API for x_bonemeal Mod
---@author Juraj Vajda
---@license GNU LGPL 2.1
----
---Get creative mode setting from minetest.conf
local creative_mod_cache = minetest.settings:get_bool('creative_mode')
---Check if creating mode is enabled or player has creative privs
---@param name string name
---@return boolean
function x_farming.x_bonemeal.is_creative(name)
return creative_mod_cache or minetest.check_player_privs(name, { creative = true })
end
---Check if node has a soil below its self
---@param under Vector of position
---@return boolean
function x_farming.x_bonemeal.is_on_soil(under)
local below = minetest.get_node_or_nil({ x = under.x, y = under.y - 1, z = under.z })
if not below then
return false
end
if minetest.get_item_group(below.name, 'soil') == 0 and below.name ~= 'mcl_farming:soil_wet' then
return false
end
return true
end
---Check if node has a sand below its self
---@param under Vector of position
---@return boolean
function x_farming.x_bonemeal.is_on_sand(under)
local below = minetest.get_node_or_nil({ x = under.x, y = under.y - 1, z = under.z })
if not below then
return false
end
if minetest.get_item_group(below.name, 'sand') == 0
and minetest.get_item_group(below.name, 'everness_sand') == 0
then
return false
end
return true
end
---Growth steps for farming plants, there is no way of getting them dynamically,
--- so they are defined in the local table variable
local farming_steps = {
['farming:wheat'] = 8,
['farming:cotton'] = 8,
['x_farming:coffee'] = 5,
['x_farming:corn'] = 10,
['x_farming:obsidian_wart'] = 6,
['x_farming:melon'] = 8,
['x_farming:pumpkin'] = 8,
['x_farming:carrot'] = 8,
['x_farming:potato'] = 8,
['x_farming:beetroot'] = 8,
['x_farming:strawberry'] = 4,
['x_farming:stevia'] = 8,
['x_farming:soybean'] = 7,
['x_farming:salt'] = 7,
['x_farming:barley'] = 8,
['x_farming:cotton'] = 8,
}
---Particle and sound effect after the bone meal is successfully used
---@param pos Vector containing position
function x_farming.x_bonemeal.particle_effect(pos)
minetest.sound_play('x_farming_x_bonemeal_grow', {
pos = pos,
gain = 0.5,
})
minetest.add_particlespawner({
amount = 6,
time = 3,
minpos = { x = pos.x - 0.4, y = pos.y - 0.4, z = pos.z - 0.4 },
maxpos = { x = pos.x + 0.4, y = pos.y, z = pos.z + 0.4 },
minvel = { x = 0, y = 0, z = 0 },
maxvel = { x = 0, y = 0.1, z = 0 },
minacc = vector.new({ x = 0, y = 0, z = 0 }),
maxacc = vector.new({ x = 0, y = 0.1, z = 0 }),
minexptime = 2,
maxexptime = 3,
minsize = 1,
maxsize = 3,
texture = 'x_farming_x_bonemeal_particles.png',
animation = {
type = 'vertical_frames',
aspect_w = 8,
aspect_h = 8,
length = 3,
},
})
end
function x_farming.x_bonemeal.tableContains(table, value)
local found = false
if not table or type(table) ~= 'table' then
return found
end
for k, v in ipairs(table) do
if v == value then
found = true
break
end
end
return found
end
function x_farming.x_bonemeal.groupContains(groups, fertility, value)
local found = false
if not groups or type(groups) ~= 'table' then
return found
end
if groups[fertility] and groups[fertility] == value then
found = true
end
return found
end
---Handle growth of decorations based on biome
---@param itemstack ItemStack
---@param user ObjectRef | nil
---@param pointed_thing PointedThingDef
---@return { ['success']: boolean, ['itemstack']: ItemStack }
function x_farming.x_bonemeal.grow_grass_and_flowers(itemstack, user, pointed_thing)
local result = {
success = false,
itemstack = itemstack
}
local node = minetest.get_node(pointed_thing.under)
if not node then
return result
end
local pos0 = vector.subtract(pointed_thing.under, 3)
local pos1 = vector.add(pointed_thing.under, 3)
local biome_data = minetest.get_biome_data(pointed_thing.under)
if not biome_data then
return result
end
local biome_name = minetest.get_biome_name(biome_data.biome)
if not biome_name then
return result
end
local random_number = math.random(2, 6)
local registered_decorations_filtered = {}
---@type ItemStack | nil
local returned_itemstack
local node_def = minetest.registered_nodes[node.name]
local below_water = false
local floats_on_water = false
local node_in_decor = false
local positions_dirty
local positions = {}
local decor_place_on = {}
-- print('biome_name', biome_name)
---check 1 node below pointed node (floats on water)
local test_node = minetest.get_node({
x = pointed_thing.under.x,
y = pointed_thing.under.y - 1,
z = pointed_thing.under.z
})
local test_node_def = minetest.registered_nodes[test_node.name]
if test_node_def
and test_node_def.liquidtype == 'source'
and minetest.get_item_group(test_node_def.name, 'water') > 0
then
floats_on_water = true
end
---check 2 nodes above pointed nodes (below water)
local water_nodes_above = 0
for i = 1, 2 do
local test_node2 = minetest.get_node({
x = pointed_thing.under.x,
y = pointed_thing.under.y + i,
z = pointed_thing.under.z
})
local test_node_def2 = minetest.registered_nodes[test_node2.name]
if test_node_def2
and test_node_def2.liquidtype == 'source'
and minetest.get_item_group(test_node_def2.name, 'water') > 0
then
water_nodes_above = water_nodes_above + 1
end
end
if water_nodes_above == 2 then
below_water = true
end
if below_water then
positions_dirty = minetest.find_nodes_in_area(pos0, pos1, node.name)
elseif floats_on_water then
positions_dirty = minetest.find_nodes_in_area(pos0, pos1, 'air')
else
positions_dirty = minetest.find_nodes_in_area_under_air(pos0, pos1, node.name)
end
---find suitable decorations
for _, v in pairs(minetest.registered_decorations) do
---only for 'simple' decoration types
if v.deco_type == 'simple' then
---filter based on biome name in `biomes` table and node name in `place_on` table
if x_farming.x_bonemeal.tableContains(v.biomes, biome_name) then
table.insert(registered_decorations_filtered, v)
end
end
---clicked node is in decoration
local _decoration = v.decoration
if type(v.decoration) == 'string' then
_decoration = { v.decoration }
end
if x_farming.x_bonemeal.tableContains(_decoration, node.name) then
node_in_decor = true
end
---all nodes on which decoration can be placed on
---indexed by name
if not decor_place_on[v.place_on] then
if type(v.place_on) == 'string' then
decor_place_on[v.place_on] = true
elseif type(v.place_on) == 'table' then
for _, v2 in ipairs(v.place_on) do
decor_place_on[v2] = true
end
end
end
end
---find suitable positions
for j, pos_value in ipairs(positions_dirty) do
local node_at_pos = minetest.get_node(pos_value)
if below_water then
---below water
local water_nodes_above2 = 0
---check if 2 nodes above are water
for i = 1, 2 do
local test_node3 = minetest.get_node({ x = pos_value.x, y = pos_value.y + i, z = pos_value.z })
local test_node_def3 = minetest.registered_nodes[test_node3.name]
if test_node_def3
and test_node_def3.liquidtype == 'source'
and minetest.get_item_group(test_node_def3.name, 'water') > 0
then
water_nodes_above2 = water_nodes_above2 + 1
end
end
if water_nodes_above2 == 2 and decor_place_on[test_node.name] then
table.insert(positions, pos_value)
end
else
---above water (not on water)
if decor_place_on[node_at_pos.name] then
table.insert(positions, pos_value)
end
end
end
---find suitable positions (float on water)
if floats_on_water then
for _, pos_value in ipairs(positions_dirty) do
local node_at_pos_below = minetest.get_node({ x = pos_value.x, y = pos_value.y - 1, z = pos_value.z })
local test_node_def4 = minetest.registered_nodes[node_at_pos_below.name]
if test_node_def4
and test_node_def4.liquidtype == 'source'
and minetest.get_item_group(test_node_def4.name, 'water') > 0
then
table.insert(positions, pos_value)
end
end
end
local returned_itemstack_success = 0
---place decorations on random positions
if #positions > 0 and #registered_decorations_filtered > 0 then
for i = 1, random_number do
local idx = math.random(1, #positions)
local random_pos = positions[idx]
local random_decor = registered_decorations_filtered[math.random(1, #registered_decorations_filtered)]
local random_decor_item = random_decor.decoration
if floats_on_water and node_in_decor then
random_decor_item = node.name
elseif type(random_decor.decoration) == 'table' then
random_decor_item = random_decor.decoration[math.random(1, #random_decor.decoration)]
end
local random_decor_item_def = minetest.registered_nodes[random_decor_item]
if random_pos ~= nil and random_decor_item_def.drawtype ~= 'airlike' then
if random_decor_item_def.on_place ~= nil and node_def and not node_def.on_rightclick then
---on_place
local pt = {
type = 'node',
above = {
x = random_pos.x,
y = random_pos.y + 1,
z = random_pos.z
},
under = {
x = random_pos.x,
y = random_pos.y,
z = random_pos.z
}
}
if floats_on_water then
pt.above.y = random_pos.y
pt.under.y = random_pos.y - 1
end
returned_itemstack = random_decor_item_def.on_place(ItemStack(random_decor_item), user, pt)
if returned_itemstack and returned_itemstack:is_empty() then
returned_itemstack_success = returned_itemstack_success + 1
x_farming.x_bonemeal.particle_effect(pt.above)
end
elseif random_decor_item_def ~= nil then
---everything else
local pos_y = 1
if random_decor.place_offset_y ~= nil then
pos_y = random_decor.place_offset_y
end
x_farming.x_bonemeal.particle_effect(random_pos)
minetest.set_node({
x = random_pos.x,
y = random_pos.y + pos_y,
z = random_pos.z
},
{ name = random_decor_item })
end
table.remove(positions, idx)
else
return result
end
end
else
return result
end
---take item
if user and returned_itemstack_success > 0
and not x_farming.x_bonemeal.is_creative(user:get_player_name())
then
itemstack:take_item()
end
result.success = true
result.itemstack = itemstack
return result
end
---Handle farming and farming addons plants.
---Needed to copy this function from minetest_game and modify it in order to ommit some checks (e.g. light..)
---@param itemstack ItemStack
---@param user ObjectRef | nil
---@param pointed_thing PointedThingDef
---@return { ['success']: boolean, ['itemstack']: ItemStack }
function x_farming.x_bonemeal.grow_farming(itemstack, user, pointed_thing)
local result = {
success = false,
itemstack = itemstack
}
local pos_under = pointed_thing.under
local replace_node_name = minetest.get_node(pos_under).name
local ndef = minetest.registered_nodes[replace_node_name]
local take_item = false
if not ndef.next_plant
or ndef.next_plant == 'x_farming:pumpkin_fruit'
or ndef.next_plant == 'x_farming:melon_fruit'
then
return result
end
local pos0 = vector.subtract(pointed_thing.under, 3)
local pos1 = vector.add(pointed_thing.under, 3)
local positions = minetest.find_nodes_in_area(pos0, pos1, { 'group:plant', 'group:seed' })
for i, pos in ipairs(positions) do
local isFertile = false
replace_node_name = minetest.get_node(pos).name
---check if on wet soil
local below = minetest.get_node({ x = pos.x, y = pos.y - 1, z = pos.z })
local below_def = minetest.registered_nodes[below.name]
if minetest.get_item_group(below.name, 'soil') == 3 or below.name == 'mcl_farming:soil_wet' then
local current_step = tonumber(string.reverse(string.reverse(replace_node_name):split('_')[1]))
local max_step = farming_steps[replace_node_name:gsub('_%d+', '', 1)]
---check if seed
---farming:seed_wheat
local mod_plant = replace_node_name:split(':')
---seed_wheat
local seed_plant = mod_plant[2]:split('_')
local seed_name = replace_node_name
if seed_plant[1] == 'seed' then
current_step = 0
if replace_node_name == 'x_farming:seed_obsidian_wart' then
replace_node_name = mod_plant[1] .. ':' .. seed_plant[2] .. '_' .. seed_plant[3]
else
replace_node_name = mod_plant[1] .. ':' .. seed_plant[2]
end
max_step = farming_steps[replace_node_name]
replace_node_name = replace_node_name .. '_' .. current_step
else
if string.find(replace_node_name, 'obsidian_wart') then
seed_name = mod_plant[1] .. ':seed_' .. seed_plant[1] .. '_' .. seed_plant[2]
else
seed_name = mod_plant[1] .. ':seed_' .. seed_plant[1]
end
end
---search for fertility (again after checking soil)
local seed_def = minetest.registered_nodes[seed_name]
if seed_def and below_def then
if below_def.groups then
for _, v in ipairs(seed_def.fertility) do
if not isFertile then
isFertile = x_farming.x_bonemeal.groupContains(below_def.groups, v, 1) or below.name == 'mcl_farming:soil_wet'
end
end
end
end
if current_step ~= nil and max_step ~= nil and current_step ~= max_step and isFertile then
local available_steps = max_step - current_step
local new_step = max_step - available_steps + math.random(available_steps)
local new_plant = replace_node_name:gsub('_%d+', '_' .. new_step, 1)
take_item = true
local placenode_def = minetest.registered_nodes[new_plant]
local placenode = { name = new_plant }
if placenode_def and placenode_def.place_param2 then
placenode.param2 = placenode_def.place_param2
end
x_farming.x_bonemeal.particle_effect(pos)
minetest.swap_node(pos, placenode)
end
end
end
---take item if not in creative
if user and not x_farming.x_bonemeal.is_creative(user:get_player_name()) and take_item then
itemstack:take_item()
end
return {
success = true,
itemstack = itemstack
}
end
---XBonemeal on_use
---@param self table x_farming.x_bonemeal
---@param itemstack ItemStack
---@param user ObjectRef | nil
---@param pointed_thing any
---@return { ['success']: boolean, ['itemstack']: ItemStack }
function x_farming.x_bonemeal.on_use(self, itemstack, user, pointed_thing)
local result = {
success = false,
itemstack = itemstack
}
if not user then
return result
end
local under = pointed_thing.under
if not under then
return result
end
if pointed_thing.type ~= 'node' then
return result
end
if minetest.is_protected(under, user:get_player_name()) then
return result
end
local node = minetest.get_node(under)
if not node then
return result
end
if node.name == 'ignore' then
return result
end
local mod = node.name:split(':')[1]
if (mod == 'farming' or mod == 'x_farming')
and not string.find(node.name, '_sapling')
and not string.find(node.name, '_seedling')
then
--
-- Farming
--
return self.grow_farming(itemstack, user, pointed_thing)
elseif self.tree_defs[node.name] then
--
-- Default (Trees, Bushes, Papyrus)
--
local def = self.tree_defs[node.name]
local chance = math.random(1, def.chance)
if chance == 1 then
local success = def.grow_tree(under)
if not success then
return result
end
self.particle_effect({ x = under.x, y = under.y + 1, z = under.z })
end
-- take item if not in creative
if not self.is_creative(user:get_player_name()) then
itemstack:take_item()
end
return {
success = true,
itemstack = itemstack
}
else
return self.grow_grass_and_flowers(itemstack, user, pointed_thing)
end
end
--- API for registering tree growing from saplings using bonemeal
function x_farming.x_bonemeal.register_tree_defs(self, defs)
if not defs or type(defs) ~= 'table' then
minetest.log('warning', '[x_farming][x_bonemeal] Missing or incorrect definition: \n' .. dump(defs))
end
for _, value in ipairs(defs) do
local def = table.copy(value)
if not self.tree_defs[def.name] then
self.tree_defs[def.name] = value
end
end
end
-- TheTermos (MIT)
-- vec components can be omitted e.g. vec={y=1}
function x_farming.pos_shift(pos, vec)
vec.x = vec.x or 0
vec.y = vec.y or 0
vec.z = vec.z or 0
return {
x = pos.x + vec.x,
y = pos.y + vec.y,
z = pos.z + vec.z
}
end
-- TheTermos (MIT)
function x_farming.get_node_pos(pos)
return {
x = math.floor(pos.x + 0.5),
y = math.floor(pos.y + 0.5),
z = math.floor(pos.z + 0.5),
}
end
-- TheTermos (MIT)
function x_farming.nodeatpos(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return minetest.registered_nodes[node.name]
end
end
-- TheTermos (MIT)
function x_farming.get_node_height(pos)
local npos = x_farming.get_node_pos(pos)
local node = x_farming.nodeatpos(npos)
if node == nil then
return nil
end
if node.walkable then
if node.drawtype == 'nodebox' then
if node.node_box and node.node_box.type == 'fixed' then
if type(node.node_box.fixed[1]) == 'number' then
return npos.y + node.node_box.fixed[5], 0, false
elseif type(node.node_box.fixed[1]) == 'table' then
return npos.y + node.node_box.fixed[1][5], 0, false
else
-- todo handle table of boxes
return npos.y + 0.5, 1, false
end
elseif node.node_box and node.node_box.type == 'leveled' then
return minetest.get_node_level(pos) / 64 - 0.5 + x_farming.get_node_pos(pos).y, 0, false
else
-- the unforeseen
return npos.y + 0.5, 1, false
end
else
-- full node
return npos.y + 0.5, 1, false
end
else
local liquidflag = false
if node.drawtype == 'liquid' then liquidflag = true end
return npos.y - 0.5, -1, liquidflag
end
end
-- TheTermos (MIT)
-- get_terrain_height
-- steps(optional) number of recursion steps; default=3
-- dir(optional) is 1=up, -1=down, 0=both; default=0
-- liquidflag(forbidden) never provide this parameter.
-- dir is 1=up, -1=down, 0=both
function x_farming.get_terrain_height(pos, steps, dir, liquidflag)
steps = steps or 3
dir = dir or 0
local h, f, l = x_farming.get_node_height(pos)
if h == nil then
return nil
end
if l then
liquidflag = true
end
if f == 0 then
return h, liquidflag
end
if dir == 0 or dir == f then
steps = steps - 1
if steps <= 0 then
return nil
end
return x_farming.get_terrain_height(x_farming.pos_shift(pos, { y = f }), steps, f, liquidflag)
else
return h, liquidflag
end
end
-- TheTermos (MIT)
-- modified by SaKeL
function x_farming.get_spawn_pos_abr(dtime, intrvl, radius, chance, reduction)
dtime = math.min(dtime, 0.1)
local players = minetest.get_connected_players()
intrvl = 1 / intrvl
if math.random() < dtime * (intrvl * #players) then
-- choose random player
local player = players[math.random(#players)]
local vel = player:get_velocity()
local spd = vector.length(vel)
chance = (1 - chance) * 1 / (spd * 0.75 + 1)
local yaw
if spd > 1 then
-- spawn in the front arc
yaw = minetest.dir_to_yaw(vel) + math.random() * 0.35 - 0.75
else
-- random yaw
yaw = math.random() * math.pi * 2 - math.pi
end
local pos = player:get_pos()
local dir = vector.multiply(minetest.yaw_to_dir(yaw), radius)
local pos2 = vector.add(pos, dir)
pos2.y = pos2.y - 5
local height, liquidflag = x_farming.get_terrain_height(pos2, 32)
if height then
local objs = minetest.find_node_near(pos, radius * 1.1, { 'group:bee' }) or {}
-- count mobs in abrange
for _, obj in ipairs(objs) do
chance = chance + (1 - chance) * reduction
end
if chance < math.random() then
pos2.y = height
objs = minetest.get_objects_inside_radius(pos2, radius * 0.95)
-- do not spawn if another player around
for _, obj in ipairs(objs) do
if obj:is_player() then
return
end
end
return pos2, liquidflag
end
end
end
end
function x_farming.on_flood_candle(pos, oldnode, newnode)
local drops = minetest.get_node_drops(oldnode)
for _, item_name in ipairs(drops) do
minetest.add_item(pos, ItemStack(item_name))
end
-- Play flame-extinguish sound if liquid is not an 'igniter'
local nodedef = minetest.registered_items[newnode.name]
if not (nodedef and nodedef.groups
and nodedef.groups.igniter and nodedef.groups.igniter > 0)
and minetest.get_item_group(oldnode.name, 'candle_on') > 0
then
minetest.sound_play(
'x_farming_extinguish_candle',
{ pos = pos, max_hear_distance = 16, gain = 0.07 },
true
)
end
-- Remove the torch node
return false
end
-- Feasts
function x_farming.register_feast(name, def)
local g = {
-- MTG
choppy = 3,
oddly_breakable_by_hand = 3,
compost = 100,
no_silktouch = 1,
-- MCL
handy = 1,
shearsy = 1,
deco_block = 1,
non_mycelium_plant = 1,
fire_encouragement = 60,
fire_flammability = 100,
dig_by_water = 1,
destroy_by_lava_flow = 1,
compostability = 100,
food = 2,
eatable = 1,
-- ALL
flammable = 2,
attached_node = 1,
}
-- merge groups from `def`
if def.groups then
for group, value in pairs(def.groups) do
g[group] = value
end
end
local _def = {
description = def.description,
short_description = def.short_description or def.description,
drawtype = 'mesh',
mesh = def.mesh,
use_texture_alpha = def.use_texture_alpha or 'clip',
inventory_image = def.inventory_image or ('x_farming_' .. name .. '_item.png'),
wield_image = def.wield_image or ('x_farming_' .. name .. '_item.png'),
wield_scale = { x = 2, y = 2, z = 1 },
paramtype = 'light',
paramtype2 = '4dir',
is_ground_content = false,
walkable = false,
selection_box = def.selection_box,
groups = g,
_mcl_blast_resistance = 0,
_mcl_hardness = 0,
sounds = def.sounds or x_farming.node_sound_wood_defaults(),
sunlight_propagates = true,
}
for i = 1, def.steps do
local d = table.copy(_def)
d._next_step = i + 1
d.tiles = {
{ name = 'x_farming_' .. name .. '_mesh.png', backface_culling = def.tiles_backface_culling or false },
{ name = 'x_farming_' .. name .. '_mesh_' .. i .. '.png', backface_culling = def.tiles_backface_culling or false },
}
if i ~= 1 then
d.groups['not_in_creative_inventory'] = 1
end
-- last (no more food) step
if i == def.steps then
d.drop = def.last_drop or 'x_farming:bowl'
else
d.drop = {
max_items = def.steps - i,
items = {
{
rarity = 1,
items = {
'x_farming:bowl_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:bowl_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:bowl_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:bowl_' .. name
}
},
}
}
end
d.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local n = minetest.registered_nodes[node.name]
local p_name = clicker:get_player_name()
local inv = clicker:get_inventory()
local stack_name = itemstack:get_name()
if not n then
return itemstack
end
-- last (no more food) step
if n._next_step > def.steps then
minetest.chat_send_player(p_name, S('There is no more food left!'))
return itemstack
end
if stack_name == 'x_farming:bowl' then
minetest.swap_node(pos, { name = 'x_farming:' .. name .. '_' .. n._next_step, param2 = node.param2 })
if not minetest.is_creative_enabled(p_name) then
itemstack:take_item()
end
minetest.sound_play('x_farming_wooden_bowl', {
pos = pos,
gain = 0.4,
max_hear_distance = 16
})
local stack_bowl = ItemStack({ name = 'x_farming:bowl_' .. name })
if inv and inv:room_for_item('main', stack_bowl) then
inv:add_item('main', stack_bowl)
else
-- drop on the ground
minetest.add_item(clicker:get_pos(), stack_bowl)
end
else
minetest.chat_send_player(p_name, S('You need to hold empty bowl if you want to take portion of the food!'))
end
return itemstack
end
-- Node
minetest.register_node('x_farming:' .. name .. '_' .. i, d)
-- Craftitem definition
local craftitem_def = {
description = def.short_description .. ' ' .. S('Bowl') .. '\n' .. S('Compost chance') .. ': 100%\n'
.. minetest.colorize(x_farming.colors.brown, S('Hunger') .. ': 8'),
inventory_image = 'x_farming_bowl_' .. name .. '.png',
wield_image = 'x_farming_bowl_' .. name .. '.png',
groups = {
-- MTG
-- X Farming
compost = 100,
-- MCL
food = 3,
eatable = 10,
compostability = 100,
},
-- MCL
_mcl_saturation = 12.0,
}
if minetest.get_modpath('farming') then
craftitem_def.on_use = minetest.item_eat(8)
end
if minetest.get_modpath('mcl_farming') then
craftitem_def.on_place = minetest.item_eat(8)
craftitem_def.on_secondary_use = minetest.item_eat(8)
end
-- Craftitem
minetest.register_craftitem('x_farming:bowl_' .. name, craftitem_def)
end
end
-- Pies
function x_farming.register_pie(name, def)
local g = {
-- MTG
choppy = 3,
oddly_breakable_by_hand = 3,
compost = 100,
no_silktouch = 1,
-- MCL
handy = 1,
shearsy = 1,
deco_block = 1,
non_mycelium_plant = 1,
fire_encouragement = 60,
fire_flammability = 100,
dig_by_water = 1,
destroy_by_lava_flow = 1,
compostability = 100,
food = 2,
eatable = 1,
-- ALL
flammable = 2,
attached_node = 1,
}
-- merge groups from `def`
if def.groups then
for group, value in pairs(def.groups) do
g[group] = value
end
end
local _def = {
description = def.description,
short_description = def.short_description or def.description,
drawtype = 'mesh',
mesh = def.mesh,
use_texture_alpha = def.use_texture_alpha or 'clip',
inventory_image = def.inventory_image or ('x_farming_' .. name .. '_item.png'),
wield_image = def.wield_image or ('x_farming_' .. name .. '_item.png'),
wield_scale = { x = 2, y = 2, z = 1 },
paramtype = 'light',
paramtype2 = '4dir',
is_ground_content = false,
walkable = false,
selection_box = {
type = 'fixed',
fixed = { -7 / 16, -8 / 16, -7 / 16, 7 / 16, -3 / 16, 7 / 16 }
},
groups = g,
_mcl_blast_resistance = 0,
_mcl_hardness = 0,
sounds = def.sounds or x_farming.node_sound_wood_defaults(),
sunlight_propagates = true,
item_eat = 6
}
for i = 1, def.steps do
local d = table.copy(_def)
d._next_step = i + 1
d.tiles = {
{ name = 'x_farming_' .. name .. '_mesh_' .. i .. '.png', backface_culling = def.tiles_backface_culling or false },
}
if i ~= 1 then
d.groups['not_in_creative_inventory'] = 1
end
d.drop = {
max_items = def.steps - i + 1,
items = {
{
rarity = 1,
items = {
'x_farming:slice_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:slice_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:slice_' .. name
}
},
{
rarity = 1,
items = {
'x_farming:slice_' .. name
}
},
}
}
d.on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
local n = minetest.registered_nodes[node.name]
if not n then
return itemstack
end
if i < def.steps then
minetest.swap_node(pos, { name = 'x_farming:' .. name .. '_' .. n._next_step, param2 = node.param2 })
else
minetest.remove_node(pos)
minetest.check_for_falling(pos)
end
local sound_name = 'x_farming_wooden_bowl'
if minetest.get_modpath('hunger_ng') then
hunger_ng.alter_hunger(clicker:get_player_name(), _def.item_eat)
sound_name = 'hunger_ng_eat'
elseif minetest.get_modpath('hbhunger') then
hbhunger.eat(_def.item_eat, nil, ItemStack({ name = 'x_farming:' .. name .. '_1' }), clicker, pointed_thing)
sound_name = nil
elseif minetest.get_modpath('stamina') and minetest.global_exists('stamina') then
-- extra check for global variable since there are some mods called "stamina" without registering global "stamina" namespace
-- @see https://content.minetest.net/threads/6791/
stamina.change_saturation(clicker, _def.item_eat)
sound_name = 'stamina_eat'
elseif minetest.get_modpath('mcl_hunger') then
local h = mcl_hunger.get_hunger(clicker)
mcl_hunger.set_hunger(clicker, math.min(h + _def.item_eat, 20))
sound_name = 'mcl_hunger_bite'
else
minetest.item_eat(_def.item_eat)
end
if sound_name then
minetest.sound_play(sound_name, { pos = pos, gain = 0.7, max_hear_distance = 5 }, true)
end
return itemstack
end
-- Node
minetest.register_node('x_farming:' .. name .. '_' .. i, d)
local craftitem_def = {
description = def.short_description .. ' ' .. S('Slice') .. '\n' .. S('Compost chance') .. ': 100%\n'
.. minetest.colorize(x_farming.colors.brown, S('Hunger') .. ': ' .. _def.item_eat),
inventory_image = 'x_farming_slice_' .. name .. '.png',
wield_image = 'x_farming_slice_' .. name .. '.png',
groups = {
-- MTG
-- X Farming
compost = 100,
-- MCL
food = 3,
eatable = 10,
compostability = 100,
},
-- MCL
_mcl_saturation = 10.0,
}
if minetest.get_modpath('farming') then
craftitem_def.on_use = minetest.item_eat(_def.item_eat)
end
if minetest.get_modpath('mcl_farming') then
craftitem_def.on_place = minetest.item_eat(_def.item_eat)
craftitem_def.on_secondary_use = minetest.item_eat(_def.item_eat)
end
-- Craftitem
minetest.register_craftitem('x_farming:slice_' .. name, craftitem_def)
end
end
--
-- MCL
--
function x_farming.mcl.create_soil(pos, inv)
if pos == nil then
return false
end
local node = minetest.get_node(pos)
local name = node.name
local above = minetest.get_node({ x = pos.x, y = pos.y + 1, z = pos.z })
if minetest.get_item_group(name, 'cultivatable') == 2 then
if above.name == 'air' then
node.name = 'mcl_farming:soil'
minetest.set_node(pos, node)
minetest.sound_play('x_farming_dirt_hit', { pos = pos, gain = 0.5 }, true)
return true
end
elseif minetest.get_item_group(name, 'cultivatable') == 1 then
if above.name == 'air' then
node.name = 'mcl_core:dirt'
minetest.set_node(pos, node)
minetest.sound_play('x_farming_dirt_hit', { pos = pos, gain = 0.6 }, true)
return true
end
end
return false
end
function x_farming.mcl.hoe_on_place_function(uses)
return function(itemstack, user, pointed_thing)
-- Call on_rightclick if the pointed node defines it
local node = minetest.get_node(pointed_thing.under)
-- Custom: add support for MTG definition farming
local regN = minetest.registered_nodes
if regN[node.name].soil ~= nil
and regN[node.name].soil.wet ~= nil
and regN[node.name].soil.dry ~= nil
then
return x_farming.hoe_on_use(itemstack, user, pointed_thing, uses)
end
if user and not user:get_player_control().sneak then
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, user, itemstack) or itemstack
end
end
if minetest.is_protected(pointed_thing.under, user:get_player_name()) then
minetest.record_protection_violation(pointed_thing.under, user:get_player_name())
return itemstack
end
if x_farming.mcl.create_soil(pointed_thing.under, user:get_inventory()) then
if not minetest.is_creative_enabled(user:get_player_name()) then
itemstack:add_wear_by_uses(uses)
end
return itemstack
end
end
end