-- This is the Minefall garbage core repository.
-- Author: Astrobe
-- Licence (code): same as Minetest.
-- Licence (sound): teleport sound from Nether mod, CC0 1.0

local Version="Fear Of The Dark"

mfplayers = {}
local MAX_ENERGY=5 
local talents=minetest.get_mod_storage()

local random=math.random
mf={}

function mf.is_keymaster(name) return mfplayers[name].talent=="keymaster" end
function mf.is_flyer(name) return mfplayers[name].talent=="flyer" end
function mf.is_diver(name) return mfplayers[name].talent=="diver" end

function mf.clan_ok(name, pos)
	local meta=minetest.get_meta(pos)
	if not meta then return true end
	local owner=meta:get_string("owner") or ""
	if owner=="" then return true end
	if (string.len(owner)+string.len(name))%2==0 then return true end
	minetest.chat_send_player(name, "Sorry, this was placed by the other clan")
end

function mf.threatWarn(player)
	local playerName=player:get_player_name()
	if playerName and mfplayers[playerName] then mfplayers[playerName].warnTimer=2 end
end



hb.register_hudbar("energy", 0xFFFFFF, "Mana",
{ bar = "hudbars_bar_breath.png", icon = nil },
0, 0,
false, "%s: %.1f/%.1f")

minetest.register_on_item_eat(function(hp_change, replace, itemstack, user)
	if hp_change + user:get_hp() <= 20 then return end
	local name=user:get_player_name()
	if not name then return end
	local e=(hp_change+user:get_hp() - 20)/2
	mfplayers[name].energy=math.min(mfplayers[name].max_energy, mfplayers[name].energy+e)
	hb.change_hudbar(user, "energy", mfplayers[name].energy)
	return nil
	--[[
	-- Refill mana bar first, and then the HP bar.
	-- Mana has non-integer values; rounding is not in favor of the player.
	if hp_change<=0 then return end
	local name=user:get_player_name()
	if not name then return end
	local mfp=mfplayers[name]
	if not mfp then return end
	local missing_mana=math.min(math.ceil(mfp.max_energy-mfp.energy))
	local to_mana=math.min(missing_mana, hp_change)
	mfp.energy=math.min(mfp.energy+to_mana, mfp.max_energy)
	hb.change_hudbar(user, "energy", mfp.energy)
	local to_hp=hp_change-to_mana
	itemstack:take_item()
	if to_hp <= 0 then return itemstack end
	user:set_hp(user:get_hp()+to_hp)
	return itemstack
	]]
end)
	


local max=math.max
local band=bit.band

local alertCrosshair=
{
	hud_elem_type="image",
	position= {x=0.5, y=0.5},
	text="crosshair.png^[colorize:red",
	z_index=1,
	scale={x=1,y=1},
}

minetest.register_globalstep(function(dtime)
	--Loop through all connected players
	--[[
	local lag=dtime>=1
	if lag then minetest.log("warning", "Lag detected") end
	]]

	for playerName,playerInfo in pairs(mfplayers) do
		local player = minetest.get_player_by_name(playerName)
		local change=false
		local maxe=playerInfo.max_energy
		local cached_e=playerInfo.cached_e or 0
		-- if lag happens while a player is levitating, they might fall hard or go higher than expected.
		-- We give a free damage cancellation so that they don't take unfair damage.
		-- if lag then mfplayers[playerName].shielded=true end
		if player ~= nil then
				local physics=player:get_physics_override()
				local jump=band(player:get_player_control_bits(),16)~=0
				if playerInfo.energy>dtime and jump then
					physics.gravity= (mf.perk(playerName)=="lift" and -0.2) or -0.1
					local drain= (mf.is_flyer(playerName) and 1) or 4 
					playerInfo.energy=max(playerInfo.energy-dtime*drain, 0)
					change=true
				elseif playerInfo.energy < maxe then
					physics.gravity=1
					playerInfo.energy=math.min(playerInfo.energy+dtime*playerInfo.regen, maxe)
					change=true
				else
					change=physics.gravity~=1
					physics.gravity=1
				end
				--[[
				if playerInfo.invisible then
					playerInfo.energy=max(playerInfo.energy-3*dtime, 0)
					if playerInfo.energy==0 then
						playerInfo.invisible=false
						player:set_properties {visual_size={x=1, y=1}}
						player:hud_set_flags{ crosshair=true }
					end
					change=true
				end
				]]

				if change==true then
					player:set_physics_override(physics)
					if math.abs(cached_e-playerInfo.energy)>=0.1 or (playerInfo.energy==maxe and cached_e ~= maxe) then
						hb.change_hudbar(player, "energy", playerInfo.energy, max(maxe,5))
						playerInfo.cached_e=playerInfo.energy
					end
				end
				local mfp=mfplayers[playerName]
				if mfp.warnTimer>0 then mfp.warnTimer=mfp.warnTimer-dtime end
				local hudId=mfp.hudWarn
				if mfp.warnTimer>0 and not hudId then
					mfp.hudWarn=player:hud_add(alertCrosshair)
				elseif mfp.warnTimer<=0 and hudId then
					player:hud_remove(hudId)
					mfp.hudWarn=nil
				end
		end
	end
end)


-- Trash can by Prestidigitator
--[[
    local trashInv = minetest.create_detached_inventory(
                        "trash",
                        {
                           on_put = function(inv, toList, toIndex, stack, player)
                              inv:set_stack(toList, toIndex, ItemStack(nil))
                           end
                        })
    trashInv:set_size("main", 1)

    minetest.register_on_joinplayer(
       function(player)
          player:set_inventory_formspec(
             "size[10,7.5]"..
             "list[current_player;main;0,3.5;8,4;]"..
             "list[current_player;craft;3,0;3,3;]"..
             "list[current_player;craftpreview;7,1;1,1;]"..
             "label[9,3.5;Trash]"..
             "list[detached:trash;main;9,4.5;1,1;]"
             )
    end)
--]]
-- watercraft
--[[
minetest.register_craft({
	output = 'default:snowblock 4',
	type="shapeless",
	recipe = { "default:ice" }
})
]]


-- News code derived from "szutil_motd".
-- Calculate form dimensions (configurable) and spec strings.
local fspref, fssuff
do
	local fsw = 15
	local fsh = 12
	local tbw = fsw - 0.5
	local tbh = fsh - 0.5
	fspref = "size[" .. fsw .. "," .. fsh .. ",true]" .. "textarea[0.25,0.25;" .. tbw .. "," .. tbh .. ";;;"
	fssuff = "]button_exit[0," .. tbh .. ";" .. fsw .. ",1;ok;Continue]"
end

local function sendmotd(name)
	local news=""
	news=news..
	"= Minefall: ".. Version.." =\n\n" ..

	"\nANNOUNCEMENTS:\n\n"..
	"New: glowing giant mushroom.\n"..
	"New: toilet (furniture).\n"..
	"New: confusion wand (rare drop of lootboxes).\n"..
	"New: abyss bookshelf (found in newly generated abyss ruins).\n"..
	"New: gold lamp.\n"..
	"Changed: farming (see help).\n"..
	"Changed: mushrooms turn to dirt the log they grow on.\n"..
	"Changed: traders no longer accept red mushrooms.\n"..
	"Changed: Void Chest is a bit cheaper, no longer level-restricted.\n"..
	"Changed: (again) Abyss Dungeon Bricks mimick wall blocks.\n"..
	"Changed: Compass can detect lava.\n"..
	"Changed: beacons no longer are \"furniture\"\n"..
	"Changed: at beginner level, the focus wand summons a trader (replaces feeding green mese to beacon).\n"..
	"Changed: cobwebs have a small chance to drop strings.\n"..
	"Changed: 2 bronze ingots replaces coal as the common lootbox drop.\n"..
	"Changed: ice wand's ice blocks last longer; direct mob hit stuns it for several seconds.\n"..
	"Reminder: old metal mimicks will be removed in next version.\n"..
	"Reminder: coal blocks will be removed in next release\n"..
	"\nYou can recall this screen with the /news command.\n"
	minetest.show_formspec(
	name,
	"mf",
	fspref..minetest.formspec_escape(news)..fssuff
	)
end
minetest.register_chatcommand("news", { func = function(name) sendmotd(name) end})


minetest.register_on_newplayer(function(player)
	local name=player:get_player_name()
	sendmotd(name)
	if minetest.check_player_privs(name, "privs") then
		player:get_inventory():add_item("main", "craftguide:book")
		rp.award(name, 30)
	end
	minetest.chat_send_all(minetest.colorize("orange", "Welcome to "..name.." !"))
	end)

minetest.register_on_joinplayer(function(player)
	local playerName = player:get_player_name()

	mfplayers[playerName] = {max_energy=MAX_ENERGY, energy=0, drain=2, regen=1, shielded=true, warnTimer=0}
	mfplayers[playerName].talent=talents:get_string(playerName)
	hb.init_hudbar(player, "energy")
	-- we do want players to use compass and waypoints instead of unusable 3D coordinates.
	player:hud_set_flags {basic_debug=false}
	player:set_moon {scale=4}
	player:set_sky {body_orbit_tilt=15, fog={fog_distance=240}}

	-- hide nametag
	local c=player:get_nametag_attributes().color
	c.a=0
	player:set_nametag_attributes {color=c}
	-- show name when pointing and allow zoom
	player:set_properties { infotext=playerName }
	--player:set_lighting { shadows={intensity=0.1 } }
	player:set_lighting { shadows={intensity=0 } }
	local phy=player:get_physics_override()
	player:set_physics_override(phy)

	if not (minetest.get_player_privs(playerName).interact == true)  then
		sendmotd(playerName)
	end
	-- minetest.chat_send_player(playerName, "Perk=" .. mf.perk(playerName))
end)

minetest.register_on_leaveplayer(function(player)
	local playerName = player:get_player_name()
	mfplayers[playerName] = nil

end)


minetest.register_abm {
	label="Unified fire/lava cooling",
		nodenames={"fire:basic_flame", "default:lava_source"},
		neighbors={"group:cools_lava"},
		interval=5,
		chance=1,
		catch_up=false,
		action=function(pos, node, active_object_count, active_object_count_wider)
			local cooler=minetest.find_node_near(pos, 1, {"group:cools_lava"})
			if not cooler then return end
			minetest.remove_node(cooler)
			if node.name=="fire:basic_flame" then
				minetest.remove_node(pos)
			else
				local blocks={"default:obsidian", "default:basalt"}
				minetest.set_node(pos, {name=blocks[random(1, #blocks)]})
			end

			minetest.sound_play("default_cool_lava", {pos=pos, max_hear_distance=16, gain=0.20})
			end
}

-- Water sports
minetest.register_abm {
	label="Convert flowing sea water into sea water sources",
	nodenames={"default:water_flowing"},
	interval=7,
	chance=1,
	catch_up=false,
	max_y=10, -- sea level can raise.
	action=function(pos, node, active_object_count, active_object_count_wider)
		local u={x=pos.x, y=pos.y-1, z=pos.z}
		local n=minetest.get_node(u).name
		local d=minetest.registered_nodes[n]
		if d and (d.walkable~=false or n=="default:water_source") then minetest.set_node(pos, {name="default:water_source"}) end
	end }

minetest.register_abm {
	label="Springs",
	nodenames={"default:river_water_flowing", "default:river_water_source"},
	interval=241,
	chance=4,
	catch_up=false,
	max_y=70, -- that's about the limit for the swamp biome
	min_y=0,
	action=function(pos, node, active_object_count, active_object_count_wider)
		local u={x=pos.x, y=pos.y-1, z=pos.z}
		local n=minetest.get_node(u).name
		if n=="default:dirt" or n=="default:limestone" then
			minetest.swap_node(u, {name="default:gravel"})
			if n=="default:limestone" then
				minetest.swap_node(pos, {name="default:river_water_source"})
			end
		end
	end }

local function node_explode(pos)
	minetest.remove_node(pos)
	minetest.after(math.random()/2, function(pos) mobs:explosion(pos, 3) end, pos)
end


--[[
minetest.register_craft{
	output="default:diamond",
	type="shapeless",
	recipe= {"default:coalblock","default:mese_crystal","default:coalblock"}
}
]]


--[[
minetest.register_craft {
	type="cooking",
	recipe="group:tree",
	output="default:coal_lump",
	cooktime=90,
}
]]

-- Floods destroy foliage, in order to get consistent landscapes in particular in
-- cold swamps.

for node, _ in pairs(minetest.registered_nodes) do
	if minetest.get_item_group(node, "leafdecay_drop")>0 then
		minetest.override_item(node, { floodable=true })
	end
end

sfinv.register_page("credits", {
	title="Credits",
	get= function(self, player, context)
		local credits="textarea[0.1,0;8,10;;;"..
			minetest.formspec_escape(
			"Minefall by Astrobe, 2024\n"..
			"Version codename: \""..Version.."\"\n"..
			"Source code is available via ContentDB: https://content.minetest.net/packages/Astrobe/minefall\n"..
			"\nSPECIAL THANKS\n"..
			"D00med for his wonderful and lightweight mobs\n"..
			"TenPlus1 for his many mods\n".. 
			"The Farlands team for their inspirational Minetest game\n"..
			"Red5 Studio for Firefall, which this game tries to replace.\n"..
			"\nTHANKS\n"..
			"The Minetest and MTG team\n"..
			"The minetest modding community for their many mods\n"
			).."]"
		return sfinv.make_formspec(player, context, credits, false, nil)
		end
	})

minetest.register_chatcommand("talent", 
{
	description="Select a talent",
	params="<talent name>",
	func = function(name, param)
		local player=minetest.get_player_by_name(name)
		if not player then return end
		local talent=mfplayers[name].talent
		if talent == "" then talent= nil end
		local bmode=bones.get_mode_for(player)
		if param=="" then
			minetest.chat_send_player(name, "Choosing (or changing) your talent costs 40 RP. You will also be promoted to Advanced player. The available talents are:")
			minetest.chat_send_player(name, "- traveler: beacon teleportation requires a yellow (instead of grey) mese crystal fragment.")
			minetest.chat_send_player(name, "- fighter: mana absorbs 1 point of damage.")
			minetest.chat_send_player(name, "- flyer: levitation mana cost divided by 4.")
			if talent then
				minetest.chat_send_player(name,"Your current talent is: "..
				({keymaster="traveler", diver="fighter", flyer="flyer"})[talent])
			end
			return true
		end
		if bmode=="keep" then
			minetest.chat_send_player(name, "You need to level up to Regular player first")
			return true
		end 
		-- Due to various name changes, we have to transpose. Yes, it's a mess.
		local realTalent=({traveler="keymaster", fighter="diver", flyer="flyer" })[param]
		if not realTalent then
			minetest.chat_send_player(name, "This is not the name of a talent")
			return true
		end
		if realTalent==mfplayers[name].talent then
			minetest.chat_send_player(name, "This is already your talent.")
			return true
		end
		if rp.get_player_count(name) <= 40 then
			minetest.chat_send_player(name, "You don't have enough RPs")
			return true
		end
		talents:set_string(name, realTalent)
		if bmode ~= "drop" then
			minetest.chat_send_all(minetest.colorize("green", name.." became a Advanced player!"))
			bones.set_mode_for(player, "drop")	
			minetest.sound_play("ding", {gain=1}, true)
		end
		mfplayers[name].talent=realTalent
		minetest.chat_send_player(name, "You have now the '"..param.."' talent")
		rp.award(name, -40) 
		return true	
	end
})

--[[
minetest.register_chatcommand("h",
{
	description="test",
	params="",
	func=function(name, param)
		local player=minetest.get_player_by_name(name)
		if not player then return end
		local p=vector.round(player:get_pos())
		minetest.chat_send_player(name, "pos = "..minetest.serialize(p))
		local h=minetest.hash_node_position(p)
		minetest.chat_send_player(name, "hash/dehash = ".. minetest.serialize(minetest.get_position_from_hash(h)))
		return true
	end
})
--]]

-- This randomly rotates dirt-like nodes, so that if players take a path frequently,
-- it will show in a more-or-less subtle way.
local disturbable={
	["default:dirt_with_grass"]=true,
	["default:dirt_with_dry_grass"]=true,
	["default:dirt_with_coniferous_litter"]=true,
	["default:dirt_with_snow"]=true,
	["default:gravel"]=true,
	--["default:dirt"]=true,
	["default:dry_dirt"]=true,
	["default:dry_dirt_with_stones"]=true,
	["default:snowblock"]=true,
	["default:sand"]=true,
	["default:desert_sand"]=true,
}


local acc=0
minetest.register_globalstep(function(dtime)
	-- TODO: merg with the other global step callback
	acc=acc+dtime
	if acc >= random(3,10) then
		acc=0
		for _, player in ipairs(minetest.get_connected_players()) do
			local pos=player:get_pos()
			pos.y=pos.y-0.5
			local node=minetest.get_node(pos)
			-- I don't think we have to check for protection for that. This
			-- won't even make a falling node fall.
			if node and disturbable[node.name] and (node.param2 or 0) == 0 then
				node.param2=random(1,23)
				minetest.set_node(pos, node)
				minetest.check_for_falling(pos)
			end
		end
	end
end)


-- ALCHEMY
-- Not sure about that. Maybe it is a too good mese sink,
-- maybe it removes the incentive to to more dangerous places 
-- to mine better metals.
--[[
minetest.register_craft {
	output = 'default:tin_lump',
	type="shapeless",
	recipe = {"default:mese_crystal", "default:copper_ingot" }
} 
minetest.register_craft {
	output = 'default:iron_lump',
	type="shapeless",
	recipe = {"default:mese_crystal", "default:tin_ingot" }
} 
minetest.register_craft {
	output = 'default:gold_lump',
	type="shapeless",
	recipe = {"default:mese_crystal", "default:steel_ingot" }
} 
]]

minetest.override_item("default:stone_with_coal", { on_blast=node_explode, on_burn=node_explode })

minetest.register_on_prejoinplayer(function(name, ip)
	if string.find(name, "[^%a-]") then
		minetest.log("Rejecting name: ["..name.."]")
		return "names can only contain letters."
		 end
	end)

-- All I wanted is to be able to check if the user entered a seed,
-- so that if they didn't I could use a nice default seed for an easier start.
-- fixed_map_seed doesn't help; the design of this part of the API feels FUBAR.
-- Eventually I had to use a footer image for the menu to tell the user to use that seed.
--[[
local userSeed=minetest.get_mapgen_params("seed").seed
if userSeed==0 then userSeed="" else userSeed="12341234" end

local userSeed=minetest.settings:get("fixed_map_seed")

minetest.set_mapgen_params
{
	mgname="v7",
	flags="dungeons,caves",
}

]]


for k,v in pairs{
	mgv7_spflags="mountains,noridges,nofloatlands,nocaverns",
} do minetest.set_mapgen_setting(k, v, true) end

function mf.perk(name)
	-- singlerplayer is fisher. Chosen so that the player won't be
	-- too surprised if they go online (=>different name). Also, this gift allows
	-- the player to spend more cotton on cloth and get NPC traders earlier, which
	-- helps with shortening the early game.
	local perks={"fisher", "ranger", "lift"}
	return perks[string.byte(name, -1)%3+1]
end

minetest.register_abm {
	label="stone erosion",
	nodenames={"group:stone"},
	interval=257,
	chance=100000,
	catch_up=false,
	action=function(pos, node)
		if minetest.is_protected(pos) then return end
		pos.y=pos.y-1
		if minetest.get_node(pos).name=="air" then
			pos.y=pos.y+1
			minetest.spawn_falling_node(pos)
			pos.y=pos.y+1
			minetest.check_for_falling(pos)
		end
	end
}

minetest.register_abm {
	label="coral spread",
	nodenames={"default:sand"},
	neighbors="default:water_source",
	interval= 257,
	chance=100,
	catch_up=false,
	max_y=-2,
	action=function(pos, node)
		if minetest.find_node_near(pos, 1, "default:coral") then
			if random(2)==1 then
				minetest.set_node(pos, {name="default:coral"})	
			else
				minetest.set_node(pos, {name="default:coral2"})
			end
		end
	end
}

