-- v1.3.0

local tnt = {}

local redstone = require("redstone")
local npcManager = require("npcManager")

local floor, clamp = math.floor, math.clamp

tnt.name = "tnt"
tnt.id = NPC_ID
tnt.order = 0.66

tnt.onRedPower = function(n, c, power, dir, hitbox)
  redstone.setEnergy(n, power)
end

tnt.onDispense = function(n)
  n.data.isFused = true
  n.data.timer = tnt.config.explosiontimer
end

tnt.config = npcManager.setNpcSettings({
	id = tnt.id,

  width = 32,
  height = 32,

	gfxwidth = 32,
	gfxheight = 32,
	gfxoffsetx = 0,
	gfxoffsety = 0,

	frames = 2,
	framespeed = 8,
	framestyle = 0,
  invisible = false,

  nogravity = false,
	jumphurt = true,
  notcointransformable = true,
	nohurt = true,
	noyoshi = true,
  blocknpc = true,
  playerblock = true,
  playerblocktop = true,
  npcblock = true,
  disabledespawn = false,

  effectid = 800, -- The explosion effect ID
  explosionradius = 128,    -- The radius of the explosion
  explosiontimer = 130,  -- The time it takes for the bomb to explode
  destroyblock = false    -- If set to true, the explosion will destory blocks (when false, certain blocks like brick blocks still explode)
})



-- What should an NPC do when hit by an explosion
local function tntNPCFilter(v)
  return not v.isGenerator and not v.isHidden and not v.friendly and v:mem(0x124, FIELD_BOOL) and v.id ~= 13 and v.id ~= 291 and not NPC.config[v.id].isinteractable
end

local function onExplosionNPC(n, tnt)
  if tntNPCFilter(n) then
    n:harm(HARM_TYPE_NPC)
  end

  if redstone.is.sickblock(n.id) then
    redstone.setEnergy(n, 15)
  elseif not NPC.config[n.id].nogravity then
    local t = vector.v2(n.x + 0.5*n.width - tnt.data.explosionhitbox.x, n.y + 0.5*n.height - tnt.data.explosionhitbox.y)
    t = 6*t:normalise()
    n.speedX, n.speedY = clamp(n.speedX + t.x, -8, 8), clamp(1.1*(n.speedY + t.y), -8, 8)
  end
end

-- What should a block do when hit by an explosion
local function onExplosionBlock(b, tnt)
  if (Block.SOLID_MAP[b.id] or Block.SEMISOLID_MAP[b.id]) and not Block.SIZEABLE_MAP[b.id] then
    if tnt.config.destroyblock then
      b:remove(true)
    else
      if Block.config[b.id].smashable ~= 3 then
        b:hit()
      else
        b:remove(true)
      end
    end
  end
end

-- What should a player do when hit by an explosion
local function onExplosionPlayer(p, tnt)
  if not p:mem(0x4A, FIELD_BOOL) then -- In statue form
    p:harm()
    local t = vector.v2(p.x + 0.5*p.width - tnt.data.explosionhitbox.x, p.y + 0.5*p.height - tnt.data.explosionhitbox.y)
    t = 8*t:normalise()
    if p:isGroundTouching() and t.y < 0 then
      p.y = p.y - 4
      p:mem(0x146, FIELD_WORD, 0)
    end
    p.speedX, p.speedY = clamp(p.speedX + t.x, -12, 12), clamp(1.1*(p.speedY + t.y), -15, 15)
  end
end



local TYPE_TNT = 0
local TYPE_MINECART = 1

local sfxcharge = Audio.SfxOpen(Misc.resolveFile("tnt-charge.ogg"))
local sfxexplode  = Audio.SfxOpen(Misc.resolveFile("tnt-explosion.ogg"))

function tnt.prime(n)
  local data = n.data

  data.animFrame = data.animFrame or 0
  data.animTimer = data.animTimer or 0

  data.frameX = data._settings.type or 0
  data.frameY = data.frameY or 0

  data.timer = data.timer or 0
  data.isFused = data.isFused or false
  data.beenBlowned = data.beenBlowned or false
  data.blownedTimer = data.blownedTimer or 0

  data.explosionhitbox = Colliders.Circle(0, 0, tnt.config.explosionradius)
  data.redarea = redstone.basicRedArea(n)
end

local function explode(n)
  local data = n.data

  SFX.play(sfxexplode)
  data.explosionhitbox.x, data.explosionhitbox.y = n.x + 0.5*n.width, n.y + 0.5*n.height
  local e = Effect.spawn(tnt.config.effectid, data.explosionhitbox.x, data.explosionhitbox.y)
  e.x, e.y = e.x - 0.5*e.width, e.y - 0.5*e.height

  for _, v in ipairs(Colliders.getColliding{a = data.explosionhitbox, b = NPC.ALL, btype = Colliders.NPC, filter = function(v) return v ~= n and not v.isHiddend end}) do
    if v.id == tnt.id then
      local vdata = v.data
      if vdata.isFused then
        vdata.timer = 0
      else
        vdata.beenBlowned = true
        vdata.blownedTimer = 5
        if vdata.frameX == TYPE_TNT then
          vdata.timer = floor(tnt.config.explosiontimer*0.5)
        elseif vdata.frameX == TYPE_MINECART then
          vdata.timer = 5
        end
      end
    end
    onExplosionNPC(v, n)
  end
  for _, b in ipairs(Colliders.getColliding{a = data.explosionhitbox, b = Block.ALL, btype = Colliders.BLOCK, filter = function(v) return not v.isHidden end}) do
    onExplosionBlock(b, n)
  end
  for _, p in ipairs(Player.get()) do
    if Colliders.collide(data.explosionhitbox, p) then
      onExplosionPlayer(p, n)
    end
  end

  n:kill()
end

local function explodeOnContact(n)
  local data = n.data
  redstone.updateRedArea(n)
  for _, p in ipairs(Player.get()) do
    if Colliders.collide(data.redarea, p) then
      data.isFused = true
      data.timer = 5
      break
    end
  end
end

function tnt.onRedTick(n)
  local data = n.data
  data.observ = false

  if data.frameX == TYPE_TNT then
    if data.isFused then
      redstone.applyFriction(n)
    else
      n.speedY = -Defines.npc_grav
    end
  elseif data.frameX == TYPE_MINECART then
    if data.isFused then
      n.speedX = 0
    else
      n.speedX = n.direction*tnt.config.speed
    end
  end

  if data.frameX == TYPE_MINECART and not data.isFused then
    explodeOnContact(n)
  end

  if data.beenBlowned then
    data.blownedTimer = data.blownedTimer - 1
    if data.blownedTimer <= 0 then
      data.beenBlowned = false
      data.isFused = true
    end
  elseif data.isFused then
    data.timer = data.timer - 1
    if data.timer <= 0 then
      explode(n)
    end
  elseif data.power > 0 then
      SFX.play(sfxcharge)
      if data.frameX == TYPE_TNT then
        data.timer = tnt.config.explosiontimer
      elseif data.frameX == TYPE_MINECART then
        data.timer = 5
      end
      data.isFused = true
      data.observ = true
  end

  if data.isFused then
    data.frameY = 1
  else
    data.frameY = 0
  end

  redstone.resetPower(n)
end

tnt.onRedDraw = redstone.drawNPC

redstone.register(tnt)

return tnt
