-- debatably useful, aside for teleportation. It'd make more sense to have a similar
-- active skill like "anchor" in S4 League. Also, handling teleportations is a huge
-- pain and there's a minuscule percentage to go over a wall. Not worth it

local S = core.get_translator("block_league")

audio_lib.register_sound("sfx", "bl_kunai_swing", S("Kunai swings"))
audio_lib.register_sound("sfx", "bl_kunai_teleport", S("Player teleports (kunai)"), {pitch = 1.1})
audio_lib.register_sound("sfx", "bl_kunai_throw", S("Kunai thrown"))

local function teleport() end
local function reset_kunai() end
local function check_rays_coll() end
local function find_tp_pos() end

local POINTS_TO_CHECK = 3
local get_node = core.get_node

local DMG1 = 3
local DMG2 = 6
local THROW_SPEED = 30
local THROW_STAMINA = 65

weapons_lib.register_weapon("block_league:kunai", {
  groups = {bl_sword = 1},
  description = S("Kunai"),
  _profile_description = S("What's funnier than ninjas? Teleporting ninjas!"),

  wield_image = "bl_kunai.png",
  wield_scale = {x=0.9, y=0.9, z=0.9},
  inventory_image = "bl_kunai.png",

  weapon_type = "melee",
  crosshair = "bl_kunai_crosshair.png",

  can_use_weapon = function(player, action)
    local is_thrown = player:get_wielded_item():get_meta():get_string("wield_image") == "blank.png"

    if action.type == "punch" then
      return block_league.util_weapon_use_check(player) and not is_thrown
    end

    local has_enough_sp = true
    local p_name = player:get_player_name()

    if arena_lib.is_player_playing(p_name, "block_league") then
      local arena = arena_lib.get_arena_by_player(p_name)
      if arena.players[p_name].stamina < THROW_STAMINA then
        has_enough_sp = false
      end
    end

    return block_league.util_weapon_use_check(player) and not is_thrown and has_enough_sp
    end,

  can_alter_speed = function(player)
    return block_league.util_on_can_alter_speed(player:get_player_name())
  end,

  on_after_hit = function(hitter, weapon, action, objects_hit, total_damage)
    block_league.util_on_after_hit(hitter, weapon, objects_hit, total_damage)
  end,

  on_recovery_end = function(player, weapon, action)
    block_league.util_on_recovery_end(player, weapon, action)
  end,

  action1 = {
    type = "punch",
    _description = S("slash, @1♥", "<style color=#f66c77>" .. DMG1),
    damage = DMG1,
    delay = 0.5,
    sound = "bl_kunai_swing",

    on_hit = function(hitter, target, weapon, action, hit_point, damage, knockback)
      return block_league.util_on_hit(hitter, target, weapon, action, hit_point, damage, knockback)
    end,

    on_end = function(player, weapon, action)
      block_league.util_on_end_melee(player:get_player_name(), weapon.name)
    end,
  },

  action2 = {
    type = "custom",
    _description = S("throws. Teleports if hits a node, @1♥ @2»", "<style color=#f66c77>" .. DMG2, "<style color=#ffe446>" .. THROW_STAMINA),
    damage = DMG2,
    delay = 0.6,
    sound = "bl_kunai_throw",

    on_use = function(player, weapon, action)
      local p_name = player:get_player_name()
      local pos = player:get_pos()
      local pos_head = vector.new(pos.x, pos.y + player:get_properties().eye_height, pos.z)
      local spawn_pos = vector.add(pos_head, vector.multiply(player:get_look_dir(), 0.8)) -- evita che colpisca chi lancia

      local ray = core.raycast(pos_head, vector.add(pos_head, vector.multiply(player:get_look_dir(), 0.9)))
      local will_hit_a_wall = false

      -- se ho un nodo solido subito davanti, teletrasporto istantaneamente onde evitare che trapassi
      for hit in ray do
        if hit.type == "node" and core.registered_nodes[core.get_node(hit.under).name].walkable then
          will_hit_a_wall = true
        end
      end

      local staticdata = core.serialize({action = action, p_name = p_name, obstructed = will_hit_a_wall})

      core.add_entity(spawn_pos, "block_league:kunai", staticdata)

      if arena_lib.is_player_playing(p_name, "block_league") then
        local arena = arena_lib.get_arena_by_player(p_name)
        arena.players[p_name].stamina = arena.players[p_name].stamina - THROW_STAMINA
      end
    end,
  }
})





----------------------------------------------
-------------- EXTRA CROSSHAIR ---------------
----------------------------------------------

local had_kunai = {}     -- KEY: p_name; VALUE = bool
local prev_stamina = {}     -- KEY: p_name; VALUE: (int) prev stamina


core.register_globalstep(function ()
  for _, player in ipairs(arena_lib.get_players_in_minigame("block_league", true)) do
    local p_name = player:get_player_name()

    if arena_lib.is_player_playing(p_name) and block_league.has_weapon_equipped(p_name, "block_league:kunai") then
      local has_kunai = player:get_wielded_item():get_name() == "block_league:kunai"
      local p_prev_stamina = prev_stamina[p_name] or 0
      local stamina = arena_lib.get_arena_by_player(p_name).players[p_name].stamina

      -- weapon check
      if has_kunai and not had_kunai[p_name] then
        local panel = panel_lib.get_panel(p_name, "bl_crosshair_overlay")
        local img = stamina >= THROW_STAMINA and "bl_kunai_crosshair_overlay.png" or "bl_kunai_crosshair_overlay_empty.png"

        panel:show()
        panel:update({bg = img})
        had_kunai[p_name] = true

      elseif had_kunai[p_name] and not has_kunai then
        local panel = panel_lib.get_panel(p_name, "bl_crosshair_overlay")

        panel:hide()
        had_kunai[p_name] = false
      end

      -- stamina check
      if p_prev_stamina >= THROW_STAMINA and stamina < THROW_STAMINA then
        local panel = panel_lib.get_panel(p_name, "bl_crosshair_overlay")
        panel:update({bg = "bl_kunai_crosshair_overlay_empty.png"})

      elseif p_prev_stamina < THROW_STAMINA and stamina >= THROW_STAMINA then
        local panel = panel_lib.get_panel(p_name, "bl_crosshair_overlay")
        panel:update({bg = "bl_kunai_crosshair_overlay.png"})
      end

      prev_stamina[p_name] = stamina
    end
  end
end)





----------------------------------------------
------------------ ENTITÀ --------------------
----------------------------------------------

local kunai_entity = {
  initial_properties = {
    visual = "upright_sprite",
    textures = {"bl_kunai.png", "bl_kunai.png"},
    physical = true,
    collide_with_objects = true,
    collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
    static_save = false,
  },

  _p_name = "",
  _action = {},
  _lifetime = 0.8,
  _current_lifetime = 0.0,
  _last_pos = {}, -- rays check

  on_activate = function(self, staticdata)
    local kunai = self.object
    if not staticdata then kunai:remove() return end

    local staticdata_tbl = core.deserialize(staticdata)

    self._p_name = staticdata_tbl.p_name
    self._action = staticdata_tbl.action
    self._last_pos = kunai:get_pos()

    local thrower = core.get_player_by_name(self._p_name)

    -- se c'è un nodo nel mezzo, annulla
    if staticdata_tbl.obstructed then
      teleport(thrower, self._p_name, kunai)
      kunai:remove()
      return
    end

    kunai:set_armor_groups({immortal = 1})

    local thr_inv = thrower:get_inventory()
    local kunai_item = ""

    -- finta rimozione kunai dall'inventario
    for i = 1, thrower:hud_get_hotbar_itemcount() do
      if thr_inv:get_stack("main", i):get_name() == "block_league:kunai" then
        kunai_item = thr_inv:get_stack("main", i)
        kunai_item:get_meta():set_string("wield_image", "blank.png")
        kunai_item:get_meta():set_string("inventory_image", "blank.png")
        thr_inv:set_stack("main", i, kunai_item)
        break
      end
    end

    local dir = thrower:get_look_dir()

    -- vel
    kunai:set_velocity({
      x=(dir.x * THROW_SPEED),
      y=(dir.y * THROW_SPEED),
      z=(dir.z * THROW_SPEED),
    })

    -- rot
    local yaw = thrower:get_look_horizontal()
    local oblique_rot = -0.75 -- negative values rotate it clock-wise
    local side_view_rotation = 1.1 -- means the kunai faces the player for 90%
    local pl_right_dir = vector.normalize(vector.new(math.cos(yaw), oblique_rot, math.sin(yaw)))
    local final_dir = thrower:get_look_dir()/side_view_rotation + pl_right_dir

    kunai:set_rotation(vector.dir_to_rotation(final_dir))
  end,


  on_step = function(self, dtime, moveresult)
    local p_name = self._p_name
    local thrower = core.get_player_by_name(p_name)
    local kunai = self.object

    self._current_lifetime = self._current_lifetime + dtime

    if self._current_lifetime >= self._lifetime then
      if thrower then
        reset_kunai(thrower)
      end
      kunai:remove()
      return
    end

    if check_rays_coll(self._last_pos, kunai:get_pos()) then
      if thrower then
        reset_kunai(thrower)
      end
      kunai:remove()
      return
    end

    self._last_pos = kunai:get_pos()

    if not moveresult.collides or not thrower then return end

    for _, coll in pairs(moveresult.collisions) do
      if coll.type == "object" then
        local target = coll.object
        if target:is_player() then
          if arena_lib.is_player_playing(target:get_player_name(), "block_league") then
            local arena = arena_lib.get_arena_by_player(p_name)
            local team_thrower = arena.players[self._p_name].teamID
            local team_target = arena.players[target:get_player_name()].teamID

            if team_thrower ~= team_target then
              weapons_lib.apply_damage(thrower, {object = target}, core.registered_nodes["block_league:kunai"], self._action)
            end
          end
        end

      elseif coll.type == "node" then
        teleport(thrower, p_name, kunai)
      end

      reset_kunai(thrower)
      kunai:remove()
      return
    end
  end,
}


core.register_entity("block_league:kunai", kunai_entity)




----------------------------------------------
---------------FUNZIONI LOCALI----------------
----------------------------------------------

function teleport(thrower, p_name, kunai)
  if not arena_lib.is_player_playing(p_name, "block_league") or thrower:get_hp() > 0 then
    local p_pos = thrower:get_pos()
    local head_pos = vector.new(p_pos.x, p_pos.y + thrower:get_properties().eye_height, p_pos.z)

    thrower:set_pos(find_tp_pos(kunai:get_pos(), head_pos))
    thrower:add_velocity(vector.new(0,7,0))
    audio_lib.play_sound("bl_kunai_teleport", {to_player = p_name})
  end
end



function reset_kunai(thrower)
  local thr_inv = thrower:get_inventory()

  for i = 1, thrower:hud_get_hotbar_itemcount() do
    local item = thr_inv:get_stack("main", i)
    if item:get_name() == "block_league:kunai" and item:get_meta():get_string("wield_image") == "blank.png" then
      thr_inv:set_stack("main", i, "block_league:kunai")
      break
    end
  end
end



-- derived from Wuzzy's get_intermediate_positions function on Glitch, GPL 3.0
-- https://content.luanti.org/packages/Wuzzy/glitch/
function check_rays_coll(pos1, pos2)
  local posses = { pos1 }
  local sx, sy, sz, ex, ey, ez

  sx = math.min(pos1.x, pos2.x)
  sy = math.min(pos1.y, pos2.y)
  sz = math.min(pos1.z, pos2.z)
  ex = math.max(pos1.x, pos2.x)
  ey = math.max(pos1.y, pos2.y)
  ez = math.max(pos1.z, pos2.z)

  local xup, yup, zup

  xup = pos1.x < pos2.x
  yup = pos1.y < pos2.y
  zup = pos1.z < pos2.z

  local x,y,z
  local steps = POINTS_TO_CHECK - 1

  for s=1, steps-1 do
    if xup then
      x = sx + (ex - sx) * (s/steps)
    else
      x = sx + (ex - sx) * ((steps-s)/steps)
    end
    if yup then
      y = sy + (ey - sy) * (s/steps)
    else
      y = sy + (ey - sy) * ((steps-s)/steps)
    end
    if zup then
      z = sz + (ez - sz) * (s/steps)
    else
      z = sz + (ez - sz) * ((steps-s)/steps)
    end
    table.insert(posses, vector.new(x,y,z))
  end

  table.insert(posses, pos2)

  for i = 1, #posses do
    local node = get_node(posses[i]).name

    if node == "block_league:rays_blue" or node == "block_league:rays_orange" then
      return true
    end
  end
end



-- function from Enderpearl mod by Giov4, AGPL
function find_tp_pos(pos, head_pos)
  local dir = vector.direction(head_pos, pos)
  local padding = vector.multiply(dir, 0.85)
  local tries = {
    -padding,
    -dir,
    -dir*2,
    -dir*3,
    -dir*4,
    -dir*10  -- prevents getting stuck in a block teleporting you, at most, 10 nodes away from it
  }

  for _, d in ipairs(tries) do
    local ppos = vector.add(pos, d)
    local head_ppos = vector.add(ppos, vector.new(0,1.5,0))
    local node = core.get_node_or_nil(ppos)
    local head_node = core.get_node_or_nil(head_ppos)

    if node and head_node then
      local def = core.registered_nodes[node.name]
      local head_def = core.registered_nodes[head_node.name]

      if (def and not def.walkable) and (head_def and not head_def.walkable) then
        return ppos
      end
    end
  end
  return pos
end
