2017-04-01 03:54:58 +02:00
local plant_lists = { }
2020-09-05 19:49:12 +02:00
local plant_nodename_to_id_list = { }
local function get_intervals_counter ( pos , interval , chance )
local meta = minetest.get_meta ( pos )
local time_speed = tonumber ( minetest.settings : get ( ' time_speed ' ) or 72 )
local current_game_time
if time_speed == nil then
return 1
end
if ( time_speed < 0.1 ) then
return 1
end
local time_multiplier = 86400 / time_speed
current_game_time = .0 + ( ( minetest.get_day_count ( ) + minetest.get_timeofday ( ) ) * time_multiplier )
local approx_interval = math.max ( interval , 1 ) * math.max ( chance , 1 )
local last_game_time = meta : get_string ( " last_gametime " )
if last_game_time then
last_game_time = tonumber ( last_game_time )
end
if not last_game_time or last_game_time < 1 then
last_game_time = current_game_time - approx_interval / 10
elseif last_game_time == current_game_time then
current_game_time = current_game_time + approx_interval
end
local elapsed_game_time = .0 + current_game_time - last_game_time
meta : set_string ( " last_gametime " , tostring ( current_game_time ) )
return elapsed_game_time / approx_interval
end
local function get_avg_light_level ( pos )
local node_light = tonumber ( minetest.get_node_light ( pos ) or 0 )
local meta = minetest.get_meta ( pos )
local counter = meta : get_int ( " avg_light_count " )
local summary = meta : get_int ( " avg_light_summary " )
if counter > 99 then
counter = 51
summary = math.ceil ( ( summary + 0.0 ) / 2.0 )
else
counter = counter + 1
end
summary = summary + node_light
meta : set_int ( " avg_light_count " , counter )
meta : set_int ( " avg_light_summary " , summary )
return math.ceil ( ( summary + 0.0 ) / counter )
end
2017-04-01 03:54:58 +02:00
function mcl_farming : add_plant ( identifier , full_grown , names , interval , chance )
plant_lists [ identifier ] = { }
plant_lists [ identifier ] . full_grown = full_grown
plant_lists [ identifier ] . names = names
2020-09-05 19:49:12 +02:00
plant_lists [ identifier ] . interval = interval
plant_lists [ identifier ] . chance = chance
2017-03-13 23:09:27 +01:00
minetest.register_abm ( {
2017-05-15 00:45:54 +02:00
label = string.format ( " Farming plant growth (%s) " , identifier ) ,
2017-03-13 23:09:27 +01:00
nodenames = names ,
interval = interval ,
chance = chance ,
action = function ( pos , node )
2020-09-05 19:49:12 +02:00
local low_speed = minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name ~= " mcl_farming:soil_wet "
mcl_farming : grow_plant ( identifier , pos , node , false , false , low_speed )
2017-04-01 03:54:58 +02:00
end ,
2017-03-13 23:09:27 +01:00
} )
2020-09-05 19:49:12 +02:00
for _ , nodename in pairs ( names ) do
plant_nodename_to_id_list [ nodename ] = identifier
end
2017-03-13 23:09:27 +01:00
end
2017-04-01 03:54:58 +02:00
-- Attempts to advance a plant at pos by one or more growth stages (if possible)
-- identifier: Identifier of plant as defined by mcl_farming:add_plant
-- pos: Position
-- node: Node table
-- stages: Number of stages to advance (optional, defaults to 1)
2019-02-08 05:36:43 +01:00
-- ignore_light: if true, ignore light requirements for growing
2017-05-25 03:24:11 +02:00
-- Returns true if plant has been grown by 1 or more stages.
-- Returns false if nothing changed.
2020-09-05 19:49:12 +02:00
function mcl_farming : grow_plant ( identifier , pos , node , stages , ignore_light , low_speed )
local average_light_level = get_avg_light_level ( pos )
local plant_info = plant_lists [ identifier ]
local intervals_counter = get_intervals_counter ( pos , plant_info.interval , plant_info.chance )
local low_speed = low_speed or false
if low_speed then
if intervals_counter < 1.01 and math.random ( 0 , 9 ) > 0 then
return
else
intervals_counter = intervals_counter / 10
end
end
if not minetest.get_node_light ( pos ) and not ignore_light and intervals_counter < 1.5 then
2017-05-25 03:24:11 +02:00
return false
2017-04-01 03:54:58 +02:00
end
2020-09-05 19:49:12 +02:00
if minetest.get_node_light ( pos ) < 10 and not ignore_light and intervals_counter < 1.5 then
2017-05-25 03:24:11 +02:00
return false
2017-04-01 03:54:58 +02:00
end
2020-09-05 19:49:12 +02:00
if intervals_counter >= 1.5 then
if average_light_level < 0.1 then
return false
end
if average_light_level < 10 then
intervals_counter = intervals_counter * average_light_level / 10
end
end
2017-04-01 03:54:58 +02:00
local step = nil
for i , name in ipairs ( plant_info.names ) do
if name == node.name then
step = i
break
end
end
if step == nil then
2017-05-25 03:24:11 +02:00
return false
2017-04-01 03:54:58 +02:00
end
if not stages then
stages = 1
end
2020-09-05 19:49:12 +02:00
stages = stages + math.ceil ( intervals_counter )
2017-04-01 03:54:58 +02:00
local new_node = { name = plant_info.names [ step + stages ] }
if new_node.name == nil then
new_node.name = plant_info.full_grown
end
new_node.param = node.param
new_node.param2 = node.param2
minetest.set_node ( pos , new_node )
2017-05-25 03:24:11 +02:00
return true
2017-04-01 03:54:58 +02:00
end
2017-03-13 23:09:27 +01:00
function mcl_farming : place_seed ( itemstack , placer , pointed_thing , plantname )
local pt = pointed_thing
if not pt then
return
end
if pt.type ~= " node " then
return
end
-- Use pointed node's on_rightclick function first, if present
local node = minetest.get_node ( pt.under )
if placer and not placer : 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 ( pt.under , node , placer , itemstack ) or itemstack
end
end
2017-03-14 03:56:33 +01:00
2017-03-13 23:09:27 +01:00
local pos = { x = pt.above . x , y = pt.above . y - 1 , z = pt.above . z }
local farmland = minetest.get_node ( pos )
pos = { x = pt.above . x , y = pt.above . y , z = pt.above . z }
local place_s = minetest.get_node ( pos )
if string.find ( farmland.name , " mcl_farming:soil " ) and string.find ( place_s.name , " air " ) then
2020-04-07 00:55:45 +02:00
minetest.sound_play ( minetest.registered_nodes [ plantname ] . sounds.place , { pos = pos } , true )
2017-03-14 21:25:20 +01:00
minetest.add_node ( pos , { name = plantname , param2 = minetest.registered_nodes [ plantname ] . place_param2 } )
2020-09-05 19:49:12 +02:00
local intervals_counter = get_intervals_counter ( pos , 1 , 1 )
2017-03-13 23:09:27 +01:00
else
return
end
2020-07-10 16:08:40 +02:00
if not minetest.is_creative_enabled ( placer : get_player_name ( ) ) then
2017-03-13 23:09:27 +01:00
itemstack : take_item ( )
end
return itemstack
end
2017-03-13 22:30:37 +01:00
--[[ Helper function to create a gourd (e.g. melon, pumpkin), the connected stem nodes as
- full_unconnected_stem : itemstring of the full - grown but unconnceted stem node . This node must already be done
- connected_stem_basename : prefix of the itemstrings used for the 4 connected stem nodes to create
2017-03-14 04:17:35 +01:00
- stem_itemstring : Desired itemstring of the fully - grown unconnected stem node
- stem_def : Partial node definition of the fully - grown unconnected stem node . Many fields are already defined . You need to add ` tiles ` and ` description ` at minimum . Don ' t define on_construct without good reason
- stem_drop : Drop probability table for all stem
2017-03-13 22:30:37 +01:00
- gourd_itemstring : Desired itemstring of the full gourd node
2022-02-23 18:14:44 +01:00
- gourd_def : ( almost ) full definition of the gourd node . This function will add on_construct and after_destruct to the definition for unconnecting any connected stems
2017-03-13 22:43:47 +01:00
- grow_interval : Will attempt to grow a gourd periodically at this interval in seconds
- grow_chance : Chance of 1 / grow_chance to grow a gourd next to the full unconnected stem after grow_interval has passed . Must be a natural number
2017-07-17 14:26:25 +02:00
- connected_stem_texture : Texture of the connected stem
2017-07-17 15:00:20 +02:00
- gourd_on_construct_extra : Custom on_construct extra function for the gourd . Will be called after the stem check code
2017-03-13 22:30:37 +01:00
] ]
2017-07-17 15:00:20 +02:00
function mcl_farming : add_gourd ( full_unconnected_stem , connected_stem_basename , stem_itemstring , stem_def , stem_drop , gourd_itemstring , gourd_def , grow_interval , grow_chance , connected_stem_texture , gourd_on_construct_extra )
2017-03-13 22:30:37 +01:00
local connected_stem_names = {
connected_stem_basename .. " _r " ,
connected_stem_basename .. " _l " ,
connected_stem_basename .. " _t " ,
connected_stem_basename .. " _b " ,
}
2017-03-14 03:56:33 +01:00
local neighbors = {
{ x =- 1 , y = 0 , z = 0 } ,
{ x = 1 , y = 0 , z = 0 } ,
{ x = 0 , y = 0 , z =- 1 } ,
{ x = 0 , y = 0 , z = 1 } ,
}
2017-03-14 04:17:35 +01:00
-- Connect the stem at stempos to the first neighboring gourd block.
-- No-op if not a stem or no gourd block found
2017-03-14 03:56:33 +01:00
local try_connect_stem = function ( stempos )
local stem = minetest.get_node ( stempos )
if stem.name ~= full_unconnected_stem then
return false
end
for n = 1 , # neighbors do
local offset = neighbors [ n ]
local blockpos = vector.add ( stempos , offset )
local block = minetest.get_node ( blockpos )
if block.name == gourd_itemstring then
if offset.x == 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 1 ] } )
elseif offset.x == - 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 2 ] } )
elseif offset.z == 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 3 ] } )
elseif offset.z == - 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 4 ] } )
end
return true
end
end
end
2017-03-13 22:30:37 +01:00
-- Register gourd
2022-02-23 18:14:44 +01:00
if not gourd_def.after_destruct then
gourd_def.after_destruct = function ( blockpos , oldnode )
2017-03-13 22:30:37 +01:00
-- Disconnect any connected stems, turning them back to normal stems
for n = 1 , # neighbors do
local offset = neighbors [ n ]
local expected_stem = connected_stem_names [ n ]
local stempos = vector.add ( blockpos , offset )
local stem = minetest.get_node ( stempos )
if stem.name == expected_stem then
minetest.add_node ( stempos , { name = full_unconnected_stem } )
2017-03-14 03:56:33 +01:00
try_connect_stem ( stempos )
2017-03-13 22:30:37 +01:00
end
end
end
end
2017-03-14 03:56:33 +01:00
if not gourd_def.on_construct then
gourd_def.on_construct = function ( blockpos )
-- Connect all unconnected stems at full size
for n = 1 , # neighbors do
local stempos = vector.add ( blockpos , neighbors [ n ] )
2017-03-14 04:17:35 +01:00
try_connect_stem ( stempos )
2017-03-14 03:56:33 +01:00
end
2017-07-17 15:00:20 +02:00
-- Call custom on_construct
if gourd_on_construct_extra then
gourd_on_construct_extra ( blockpos )
end
2017-03-14 03:56:33 +01:00
end
end
2017-03-13 22:30:37 +01:00
minetest.register_node ( gourd_itemstring , gourd_def )
2017-03-14 04:17:35 +01:00
-- Register unconnected stem
-- Default values for the stem definition
if not stem_def.selection_box then
stem_def.selection_box = {
type = " fixed " ,
fixed = {
{ - 0.15 , - 0.5 , - 0.15 , 0.15 , 0.5 , 0.15 }
} ,
}
end
if not stem_def.paramtype then
stem_def.paramtype = " light "
end
if not stem_def.drawtype then
stem_def.drawtype = " plantlike "
end
if stem_def.walkable == nil then
stem_def.walkable = false
end
if stem_def.sunlight_propagates == nil then
stem_def.sunlight_propagates = true
end
if stem_def.drop == nil then
stem_def.drop = stem_drop
end
if stem_def.groups == nil then
2017-05-20 04:11:14 +02:00
stem_def.groups = { dig_immediate = 3 , not_in_creative_inventory = 1 , plant = 1 , attached_node = 1 , dig_by_water = 1 , destroy_by_lava_flow = 1 , }
2017-03-14 04:17:35 +01:00
end
if stem_def.sounds == nil then
stem_def.sounds = mcl_sounds.node_sound_leaves_defaults ( )
end
if not stem_def.on_construct then
stem_def.on_construct = function ( stempos )
-- Connect stem to gourd (if possible)
try_connect_stem ( stempos )
end
end
minetest.register_node ( stem_itemstring , stem_def )
2017-03-13 22:30:37 +01:00
-- Register connected stems
local connected_stem_tiles = {
{ " blank.png " , --top
" blank.png " , -- bottom
" blank.png " , -- right
" blank.png " , -- left
2017-07-17 14:26:25 +02:00
connected_stem_texture , -- back
connected_stem_texture .. " ^[transformFX90 " --front
2017-03-13 22:30:37 +01:00
} ,
{ " blank.png " , --top
" blank.png " , -- bottom
" blank.png " , -- right
" blank.png " , -- left
2017-07-17 14:26:25 +02:00
connected_stem_texture .. " ^[transformFX90 " , --back
connected_stem_texture , -- front
2017-03-13 22:30:37 +01:00
} ,
{ " blank.png " , --top
" blank.png " , -- bottom
2017-07-17 14:26:25 +02:00
connected_stem_texture .. " ^[transformFX90 " , -- right
connected_stem_texture , -- left
2017-03-13 22:30:37 +01:00
" blank.png " , --back
" blank.png " , -- front
} ,
{ " blank.png " , --top
" blank.png " , -- bottom
2017-07-17 14:26:25 +02:00
connected_stem_texture , -- right
connected_stem_texture .. " ^[transformFX90 " , -- left
2017-03-13 22:30:37 +01:00
" blank.png " , --back
" blank.png " , -- front
}
}
local connected_stem_nodebox = {
{ - 0.5 , - 0.5 , 0 , 0.5 , 0.5 , 0 } ,
{ - 0.5 , - 0.5 , 0 , 0.5 , 0.5 , 0 } ,
{ 0 , - 0.5 , - 0.5 , 0 , 0.5 , 0.5 } ,
{ 0 , - 0.5 , - 0.5 , 0 , 0.5 , 0.5 } ,
}
2017-03-14 02:43:48 +01:00
local connected_stem_selectionbox = {
{ - 0.1 , - 0.5 , - 0.1 , 0.5 , 0.2 , 0.1 } ,
{ - 0.5 , - 0.5 , - 0.1 , 0.1 , 0.2 , 0.1 } ,
{ - 0.1 , - 0.5 , - 0.1 , 0.1 , 0.2 , 0.5 } ,
{ - 0.1 , - 0.5 , - 0.5 , 0.1 , 0.2 , 0.1 } ,
}
2017-03-13 22:30:37 +01:00
for i = 1 , 4 do
minetest.register_node ( connected_stem_names [ i ] , {
_doc_items_create_entry = false ,
paramtype = " light " ,
sunlight_propagates = true ,
walkable = false ,
2017-03-14 04:17:35 +01:00
drop = stem_drop ,
2017-03-13 22:30:37 +01:00
drawtype = " nodebox " ,
node_box = {
type = " fixed " ,
fixed = connected_stem_nodebox [ i ]
} ,
selection_box = {
type = " fixed " ,
2017-03-14 02:43:48 +01:00
fixed = connected_stem_selectionbox [ i ]
2017-03-13 22:30:37 +01:00
} ,
tiles = connected_stem_tiles [ i ] ,
2021-02-18 14:00:17 +01:00
use_texture_alpha = minetest.features . use_texture_alpha_string_modes and " clip " or true ,
2017-05-20 04:11:14 +02:00
groups = { dig_immediate = 3 , not_in_creative_inventory = 1 , plant = 1 , attached_node = 1 , dig_by_water = 1 , destroy_by_lava_flow = 1 , } ,
2017-03-13 22:30:37 +01:00
sounds = mcl_sounds.node_sound_leaves_defaults ( ) ,
_mcl_blast_resistance = 0 ,
} )
2017-03-21 04:56:16 +01:00
if minetest.get_modpath ( " doc " ) then
doc.add_entry_alias ( " nodes " , full_unconnected_stem , " nodes " , connected_stem_names [ i ] )
end
2017-03-13 22:30:37 +01:00
end
minetest.register_abm ( {
label = " Grow gourd stem to gourd ( " .. full_unconnected_stem .. " → " .. gourd_itemstring .. " ) " ,
nodenames = { full_unconnected_stem } ,
neighbors = { " air " } ,
2017-03-13 22:43:47 +01:00
interval = grow_interval ,
chance = grow_chance ,
2017-03-13 22:30:37 +01:00
action = function ( stempos )
local light = minetest.get_node_light ( stempos )
if light and light > 10 then
-- Check the four neighbors and filter out neighbors where gourds can't grow
local neighbors = {
{ x =- 1 , y = 0 , z = 0 } ,
{ x = 1 , y = 0 , z = 0 } ,
{ x = 0 , y = 0 , z =- 1 } ,
{ x = 0 , y = 0 , z = 1 } ,
}
for n =# neighbors , 1 , - 1 do
local offset = neighbors [ n ]
local blockpos = vector.add ( stempos , offset )
2022-01-13 07:17:24 +01:00
local floorpos = { x = blockpos.x , y = blockpos.y - 1 , z = blockpos.z }
local floor = minetest.get_node ( floorpos )
2017-03-13 22:30:37 +01:00
local block = minetest.get_node ( blockpos )
local soilgroup = minetest.get_item_group ( floor.name , " soil " )
2017-11-14 23:44:53 +01:00
if not ( ( minetest.get_item_group ( floor.name , " grass_block " ) == 1 or floor.name == " mcl_core:dirt " or soilgroup == 2 or soilgroup == 3 ) and block.name == " air " ) then
2017-03-13 22:30:37 +01:00
table.remove ( neighbors , n )
end
end
2017-05-15 21:55:01 +02:00
-- Gourd needs at least 1 free neighbor to grow
2017-03-13 22:30:37 +01:00
if # neighbors > 0 then
-- From the remaining neighbors, grow randomly
local r = math.random ( 1 , # neighbors )
local offset = neighbors [ r ]
local blockpos = vector.add ( stempos , offset )
2022-01-13 07:17:24 +01:00
local floorpos = { x = blockpos.x , y = blockpos.y - 1 , z = blockpos.z }
local floor = minetest.get_node ( floorpos )
2017-03-13 22:30:37 +01:00
local p2
if offset.x == 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 1 ] } )
p2 = 3
elseif offset.x == - 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 2 ] } )
p2 = 1
elseif offset.z == 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 3 ] } )
p2 = 2
elseif offset.z == - 1 then
minetest.set_node ( stempos , { name = connected_stem_names [ 4 ] } )
p2 = 0
end
2017-05-15 21:55:01 +02:00
-- Place the gourd
2017-03-13 23:13:48 +01:00
if gourd_def.paramtype2 == " facedir " then
minetest.add_node ( blockpos , { name = gourd_itemstring , param2 = p2 } )
else
minetest.add_node ( blockpos , { name = gourd_itemstring } )
end
2017-05-19 13:22:29 +02:00
-- Reset farmland, etc. to dirt when the gourd grows on top
if minetest.get_item_group ( floor.name , " dirtifies_below_solid " ) == 1 then
2017-05-15 21:55:01 +02:00
minetest.set_node ( floorpos , { name = " mcl_core:dirt " } )
end
2017-03-13 22:30:37 +01:00
end
end
end ,
} )
end
2017-07-21 19:47:20 +02:00
-- Used for growing gourd stems. Returns the intermediate color between startcolor and endcolor at a step
-- * startcolor: ColorSpec in table form for the stem in its lowest growing stage
-- * endcolor: ColorSpec in table form for the stem in its final growing stage
-- * step: The nth growth step. Counting starts at 1
-- * step_count: The number of total growth steps
function mcl_farming : stem_color ( startcolor , endcolor , step , step_count )
local color = { }
local function get_component ( startt , endd , step , step_count )
return math.floor ( math.max ( 0 , math.min ( 255 , ( startt + ( ( ( step - 1 ) / step_count ) * endd ) ) ) ) )
end
color.r = get_component ( startcolor.r , endcolor.r , step , step_count )
color.g = get_component ( startcolor.g , endcolor.g , step , step_count )
color.b = get_component ( startcolor.b , endcolor.b , step , step_count )
local colorstring = string.format ( " #%02X%02X%02X " , color.r , color.g , color.b )
return colorstring
end
2020-09-05 19:49:12 +02:00
minetest.register_lbm ( {
label = " Add growth for unloaded farming plants " ,
name = " mcl_farming:growth " ,
nodenames = { " group:plant " } ,
run_at_every_load = true ,
action = function ( pos , node )
local identifier = plant_nodename_to_id_list [ node.name ]
if not identifier then
return
end
local low_speed = minetest.get_node ( { x = pos.x , y = pos.y - 1 , z = pos.z } ) . name ~= " mcl_farming:soil_wet "
mcl_farming : grow_plant ( identifier , pos , node , false , false , low_speed )
end ,
} )
2022-04-18 07:32:47 +02:00
-- This function returns a nodebox that imitates a plantlike grid (#)
-- drawtype, but is shifted 1/16 lower, into the empty space above
-- farming soil. The regular plantlike drawtype can not do this.
function mcl_farming : get_plantlike_grid_nodebox ( )
return {
type = " fixed " ,
fixed = {
{ 4 / 16 , - 9 / 16 , - 8 / 16 , 4 / 16 , 7 / 16 , 8 / 16 } ,
{ - 4 / 16 , - 9 / 16 , - 8 / 16 , - 4 / 16 , 7 / 16 , 8 / 16 } ,
{ - 8 / 16 , - 9 / 16 , - 4 / 16 , 8 / 16 , 7 / 16 , - 4 / 16 } ,
{ - 8 / 16 , - 9 / 16 , 4 / 16 , 8 / 16 , 7 / 16 , 4 / 16 } ,
}
}
end
-- This function takes a texture and returns a modified texture where
-- the bottom row is at the top, assuming 16px × 16px textures. This
-- is used to align textures to a “plantlike” nodebox shifted 1/16
-- below its own node – into the empty space above farming soil.
function mcl_farming : align_plantlike_nodebox_texture ( texture )
local texture = texture : gsub ( " %^ " , " \\ %^ " ) : gsub ( " : " , " \\ : " )
return " [combine:16x16:0,-15= " .. texture .. " :0,1= " .. texture
end