diff options
Diffstat (limited to 'init.lua')
-rw-r--r-- | init.lua | 385 |
1 files changed, 351 insertions, 34 deletions
@@ -1,6 +1,7 @@ --[[ - Minetest Farming Redo Mod 1.14 (11th May 2015) + Minetest Farming Redo Mod 1.20 (20th May 2015) by TenPlus1 + NEW growing routine by prestidigitator ]] farming = {} @@ -8,6 +9,39 @@ farming.mod = "redo" farming.path = minetest.get_modpath("farming") farming.hoe_on_use = default.hoe_on_use +farming.DEBUG = false +-- farming.DEBUG = {} -- Uncomment to turn on profiling code/functions + +local DEBUG_abm_runs = 0 +local DEBUG_abm_time = 0 +local DEBUG_timer_runs = 0 +local DEBUG_timer_time = 0 +if farming.DEBUG then + function farming.DEBUG.reset_times() + DEBUG_abm_runs = 0 + DEBUG_abm_time = 0 + DEBUG_timer_runs = 0 + DEBUG_timer_time = 0 + end + + function farming.DEBUG.report_times() + local abm_n = DEBUG_abm_runs + local abm_dt = DEBUG_abm_time + local abm_avg = (abm_n > 0 and abm_dt / abm_n) or 0 + local timer_n = DEBUG_timer_runs + local timer_dt = DEBUG_timer_time + local timer_avg = (timer_n > 0 and timer_dt / timer_n) or 0 + local dt = abm_dt + timer_dt + print("ABM ran for "..abm_dt.."µs over "..abm_n.." runs: ".. + abm_avg.."µs/run") + print("Timer ran for "..timer_dt.."µs over "..timer_n.." runs: ".. + timer_avg.."µs/run") + print("Total farming time: "..dt.."µs") + end +else +end + +local statistics = dofile(farming.path.."/statistics.lua") dofile(farming.path.."/soil.lua") dofile(farming.path.."/hoes.lua") dofile(farming.path.."/grass.lua") @@ -31,6 +65,319 @@ dofile(farming.path.."/donut.lua") dofile(farming.path.."/mapgen.lua") dofile(farming.path.."/compatibility.lua") -- Farming Plus compatibility +-- Utility Functions + +local time_speed = tonumber(minetest.setting_get("time_speed")) or 72 +local SECS_PER_CYCLE = (time_speed > 0 and 24 * 60 * 60/time_speed) or nil + +local function clamp(x, min, max) + return (x < min and min) or (x > max and max) or x +end + +local function in_range(x, min, max) + return min <= x and x <= max +end + +--- Tests the amount of day or night time between two times. + -- + -- @param t_game + -- The current time, as reported by mintest.get_gametime(). + -- @param t_day + -- The current time, as reported by mintest.get_timeofday(). + -- @param dt + -- The amount of elapsed time. + -- @param count_day + -- If true, count elapsed day time. Otherwise, count elapsed night time. + -- @return + -- The amount of day or night time that has elapsed. + -- +local function day_or_night_time(t_game, t_day, dt, count_day) + local t1_day = t_day - dt / SECS_PER_CYCLE + + local t1_c, t2_c -- t1_c < t2_c and t2_c always in [0, 1) + if count_day then + if t_day < 0.25 then + t1_c = t1_day + 0.75 -- Relative to sunup, yesterday + t2_c = t_day + 0.75 + else + t1_c = t1_day - 0.25 -- Relative to sunup, today + t2_c = t_day - 0.25 + end + else + if t_day < 0.75 then + t1_c = t1_day + 0.25 -- Relative to sundown, yesterday + t2_c = t_day + 0.25 + else + t1_c = t1_day - 0.75 -- Relative to sundown, today + t2_c = t_day - 0.75 + end + end + + local dt_c = clamp(t2_c, 0, 0.5) - clamp(t1_c, 0, 0.5) -- this cycle + if t1_c < -0.5 then + local nc = math.floor(-t1_c) + t1_c = t1_c + nc + dt_c = dt_c + 0.5 * nc + clamp(-t1_c - 0.5, 0, 0.5) + end + + return dt_c * SECS_PER_CYCLE +end + +--- Tests the amount of elapsed day time. + -- + -- @param dt + -- The amount of elapsed time. + -- @return + -- The amount of day time that has elapsed. + -- +local function day_time(dt) + return day_or_night_time(minetest.get_gametime(), minetest.get_timeofday(), dt, true) +end + +--- Tests the amount of elapsed night time. + -- + -- @param dt + -- The amount of elapsed time. + -- @return + -- The amount of night time that has elapsed. + -- +local function night_time(time_game, time_day, dt, count_day) + return day_or_night_time(minetest.get_gametime(), minetest.get_timeofday(), dt, false) +end + + +-- Growth Logic + +local STAGE_LENGTH_AVG = 160.0 +local STAGE_LENGTH_DEV = STAGE_LENGTH_AVG / 6 +local MIN_LIGHT = 13 +local MAX_LIGHT = 1000 + +--- Determines plant name and stage from node. + -- + -- Separates node name on the last underscore (_). + -- + -- @param node + -- Node or position table, or node name. + -- @return + -- List (plant_name, stage), or nothing (nil) if node isn't loaded + -- +local function plant_name_stage(node) + local name + if type(node) == 'table' then + if node.name then + name = node.name + elseif node.x and node.y and node.z then + node = minetest.get_node_or_nil(node) + name = node and node.name + end + else + name = tostring(node) + end + if not name or name == "ignore" then return nil end + + local sep_pos = name:find("_[^_]+$") + if sep_pos and sep_pos > 1 then + local stage = tonumber(name:sub(sep_pos + 1)) + if stage and stage >= 0 then + return name:sub(1, sep_pos - 1), stage + end + end + + return name, 0 +end + +--- Map from node name to + -- { plant_name = ..., name = ..., stage = n, stages_left = { node_name, ... } } +local plant_stages = {} +farming.plant_stages = plant_stages + +--- Registers the stages of growth of a (possible plant) node. + -- + -- @param node + -- Node or position table, or node name. + -- @return + -- The (possibly zero) number of stages of growth the plant will go through + -- before being fully grown, or nil if not a plant. + -- +local register_plant_node +-- Recursive helper +local function reg_plant_stages(plant_name, stage, force_last) + local node_name = plant_name and plant_name .. "_" .. stage + local node_def = node_name and minetest.registered_nodes[node_name] + if not node_def then return nil end + + local stages = plant_stages[node_name] + if stages then return stages end + + if minetest.get_item_group(node_name, "growing") > 0 then + local ns = reg_plant_stages(plant_name, stage + 1, true) + + local stages_left = (ns and { ns.name, unpack(ns.stages_left) }) or {} + stages = { plant_name = plant_name, name = node_name, stage = stage, stages_left = stages_left } + + if #stages_left > 0 then + local old_constr = node_def.on_construct + local old_destr = node_def.on_destruct + minetest.override_item(node_name, + { + on_construct = function(pos) + if old_constr then old_constr(pos) end + farming.handle_growth(pos) + end, + + on_destruct = function(pos) + minetest.get_node_timer(pos):stop() + if old_destr then old_destr(pos) end + end, + + on_timer = function(pos, elapsed) + return farming.plant_growth_timer(pos, elapsed, node_name) + end, + }) + end + elseif force_last then + stages = { plant_name = plant_name, name = node_name, stage = stage, stages_left = {} } + else + return nil + end + + plant_stages[node_name] = stages + return stages +end + +register_plant_node = function(node) + local plant_name, stage = plant_name_stage(node) + if plant_name then + local stages = reg_plant_stages(plant_name, stage, false) + return stages and #stages.stages_left + else + return nil + end +end + +local function set_growing(pos, stages_left) + if not stages_left then return end + + local timer = minetest.get_node_timer(pos) + if stages_left > 0 then + if not timer:is_started() then + local stage_length = statistics.normal(STAGE_LENGTH_AVG, STAGE_LENGTH_DEV) + stage_length = clamp(stage_length, 0.5 * STAGE_LENGTH_AVG, 3.0 * STAGE_LENGTH_AVG) + timer:set(stage_length, -0.5 * math.random() * STAGE_LENGTH_AVG) + end + elseif timer:is_started() then + timer:stop() + end +end + +--- Detects a plant type node at the given position, starting or stopping the plant growth timer as appopriate + -- + -- @param pos + -- The node's position. + -- @param node + -- The cached node table if available, or nil. + -- +function farming.handle_growth(pos, node) + if not pos then return end + local stages_left = register_plant_node(node or pos) + if stages_left then set_growing(pos, stages_left) end +end + +minetest.after(0, + function() + for _, node_def in pairs(minetest.registered_nodes) do + register_plant_node(node_def) + end + end) + +local abm_func = farming.handle_growth +if farming.DEBUG then + local normal_abm_func = abm_func + abm_func = function(...) + local t0 = minetest.get_us_time() + local r = { normal_abm_func(...) } + local t1 = minetest.get_us_time() + DEBUG_abm_runs = DEBUG_abm_runs + 1 + DEBUG_abm_time = DEBUG_abm_time + (t1 - t0) + return unpack(r) + end +end + +-- Just in case a growing type or added node is missed (also catches existing +-- nodes added to map before timers were incorporated). +minetest.register_abm({ + nodenames = { "group:growing" }, + interval = 300, + chance = 1, + action = abm_func}) + +--- Plant timer function. + -- + -- Grows plants under the right conditions. + -- +function farming.plant_growth_timer(pos, elapsed, node_name) + local stages = plant_stages[node_name] + if not stages then return false end + + local max_growth = #stages.stages_left + if max_growth <= 0 then return false end + + if stages.plant_name == "farming:cocoa" then + if not minetest.find_node_near(pos, 1, { "default:jungletree", "moretrees:jungletree_leaves_green" }) then + return true + end + else + local under = minetest.get_node_or_nil({ x = pos.x, y = pos.y - 1, z = pos.z }) + if not under or under.name ~= "farming:soil_wet" then return true end + end + + local growth + + local light_pos = { x = pos.x, y = pos.y + 1, z = pos.z } + local lambda = elapsed / STAGE_LENGTH_AVG + if lambda < 0.1 then return true end + if max_growth == 1 or lambda < 2.0 then + local light = minetest.get_node_light(light_pos) + if not in_range(light, MIN_LIGHT, MAX_LIGHT) then return true end + growth = 1 + else + local night_light = minetest.get_node_light(light_pos, 0) + local day_light = minetest.get_node_light(light_pos, 0.5) + local night_growth = in_range(night_light, MIN_LIGHT, MAX_LIGHT) + local day_growth = in_range(day_light, MIN_LIGHT, MAX_LIGHT) + + if not night_growth then + if not day_growth then return true end + lambda = day_time(elapsed) / STAGE_LENGTH_AVG + elseif not day_growth then + lambda = night_time(elapsed) / STAGE_LENGTH_AVG + end + + growth = statistics.poisson(lambda, max_growth) + if growth < 1 then return true end + end + + minetest.swap_node(pos, { name = stages.stages_left[growth] }) + + return growth ~= max_growth +end + +if farming.DEBUG then + local timer_func = farming.plant_growth_timer; + farming.plant_growth_timer = function(pos, elapsed, node_name) + local t0 = minetest.get_us_time() + + local r = { timer_func(pos, elapsed, node_name) } + + local t1 = minetest.get_us_time() + DEBUG_timer_runs = DEBUG_timer_runs + 1 + DEBUG_timer_time = DEBUG_timer_time + (t1 - t0) + return unpack(r) + end +end + + -- Place Seeds on Soil function farming.place_seed(itemstack, placer, pointed_thing, plantname) @@ -72,38 +419,6 @@ function farming.place_seed(itemstack, placer, pointed_thing, plantname) end end --- Single ABM Handles Growing of All Plants - -minetest.register_abm({ - nodenames = {"group:growing"}, - neighbors = {"farming:soil_wet", "default:jungletree"}, - interval = 80, - chance = 2, - - action = function(pos, node) - - -- split plant name (e.g. farming:wheat_1) - local plant = node.name:split("_")[1].."_" - local numb = node.name:split("_")[2] - - -- fully grown ? - if not minetest.registered_nodes[plant..(numb + 1)] then return end - - -- cocoa pod on jungle tree ? - if plant ~= "farming:cocoa_" then - - -- growing on wet soil ? - if minetest.get_node({x=pos.x,y=pos.y-1,z=pos.z}).name ~= "farming:soil_wet" then return end - end - - -- enough light ? - if minetest.get_node_light(pos) < 13 then return end - - -- grow - minetest.set_node(pos, {name=plant..(numb + 1)}) - - end -}) -- Function to register plants (for compatibility) @@ -163,7 +478,8 @@ farming.register_plant = function(name, def) g = {snappy = 3, flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1} end - minetest.register_node(mname .. ":" .. pname .. "_" .. i, { + local node_name = mname .. ":" .. pname .. "_" .. i + minetest.register_node(node_name, { drawtype = "plantlike", waving = 1, tiles = {mname .. "_" .. pname .. "_" .. i .. ".png"}, @@ -176,6 +492,7 @@ farming.register_plant = function(name, def) groups = g, sounds = default.node_sound_leaves_defaults(), }) + register_plant_node(node_name) end -- Return info |