dfcaverns/df_primordial_items/giant_mycelium.lua
FaceDeer 0a0c97b74e
Mineclone compatibility (#36)
* latest versions of mapgen_helper and subterrane

* initial work

* call it "minetest_game" compatibility instead of "default" compatibility

* Getting started on moving all default dependencies to a single root mod

* distilling out more dependencies

* fix some typos

* more default dependencies

* beds

* start getting some MCL equivalents set up

* mine gas dependencies

* reorganize

* add some mapgen stuff

* getting close to actually being able to run mineclone with this

* it actually runs! Crashes with out-of-memory error when you try to go below -64, but it's a start.

* hide flowing pit plasma from creative inventory

* mineclone 5 settings

* Update subterrane

* get rid of meselamp dependency, bring in ancient lanterns from deep roads

* stair dependencies

* add mcl fences

* add mcl blast resistance and hardness to everything. Also an alternate name for Nethercap in MCL, since "nether" has another meaning there.

* buckets of oil and syrup should work in mineclone now

* 'splosions!

* make hunters avoid repaired ancient lanterns

* mapgen torchspine wasn't having its timer set

* split mapgen compatibility code into its own file

* bypass dependency indirection for df_farming.

* apply threshold alpha to food items, they look better that way

* bypass dependency indirection for df_mapitems

* bypass dependency indirection for df_primordial_items

* bypass dependency indirection for df_trees

* bypass dependency indirection for df_underworld_items

* bypass dependency indirection for df_caverns

* fixing up the puzzle seal to work in both

* fix puzzle seal formspec for mcl

* create built-in substitutes for fireflies and bones mods for when those aren't available

* set up mcl dungeon loot for underworld warriors, port over some translations from default

* overlooked a debug

* add itemslot backgrounds for mcl

* added mineclone groups to all registered nodes. TODO: craftitems.

This was extremely tedious. Completely untested, aside from simply running the game.

* minor fixes to the built-in bones node

* eatable group for edibles

* clean up some TODOs

* adjust pit plasma abm conditions

* add df_ambience

* fixing up explosions. Also make slade sand diggable in mcl, and fix a bug in web generators

* make tower cap caves less chirpy, fix bigger goblin hut schematic, allow glowing spindlestem extract bottles

* avoid an undeclared global check

* alas, cave pearls aren't set up right for attached_node_facedir.

* bunch of work on mineclone ores, moved main config into df_dependencies

* add a few more ores

* update depends in mod.conf

* add opaque group to light-sensitive dirt types

Mineclone adds the "opaque" group only to non-light nodes, and torches check for the opaque group to decide whether they can be placed there. light-sensitive nodes need the "light" paramtype to work, though. So adding the opaque group manually to these.

* add a touch of reverb to one of the whisper tracks, it didn't fit with the others without it

* bloodthorn also needs to be set opaque

* add sound to torchspines

* isolate Nethercap translation name to make it easier to swap out in Mineclone contexts

* ambience tweak

* fix dirt spreads

https://github.com/FaceDeer/dfcaverns/issues/35

* adding achievements! Almost all untested yet.

* fix a few achievements glitches

* tweak some more achievements, add delay when achievements unlock other achievements

* fix farming achievements, fix spindlestem callbacks on place

* icons for farming achievements

* more achievement icons, fix ancient lantern achievement

* two more icons, update text

* add icons for upper cavern layers

* all achievements have icons now. Also add another sound to the magma sea ambience

* hook into awards' trigger system to get "x/y" progress displayed for the multi-achievement achievements

* ironically, Mineclone has an old version of awards that is incompatible with my trigger work.

* every award should now have a description and a difficulty.

* removing leftovers

* missing callbacks for farmables

* put growth restrictions on farmables so the achievement doesn't trigger inappropriately.

* enable ores in MCL2, fix some undeclared globals, fix icecap growth in MCL (achievements are a good debugging tool)

* get *all* the copper and iron containing nodes

* fix old awards mod's handling of grouped dig items

* Add a little bonus for players who activate a slade breacher - a handheld 'splosion tool

* used the wrong drop method

* beef up explosions in MCL, make slade brick craftable from slade sand and lava

* better creative mode checks, fix crash when digging walls in MCL

* Update subterrane

* various bugfixes and tweaks

* whipping up a simple puzzle chest to give a clue about ancient numbering

The coding is done, but UI and a node mesh need to be done

* prepare for some art tomorrow

* chest node and sounds

* images for the combination dials

* add puzzle chests to the underworld buildings

* update translations

* oops, can't initialize the contents of puzzle chests every time or players get endless stuff

* add backgrounds to item slots in MCL

* wrap the existing function rather than copy and pasting it

* fix bucket dependency in cooking recipes

* add _mcl_saturation to edibles
2022-08-28 23:48:44 -06:00

441 lines
17 KiB
Lua

-- This file defines a type of root-like growth that spreads over the surface of the ground in a random web-like pattern
local S = minetest.get_translator(minetest.get_current_modname())
-- hub_thickness -- the bit in the middle that's seen at the ends and corners of long hypha runs
-- connector_thickness
local get_node_box = function(hub_thickness, connector_thickness)
return {
type = "connected",
fixed = {-hub_thickness,-hub_thickness,-hub_thickness,hub_thickness,hub_thickness,hub_thickness},
connect_top = {-connector_thickness, 0, -connector_thickness, connector_thickness, 0.5, connector_thickness},
connect_bottom = {-connector_thickness, -0.5, -connector_thickness, connector_thickness, 0, connector_thickness},
connect_back = {-connector_thickness, -connector_thickness, 0, connector_thickness, connector_thickness, 0.5},
connect_right = {0, -connector_thickness, -connector_thickness, 0.5, connector_thickness, connector_thickness},
connect_front = {-connector_thickness, -connector_thickness, -0.5, connector_thickness, connector_thickness, 0},
connect_left = {-0.5, -connector_thickness, -connector_thickness, 0, connector_thickness, connector_thickness},
disconnected = {-connector_thickness, -connector_thickness, -connector_thickness, connector_thickness, connector_thickness, connector_thickness},
}
end
minetest.register_node("df_primordial_items:giant_hypha_root", {
description = S("Rooted Giant Hypha"),
_doc_items_longdesc = df_primordial_items.doc.giant_hyphae_desc,
_doc_items_usagehelp = df_primordial_items.doc.giant_hyphae_usage,
tiles = {
{name="dfcaverns_mush_giant_hypha.png"},
},
connects_to = {"group:soil", "group:hypha"},
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
drawtype = "nodebox",
node_box = get_node_box(0.1875, 0.25),
collision_box = get_node_box(0.125, 0.1875),
paramtype = "light",
light_source = 2,
is_ground_content = false,
climbable = true,
groups = {oddly_breakable_by_hand = 1, choppy = 2, hypha = 1, handy=1,axey=1, tree=1, flammable=2, building_block=1, material_wood=1, fire_encouragement=1, fire_flammability=1},
sounds = df_trees.node_sound_tree_soft_fungus_defaults(),
drop = {
max_items = 1,
items = {
{
items = {"df_primordial_items:mycelial_fibers","df_primordial_items:giant_hypha_apical_meristem"},
rarity = 100,
},
{
items = {"df_primordial_items:mycelial_fibers"},
},
},
},
_mcl_blast_resistance = 2,
_mcl_hardness = 2,
})
minetest.register_node("df_primordial_items:giant_hypha", {
description = S("Giant Hypha"),
_doc_items_longdesc = df_primordial_items.doc.giant_hyphae_desc,
_doc_items_usagehelp = df_primordial_items.doc.giant_hyphae_usage,
tiles = {
{name="dfcaverns_mush_giant_hypha.png"},
},
connects_to = {"group:hypha"},
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
drawtype = "nodebox",
node_box = get_node_box(0.1875, 0.25),
collision_box = get_node_box(0.125, 0.1875),
paramtype = "light",
light_source = 2,
is_ground_content = false,
climbable = true,
groups = {oddly_breakable_by_hand = 1, choppy = 2, hypha = 1, handy=1,axey=1, tree=1, flammable=2, building_block=1, material_wood=1, fire_encouragement=1, fire_flammability=1},
sounds = df_trees.node_sound_tree_soft_fungus_defaults(),
drop = {
max_items = 1,
items = {
{
items = {"df_primordial_items:mycelial_fibers","df_primordial_items:giant_hypha_apical_meristem"},
rarity = 100,
},
{
items = {"df_primordial_items:mycelial_fibers"},
},
},
},
_mcl_blast_resistance = 2,
_mcl_hardness = 2,
})
minetest.register_craftitem("df_primordial_items:mycelial_fibers", {
description = S("Giant Mycelial Fibers"),
_doc_items_longdesc = df_primordial_items.doc.mycelial_fibers_desc,
_doc_items_usagehelp = df_primordial_items.doc.mycelial_fibers_usage,
groups = {wool = 1},
inventory_image = "dfcaverns_mush_mycelial_fibers.png",
})
minetest.register_craftitem("df_primordial_items:mycelial_thread", {
description = S("Mycelial thread"),
_doc_items_longdesc = df_primordial_items.doc.mycelial_thread_desc,
_doc_items_usagehelp = df_primordial_items.doc.mycelial_thread_usage,
inventory_image = "dfcaverns_pig_tail_thread.png",
groups = {flammable = 1, thread = 1},
})
minetest.register_craft({
output = "df_primordial_items:mycelial_thread 4",
type = "shapeless",
recipe = { "df_primordial_items:mycelial_fibers"},
})
-- Check each of the six cardinal directions to see if it's buildable-to,
-- if it has an adjacent "soil" node (or if it's going out over the corner of an adjacent soil node),
-- and does *not* have an adjacent hypha already.
-- By growing with these conditions hyphae will hug the ground and will not immediately loop back on themselves
-- (though they can run into other pre-existing growths, forming larger loops - which is fine, large loops are nice)
local ystride = 3
local zstride = 9
local get_item_group = minetest.get_item_group
local get_node = minetest.get_node
local registered_nodes = minetest.registered_nodes
local math_random = math.random
local find_mycelium_growth_targets = function(pos)
local nodes = {}
local pos_x = pos.x
local pos_y = pos.y
local pos_z = pos.z
for x = -1, 1 do
for y = -1, 1 do
for z = -1, 1 do
if not (x == y and y == z) then -- we don't care about the diagonals or the center node
local node = get_node({x=pos_x+x, y=pos_y+y, z=pos_z+z})
local node_name = node.name
if node_name == "ignore" then
-- Pause growth! We're at the edge of the known world.
return nil
end
if get_item_group(node_name, "soil") > 0 or
get_item_group(node_name, "stone") > 0 and math_random() < 0.5 then -- let hyphae explore out over stone
nodes[x + y*ystride + z*zstride] = "soil"
elseif get_item_group(node_name, "hypha") > 0 then
nodes[x + y*ystride + z*zstride] = "hypha"
elseif registered_nodes[node_name] and registered_nodes[node_name].buildable_to then
nodes[x + y*ystride + z*zstride] = "buildable"
end
end
end
end
end
--TODO there's probably some clever way to turn this into a subroutine, but I'm tired right now and
--copy and pasting is easy and nobody's going to decide whether to hire or fire me based on this
--particular snippet of code so what the hell. I'll fix it later when that clever way comes to me.
local valid_targets = {}
if nodes[-1] == "buildable" and
-- test for soil to directly support new growth
(nodes[-1 -ystride] == "soil" or
nodes[-1 +ystride] == "soil" or
nodes[-1 -zstride] == "soil" or
nodes[-1 +zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-ystride] == "soil" or
nodes[ystride] == "soil" or
nodes[-zstride] == "soil" or
nodes[zstride] == "soil")
and not -- no adjacent hypha
(nodes[-1 -ystride] == "hypha" or
nodes[-1 +ystride] == "hypha" or
nodes[-1 -zstride] == "hypha" or
nodes[-1 +zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x-1, y=pos_y, z=pos_z})
end
if nodes[1] == "buildable" and
-- test for soil to directly support new growth
(nodes[1 -ystride] == "soil" or
nodes[1 +ystride] == "soil" or
nodes[1 -zstride] == "soil" or
nodes[1 +zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-ystride] == "soil" or
nodes[ystride] == "soil" or
nodes[-zstride] == "soil" or
nodes[zstride] == "soil")
and not -- no adjacent hypha
(nodes[1 -ystride] == "hypha" or
nodes[1 +ystride] == "hypha" or
nodes[1 -zstride] == "hypha" or
nodes[1 +zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x+1, y=pos_y, z=pos_z})
end
if nodes[-ystride] == "buildable" and
-- test for soil to directly support new growth
(nodes[-1 -ystride] == "soil" or
nodes[1 -ystride] == "soil" or
nodes[-ystride -zstride] == "soil" or
nodes[-ystride +zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-1] == "soil" or
nodes[1] == "soil" or
nodes[-zstride] == "soil" or
nodes[zstride] == "soil")
and not -- no adjacent hypha
(nodes[-1 -ystride] == "hypha" or
nodes[1 -ystride] == "hypha" or
nodes[-ystride -zstride] == "hypha" or
nodes[-ystride +zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x, y=pos_y-1, z=pos_z})
end
if nodes[ystride] == "buildable" and
-- test for soil to directly support new growth
(nodes[-1 +ystride] == "soil" or
nodes[1 +ystride] == "soil" or
nodes[ystride -zstride] == "soil" or
nodes[ystride +zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-1] == "soil" or
nodes[1] == "soil" or
nodes[-zstride] == "soil" or
nodes[zstride] == "soil")
and not -- no adjacent hypha
(nodes[-1] == "hypha" or
nodes[1 + ystride] == "hypha" or
nodes[ystride -zstride] == "hypha" or
nodes[ystride +zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x, y=pos_y+1, z=pos_z})
end
if nodes[-zstride] == "buildable" and
-- test for soil to directly support new growth
(nodes[-1 -zstride] == "soil" or
nodes[1 -zstride] == "soil" or
nodes[-ystride -zstride] == "soil" or
nodes[ystride -zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-1] == "soil" or
nodes[1] == "soil" or
nodes[-ystride] == "soil" or
nodes[ystride] == "soil")
and not -- no adjacent hypha
(nodes[-1 -zstride] == "hypha" or
nodes[1 -zstride] == "hypha" or
nodes[-ystride -zstride] == "hypha" or
nodes[ystride -zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x, y=pos_y, z=pos_z-1})
end
if nodes[zstride] == "buildable" and
-- test for soil to directly support new growth
(nodes[-1 +zstride] == "soil" or
nodes[1 +zstride] == "soil" or
nodes[-ystride +zstride] == "soil" or
nodes[ystride +zstride] == "soil" or
-- test for soil "around the corner" to allow for growth over an edge
nodes[-1] == "soil" or
nodes[1] == "soil" or
nodes[-ystride] == "soil" or
nodes[ystride] == "soil")
and not -- no adjacent hypha
(nodes[-1 +zstride] == "hypha" or
nodes[1 +zstride] == "hypha" or
nodes[-ystride + zstride] == "hypha" or
nodes[ystride +zstride] == "hypha")
then
table.insert(valid_targets, {x=pos_x, y=pos_y, z=pos_z+1})
end
return valid_targets
end
local grow_mycelium = function(pos, meristem_name)
local new_meristems = {}
-- Can we grow? If so, pick a random direction and add a new meristem there
local targets = find_mycelium_growth_targets(pos)
if targets == nil then
return nil -- We hit the edge of the known world, pause!
end
local target_count = #targets
if target_count > 0 then
local target = targets[math.random(1,target_count)]
minetest.set_node(target, {name=meristem_name})
table.insert(new_meristems, target)
else
--nowhere to grow, turn into a rooted hypha and we're done
minetest.set_node(pos, {name="df_primordial_items:giant_hypha_root"})
return new_meristems
end
if math.random() < 0.06 then -- Note: hypha growth pattern is very sensitive to this branching factor. Higher than about 0.06 will blanket the landscape with fungus.
-- Split - try again from here next time
table.insert(new_meristems, pos)
-- Otherwise, just turn into a hypha and we're done
elseif math.random() < 0.333 then
minetest.set_node(pos, {name="df_primordial_items:giant_hypha_root"})
else
minetest.set_node(pos, {name="df_primordial_items:giant_hypha"})
end
return new_meristems
end
local min_growth_delay = tonumber(minetest.settings:get("dfcaverns_mycelium_min_growth_delay")) or 240
local max_growth_delay = tonumber(minetest.settings:get("dfcaverns_mycelium_max_growth_delay")) or 400
local avg_growth_delay = (min_growth_delay + max_growth_delay) / 2
minetest.register_node("df_primordial_items:giant_hypha_apical_meristem", {
description = S("Giant Hypha Apical Meristem"),
tiles = {
{name="dfcaverns_mush_giant_hypha.png^[brighten"},
},
connects_to = {"group:hypha"},
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
drawtype = "nodebox",
light_source = 6,
node_box = get_node_box(0.25, 0.375),
paramtype = "light",
is_ground_content = false,
groups = {oddly_breakable_by_hand = 1, choppy = 2, hypha = 1, light_sensitive_fungus = 13, handy=1,axey=1, tree=1, flammable=2, building_block=1, material_wood=1, fire_encouragement=1, fire_flammability=1},
_dfcaverns_dead_node = "df_primordial_items:giant_hypha_root",
sounds = df_trees.node_sound_tree_soft_fungus_defaults(),
_mcl_blast_resistance = 2,
_mcl_hardness = 2,
on_construct = function(pos)
if df_primordial_items.giant_mycelium_growth_permitted(pos) then
minetest.get_node_timer(pos):start(math.random(min_growth_delay, max_growth_delay))
end
end,
on_destruct = function(pos)
minetest.get_node_timer(pos):stop()
end,
on_timer = function(pos, elapsed)
if df_farming and df_farming.kill_if_sunlit(pos) then
return
end
if elapsed > max_growth_delay then
-- We've been unloaded for a while, need to do multiple growth iterations.
local iterations = math.floor(elapsed / avg_growth_delay) -- the number of iterations we've missed
local stack = {pos} -- initialize with the current location
for i = 1, iterations do
local new_stack = {} -- populate this with new node output.
for _, stackpos in ipairs(stack) do -- for each currently growing location
local ret = grow_mycelium(stackpos, "df_primordial_items:giant_hypha_apical_meristem")
if ret == nil then
-- We hit the edge of the known world, stop and retry later
minetest.get_node_timer(stackpos):start(math.random(min_growth_delay,max_growth_delay))
else
for _, retpos in ipairs(ret) do
-- put the new locations into new_stack
table.insert(new_stack, retpos)
end
end
end
stack = new_stack -- replace the old stack with the new
end
for _, donepos in ipairs(stack) do
-- After all the iterations are done, if there's any leftover growing positions set a timer for each of them
minetest.get_node_timer(donepos):start(math.random(min_growth_delay,max_growth_delay))
end
else
-- just do one iteration.
local new_meristems = grow_mycelium(pos, "df_primordial_items:giant_hypha_apical_meristem")
if new_meristems == nil then
-- We hit the end of the known world, try again later. Unlikely in this case, but theoretically possible I guess.
minetest.get_node_timer(pos):start(math.random(min_growth_delay,max_growth_delay))
else
for _, newpos in ipairs(new_meristems) do
minetest.get_node_timer(newpos):start(math.random(min_growth_delay,max_growth_delay))
end
end
end
end,
})
-- this version grows instantly, it is meant for mapgen usage.
local grow_mycelium_immediately = function(pos)
local stack = {pos}
while #stack > 0 do
local pos = table.remove(stack)
if not (df_farming and df_farming.kill_if_sunlit(pos)) then
local new_poses = grow_mycelium(pos, "df_primordial_items:giant_hypha_apical_mapgen")
if new_poses then
for _, new_pos in ipairs(new_poses) do
table.insert(stack, new_pos)
end
else
-- if we hit the end of the world, just stop. There'll be a mapgen meristem left here, re-trigger it.
minetest.get_node_timer(pos):start(math.random(10,60))
end
end
end
end
minetest.register_node("df_primordial_items:giant_hypha_apical_mapgen", {
description = S("Giant Hypha Apical Meristem"),
tiles = {
{name="dfcaverns_mush_giant_hypha.png^[brighten"},
},
connects_to = {"group:hypha"},
connect_sides = { "top", "bottom", "front", "left", "back", "right" },
drawtype = "nodebox",
_dfcaverns_dead_node = "df_primordial_items:giant_hypha_root",
light_source = 6,
node_box = get_node_box(0.25, 0.375),
paramtype = "light",
is_ground_content = false,
groups = {oddly_breakable_by_hand = 1, choppy = 2, hypha = 1, not_in_creative_inventory = 1, light_sensitive_fungus = 13, handy=1,axey=1, tree=1, flammable=2, building_block=1, material_wood=1, fire_encouragement=1, fire_flammability=1},
_mcl_blast_resistance = 2,
_mcl_hardness = 2,
sounds = df_trees.node_sound_tree_soft_fungus_defaults(),
on_timer = function(pos, elapsed)
grow_mycelium_immediately(pos)
end,
on_construct = function(pos)
minetest.get_node_timer(pos):start(1)
end,
on_destruct = function(pos)
minetest.get_node_timer(pos):stop()
end,
})
-- Just in case mapgen fails to trigger the timer on a mapgen mycelium this LBM will clean up.
minetest.register_lbm({
label = "ensure mapgen mycelium has a timer running",
name = "df_primordial_items:ensure_mapgen_mycelium_timer",
nodenames = {"df_primordial_items:giant_hypha_apical_mapgen"},
run_at_every_load = true,
action = function(pos, node)
local timer = minetest.get_node_timer(pos)
if not timer:is_started() then
timer:start(math.random(1,10))
end
end,
})