-- This file registers ABMs

-- Maximum height of plants:
-- Sugarcane
local MAX_SUGARCANE_HEIGHT = 3
-- Papyrus
local MAX_PAPYRUS_HEIGHT = 4
-- Cactus on non-ash block
local MAX_CACTUS_HEIGHT = 3
-- Cactus on ash
local MAX_CACTUS_HEIGHT_ASH = 2

--
-- Lava cooling
--

local play_extinguish_sound = function(pos, gain, max_hear_distance)
	local node = minetest.get_node({x=pos.x, y=pos.y+1, z=pos.z})
	local def = minetest.registered_nodes[node.name]
	if node.name == "air" or (def and def.liquidtype == "flowing") then
		minetest.sound_play({name="fire_extinguish_flame", gain = gain}, {pos=pos, max_hear_distance = max_hear_distance}, true)
	end
end

-- When water touches lava (horizontally or vertically), the lava
-- or water node is transformed into a solid node.
minetest.register_abm({
	label = "Cool lava",
	-- We look at lava cooling from the water perspective because
	-- lava is WAY more abundant in the Hades world by default.
	-- This should generally reduce the number of ABM calls.
	nodenames = {"group:water"},
	neighbors = {"group:lava"},
	interval = 1,
	chance = 1,
	action = function(pos, node, active_object_count, active_object_count_wider)
		local sound = false
		local next_src = "hades_core:stone_baked"
		local next_flow = "hades_core:gravel_volcanic"
		local neighbors = {
			-- Water on lava, will transform the lava node
			{ vector.new(0,-1,0), "hades_core:tuff_baked", "hades_core:gravel_volcanic" },
			-- Lava on water, will transform the water node
			{ vector.new(0,1,0), "hades_core:basalt", "hades_core:stone_baked", true },
			-- Water next to lava (horizontally), will transform the lava node
			{ vector.new(0,0,1), next_src, next_flow },
			{ vector.new(1,0,0), next_src, next_flow },
			{ vector.new(0,0,-1), next_src, next_flow },
			{ vector.new(-1,0,0), next_src, next_flow },
		}
		for n=1, #neighbors do
			local npos = vector.add(pos, neighbors[n][1])
			local nnode = minetest.get_node(npos)
			local replace_water = neighbors[n][4] == true
			if replace_water then
				if minetest.get_item_group(nnode.name, "lava") ~= 0 then
					if node.name == "hades_core:water_source" then
						minetest.set_node(pos, {name=neighbors[n][2]})
						sound = true
					elseif node.name == "hades_core:water_flowing" then
						minetest.set_node(pos, {name=neighbors[n][3]})
						sound = true
					end
				end
			elseif nnode.name == "hades_core:lava_source" then
				minetest.set_node(npos, {name=neighbors[n][2]})
				sound = true
			elseif nnode.name == "hades_core:lava_flowing" then
				minetest.set_node(npos, {name=neighbors[n][3]})
				sound = true
			end
		end
		if sound then
			play_extinguish_sound(pos, 0.2, 16)
		end
	end,
})

--
-- Stone transformation
--

minetest.register_abm({
	label = "Burn stone",
	nodenames = {"hades_core:stone", "hades_core:mossystone"},
	neighbors = {"group:lava"},
	interval = 25,
	chance = 15,
	action = function(pos, node)
		local nn
		if node.name == "hades_core:mossystone" then
			nn = "hades_core:stone"
		else
			nn = "hades_core:stone_baked"
		end
		minetest.set_node(pos, {name=nn})
	end,
})

--
-- Tuff transformation
--

minetest.register_abm({
	label = "Burn tuff",
	nodenames = {"hades_core:tuff", "hades_core:mossytuff"},
	neighbors = {"group:lava"},
	interval = 25,
	chance = 15,
	action = function(pos, node)
		local nn = "hades_core:tuff_baked"
		if node.name == "hades_core:mossytuff" then
			minetest.set_node(pos, {name="hades_core:tuff"})
		else
			minetest.set_node(pos, {name="hades_core:tuff_baked"})
		end
	end,
})



--
-- Create marble
-- Requirements: chondrite at Y <= -500 next to water and lava is within reach of 4 nodes
--

minetest.register_abm({
	label = "Create marble",
	nodenames = {"hades_core:chondrite"},
	neighbors = {"group:water"},
	interval = 171,
	chance = 55,
	max_y = -500,
	action = function(pos, node)
		if minetest.find_node_near(pos, 4, {"group:lava"}) == nil then
			return
		else
			minetest.set_node(pos, {name="hades_core:marble"})
		end
	end,
})


--
-- Create obsidian
-- Requirements: Volcanic gravel at Y <= -1000 next to lava and water is within reach of 4 nodes
--

minetest.register_abm({
	label = "Create obsidian",
	nodenames = {"hades_core:gravel_volcanic"},
	neighbors = {"group:lava"},
	interval = 180,
	chance = 65,
	max_y = -1000,
	action = function(pos, node)
		if minetest.find_node_near(pos, 4, {"group:water"}) == nil then
			return
		else
			minetest.set_node(pos, {name="hades_core:obsidian"})
		end
	end,
})

-- Takes a node name and if it's capable of being covered by grass,
-- returns the node name of the next level of "grassiness",
-- otherwise it returns nil
function hades_core.get_next_grass_cover_level(nodename)
	if nodename == "hades_core:dirt" then
		return "hades_core:dirt_with_grass_l1"
	elseif nodename == "hades_core:dirt_with_grass_l1" then
		return "hades_core:dirt_with_grass_l2"
	elseif nodename == "hades_core:dirt_with_grass_l2" then
		return "hades_core:dirt_with_grass_l3"
	elseif nodename == "hades_core:dirt_with_grass_l3" then
		return "hades_core:dirt_with_grass"
	end
end

-- Dirt-with-Grass ABMs
minetest.register_abm({
	label = "Increase Dirt-with-Grass grass level on Dirt-with-Grass under bright light",
	nodenames = {"group:dirt_with_grass"},
	interval = 2,
	chance = 200,
	action = function(pos, node)
		local above = {x=pos.x, y=pos.y+1, z=pos.z}
		local name = minetest.get_node(above).name
		local nodedef = minetest.registered_nodes[name]
		if nodedef and (nodedef.sunlight_propagates or nodedef.paramtype == "light")
				and nodedef.liquidtype == "none"
				and (minetest.get_node_light(above) or 0) >= 13 then
			local nextnode = hades_core.get_next_grass_cover_level(node.name)
			if nextnode then
				minetest.set_node(pos, {name = nextnode, param2 = node.param2})
			end
		end
	end
})

minetest.register_abm({
	label = "Increase Dirt-with-Grass level on Dirt-with-Grass under air in faint light",
	nodenames = {"group:dirt_with_grass"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
		local name = minetest.get_node(pos).name
		local node = minetest.get_node({x=pos.x,y=pos.y+1,z=pos.z})
		if node.name == "air" and (minetest.get_node_light(pos) or 0) >= 8 then
			local nextnode = hades_core.get_next_grass_cover_level(node.name)
			if nextnode then
				minetest.set_node(pos, {name = nextnode, param2 = node.param2})
			end
		end
	end,
})

-- [DEPRECATED, use hades_seasons.get_seasonal_palette_color_param2 instead!]
--
-- Returns the param2 the grass node should currently (for seasons)
-- use, this determines the grass color.
-- * old_param2: Optional parameter for current param2 of grass
--               node, used for intermediate colors
function hades_core.get_seasonal_grass_color_param2(old_param2)
	return hades_seasons.get_seasonal_palette_color_param2(old_param2)
end

minetest.register_abm({
	label = "Turn covered 'dirt with grass' back to dirt, update seasonal grass color",
	nodenames = {"group:dirt_with_grass"},
	interval = 2,
	chance = 20,
	action = function(pos, node)
		local above = {x=pos.x, y=pos.y+1, z=pos.z}
		local name = minetest.get_node(above).name
		local nodedef = minetest.registered_nodes[name]
		if name ~= "ignore" and nodedef
				and not ((nodedef.sunlight_propagates or nodedef.paramtype == "light")
				and nodedef.liquidtype == "none") then
			minetest.set_node(pos, {name = "hades_core:dirt", param2 = node.param2})
		else
			local old_param2 = node.param2
			local new_param2 = hades_seasons.get_seasonal_palette_color_param2(old_param2)
			if new_param2 ~= old_param2 then
				minetest.set_node(pos, {name = node.name, param2 = new_param2})
			end
		end
	end
})

minetest.register_abm({
	label = "Burn cobblestone",
	nodenames = {"hades_core:cobble", "hades_core:mossycobble"},
	neighbors = {"group:lava"},
	interval = 45,
	chance = 15,
	action = function(pos, node)
		local nn
		if node.name == "hades_core:mossycobble" then
			nn = "hades_core:cobble"
		else
			nn = "hades_core:cobble_baked"
		end
		minetest.set_node(pos, {name=nn})
	end,
})

minetest.register_abm({
	label = "Create gravel near water",
	nodenames = {"hades_core:mossycobble"},
	neighbors = {"hades_core:water_flowing"},
	interval = 500,
	chance = 75,
	action = function(pos, node)
		minetest.set_node(pos, {name="hades_core:gravel"})
	end,
})


--
-- Ash transformation
--

minetest.register_abm({
	label = "Create volcanic and fertile sand",
	nodenames = {"group:ash_fertilizer"}, -- used by most leaves
	interval = 60,
	chance = 130,
	action = function(pos, node)
		local d = minetest.get_item_group(node.name, "ash_fertilizer")
		local pos1 = vector.add(pos, {x=d ,y=-1, z=d})
		local pos2 = vector.add(pos, {x=-d, y=-d*2, z=-d})
		-- Turn ash to fertile sand
		local ashes = minetest.find_nodes_in_area(pos1, pos2, {"hades_core:ash", "hades_core:volcanic_sand"})
		if #ashes == 0 then
			return
		end
		local ash = ashes[math.random(1, #ashes)]
		local aname = minetest.get_node({x=ash.x,y=ash.y+1,z=ash.z}).name
		local def = minetest.registered_nodes[aname]
		if def and def.walkable then
			return
		end
		aname = minetest.get_node(ash).name
		if aname == "hades_core:ash" then
			minetest.set_node(ash, {name="hades_core:volcanic_sand"})
		else
			minetest.set_node(ash, {name="hades_core:fertile_sand"})
		end
	end,
})


--
-- Fertile sand transformation
--

minetest.register_abm({
	label = "Create dirt (direct neighboring water)",
	nodenames = {"hades_core:fertile_sand"},
	neighbors = {"group:water"},
	interval = 25,
	chance = 5,
	action = function(pos, node)
		minetest.set_node(pos, {name="hades_core:dirt"})
	end,
})

minetest.register_abm({
	label = "Create dirt (extended water check)",
	nodenames = {"hades_core:fertile_sand"},
	neighbors = {"group:dirt"},
	interval = 50,
	chance = 5,
	action = function(pos, node)
		if minetest.find_node_near(pos, 10, {"group:water"}) == nil then
			return
		else
			minetest.set_node(pos, {name="hades_core:dirt"})
		end
	end,
})

minetest.register_abm({
	label = "Create clay",
	nodenames = {"hades_core:ash","hades_core:volcanic_sand"},
	neighbors = {"group:water"},
	interval = 700,
	chance = 75,
	action = function(pos, node)
              minetest.set_node(pos, {name="hades_core:clay"})				
	end,
})

--
-- Sugarcane growing
--

minetest.register_abm({
	label = "Grow or rot sugarcane",
	nodenames = {"hades_core:sugarcane", "hades_core:seed_sugarcane"},
	interval = 55,
	chance = 35,
	action = function(pos, node)
		local bpos = vector.offset(pos, 0, -1, 0)
		local bname = minetest.get_node(bpos).name
		-- Reserve node when on plant pot
		if bname == "hades_furniture:plant_pot" then
			return
		end
		-- Grow sugarcane on dirt in summer and fall
		if minetest.get_item_group(bname, "dirt") > 0 then
			local season = hades_seasons.get_season()
			if season ~= hades_seasons.SEASON_SUMMER and season ~= hades_seasons.SEASON_FALL then
				return
			end
			if node.name == "hades_core:sugarcane" then
				local height = 0
				while minetest.get_node(pos).name == "hades_core:sugarcane" and height < 3 do
					height = height+1
					pos.y = pos.y+1
				end
				if height < MAX_SUGARCANE_HEIGHT then
					if minetest.get_node(pos).name == "air" then
						minetest.set_node(pos, {name="hades_core:sugarcane"})
					end
				end
			else
				minetest.set_node(pos, {name="hades_core:sugarcane"})
			end
		-- Rot sugarcane if not on dirt or another sugarcane
		elseif bname ~= "hades_core:sugarcane" then
			if node.name == "hades_core:sugarcane" then
				minetest.set_node(pos, {name="hades_core:sugarcane_rotten", param2=node.param2})
			else
				minetest.remove_node(pos)
			end
		end
	end,
})

minetest.register_abm({
	label = "Spawn sugarcane near papyrus",
	nodenames = {"hades_core:dirt_with_grass"},
	neighbors = {"hades_core:papyrus"},
	interval = 500,
	chance = 25,
	action = function(pos, node)
		if minetest.find_node_near(pos, 10, {"hades_core:sugarcane"}) == nil then
			pos.y = pos.y+1
			if minetest.get_node(pos).name == "air" then
				minetest.set_node(pos, {name="hades_core:sugarcane"})
			end
		end				
	end,
})

--
-- Cactus growing
--

-- Cactus fig dies when it's not summer, or if the
-- fig is not on a cactus growing on fertile sand
minetest.register_abm({
	label = "Cactus fig death",
	nodenames = {"hades_core:cactus_fig"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
		local die = false
		if hades_seasons.get_season() ~= hades_seasons.SEASON_SUMMER then
			die = true
		else
			local cpos = table.copy(pos)
			cpos.y = cpos.y - 1
			local cnode = minetest.get_node(cpos).name
			local height = 0
			while cnode == "hades_core:cactus" do
				cpos.y = cpos.y - 1
				height = height + 1
				cnode = minetest.get_node(cpos).name
				if cnode ~= "hades_core:cactus" then
					if cnode == "hades_core:fertile_sand" then
						return
					end
				end
				if height > MAX_CACTUS_HEIGHT then
					break
				end
			end
			die = true
		end
		if die then
			minetest.remove_node(pos)
			-- node decay particles
			minetest.add_particlespawner({
				amount = math.random(10, 20),
				time = 0.1,
				minpos = vector.add(pos, {x=-0.3, y=-0.4, z=-0.3}),
				maxpos = vector.add(pos, {x=0.3, y=2/16, z=0.3}),
				minvel = {x=-0.2, y=-0.2, z=-0.2},
				maxvel = {x=0.2, y=0.1, z=0.2},
				minacc = {x=0, y=-9.81, z=0},
				maxacc = {x=0, y=-9.81, z=0},
				minexptime = 0.1,
				maxexptime = 0.5,
				minsize = 0.5,
				maxsize = 1.0,
				collisiondetection = true,
				vertical = false,
				node = node,
			})
		end
	end
})

minetest.register_abm({
	label = "Grow (or rot) cactus, cactus seed and cactus fig",
	nodenames = {"hades_core:cactus", "hades_core:seed_cactus"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
		pos.y = pos.y-1
		local name = minetest.get_node(pos).name
		local max_height = 0
		-- Grow on sand and ash
		if minetest.get_item_group(name, "sand") ~= 0 then
			max_height = MAX_CACTUS_HEIGHT
		elseif minetest.get_item_group(name, "ash") ~= 0 then
			max_height = MAX_CACTUS_HEIGHT_ASH
		-- Survive (but don't grow) on cactus, plant pot and dirt, rot on any other node
		elseif name ~= "hades_core:cactus" and name ~= "hades_furniture:plant_pot" and minetest.get_item_group(name, "dirt") ~= 0 then
			pos.y = pos.y + 1
			minetest.set_node(pos, {name="hades_core:cactus_rotten"})
			return
		end
		-- Growing
		if max_height > 0 then
			pos.y = pos.y+1
			if node.name == "hades_core:cactus" then
				local height = 0
				while minetest.get_node(pos).name == "hades_core:cactus" and height < max_height+1 do
					height = height+1
					pos.y = pos.y+1
				end
				if height == max_height then
					-- Grow cactus fig on fully-grown cactus growing on fertile sand in summer
					if minetest.get_node(pos).name == "air" and name == "hades_core:fertile_sand" and hades_seasons.get_season() == hades_seasons.SEASON_SUMMER then
						minetest.set_node(pos, {name="hades_core:cactus_fig", param2=1})
					end
				elseif height < max_height then
					if minetest.get_node(pos).name == "air" then
						minetest.set_node(pos, {name="hades_core:cactus"})
					end
				end
			else
				minetest.set_node(pos, {name="hades_core:cactus"})
			end
		end
	end,
})

--
-- Papyrus growing
--

minetest.register_abm({
	label = "Grow or rot papyrus",
	nodenames = {"hades_core:papyrus", "hades_core:seed_papyrus"},
	interval = 50,
	chance = 20,
	action = function(pos, node)
		local bpos = vector.offset(pos, 0, -1, 0)
		local bname = minetest.get_node(bpos).name
		-- Reserve node when on plant pot
		if bname == "hades_furniture:plant_pot" then
			return
		end
		-- Grow on dirt
		if minetest.get_item_group(bname, "dirt") > 0 then
			-- ... if water is nearby
			if minetest.find_node_near(bpos, 3, {"group:water"}) == nil then
				-- if no water nearby, don't grow, but also don't rot
				return
			end
			if node.name == "hades_core:papyrus" then
				local height = 0
				while minetest.get_node(pos).name == "hades_core:papyrus" and height < 4 do
					height = height+1
					pos.y = pos.y+1
				end
				if height < MAX_PAPYRUS_HEIGHT then
					if minetest.get_node(pos).name == "air" then
						minetest.set_node(pos, {name="hades_core:papyrus"})
					end
				end
			else
				minetest.set_node(pos, {name="hades_core:papyrus"})
			end
		-- Rot if not on dirt or another papyrus
		elseif bname ~= "hades_core:papyrus" then
			if node.name == "hades_core:papyrus" then
				minetest.set_node(pos, {name="hades_core:papyrus_rotten", param2=node.param2})
			else
				minetest.remove_node()
			end
		end
	end,
})

