diff options
| -rw-r--r-- | README.txt | 1 | ||||
| -rw-r--r-- | beanpole.lua | 4 | ||||
| -rw-r--r-- | init.lua | 385 | ||||
| -rw-r--r-- | init.lua_orig | 192 | ||||
| -rw-r--r-- | statistics.lua | 150 | 
5 files changed, 696 insertions, 36 deletions
| @@ -13,6 +13,7 @@ This mod works by adding your new plant to the {growing=1} group and numbering t  Changelog: +1.20 - NEW growing routine added that allows crops to grow while player is away doing other things (thanks prestidigitator)  1.14 - Added Green Beans from Crops mod (thanks sofar), little bushels in the wild but need to be grown using beanpoles crafted with 4 sticks (2 either side)  1.13 - Fixed seed double-placement glitch.  Mapgen now uses 0.4.12+ for plant generation  1.12 - Player cannot place seeds in protected area, also growing speeds changed to match defaults diff --git a/beanpole.lua b/beanpole.lua index 0387fa0..895b6fa 100644 --- a/beanpole.lua +++ b/beanpole.lua @@ -65,7 +65,7 @@ minetest.register_craft({  	}  }) --- Define Corn growth stages +-- Define Green Bean growth stages  minetest.register_node("farming:beanpole_1", {  	drawtype = "plantlike", @@ -140,7 +140,7 @@ minetest.register_node("farming:beanpole_4", {  	sounds = default.node_sound_leaves_defaults(),  }) --- Last stage of Corn growth doesnnot have growing=1 so abm never has to check these +-- Last stage of Green Bean growth does not have growing=1 so abm never has to check these  minetest.register_node("farming:beanpole_5", {  	drawtype = "plantlike", @@ -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 diff --git a/init.lua_orig b/init.lua_orig new file mode 100644 index 0000000..aee9976 --- /dev/null +++ b/init.lua_orig @@ -0,0 +1,192 @@ +--[[ +	Minetest Farming Redo Mod 1.14 (11th May 2015) +	by TenPlus1 +]] + +farming = {} +farming.mod = "redo" +farming.path = minetest.get_modpath("farming") +farming.hoe_on_use = default.hoe_on_use + +dofile(farming.path.."/soil.lua") +dofile(farming.path.."/hoes.lua") +dofile(farming.path.."/grass.lua") +dofile(farming.path.."/wheat.lua") +dofile(farming.path.."/cotton.lua") +dofile(farming.path.."/carrot.lua") +dofile(farming.path.."/potato.lua") +dofile(farming.path.."/tomato.lua") +dofile(farming.path.."/cucumber.lua") +dofile(farming.path.."/corn.lua") +dofile(farming.path.."/coffee.lua") +dofile(farming.path.."/melon.lua") +dofile(farming.path.."/sugar.lua") +dofile(farming.path.."/pumpkin.lua") +dofile(farming.path.."/cocoa.lua") +dofile(farming.path.."/raspberry.lua") +dofile(farming.path.."/blueberry.lua") +dofile(farming.path.."/rhubarb.lua") +dofile(farming.path.."/beanpole.lua") +dofile(farming.path.."/donut.lua") +dofile(farming.path.."/mapgen.lua") +dofile(farming.path.."/compatibility.lua") -- Farming Plus compatibility + +-- Place Seeds on Soil + +function farming.place_seed(itemstack, placer, pointed_thing, plantname) +	local pt = pointed_thing + +	-- check if pointing at a node +	if not pt and pt.type ~= "node" then +		return +	end + +	local under = minetest.get_node(pt.under) +	local above = minetest.get_node(pt.above) + +	-- check if pointing at the top of the node +	if pt.above.y ~= pt.under.y+1 then +		return +	end + +	-- return if any of the nodes is not registered +	if not minetest.registered_nodes[under.name] +	or not minetest.registered_nodes[above.name] then +		return +	end + +	-- can I replace above node, and am I pointing at soil +	if not minetest.registered_nodes[above.name].buildable_to +	or minetest.get_item_group(under.name, "soil") < 2  +	or minetest.get_item_group(above.name, "plant") ~= 0 then -- ADDED this line for multiple seed placement bug +		return +	end + +	-- add the node and remove 1 item from the itemstack +	if not minetest.is_protected(pt.above, placer:get_player_name()) then +		minetest.add_node(pt.above, {name=plantname}) +		if not minetest.setting_getbool("creative_mode") then +			itemstack:take_item() +		end +		return itemstack +	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) + +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 = "Seed" +	end +	if not def.inventory_image then +		def.inventory_image = "unknown_item.png" +	end +	if not def.steps then +		return nil +	end + +	-- Register seed +	minetest.register_node(":" .. mname .. ":seed_" .. pname, { +		description = def.description, +		tiles = {def.inventory_image}, +		inventory_image = def.inventory_image, +		wield_image = def.inventory_image, +		drawtype = "signlike", +		groups = {seed = 1, snappy = 3, attached_node = 1}, +		paramtype = "light", +		paramtype2 = "wallmounted", +		walkable = false, +		sunlight_propagates = true, +		selection_box = {type = "fixed", fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},}, +		on_place = function(itemstack, placer, pointed_thing) +			return farming.place_seed(itemstack, placer, pointed_thing, mname .. ":"..pname.."_1") +		end +	}) + +	-- Register harvest +	minetest.register_craftitem(":" .. mname .. ":" .. pname, { +		description = pname:gsub("^%l", string.upper), +		inventory_image = mname .. "_" .. pname .. ".png", +	}) + +	-- Register growing steps +	for i=1,def.steps do +		local drop = { +			items = { +				{items = {mname .. ":" .. pname}, rarity = 9 - i}, +				{items = {mname .. ":" .. pname}, rarity= 18 - i * 2}, +				{items = {mname .. ":seed_" .. pname}, rarity = 9 - i}, +				{items = {mname .. ":seed_" .. pname}, rarity = 18 - i * 2}, +			} +		} +		 +		local g = {snappy = 3, flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1, growing = 1} +		-- Last step doesn't need growing=1 so Abm never has to check these +		if i == def.steps then +			g = {snappy = 3, flammable = 2, plant = 1, not_in_creative_inventory = 1, attached_node = 1} +		end + +		minetest.register_node(mname .. ":" .. pname .. "_" .. i, { +			drawtype = "plantlike", +			waving = 1, +			tiles = {mname .. "_" .. pname .. "_" .. i .. ".png"}, +			paramtype = "light", +			walkable = false, +			buildable_to = true, +			is_ground_content = true, +			drop = drop, +			selection_box = {type = "fixed", fixed = {-0.5, -0.5, -0.5, 0.5, -5/16, 0.5},}, +			groups = g, +			sounds = default.node_sound_leaves_defaults(), +		}) +	end + +	-- Return info +	local r = {seed = mname .. ":seed_" .. pname, harvest = mname .. ":" .. pname} +	return r +end + +--[[ Cotton (example, is already registered in cotton.lua) +farming.register_plant("farming:cotton", { +	description = "Cotton seed", +	inventory_image = "farming_cotton_seed.png", +	steps = 8, +}) +--]] diff --git a/statistics.lua b/statistics.lua new file mode 100644 index 0000000..aaa4f51 --- /dev/null +++ b/statistics.lua @@ -0,0 +1,150 @@ +local statistics = {} + +local ROOT_2 = math.sqrt(2.0) + +-- Approximations for erf(x) and erfInv(x) from +--    https://en.wikipedia.org/wiki/Error_function +local erf +local erf_inv + +local A = 8 * (math.pi - 3.0)/(3.0 * math.pi * (4.0 - math.pi)) +local B = 4.0 / math.pi +local C = 2.0/(math.pi * A) +local D = 1.0 / A + +erf = function(x) +	if x == 0 then return 0; end +	local xSq  = x * x +	local aXSq = A * xSq +	local v = math.sqrt(1.0 - math.exp(-xSq * (B + aXSq)/(1.0 + aXSq))) +	return (x > 0 and v) or -v +end + +erf_inv = function(x) +	if x == 0 then return 0; end +	if x <= -1 or x >= 1 then return nil; end +	local y = math.log(1 - x * x) +	local u = C + 0.5 * y +	local v = math.sqrt(math.sqrt(u * u - D * y) - u) +	return (x > 0 and v) or -v +end + +local function std_normal(u) +	return ROOT_2 * erf_inv(2.0 * u - 1.0) +end + +local poisson +local cdf_table = {} + +local function generate_cdf(lambda_index, lambda) +	local max = math.ceil(4 * lambda) +	local pdf = math.exp(-lambda) +	local cdf = pdf +	local t = { [0] = pdf } + +	for i = 1, max - 1 do +		pdf = pdf * lambda / i +		cdf = cdf + pdf +		t[i] = cdf +	end + +	return t +end + +for li = 1, 100 do +	cdf_table[li] = generate_cdf(li, 0.25 * li) +end + +poisson = function(lambda, max) +	if max < 2 then +		return (math.random() < math.exp(-lambda) and 0) or 1 +	elseif lambda >= 2 * max then +		return max +	end + +	local u = math.random() +	local lambda_index = math.floor(4 * lambda + 0.5) +	local cdfs = cdf_table[lambda_index] + +	if cdfs then +		lambda = 0.25 * lambda_index + +		if u < cdfs[0] then return 0; end +		if max > #cdfs then max = #cdfs + 1 else max = math.floor(max); end +		if u >= cdfs[max - 1] then return max; end + +		if max > 4 then  -- Binary search +			local s = 0 +			while s + 1 < max do +				local m = math.floor(0.5 * (s + max)) +				if u < cdfs[m] then max = m; else s = m; end +			end +		else +			for i = 1, max - 1 do +				if u < cdfs[i] then return i; end +			end +		end + +		return max +	else +		local x = lambda + math.sqrt(lambda) * std_normal(u) +		return (x < 0.5 and 0) or (x >= max - 0.5 and max) or math.floor(x + 0.5) +	end +end + +-- Error function. +statistics.erf = erf + +-- Inverse error function. +statistics.erf_inv = erf_inv + +--- Standard normal distribution function (mean 0, standard deviation 1). + -- + -- @return + --    Any real number (actually between -3.0 and 3.0). + -- +statistics.std_normal = function() +	local u = math.random() +	if u < 0.001 then +		return -3.0 +	elseif u > 0.999 then +		return 3.0 +	end +	return std_normal(u) +end + +--- Standard normal distribution function (mean 0, standard deviation 1). + -- + -- @param mu + --    The distribution mean. + -- @param sigma + --    The distribution standard deviation. + -- @return + --    Any real number (actually between -3*sigma and 3*sigma). + -- +statistics.normal = function(mu, sigma) +	local u = math.random() +	if u < 0.001 then +		return mu - 3.0 * sigma +	elseif u > 0.999 then +		return mu + 3.0 * sigma +	end +	return mu + sigma * std_normal(u) +end + +--- Poisson distribution function. + -- + -- @param lambda + --    The distribution mean and variance. + -- @param max + --    The distribution maximum. + -- @return + --    An integer between 0 and max (both inclusive). + -- +statistics.poisson = function(lambda, max) +	lambda, max = tonumber(lambda), tonumber(max) +	if not lambda or not max or lambda <= 0 or max < 1 then return 0; end +	return poisson(lambda, max) +end + +return statistics | 
