--
-- Player effects mod
--

local S = minetest.get_translator("rp_player_effects")
local DISPLAY_ICONS = false

local hud_def_type_field
if minetest.features.hud_def_type_field then
    hud_def_type_field = "type"
else
    hud_def_type_field = "hud_elem_type"
end

player_effects = {}

player_effects.effects = {}
player_effects.registered_effects = {}

local effects_file = minetest.get_worldpath() .. "/player_effects.dat"
local timer_interval = 1 -- update every second
local timer = 10

local function save_effects()
   local f = io.open(effects_file, "w")

   f:write(minetest.serialize(player_effects.effects))

   io.close(f)
end

local function load_effects()
   local f = io.open(effects_file, "r")

   if f then
      player_effects.effects = minetest.deserialize(f:read("*all"), true)

      io.close(f)
   else
      save_effects()
   end
end

local huds = {}

local function display_effect_icons(player)
   if not DISPLAY_ICONS then
      return
   end

   local name = player:get_player_name()
   for _,h in pairs(huds[name]) do
     player:hud_remove(h)
   end
   huds[name] = {}
   local i = 0
   for en, _ in pairs(player_effects.effects[name]) do
     local effect = player_effects.get_registered_effect(en)
     if effect.icon then
        local id = player:hud_add({
            [hud_def_type_field] = "image",
            position = { x = 1, y = 0 },
            offset = { x = -52 - i*52, y = 270 },
            text = effect.icon,
            scale = { x = 3, y = 3 },
            size = { x = 16, y = 16 },
            alignment = { x = 1, y = 1 },
            z_index = 100,
        })
        table.insert(huds[name], id)
        i = i + 1
     end
   end
end

function player_effects.register_effect(ename, def)
   local rd = {
      title = def.title or ename, -- good-looking name of the effect
      --~ Fallback description for a player effect, sloppy translations are OK. @1 is the technical effect identifier.
      description = def.description or S("The @1 effect", ename), -- description of what the effect does
      duration = def.duration or 1, -- how long the effect lasts, <0 is infinite and has to be disabled manually
      physics = def.physics or {}, -- physics overrides for the player
      icon = def.icon, -- effect icon for HUD (optional)
      save = def.save, -- if true, effect will be preserved after server shutdown (default: true)
   }
   if rd.save == nil then
      rd.save = true
   end

   player_effects.registered_effects[ename] = rd
end

function player_effects.get_registered_effect(ename)
   local e = player_effects.registered_effects[ename]

   if not e then
      minetest.log("error", "[rp_player_effects] Cannot find registered player effect " .. ename)

      return nil
   end

   return e
end

function player_effects.apply_effect(player, ename)
   local effect = player_effects.get_registered_effect(ename)

   if effect.duration >= 0 then
      player_effects.effects[player:get_player_name()][ename] = minetest.get_gametime() + effect.duration
   else
      player_effects.effects[player:get_player_name()][ename] = -1
   end

   local phys = {speed = 1, jump = 1, gravity = 1}

   for en, _ in pairs(player_effects.effects[player:get_player_name()]) do
      local effect = player_effects.get_registered_effect(en)

      if effect.physics.speed ~= nil then
	 phys.speed = phys.speed * effect.physics.speed
      end

      if effect.physics.jump ~= nil then
	 phys.jump = phys.jump * effect.physics.jump
      end

      if effect.physics.gravity ~= nil then
	 phys.gravity = phys.gravity * effect.physics.gravity
      end
   end

   player:set_physics_override(phys)
   display_effect_icons(player)

   save_effects()
end

function player_effects.remove_effect(player, ename)
   if player_effects.effects[player:get_player_name()][ename] == nil then return end

   local phys = {speed = 1, jump = 1, gravity = 1}

   for en, _ in pairs(player_effects.effects[player:get_player_name()]) do
      if en ~= ename then
	 local effect = player_effects.get_registered_effect(en)

	 if effect.physics.speed ~= nil then
	    phys.speed = phys.speed * effect.physics.speed
	 end

	 if effect.physics.jump ~= nil then
	    phys.jump = phys.jump * effect.physics.jump
	 end

	 if effect.physics.gravity ~= nil then
	    phys.gravity = phys.gravity * effect.physics.gravity
	 end
      end
   end

   player:set_physics_override(phys)

   player_effects.effects[player:get_player_name()][ename] = nil
   display_effect_icons(player)

   save_effects()
end

function player_effects.refresh_effects(player)
   local phys = {speed = 1, jump = 1, gravity = 1}

   local clear = {}
   local name = player:get_player_name()
   for en, _ in pairs(player_effects.effects[name]) do
      local effect = player_effects.get_registered_effect(en)
      if effect.save == false then
         table.insert(clear, en)
      else
         if effect.physics.speed ~= nil then
            phys.speed = phys.speed * effect.physics.speed
         end

         if effect.physics.jump ~= nil then
            phys.jump = phys.jump * effect.physics.jump
         end

         if effect.physics.gravity ~= nil then
            phys.gravity = phys.gravity * effect.physics.gravity
         end
      end
   end
   for e=1, #clear do
      player_effects.effects[name][clear[e]] = nil
   end

   player:set_physics_override(phys)

   display_effect_icons(player)

   save_effects()
end

function player_effects.clear_effects(player)
   -- call this if you want to clear all effects, it's faster and more efficient
   player:set_physics_override({speed = 1, jump = 1, gravity = 1})

   player_effects.effects[player:get_player_name()] = {}
   display_effect_icons(player)

   save_effects()
end

local function step(dtime)
   timer = timer + dtime

   if timer < timer_interval then
      return
   end

   timer = 0

   local gt = minetest.get_gametime()

   if player_effects.effects == nil then
      return
   end

   for _, player in pairs(minetest.get_connected_players()) do
      local name = player:get_player_name()

      for ename, endtime in pairs(player_effects.effects[name]) do
         if endtime > 0 then
            local timeleft = endtime - gt
            if timeleft <= 0 then
               player_effects.remove_effect(player, ename)
            end
         end
      end
   end
end

local function on_joinplayer(player)
   local name = player:get_player_name()

   load_effects()

   if player_effects.effects[name] == nil then
      player_effects.effects[name] = {}
   end
   huds[name] = {}

   player_effects.refresh_effects(player)

   save_effects()
end

local function on_leaveplayer(player)
   save_effects()
end

local function on_dieplayer(player)
   player_effects.clear_effects(player)
end

minetest.register_globalstep(step)
minetest.register_on_joinplayer(on_joinplayer)
minetest.register_on_leaveplayer(on_leaveplayer)
minetest.register_on_dieplayer(on_dieplayer)

minetest.register_chatcommand(
   "player_effects",
   {
      description = S("Show your current player effects"),
      func = function(name, param)
         local s = S("Current player effects:").."\n"
         local ea = 0

         for ename, endtime in pairs(player_effects.effects[name]) do
            if endtime < 0 then
               --~ @1 is a player effect name. “unlimited” refers to an effect duration
               s = s .. "  " .. S("@1: unlimited", player_effects.registered_effects[ename].title) .. "\n"
            else
               --~ @1 is a player effect name, @2 is the number of seconds the effect is still active
               s = s .. "  " .. S("@1: @2 s remaining", player_effects.registered_effects[ename].title, (endtime - minetest.get_gametime())) .. "\n"
            end

            ea = ea + 1
         end

         if ea > 0 then
            return true, s
         else
            return true, S("You currently have no effects.")
         end
      end
})
