diff --git a/LICENSE b/LICENSE index c4742e6..7cffc99 100644 --- a/LICENSE +++ b/LICENSE @@ -4,6 +4,15 @@ Minetest mod moretrees All source code: © 2013, Vanessa Ezekowitz Published under the terms and conditions of the WTFPL. +Date & cocos palm code (date_palm.lua, cocos_palm.lua) + © 2016, Rogier + Published under the terms and conditions of the WTFPL. +All date & date palm textures, date-based food, cocos flower & green coconuts: + © 2016, Rogier + Published under the terms and conditions of CC-BY-SA-3.0 Unported. + - Three of the date palm textures are modifications of existing moretrees textures + - The green coconuts are a modification of the brown coconut + - The date cake batter is a modification of the acorn muffin batter All sapling textures (textures/*_sapling.png): © 2013, Tim Huppertz Published under the terms and conditions of CC-BY-SA-3.0 Unported. diff --git a/biome_defs.lua b/biome_defs.lua index 48efee0..46228f5 100644 --- a/biome_defs.lua +++ b/biome_defs.lua @@ -24,6 +24,40 @@ moretrees.palm_biome = { max_count = 10, } +moretrees.date_palm_biome = { + surface = "default:desert_sand", + avoid_nodes = moretrees.avoidnodes, + avoid_radius = 10, + seed_diff = 339, + min_elevation = -1, + max_elevation = 10, + near_nodes = {"default:water_source"}, + near_nodes_size = 20, + near_nodes_count = 100, + near_nodes_vertical = 20, + temp_min = -0.20, + humidity_max = 0.20, + rarity = 10, + max_count = 30, +} + +moretrees.date_palm_biome_2 = { + surface = "default:desert_sand", + avoid_nodes = moretrees.avoidnodes, + avoid_radius = 10, + seed_diff = 340, + min_elevation = 11, + max_elevation = 30, + near_nodes = {"default:water_source"}, + near_nodes_size = 1, + near_nodes_count = 1, + near_nodes_vertical = 30, + temp_min = -0.20, + humidity_max = 0.20, + rarity = 10, + max_count = 30, +} + moretrees.apple_tree_biome = { surface = "default:dirt_with_grass", avoid_nodes = moretrees.avoidnodes, diff --git a/cocos_palm.lua b/cocos_palm.lua new file mode 100644 index 0000000..8dea25f --- /dev/null +++ b/cocos_palm.lua @@ -0,0 +1,283 @@ +local S = moretrees.intllib + +-- © 2016, Rogier + +-- Some constants + +local coconut_drop_ichance = 8 + +-- Make the cocos palm fruit trunk a real trunk (it is generated as a fruit) +local trunk = minetest.registered_nodes["moretrees:palm_trunk"] +local ftrunk = {} +local gftrunk = {} +for k,v in pairs(trunk) do + ftrunk[k] = v + gftrunk[k] = v +end +ftrunk.tiles = {} +gftrunk.tiles = {} +for k,v in pairs(trunk.tiles) do + ftrunk.tiles[k] = v + gftrunk.tiles[k] = v +end +ftrunk.drop = "moretrees:palm_trunk" +gftrunk.drop = "moretrees:palm_trunk" +ftrunk.after_destruct = function(pos, oldnode) + local coconuts = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, {"group:moretrees_coconut"}) + for _,coconutpos in pairs(coconuts) do + -- minetest.dig_node(coconutpos) does not cause nearby coconuts to be dropped :-( ... + --minetest.dig_node(coconutpos) + local items = minetest.get_node_drops(minetest.get_node(coconutpos).name) + minetest.remove_node(coconutpos) + for _, itemname in pairs(items) do + minetest.add_item(coconutpos, itemname) + end + end +end +-- Make the different trunk types distinguishable (but barely) +ftrunk.tiles[1] = "moretrees_palm_trunk_top.png^[transformR90" +gftrunk.tiles[1] = "moretrees_palm_trunk_top.png^[transformR180" +gftrunk.description = gftrunk.description.." (gen)" +minetest.register_node("moretrees:palm_fruit_trunk", ftrunk) +minetest.register_node("moretrees:palm_fruit_trunk_gen", gftrunk) + +local coconut_regrow_abm_spec = { + nodenames = { "moretrees:palm_fruit_trunk" }, + interval = moretrees.coconut_flower_interval, + chance = moretrees.coconut_flower_chance, + action = function(pos, node, active_object_count, active_object_count_wider) + local coconuts = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "group:moretrees_coconut") + -- Expected growth interval increases exponentially with number of coconuts already hanging. + -- Also: if more coconuts are hanging, the chance of picking an empty spot decreases as well... + if math.random(2^#coconuts) <= 2 then + -- Grow in area of 3x3 round trunk + local dx=math.floor(math.random(3)-2) + local dz=math.floor(math.random(3)-2) + local coconutpos = {x=pos.x+dx, y=pos.y, z=pos.z+dz} + local coconutnode = minetest.get_node(coconutpos) + if coconutnode.name == "air" then + minetest.set_node(coconutpos, {name="moretrees:coconut_0"}) + end + end + end +} +if moretrees.coconuts_regrow then + minetest.register_abm(coconut_regrow_abm_spec) +end + +-- Spawn initial coconuts + +-- Spawn initial coconuts +-- (Instead of coconuts, a generated-palm fruit trunk is generated with the tree. This +-- ABM converts the trunk to a regular fruit trunk, and spawns some coconuts) +minetest.register_abm({ + nodenames = { "moretrees:palm_fruit_trunk_gen" }, + interval = 1, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.swap_node(pos, {name="moretrees:palm_fruit_trunk"}) + local poslist = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "air") + local genlist = {} + for k,v in pairs(poslist) do + genlist[k] = {x = math.random(100), pos = v} + end + table.sort(genlist, function(a, b) return a.x < b.x; end) + local gen + local count = 0 + for _,gen in pairs(genlist) do + minetest.set_node(gen.pos, {name = "moretrees:coconut_3"}) + count = count + 1 + if count == 4 then + break + end + end + end, +}) + +-- Register coconuts, and make them regrow + +local coconut_growfn = function(pos, elapsed) + local node = minetest.get_node(pos) + local delay = moretrees.coconut_grow_interval + if not node then + return + elseif not moretrees.coconuts_regrow then + -- Regrowing has been turned off. Make coconust grow instantly + minetest.swap_node(pos, {name="moretrees:coconut_3"}) + return + elseif node.name == "moretrees:coconut_3" then + -- Drop coconuts (i.e. remove them), so that new coconuts can grow. + -- Coconuts will drop as items with a small chance + if math.random(coconut_drop_ichance) == 1 then + if moretrees.coconut_item_drop_ichance > 0 and math.random(moretrees.coconut_item_drop_ichance) == 1 then + local items = minetest.get_node_drops(minetest.get_node(pos).name) + for _, itemname in pairs(items) do + minetest.add_item(pos, itemname) + end + end + minetest.remove_node(pos) + end + else + -- Grow coconuts to the next stage + local offset = string.len("moretrees:coconut_x") + local n = string.sub(node.name, offset) + minetest.swap_node(pos, {name=string.sub(node.name, 1, offset-1)..n+1}) + end + -- Don't catch up when elapsed time is large. Regular visits are needed for growth... + local timer = minetest.get_node_timer(pos) + timer:start(delay + math.random(moretrees.coconut_grow_interval)) +end + +local coconut_starttimer = function(pos, elapsed) + local timer = minetest.get_node_timer(pos) + local base_interval = moretrees.coconut_grow_interval * 2 / 3 + timer:set(base_interval + math.random(base_interval), elapsed or 0) +end + +for _,suffix in ipairs({"_0", "_1", "_2", "_3", ""}) do + local name + if suffix == "_0" then + name = S("Coconut Flower") + else + name = S("Coconut") + end + local drop = "" + local coco_group = 1 + local tile = "moretrees_coconut"..suffix..".png" + local timerfn = coconut_growfn + local constructfn = coconut_starttimer + if suffix == "_3" then + drop = "moretrees:coconut" + tile = "moretrees_coconut.png" + elseif suffix == "" then + drop = nil + coco_group = nil + timerfn = nil + constructfn = nil + end + local coconutdef = { + description = name, + tiles = {tile}, + drawtype = "plantlike", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + groups = { fleshy=3, dig_immediate=3, flammable=2, moretrees_coconut=coco_group }, + inventory_image = tile.."^[transformR180", + wield_image = tile.."^[transformR180", + sounds = default.node_sound_defaults(), + drop = drop, + selection_box = { + type = "fixed", + fixed = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3} + }, + on_timer = timerfn, + on_construct = constructfn, + + } + minetest.register_node("moretrees:coconut"..suffix, coconutdef) +end + +-- convert exisiting cocos palms. This is a bit tricky... +-- Try to make sure that this is indeed a generated tree, and not manually-placed trunks and/or coconuts +if moretrees.coconuts_convert_existing_palms then + local spec = { + name = "moretrees:convert_existing_cocos_palms_to_regrow_coconuts", + nodenames = "moretrees:coconut", + action = function(pos, node, active_object_count, active_object_count_wider) + local trunks + local cvtrunks + local leaves + local coconuts + -- One regular trunk must be adjacent to the coconut + trunks = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "moretrees:palm_trunk") + if #trunks ~= 1 then + return + end + local tpos = trunks[1] + -- 1 or 2 other trunks must be one level below to the trunk being converted. + trunks = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y-1, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y-1, z=tpos.z+1}, "moretrees:palm_trunk") + if #trunks < 1 or #trunks > 2 then + return + end + -- 1 or 2 other trunks must be two levels below to the trunk being converted. + trunks = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y-2, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y-2, z=tpos.z+1}, "moretrees:palm_trunk") + if #trunks < 1 or #trunks > 2 then + return + end + -- 1 or 2 trunks must at the level of the trunk being converted. + cvtrunks = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y, z=tpos.z+1}, "moretrees:palm_trunk") + if #cvtrunks < 1 or #cvtrunks > 2 then + return + end + -- No trunks may be one level above the trunk being converted. + trunks = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y+1, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y+1, z=tpos.z+1}, "moretrees:palm_trunk") + if #trunks ~= 0 then + return + end + -- Leaves must be one level above the trunk being converted. + leaves = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y+1, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y+1, z=tpos.z+1}, "moretrees:palm_leaves") + if #leaves == 0 then + return + end + -- Leaves must be two levels above the trunk being converted. + leaves = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y+2, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y+2, z=tpos.z+1}, "moretrees:palm_leaves") + if #leaves == 0 then + return + end + -- No cocos fruit trunk may already be adjacent to the coconut + trunks = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "moretrees:palm_fruit_trunk") + if #trunks ~= 0 then + return + end + -- No cocos fruit trunk may be adjacent to or below the trunk being converted. + trunks = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y-2, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y, z=tpos.z+1}, "moretrees:palm_fruit_trunk") + if #trunks ~= 0 then + return + end + -- Convert trunk and all coconuts nearby. Maybe convert 2 trunks, just in case... + for _, tpos in pairs(cvtrunks) do + minetest.swap_node(tpos, {name = "moretrees:palm_fruit_trunk"}) + coconuts = minetest.find_nodes_in_area({x=tpos.x-1, y=tpos.y, z=tpos.z-1}, {x=tpos.x+1, y=tpos.y, z=tpos.z+1}, "moretrees:coconut") + for _, coconutpos in pairs(coconuts) do + minetest.set_node(coconutpos, {name = "moretrees:coconut_3"}) + end + end + end, + } + if minetest.register_lbm then + minetest.register_lbm(spec) + else + spec.interval = 3691 + spec.chance = 10 + minetest.register_abm(spec) + end +end + +-- If regrowing was previously disabled, but is enabled now, make sure timers are started for existing coconuts +if moretrees.coconuts_regrow then + local spec = { + name = "moretrees:restart_coconut_regrow_timer", + nodenames = "group:moretrees_coconut", + action = function(pos, node, active_object_count, active_object_count_wider) + local timer = minetest.get_node_timer(pos) + if not timer:is_started() then + coconut_starttimer(pos) + else + local timeout = timer:get_timeout() + local elapsed = timer:get_elapsed() + if timeout - elapsed > moretrees.coconut_grow_interval * 4/3 then + coconut_starttimer(pos, math.random(moretrees.coconut_grow_interval * 4/3)) + end + end + end, + } + if minetest.register_lbm then + minetest.register_lbm(spec) + else + spec.interval = 3659 + spec.chance = 10 + minetest.register_abm(spec) + end +end + diff --git a/crafts.lua b/crafts.lua index 395f602..9a0982b 100644 --- a/crafts.lua +++ b/crafts.lua @@ -63,6 +63,35 @@ minetest.register_craftitem("moretrees:raw_coconut", { on_use = minetest.item_eat(4), }) +minetest.register_craftitem("moretrees:date", { + description = S("Date"), + inventory_image = "moretrees_date.png", + on_use = minetest.item_eat(1), +}) + +minetest.register_craftitem("moretrees:date_nut_snack", { + description = S("Date & nut snack"), + inventory_image = "moretrees_date_nut_snack.png", + on_use = minetest.item_eat(4), +}) + +minetest.register_craftitem("moretrees:date_nut_batter", { + description = S("Date-nut cake batter"), + inventory_image = "moretrees_date_nut_batter.png", +}) + +minetest.register_craftitem("moretrees:date_nut_cake", { + description = S("Date-nut cake"), + inventory_image = "moretrees_date_nut_cake.png", + on_use = minetest.item_eat(32), +}) + +minetest.register_craftitem("moretrees:date_nut_bar", { + description = S("Date-nut energy bar"), + inventory_image = "moretrees_date_nut_bar.png", + on_use = minetest.item_eat(4), +}) + minetest.register_craftitem("moretrees:acorn_muffin_batter", { description = S("Acorn Muffin batter"), inventory_image = "moretrees_acorn_muffin_batter.png", @@ -109,6 +138,60 @@ for i in ipairs(moretrees.cutting_tools) do }) end +minetest.register_craft({ + type = "shapeless", + output = "moretrees:date_nut_snack", + recipe = { + "moretrees:date", + "moretrees:date", + "moretrees:date", + "moretrees:spruce_nuts", + "moretrees:cedar_nuts", + "moretrees:fir_nuts", + } +}) + +-- The date-nut cake is an exceptional food item due to its highly +-- concentrated nature (32 food units). Because of that, it requires +-- many different ingredients, and, starting from the base ingredients +-- found or harvested in nature, it requires many steps to prepare. +local flour +if minetest.registered_nodes["farming:flour"] then + flour = "farming:flour" +else + flour = "moretrees:acorn_muffin_batter" +end +minetest.register_craft({ + type = "shapeless", + output = "moretrees:date_nut_batter", + recipe = { + "moretrees:date_nut_snack", + "moretrees:date_nut_snack", + "moretrees:date_nut_snack", + "moretrees:coconut_milk", + "moretrees:date_nut_snack", + "moretrees:raw_coconut", + "moretrees:coconut_milk", + flour, + "moretrees:raw_coconut", + }, + replacements = { + { "moretrees:coconut_milk", "vessels:drinking_glass 2" } + } +}) + +minetest.register_craft({ + type = "cooking", + output = "moretrees:date_nut_cake", + recipe = "moretrees:date_nut_batter", +}) + +minetest.register_craft({ + type = "shapeless", + output = "moretrees:date_nut_bar 8", + recipe = {"moretrees:date_nut_cake"}, +}) + minetest.register_craft({ type = "shapeless", output = "moretrees:acorn_muffin_batter", diff --git a/date_palm.lua b/date_palm.lua new file mode 100644 index 0000000..0c40b72 --- /dev/null +++ b/date_palm.lua @@ -0,0 +1,746 @@ +-- Date palms. +-- +-- Date palms grow in hot and dry desert, but they require water. This makes them +-- a bit harder to find. If found in the middle of the desert, their presence +-- indicates a water source below the surface. +-- +-- As an additional feature (which can be disabled), dates automatically regrow after +-- harvesting (provided a male tree is sufficiently nearby). +-- If regrowing is enabled, then ripe dates will not hang forever. Most will disappear +-- (e.g. eaten by birds, ...), and a small fraction will drop as items. + +-- © 2016, Rogier +-- License: WTFPL + +local S = moretrees.intllib + +-- Some constants + +local dates_drop_ichance = 4 +local stems_drop_ichance = 4 +local flowers_wither_ichance = 3 + +-- implementation + +local dates_regrow_prob +if moretrees.dates_regrow_unpollinated_percent <= 0 then + dates_regrow_prob = 0 +elseif moretrees.dates_regrow_unpollinated_percent >= 100 then + dates_regrow_prob = 1 +else + dates_regrow_prob = 1 - math.pow(moretrees.dates_regrow_unpollinated_percent/100, 1/flowers_wither_ichance) +end + +-- Make the date palm fruit trunk a real trunk (it is generated as a fruit) +local trunk = minetest.registered_nodes["moretrees:date_palm_trunk"] +local ftrunk = {} +local fftrunk = {} +local mftrunk = {} +for k,v in pairs(trunk) do + ftrunk[k] = v +end +ftrunk.tiles = {} +for k,v in pairs(trunk.tiles) do + ftrunk.tiles[k] = v +end +ftrunk.drop = "moretrees:date_palm_trunk" +ftrunk.after_destruct = function(pos, oldnode) + local dates = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, {"group:moretrees_dates"}) + for _,datespos in pairs(dates) do + -- minetest.dig_node(datespos) does not cause nearby dates to be dropped :-( ... + local items = minetest.get_node_drops(minetest.get_node(datespos).name) + minetest.remove_node(datespos) + for _, itemname in pairs(items) do + minetest.add_item(datespos, itemname) + end + end +end +for k,v in pairs(ftrunk) do + mftrunk[k] = v + fftrunk[k] = v +end +fftrunk.tiles = {} +mftrunk.tiles = {} +for k,v in pairs(trunk.tiles) do + fftrunk.tiles[k] = v + mftrunk.tiles[k] = v +end +-- Make the different types of trunk distinguishable (but not too easily) +ftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR180" +ftrunk.description = ftrunk.description.." (gen)" +fftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR90" +mftrunk.tiles[1] = "moretrees_date_palm_trunk_top.png^[transformR-90" +minetest.register_node("moretrees:date_palm_fruit_trunk", ftrunk) +minetest.register_node("moretrees:date_palm_ffruit_trunk", fftrunk) +minetest.register_node("moretrees:date_palm_mfruit_trunk", mftrunk) + +-- ABM to grow new date blossoms +local date_regrow_abm_spec = { + nodenames = { "moretrees:date_palm_ffruit_trunk", "moretrees:date_palm_mfruit_trunk" }, + interval = moretrees.dates_flower_interval, + chance = moretrees.dates_flower_chance, + action = function(pos, node, active_object_count, active_object_count_wider) + local dates = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, "group:moretrees_dates") + + -- New blossom interval increases exponentially with number of dates already hanging + -- In addition: if more dates are hanging, the chance of picking an empty spot decreases as well... + if math.random(2^#dates) <= 2 then + -- Grow in area of 5x5 round trunk; higher probability in 3x3 area close to trunk + local dx=math.floor((math.random(50)-18)/16) + local dz=math.floor((math.random(50)-18)/16) + local datepos = {x=pos.x+dx, y=pos.y, z=pos.z+dz} + local datenode = minetest.get_node(datepos) + if datenode.name == "air" then + if node.name == "moretrees:date_palm_ffruit_trunk" then + minetest.set_node(datepos, {name="moretrees:dates_f0"}) + else + minetest.set_node(datepos, {name="moretrees:dates_m0"}) + end + end + end + end +} +if moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0 then + minetest.register_abm(date_regrow_abm_spec) +end + +-- Choose male or female palm, and spawn initial dates +-- (Instead of dates, a dates fruit trunk is generated with the tree. This +-- ABM converts the trunk to a female or male fruit trunk, and spawns some +-- hanging dates) +minetest.register_abm({ + nodenames = { "moretrees:date_palm_fruit_trunk" }, + interval = 1, + chance = 1, + action = function(pos, node, active_object_count, active_object_count_wider) + local type + if math.random(100) <= moretrees.dates_female_percent then + type = "f" + minetest.swap_node(pos, {name="moretrees:date_palm_ffruit_trunk"}) + else + type = "m" + minetest.swap_node(pos, {name="moretrees:date_palm_mfruit_trunk"}) + end + local dates1 = minetest.find_nodes_in_area({x=pos.x-1, y=pos.y, z=pos.z-1}, {x=pos.x+1, y=pos.y, z=pos.z+1}, "air") + local genpos + for _,genpos in pairs(dates1) do + if math.random(100) <= 20 then + if type == "m" then + minetest.set_node(genpos, {name = "moretrees:dates_n"}) + else + minetest.set_node(genpos, {name = "moretrees:dates_f4"}) + end + end + end + local dates2 = minetest.find_nodes_in_area({x=pos.x-2, y=pos.y, z=pos.z-2}, {x=pos.x+2, y=pos.y, z=pos.z+2}, "air") + for _,genpos in pairs(dates2) do + if math.random(100) <= 5 then + if type == "m" then + minetest.set_node(genpos, {name = "moretrees:dates_n"}) + else + minetest.set_node(genpos, {name = "moretrees:dates_f4"}) + end + end + end + end, +}) + +-- Dates growing functions. + +-- This is a bit complex, as the purpose is to find male flowers at horizontal distances of over +-- 100 nodes. As searching such a large area is time consuming, this is optimized in four ways: +-- - The search result (the locations of male trees) is cached, so that it can be used again +-- - Only 1/9th of the desired area is searched at a time. A new search is only performed if no male +-- flowers are found in the previously searched parts. +-- - Search results are shared with other female palms nearby. +-- - If previous searches for male palms have consumed too much CPU time, the search is skipped +-- (This means no male palms will be found, and the pollination of the flowers affected will be +-- delayed. If this happens repeatedly, eventually, the female flowers will wither...) +-- A caching method was selected that is suited for the case where most date trees are long-lived, +-- and where the number of trees nearby is limited: +-- - Locations of male palms are stored as metadata for every female palm. This means that a player +-- visiting a remote area with some date palms will not cause extensive searches for male palms as +-- long overdue blossoming ABMs are triggered for every date palm. +-- - Even when male palms *are* cut down, a cache refill will only be performed if the cached results do not +-- contain a male palm with blossoms. +-- The method will probably perform suboptimally: +-- - If female palms are frequently chopped down and replanted. +-- Freshly grown palms will need to search for male palms again +-- (this is mitigated by the long blossoming interval, which increases the chance that search +-- results have already been shared) +-- - If an area contains a large number of male and female palms. +-- In this area, every female palm will have an almost identical list of male palm locations +-- as metadata. +-- - If all male palms within range of a number of female palms have been chopped down (with possibly +-- new ones planted). Although an attempt was made to share search results in this case as well, +-- a number of similar searches will unavoidably be performed by the different female palms. +-- - If no male palms are in range of a female palm. In that case, there will be frequent searches +-- for newly-grown male palms. + +-- Search statistics - used to limit the search load. +local sect_search_stats = {} -- Search statistics - server-wide +local function reset_sect_search_stats() + sect_search_stats.count = 0 -- # of searches + sect_search_stats.skip = 0 -- # of times skipped + sect_search_stats.sum = 0 -- total time spent + sect_search_stats.min = 999999999 -- min time spent + sect_search_stats.max = 0 -- max time spent +end +reset_sect_search_stats() +sect_search_stats.last_us = 0 -- last time a search was done (microseconds, max: 2^32) +sect_search_stats.last_s = 0 -- last time a search was done (system time in seconds) + +-- Find male trunks in one section (=1/9 th) of the searchable area. +-- sect is -4 to 4, where 0 is the center section +local function find_fruit_trunks_near(ftpos, sect) + local r = moretrees.dates_pollination_distance + 2 * math.sqrt(2) + local sect_hr = math.floor(r / 3 + 0.9999) + local sect_vr = math.floor(r / 2 + 0.9999) + local t0us = core.get_us_time() + local t0s = os.time() + + -- Compute elapsed time since last search. + -- Unfortunately, the time value wraps after about 71 minutes (2^32 microseconds), + -- so it must be corrected to obtain the actual elapsed time. + if t0us < sect_search_stats.last_us then + -- Correct a simple wraparound. + -- This is not sufficient, as the time value may have wrapped more than once... + sect_search_stats.last_us = sect_search_stats.last_us - 2^32 + end + if t0s - sect_search_stats.last_s > 2^32/1000000 then + -- One additional correction is enough for our purposes. + -- For exact results, more corrections may be needed though... + -- (and even not applying this correction at all would still only yield + -- a minimal risk of a non-serious miscalculation...) + sect_search_stats.last_us = sect_search_stats.last_us - 2^32 + end + + -- Skip the search if it is consuming too much CPU time + if sect_search_stats.count > 0 and moretrees.dates_blossom_search_iload > 0 + and sect_search_stats.sum / sect_search_stats.count > moretrees.dates_blossom_search_time_treshold + and t0us - sect_search_stats.last_us < moretrees.dates_blossom_search_iload * (sect_search_stats.sum / sect_search_stats.count) then + sect_search_stats.skip = sect_search_stats.skip + 1 + return nil + end + + local all_palms = minetest.find_nodes_in_area( + { x = ftpos.x + 2 * sect.x * sect_hr - sect_hr, + y = ftpos.y - sect_vr, + z = ftpos.z + 2 * sect.z * sect_hr - sect_hr }, + { x = ftpos.x + 2 * sect.x * sect_hr + sect_hr, + y = ftpos.y + sect_vr, + z = ftpos.z + 2 * sect.z * sect_hr + sect_hr }, + {"moretrees:date_palm_mfruit_trunk", "moretrees:date_palm_ffruit_trunk"}) + + -- Collect different palms in separate lists. + local female_palms = {} + local male_palms = {} + local all_male_palms = {} + for _, pos in pairs(all_palms) do + if pos.x ~= ftpos.x or pos.y ~= ftpos.y or pos.z ~= ftpos.z then + local node = minetest.get_node(pos) + if node and node.name == "moretrees:date_palm_ffruit_trunk" then + table.insert(female_palms,pos) + elseif node then + table.insert(all_male_palms,pos) + -- In sector 0, all palms are of interest. + -- In other sectors, forget about palms that are too far away. + if sect == 0 then + table.insert(male_palms,pos) + else + local ssq = 0 + for _, c in pairs({"x", "z"}) do + local dc = pos[c] - ftpos[c] + ssq = ssq + dc * dc + end + if math.sqrt(ssq) <= r then + table.insert(male_palms,pos) + end + end + end + end + end + + -- Update search statistics + local t1us = core.get_us_time() + if t1us < t0us then + -- Wraparound. Assume the search lasted less than 2^32 microseconds (~71 min) + -- (so no need to apply another correction) + t0us = t0us - 2^32 + end + sect_search_stats.last_us = t0us + sect_search_stats.last_s = t0s + sect_search_stats.count = sect_search_stats.count + 1 + sect_search_stats.sum = sect_search_stats.sum + t1us-t0us + if t1us - t0us < sect_search_stats.min then + sect_search_stats.min = t1us - t0us + end + if t1us - t0us > sect_search_stats.max then + sect_search_stats.max = t1us - t0us + end + + return male_palms, female_palms, all_male_palms +end + +local function dates_print_search_stats(log) + local stats + if sect_search_stats.count > 0 then + stats = string.format("Male date tree searching stats: searches: %d/%d: average: %d µs (%d..%d)", + sect_search_stats.count, sect_search_stats.count + sect_search_stats.skip, + sect_search_stats.sum/sect_search_stats.count, sect_search_stats.min, sect_search_stats.max) + else + stats = string.format("Male date tree searching stats: searches: 0/0: average: (no searches yet)") + end + if log then + minetest.log("action", "[moretrees] " .. stats) + end + return true, stats +end + +minetest.register_chatcommand("dates_stats", { + description = "Print male date palm search statistics", + params = "|chat|log|reset", + privs = { server = true }, + func = function(name, param) + param = string.lower(string.trim(param)) + if param == "" or param == "chat" then + return dates_print_search_stats(false) + elseif param == "log" then + return dates_print_search_stats(true) + elseif param == "reset" then + reset_sect_search_stats() + return true + else + return false, "Invalid subcommand; expected: '' or 'chat' or 'log' or 'reset'" + end + end, +}) + +-- Find the female trunk near the female flowers to be pollinated +local function find_female_trunk(fbpos) + local trunks = minetest.find_nodes_in_area({x=fbpos.x-2, y=fbpos.y, z=fbpos.z-2}, + {x=fbpos.x+2, y=fbpos.y, z=fbpos.z+2}, + "moretrees:date_palm_ffruit_trunk") + local ftpos + local d = 99 + for x, pos in pairs(trunks) do + local ssq = 0 + for _, c in pairs({"x", "z"}) do + local dc = pos[c] - fbpos[c] + ssq = ssq + dc * dc + end + if math.sqrt(ssq) < d then + ftpos = pos + d = math.sqrt(ssq) + end + end + return ftpos +end + +-- Find male blossom near a male trunk, +-- the male blossom must be in range of a specific female blossom as well +local function find_male_blossom_near_trunk(fbpos, mtpos) + local r = moretrees.dates_pollination_distance + local blossoms = minetest.find_nodes_in_area({x=mtpos.x-2, y=mtpos.y, z=mtpos.z-2}, + {x=mtpos.x+2, y=mtpos.y, z=mtpos.z+2}, + "moretrees:dates_m0") + for x, mbpos in pairs(blossoms) do + local ssq = 0 + for _, c in pairs({"x", "z"}) do + local dc = mbpos[c] - fbpos[c] + ssq = ssq + dc * dc + end + if math.sqrt(ssq) <= r then + return mbpos + end + end + +end + +-- Find a male blossom in range of a specific female blossom, +-- using a nested list of male blossom positions +local function find_male_blossom_in_mpalms(ftpos, fbpos, mpalms) + -- Process the elements of mpalms.sect (index -4 .. 4) in random order + -- First, compute the order in which the sectors will be searched + local sect_index = {} + local sect_rnd = {} + for i = -4,4 do + local n = math.random(1023) + sect_index[n] = i + table.insert(sect_rnd, n) + end + table.sort(sect_rnd) + + -- Search the sectors + local sect_old = 0 + local sect_time = minetest.get_gametime() + for _, n in pairs(sect_rnd) do + -- Record the oldest sector, so that it can be searched if no male + -- blossoms were found + if not mpalms.sect_time[sect_index[n]] then + sect_old = sect_index[n] + sect_time = 0 + elseif mpalms.sect_time[sect_index[n]] < sect_time then + sect_old = sect_index[n] + sect_time = mpalms.sect_time[sect_index[n]] + end + if mpalms.sect[sect_index[n]] and #mpalms.sect[sect_index[n]] then + for px, mtpos in pairs(mpalms.sect[sect_index[n]]) do + local node = minetest.get_node(mtpos) + if node and node.name == "moretrees:date_palm_mfruit_trunk" then + local mbpos = find_male_blossom_near_trunk(fbpos, mtpos) + if mbpos then + return mbpos + end + elseif node and node.name ~= "ignore" then + -- no more male trunk here. + mpalms.sect[sect_index[n]][px] = nil + end + end + end + end + return nil, sect_old +end + +-- Find a male blossom in range of a specific female blossom, +-- using the cache associated with the given female trunk +-- If necessary, recompute part of the cache +local last_search_result = {} +local function find_male_blossom_with_ftrunk(fbpos,ftpos) + local meta = minetest.get_meta(ftpos) + local mpalms + local cache_changed = true + + -- Load cache. If distance has changed, start with empty cache instead. + local mpalms_dist = meta:get_int("male_palms_dist") + if mpalms_dist and mpalms_dist == moretrees.dates_pollination_distance then + mpalms = meta:get_string("male_palms") + if mpalms and mpalms ~= "" then + mpalms = minetest.deserialize(mpalms) + cache_changed = false + end + end + if not mpalms or not mpalms.sect then + mpalms = {} + mpalms.sect = {} + mpalms.sect_time = {} + meta:set_int("male_palms_dist", moretrees.dates_pollination_distance) + cache_changed = true + end + local fpalms_list + local all_mpalms_list + local sector0_searched = false + + -- Always make sure that sector 0 is cached + if not mpalms.sect[0] then + mpalms.sect[0], fpalms_list, all_mpalms_list = find_fruit_trunks_near(ftpos, {x = 0, z = 0}) + mpalms.sect_time[0] = minetest.get_gametime() + sector0_searched = true + cache_changed = true + last_search_result.female = fpalms_list + last_search_result.male = all_mpalms_list + end + + -- Find male palms + local mbpos, sect_old = find_male_blossom_in_mpalms(ftpos, fbpos, mpalms) + + -- If not found, (re)generate the cache for an additional sector. But don't search it yet (for performance reasons) + -- (Use the globally cached results if possible) + if not mbpos and not sector0_searched then + if not mpalms.sect_time[0] or mpalms.sect_time[0] == 0 or math.random(3) == 1 then + -- Higher probability of re-searching the center sector + sect_old = 0 + end + -- Use globally cached result if possible + mpalms.sect[sect_old] = nil + if sect_old == 0 and mpalms.sect_time[0] and mpalms.sect_time[0] > 0 + and last_search_result.male and #last_search_result.male then + for _, pos in pairs(last_search_result.female) do + if pos.x == ftpos.x and pos.y == ftpos.y and pos.z == ftpos.z then + mpalms.sect[sect_old] = last_search_result.male + -- Next time, don't use the cached result + mpalms.sect_time[sect_old] = nil + cache_changed = true + end + end + end + -- Else do a new search + if not mpalms.sect[sect_old] then + mpalms.sect[sect_old], fpalms_list, all_mpalms_list = find_fruit_trunks_near(ftpos, {x = (sect_old + 4) % 3 - 1, z = (sect_old + 4) / 3 - 1}) + cache_changed = true + if sect_old == 0 then + -- Save the results if it is sector 0 + -- (chance of reusing results from another sector are smaller) + last_search_result.female = fpalms_list + last_search_result.male = all_mpalms_list + end + if mpalms.sect[sect_old] then + mpalms.sect_time[sect_old] = minetest.get_gametime() + else + mpalms.sect_time[sect_old] = nil + end + end + end + + -- Share search results with other female trunks in the same area + -- Note that the list of female trunks doesn't (shouldn't :-) contain the current female trunk. + if fpalms_list and #fpalms_list and #all_mpalms_list then + local all_mpalms = {} + all_mpalms.sect = {} + all_mpalms.sect_time = {} + all_mpalms.sect[0] = all_mpalms_list + -- Don't set sect_time[0], so that the cached sector will be re-searched soon (if necessary) + local all_mpalms_serialized = minetest.serialize(all_mpalms) + for _, pos in pairs(fpalms_list) do + local fmeta = minetest.get_meta(pos) + local fdist = fmeta:get_int("male_palms_dist") + if not fdist or fdist ~= moretrees.dates_pollination_distance then + fmeta:set_string("male_palms", all_mpalms_serialized) + fmeta:set_int("male_palms_dist", moretrees.dates_pollination_distance) + end + end + end + + -- Save cache. + if cache_changed then + meta:set_string("male_palms", minetest.serialize(mpalms)) + end + + return mbpos +end + +-- Find a male blossom in range of a specific female blossom +local function find_male_blossom(fbpos) + local ftpos = find_female_trunk(fbpos) + if ftpos then + return find_male_blossom_with_ftrunk(fbpos, ftpos) + end + return nil +end + +-- Growing function for dates +local dates_growfn = function(pos, elapsed) + local node = minetest.get_node(pos) + local delay = moretrees.dates_grow_interval + local r = moretrees.dates_pollination_distance + local action + if not node then + return + elseif not moretrees.dates_regrow_pollinated and dates_regrow_prob == 0 then + -- Regrowing of dates is disabled. + if string.find(node.name, "moretrees:dates_f") then + minetest.swap_node(pos, {name="moretrees:dates_f4"}) + elseif string.find(node.name, "moretrees:dates_m") then + minetest.swap_node(pos, {name="moretrees:dates_n"}) + else + minetest.remove_node(pos) + end + return + elseif node.name == "moretrees:dates_f0" and math.random(100) <= 100 * dates_regrow_prob then + -- Dates grow unpollinated + minetest.swap_node(pos, {name="moretrees:dates_f1"}) + action = "nopollinate" + elseif node.name == "moretrees:dates_f0" and moretrees.dates_regrow_pollinated and find_male_blossom(pos) then + -- Pollinate flowers + minetest.swap_node(pos, {name="moretrees:dates_f1"}) + action = "pollinate" + elseif string.match(node.name, "0$") then + -- Make female unpollinated and male flowers last a bit longer + if math.random(flowers_wither_ichance) == 1 then + if node.name == "moretrees:dates_f0" then + minetest.swap_node(pos, {name="moretrees:dates_fn"}) + else + minetest.swap_node(pos, {name="moretrees:dates_n"}) + end + action = "wither" + else + action = "nowither" + end + elseif node.name == "moretrees:dates_f4" then + -- Remove dates, and optionally drop them as items + if math.random(dates_drop_ichance) == 1 then + if moretrees.dates_item_drop_ichance > 0 and math.random(moretrees.dates_item_drop_ichance) == 1 then + local items = minetest.get_node_drops(minetest.get_node(pos).name) + for _, itemname in pairs(items) do + minetest.add_item(pos, itemname) + end + end + minetest.swap_node(pos, {name="moretrees:dates_n"}) + action = "drop" + else + action = "nodrop" + end + elseif string.match(node.name, "n$") then + -- Remove stems. + if math.random(stems_drop_ichance) == 1 then + minetest.remove_node(pos) + return "stemdrop" + end + action = "nostemdrop" + else + -- Grow dates + local offset = 18 + local n = string.sub(node.name, offset) + minetest.swap_node(pos, {name=string.sub(node.name, 1, offset-1)..n+1}) + action = "grow" + end + -- Don't catch up when elapsed time is large. Regular visits are needed for growth... + local timer = minetest.get_node_timer(pos) + timer:start(delay + math.random(moretrees.dates_grow_interval)) + return action +end + +-- Alternate growth function for dates. +-- It calls the primary growth function, but also measures CPU time consumed. +-- Use this function to analyze date growing performance. +local stat = {} +stat.count = 0 +local dates_growfn_profiling = function(pos, elapsed) + local t0 = core.get_us_time() + local action = dates_growfn(pos, elapsed) + local t1 = core.get_us_time() + if t1 < t0 then + t1 = t1 + 2^32 + end + stat.count = stat.count + 1 + if not stat[action] then + stat[action] = {} + stat[action].count = 0 + stat[action].sum = 0 + stat[action].min = 9999999999 + stat[action].max = 0 + end + stat[action].count = stat[action].count + 1 + stat[action].sum = stat[action].sum + t1-t0 + if t1-t0 < stat[action].min then + stat[action].min = t1-t0 + end + if t1-t0 > stat[action].max then + stat[action].max = t1-t0 + end + + if stat.count % 10 == 0 then + io.write(".") + io.flush() + end + if stat.count % 100 == 0 then + print(string.format("Date grow statistics %5d:", stat.count)) + local sum = 0 + local count = 0 + if sect_search_stats.count > 0 and stat.pollinate and stat.pollinate.count > 0 then + print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)", + "search", sect_search_stats.count, + 100*sect_search_stats.count/stat.pollinate.count, + sect_search_stats.sum/sect_search_stats.count, + sect_search_stats.min, sect_search_stats.max)) + else + print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)", + "search", sect_search_stats.count, + 0, 0, 0, 0)) + end + for action,data in pairs(stat) do + if action ~= "count" then + count = count + data.count + sum = sum + data.sum + print(string.format("\t%-12s: %6d (%4.1f%%): %6dus (%d..%d)", + action, data.count, + 100*data.count/stat.count, data.sum/data.count, + data.min, data.max)) + end + end + print(string.format("\t%-12s: %6d ( 100%%): %6dus", + "TOTAL", count, sum/count)) + end +end + +-- Register dates + +local dates_starttimer = function(pos, elapsed) + local timer = minetest.get_node_timer(pos) + local base_interval = moretrees.dates_grow_interval * 2 / 3 + timer:set(base_interval + math.random(base_interval), elapsed or 0) +end + +local dates_drop = { + items = { + {items = { "moretrees:date" }}, + {items = { "moretrees:date" }}, + {items = { "moretrees:date" }}, + {items = { "moretrees:date" }}, + {items = { "moretrees:date" }, rarity = 2 }, + {items = { "moretrees:date" }, rarity = 2 }, + {items = { "moretrees:date" }, rarity = 2 }, + {items = { "moretrees:date" }, rarity = 2 }, + {items = { "moretrees:date" }, rarity = 5 }, + {items = { "moretrees:date" }, rarity = 5 }, + {items = { "moretrees:date" }, rarity = 5 }, + {items = { "moretrees:date" }, rarity = 5 }, + {items = { "moretrees:date" }, rarity = 20 }, + {items = { "moretrees:date" }, rarity = 20 }, + {items = { "moretrees:date" }, rarity = 20 }, + {items = { "moretrees:date" }, rarity = 20 }, + } +} + +for _,suffix in ipairs({"f0", "f1", "f2", "f3", "f4", "m0", "fn", "n"}) do + local name + if suffix == "f0" or suffix == "m0" then + name = S("Date Flowers") + elseif suffix == "n" or suffix == "fn" then + name = S("Date Stem") + else + name = S("Dates") + end + local dropfn = suffix == "f4" and dates_drop or "" + local datedef = { + description = name, + tiles = {"moretrees_dates_"..suffix..".png"}, + visual_scale = 2, + drawtype = "plantlike", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + groups = { fleshy=3, dig_immediate=3, flammable=2, moretrees_dates=1 }, + inventory_image = "moretrees_dates_"..suffix..".png^[transformR0", + wield_image = "moretrees_dates_"..suffix..".png^[transformR90", + sounds = default.node_sound_defaults(), + drop = dropfn, + selection_box = { + type = "fixed", + fixed = {-0.3, -0.3, -0.3, 0.3, 3.5, 0.3} + }, + on_timer = dates_growfn, + on_construct = (moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0) + and dates_starttimer, + + } + minetest.register_node("moretrees:dates_"..suffix, datedef) +end + +-- If regrowing was previously disabled, but is enabled now, make sure timers are started for existing dates +if moretrees.dates_regrow_pollinated or moretrees.dates_regrow_unpollinated_percent > 0 then + local spec = { + name = "moretrees:restart_dates_regrow_timer", + nodenames = "group:moretrees_dates", + action = function(pos, node, active_object_count, active_object_count_wider) + local timer = minetest.get_node_timer(pos) + if not timer:is_started() then + dates_starttimer(pos) + else + local timeout = timer:get_timeout() + local elapsed = timer:get_elapsed() + if timeout - elapsed > moretrees.dates_grow_interval * 4/3 then + dates_starttimer(pos, math.random(moretrees.dates_grow_interval * 4/3)) + end + end + end, + } + if minetest.register_lbm then + minetest.register_lbm(spec) + else + spec.interval = 3557 + spec.chance = 10 + minetest.register_abm(spec) + end +end + diff --git a/default_settings.txt b/default_settings.txt index 1bb85f5..0a5fc88 100644 --- a/default_settings.txt +++ b/default_settings.txt @@ -6,6 +6,7 @@ moretrees.enable_apple_tree = true moretrees.enable_oak = true moretrees.enable_sequoia = true moretrees.enable_palm = true +moretrees.enable_date_palm = true moretrees.enable_cedar = true moretrees.enable_rubber_tree = true moretrees.enable_willow = true @@ -57,6 +58,67 @@ moretrees.firs_remove_default_trees = false moretrees.firs_remove_interval = 2 moretrees.firs_remove_chance = 150 +-- Cocos palm settings + +moretrees.coconuts_regrow = true +moretrees.coconuts_convert_existing_palms = true -- Converting existing palm trees will make coconuts regrow on them as well + -- Else, they will only regrow on newly-spawned palms + -- However, conversion is not an exact science, and although an attempt is + -- made to detect whether a trunk belongs to an actual palm, some coconut trunks + -- and some coconuts may be incorrectly converted. +moretrees.coconut_flower_interval = 59 +moretrees.coconut_flower_chance = 67 +moretrees.coconut_grow_interval = 2 * moretrees.coconut_flower_interval * moretrees.coconut_flower_chance + -- Actual interval will randomly vary between 67% and 133% of this value + -- 2 * 59 * 67 ~ 2 hours. So flowers become coconuts in about 6 hours +moretrees.coconut_item_drop_ichance = 10 -- inverse probability of ripe coconuts dropping as items (instead of disappearing) + +-- Date palm settings + +-- Suggested configuration alternatives: +-- - Dates grow only when pollinated: +-- - Set dates_regrow_pollinated to true +-- - Set dates_regrow_unpollinated_percent to 0 +-- - Dates grow without pollination. Pollination disabled: +-- - Set dates_regrow_pollinated to false +-- - Set dates_regrow_unpollinated_percent to some larger positive value, e.g. 95 +-- - Dates grow, but more and faster if male flowers are nearby +-- - Set dates_regrow_pollinated to true +-- - Set dates_regrow_unpollinated_percent to some small positive value, e.g. 33 +-- - Optional but recommended: Reduce the pollination distance, e.g. to 30 + +-- Note that it should not be necessary to disable pollination for performance +-- reasons. A lot of effort has gone into ensuring that date growing will not cause lag. +-- +-- If lag is suspected, use the chat command '/dates_stats' to obtain the male dates +-- search time, as well as the counts of total number of searches requested and the +-- number of searches actually performed. + +moretrees.dates_regrow_pollinated = true -- Enable pollination. If enabled, male trees are required for dates to grow. + -- If disabled, dates_regrow_unpollinated_percent must be non-zero for dates to regrow. +moretrees.dates_regrow_unpollinated_percent = 0 -- Percentage of female dates becoming dates without being pollinated. + -- If 0, dates_regrow_pollinated must be enabled for dates to grow. +moretrees.dates_female_percent = 57 -- Ratio of female to male trees - tune this to improve # of generated trees that actually bear fruit + -- ~57% gives near optimal results for groups of 3 random trees, while it is only slightly suboptimal + -- for groups of 2 and 4 random trees (~2% less fruit than optimal). + -- Optimal values per group size: 2: 50%, 3: 57.78%, 4: 63%, 5: 66.9%, 6: 69.9%, [...], 12: 79.8% + -- So 57% is optimal for small groups of trees. As larger groups have more female palms anyway, a + -- less than optimal proportion of female to male trees is not a problem. +moretrees.dates_pollination_distance = 120 +moretrees.dates_blossom_search_time_treshold = 1000 -- If average male blossom search time (in microseconds) exceeds this, start limiting the search load. +moretrees.dates_blossom_search_iload = 10 -- Inverse fraction of CPU time that male blossom searching search may consume. + -- As searching a large area (radius: dates_pollination_distance/3 per attempt) can cause lag, + -- this limits the search frequency server-wide so that the impact on server lag is minimised + -- For personal servers, this can be set lower, or even to 1 or 0 (0 disables load limiting). + -- Obtain the current average search time using /dates_stats +moretrees.dates_flower_interval = 59 +moretrees.dates_flower_chance = 181 +moretrees.dates_grow_interval = 2 * moretrees.dates_flower_interval * moretrees.dates_flower_chance + -- As date palms have a high yield, don't grow dates too fast + -- The actual interval will vary randomly between 67% and 133% of this value. + -- 2 * 59 * 181 ~ 6 hours. So by default flowers become dates in about one (human) day. +moretrees.dates_item_drop_ichance = 10 -- inverse probability of ripe dates dropping as items (instead of disappearing) + -- Sapling settings moretrees.sapling_interval = 500 diff --git a/depends.txt b/depends.txt index c666762..7896571 100644 --- a/depends.txt +++ b/depends.txt @@ -1,6 +1,8 @@ default biome_lib +vessels stairs? moreblocks? intllib? +farming? diff --git a/init.lua b/init.lua index 843958b..2c50fd9 100644 --- a/init.lua +++ b/init.lua @@ -73,6 +73,8 @@ moretrees.cutting_tools = { dofile(modpath.."/tree_models.lua") dofile(modpath.."/node_defs.lua") +dofile(modpath.."/date_palm.lua") +dofile(modpath.."/cocos_palm.lua") dofile(modpath.."/biome_defs.lua") dofile(modpath.."/saplings.lua") dofile(modpath.."/crafts.lua") @@ -85,6 +87,7 @@ if moretrees.spawn_saplings then moretrees.spawn_oak_object = "moretrees:oak_sapling_ongen" moretrees.spawn_sequoia_object = "moretrees:sequoia_sapling_ongen" moretrees.spawn_palm_object = "moretrees:palm_sapling_ongen" + moretrees.spawn_date_palm_object = "moretrees:date_palm_sapling_ongen" moretrees.spawn_cedar_object = "moretrees:cedar_sapling_ongen" moretrees.spawn_rubber_tree_object = "moretrees:rubber_tree_sapling_ongen" moretrees.spawn_willow_object = "moretrees:willow_sapling_ongen" @@ -100,6 +103,7 @@ else moretrees.spawn_oak_object = moretrees.oak_model moretrees.spawn_sequoia_object = moretrees.sequoia_model moretrees.spawn_palm_object = moretrees.palm_model + moretrees.spawn_date_palm_object = moretrees.date_palm_model moretrees.spawn_cedar_object = moretrees.cedar_model moretrees.spawn_rubber_tree_object = moretrees.rubber_tree_model moretrees.spawn_willow_object = moretrees.willow_model @@ -131,6 +135,11 @@ if moretrees.enable_palm then biome_lib:register_generate_plant(moretrees.palm_biome, moretrees.spawn_palm_object) end +if moretrees.enable_date_palm then + biome_lib:register_generate_plant(moretrees.date_palm_biome, moretrees.spawn_date_palm_object) + biome_lib:register_generate_plant(moretrees.date_palm_biome_2, moretrees.spawn_date_palm_object) +end + if moretrees.enable_cedar then biome_lib:register_generate_plant(moretrees.cedar_biome, moretrees.spawn_cedar_object) end diff --git a/node_defs.lua b/node_defs.lua index 94b7ed0..b1df5f7 100644 --- a/node_defs.lua +++ b/node_defs.lua @@ -8,7 +8,8 @@ moretrees.treelist = { {"oak", "Oak Tree", "acorn", "Acorn", {-0.2, -0.5, -0.2, 0.2, 0, 0.2}, 0.8 }, {"sequoia", "Giant Sequoia"}, {"birch", "Birch Tree"}, - {"palm", "Palm Tree", "coconut", "Coconut", {-0.2, -0.5, -0.2, 0.2, 0, 0.2}, 1.0 }, + {"palm", "Palm Tree", "palm_fruit_trunk_gen", "Palm Tree", {-0.2, -0.5, -0.2, 0.2, 0, 0.2}, 1.0 }, + {"date_palm", "Date Palm Tree", "date_palm_fruit_trunk", "Date Palm Tree", {0, 0, 0, 0, 0, 0}, 0.0 }, {"spruce", "Spruce Tree", "spruce_cone", "Spruce Cone", {-0.2, -0.5, -0.2, 0.2, 0, 0.2}, 0.8 }, {"cedar", "Cedar Tree", "cedar_cone", "Cedar Cone", {-0.2, -0.5, -0.2, 0.2, 0, 0.2}, 0.8 }, {"willow", "Willow Tree"}, @@ -123,6 +124,8 @@ for i in ipairs(moretrees.treelist) do if treename == "palm" then droprarity = 20 decay = moretrees.palm_leafdecay_radius + elseif treename == "date_palm" then + decay = moretrees.palm_leafdecay_radius end local moretrees_leaves_inventory_image = nil @@ -430,3 +433,5 @@ minetest.register_alias("moretrees:pine_sapling", "moretrees:cedar_sapling") minetest.register_alias("moretrees:pine_leaves", "moretrees:cedar_leaves") minetest.register_alias("moretrees:pine_cone", "moretrees:cedar_cone") minetest.register_alias("moretrees:pine_nuts", "moretrees:cedar_nuts") + +minetest.register_alias("moretrees:dates", "moretrees:dates_f4") diff --git a/textures/moretrees_coconut_0.png b/textures/moretrees_coconut_0.png new file mode 100644 index 0000000..644a65f Binary files /dev/null and b/textures/moretrees_coconut_0.png differ diff --git a/textures/moretrees_coconut_1.png b/textures/moretrees_coconut_1.png new file mode 100644 index 0000000..e2889bc Binary files /dev/null and b/textures/moretrees_coconut_1.png differ diff --git a/textures/moretrees_coconut_2.png b/textures/moretrees_coconut_2.png new file mode 100644 index 0000000..86c8cf5 Binary files /dev/null and b/textures/moretrees_coconut_2.png differ diff --git a/textures/moretrees_date.png b/textures/moretrees_date.png new file mode 100644 index 0000000..ed743b5 Binary files /dev/null and b/textures/moretrees_date.png differ diff --git a/textures/moretrees_date_nut_bar.png b/textures/moretrees_date_nut_bar.png new file mode 100644 index 0000000..6895001 Binary files /dev/null and b/textures/moretrees_date_nut_bar.png differ diff --git a/textures/moretrees_date_nut_batter.png b/textures/moretrees_date_nut_batter.png new file mode 100644 index 0000000..47d8e08 Binary files /dev/null and b/textures/moretrees_date_nut_batter.png differ diff --git a/textures/moretrees_date_nut_cake.png b/textures/moretrees_date_nut_cake.png new file mode 100644 index 0000000..5084e71 Binary files /dev/null and b/textures/moretrees_date_nut_cake.png differ diff --git a/textures/moretrees_date_nut_snack.png b/textures/moretrees_date_nut_snack.png new file mode 100644 index 0000000..1766dab Binary files /dev/null and b/textures/moretrees_date_nut_snack.png differ diff --git a/textures/moretrees_date_palm_leaves.png b/textures/moretrees_date_palm_leaves.png new file mode 100644 index 0000000..de0f569 Binary files /dev/null and b/textures/moretrees_date_palm_leaves.png differ diff --git a/textures/moretrees_date_palm_sapling.png b/textures/moretrees_date_palm_sapling.png new file mode 100644 index 0000000..9596028 Binary files /dev/null and b/textures/moretrees_date_palm_sapling.png differ diff --git a/textures/moretrees_date_palm_trunk.png b/textures/moretrees_date_palm_trunk.png new file mode 100644 index 0000000..b8fa986 Binary files /dev/null and b/textures/moretrees_date_palm_trunk.png differ diff --git a/textures/moretrees_date_palm_trunk_top.png b/textures/moretrees_date_palm_trunk_top.png new file mode 100644 index 0000000..329ba46 Binary files /dev/null and b/textures/moretrees_date_palm_trunk_top.png differ diff --git a/textures/moretrees_date_palm_wood.png b/textures/moretrees_date_palm_wood.png new file mode 100644 index 0000000..c531e3c Binary files /dev/null and b/textures/moretrees_date_palm_wood.png differ diff --git a/textures/moretrees_dates.png b/textures/moretrees_dates.png new file mode 100644 index 0000000..cd8d578 Binary files /dev/null and b/textures/moretrees_dates.png differ diff --git a/textures/moretrees_dates_f0.png b/textures/moretrees_dates_f0.png new file mode 100644 index 0000000..b75d59a Binary files /dev/null and b/textures/moretrees_dates_f0.png differ diff --git a/textures/moretrees_dates_f1.png b/textures/moretrees_dates_f1.png new file mode 100644 index 0000000..535150b Binary files /dev/null and b/textures/moretrees_dates_f1.png differ diff --git a/textures/moretrees_dates_f2.png b/textures/moretrees_dates_f2.png new file mode 100644 index 0000000..e2e299c Binary files /dev/null and b/textures/moretrees_dates_f2.png differ diff --git a/textures/moretrees_dates_f3.png b/textures/moretrees_dates_f3.png new file mode 100644 index 0000000..eef43f0 Binary files /dev/null and b/textures/moretrees_dates_f3.png differ diff --git a/textures/moretrees_dates_f4.png b/textures/moretrees_dates_f4.png new file mode 100644 index 0000000..78f38f9 Binary files /dev/null and b/textures/moretrees_dates_f4.png differ diff --git a/textures/moretrees_dates_fn.png b/textures/moretrees_dates_fn.png new file mode 100644 index 0000000..db88913 Binary files /dev/null and b/textures/moretrees_dates_fn.png differ diff --git a/textures/moretrees_dates_m0.png b/textures/moretrees_dates_m0.png new file mode 100644 index 0000000..4ff61e7 Binary files /dev/null and b/textures/moretrees_dates_m0.png differ diff --git a/textures/moretrees_dates_n.png b/textures/moretrees_dates_n.png new file mode 100644 index 0000000..c12a3d9 Binary files /dev/null and b/textures/moretrees_dates_n.png differ diff --git a/tree_biomes.txt b/tree_biomes.txt index fdba90d..22e1032 100644 --- a/tree_biomes.txt +++ b/tree_biomes.txt @@ -6,6 +6,8 @@ jungle tree - 5 to +10 above +15 water, 20 10 dirt_with_grass 329 5 fir above +25 -20 to +10 n/a n/a dirt_with_grass 359 8 firs on snow above +15 -20 to +10 n/a n/a snow:snow 359 8 palm - 1 to + 1 +15 to +32 water, 15 10 sand 330 5 +date palm - 1 to +10 above +39 water, 20h,20v 100 desert_sand 339 10 +date palm +11 to +30 above +39 water, 1h,30v 1 desert_sand 340 10 apple + 1 to +10 +23 to +32 n/a n/a dirt_with grass 331 15 oak 0 to +10 + 4 to +16 n/a n/a dirt_with grass 332 15 sequoia 0 to +10 -30 to +50 n/a n/a dirt_with grass 333 10 diff --git a/tree_models.lua b/tree_models.lua index c534089..791bddb 100644 --- a/tree_models.lua +++ b/tree_models.lua @@ -88,8 +88,13 @@ moretrees.birch_model2={ thin_branches=true } +-- Coconuts can't be generated as fruit, because there is no support for the +-- special fruit trunks that allow coconuts to regrow at the correct position +-- in the tree. +-- So, a placeholder fruit trunk is spawned. An ABM will convert it to the final +-- fruit trunk, and generate the actual coconuts. moretrees.palm_model={ - axiom="FFcccccc&FFFFFddd[^&&&GR][^///&&&GR][^//////&&&GR][^***&&&GR]FA//A//A//A//A//A", + axiom="FFcccccc&FFFFFdddRA//A//A//A//A//A", rules_a="[&fb&bbb[++f--&ffff&ff][--f++&ffff&ff]&ffff&bbbb&b]", rules_b="f", rules_c="/", @@ -101,7 +106,32 @@ moretrees.palm_model={ random_level=0, trunk_type="single", thin_branches=true, - fruit="moretrees:coconut", + fruit="moretrees:palm_fruit_trunk_gen", + fruit_chance=0 +} + +-- Dates can't be generated as fruit, because there is no support for the +-- special (male and female) fruit trunks that allow dates to regrow at the +-- correct position in the tree. +-- So, a generic fruit trunk is spawned. An ABM will convert it to a male +-- or female fruit trunk, and generate the actual dates. +moretrees.date_palm_model={ + axiom="TTTTddddddddddccccccccccRT[TGGGGT]".. + "ccccc[&&a]ccccc[&&a]ccccc[&&a]ccccc[&&a]ccccc[&&a]ccccc[&&a]".. + "GGccccc[&a]ccccc[&a]ccccc[&a]ccccc[&a]ccccc[&a]ccccc[&a]".. + "GGccccc[a]ccccc[a]ccccc[a]ccccc[a]ccccc[a]ccccc[a]", + rules_a="Gffb&bbb[++f--&ffff&ff][--f++&ffff&ff]&ff&ff&bb&bb&bb", + rules_b="f", + rules_c="/", + rules_d="F", + trunk="moretrees:date_palm_trunk", + leaves="moretrees:date_palm_leaves", + angle=18, + iterations=1, + random_level=0, + trunk_type="single", + thin_branches=false, + fruit="moretrees:date_palm_fruit_trunk", fruit_chance=0 }