local mod_name = core.get_current_modname()
local mod_path = core.get_modpath(mod_name)
local S = core.get_translator(mod_name)

dofile(mod_path .. "/spawning.lua")
dofile(mod_path .. "/rock_projectile.lua")

aom_kelar = {}
function aom_kelar.do_sound(self, name, gain)
    core.sound_play(("aom_kelar_"..name), {
        gain = gain or 1,
        pos = self.object:get_pos(),
        object = self.object,
        pitch = (math.random()*0.2) + 0.9
    })
end


local function alert_nearby_kelar(self, target, dist)
    if (not target) or not target.get_luaentity then return end
    if not dist then dist = 10 end
    local nearby_objects = core.get_objects_inside_radius(self.object:get_pos(), dist)
    for _, object in pairs(nearby_objects) do
        local ent = object:get_luaentity()
        if ent and (not ent._aom_target)
        and ent.name == "aom_kelar:kelar" then
            aom_entity_api.set_state(ent, "charge")
            ent._aom_target = target
        end
    end
end

local function send_attack(self, target, base_damage)
    if core.get_modpath("aom_combat") then
        aom_combat.attack.send_attack(nil, self.object, target, {attack={
            base_damage = base_damage
        }})
    else
        target:punch(self.object, 1.0, {
            full_punch_interval = 1.0,
            damage_groups = base_damage
        }, nil)
    end
end


local kelar = {
    initial_properties = {
        visual = "mesh",
        mesh = "aom_kelar.b3d",
        textures = {"aom_kelar.png"},
        use_texture_alpha = false,
        stepheight = 0.5,
        hp_max = 20,
        physical = true,
        collisionbox = {-0.3, -0.5, -0.3, 0.3, 1, 0.3},
        selectionbox = {-0.3, -0.5, -0.3, 0.3, 1, 0.3},
        collide_with_objects = false,
        pointable = true,
        damage_texture_modifier = "^[colorize:#ff9999:50",
        static_save = false,
    },
    _name = "aom_kelar:kelar",


    on_step = aom_entity_api.do_states(),
    _drop = {
        minmax = {
            {min = 1, max = 4,  item = "aom_soil:gravel"},
            {min = 2, max = 8,  item = "aom_items:ignis"},
        },
        items = {
            {rarity = 8, items = {"aom_items:copper_nugget"}},
            {rarity = 8, items = {"aom_items:iron_nugget"}},
            {rarity = 8, items = {"aom_items:tin_nugget"}},
            {rarity = 8, items = {"aom_spawnnodes:mossy_rubble"}},
        },
    },

    _states = {
        on_step = function(self, dtime, moveresult)
            aom_entity_api.test_fall_damage(self, dtime, false)
            if self._state == "death" then
                return
            end
            if not self.object:get_pos() then
                return "die"
            end
            if not self._aom_combat_bias then
                -- gives a bias toward melee (0) or ranged (1)
                self._aom_combat_bias = math.random(0,1)
            end

            local los = self._aom_target and aom_entity_api.has_los_to_object(self, self._aom_target, nil)
            if los then self._aom_time_since_los = 0
            else self._aom_time_since_los = (self._aom_time_since_los or 0) + dtime
            end

            local dist = aom_entity_api.get_target_dist(self)
            if dist and (self._aom_time_since_los > 10 and (dist > 15) or (dist > 60)) then
                self._aom_target = nil
            end

            if self._state ~= "death" then
                aom_entity_api.push_objects_in_radius(self, dtime, 1.0, 2)
                local in_or_above_water = aom_entity_api.float_in_liquids(self, dtime * 20, {offset=vector.new(0, 1.2, 0)})
                if not in_or_above_water then
                    aom_entity_api.apply_gravity(self, dtime)
                end
            end
        end,
        idle = {
            animation = "idle",
            step = function(self, dtime, moveresult)

                aom_entity_api.get_target(self)
                aom_entity_api.get_wander(self, 300)
                aom_entity_api.decelerate(self, 0.9)

                if self._aom_to_pos and self._aom_state_time > 1 then
                    if self._aom_target then
                        return "charge"
                    else
                        return "roam"
                    end
                end
            end,
        },
        block = {
            animation = "pre_lunge",
            on_state_start = function(self)
                local vel = self.object:get_velocity()
                self.object:set_velocity(vel * 0.5)
            end,
            step = function(self, dtime, moveresult)
                aom_entity_api.rotate_to_target(self, dtime*20)
                if not self._aom_lunge_time then self._aom_lunge_time = 0.5 end
                if self._aom_lunge_time > 0 then self._aom_lunge_time = self._aom_lunge_time - dtime end
                aom_entity_api.decelerate(self, 0.9)
                if self._aom_lunge_time <= 0 then
                    self._aom_lunge_time = nil
                    return "roam"
                end
            end,
        },
        roam = {
            animation = "charge",
            on_state_start = function(self)
                if math.random() < 0.5 then
                    local ents = aom_entity_api.get_objects_of_type(self.object:get_pos(), 80, {"aom_kelar:kelar"})
                    local try = ents[math.random(1, #ents)]
                    if try then
                        self._aom_to_pos = try:get_pos()
                    end
                end
                aom_entity_api.get_path(self)
            end,
            step = function(self, dtime, moveresult)
                local tmp_vel = self.object:get_velocity()
                tmp_vel.y = 0
                if vector.length(tmp_vel) > 0.2 then
                    aom_entity_api.set_my_animation(self, "charge")
                else
                    aom_entity_api.set_my_animation(self, "idle")
                end

                aom_entity_api.get_target(self, nil, {no_to_pos=false})
                if (not self._aom_to_pos) then
                    aom_entity_api.get_wander(self, 1)
                    aom_entity_api.get_path(self)
                end
                aom_entity_api.do_path(self)
                aom_entity_api.check_jump(self, 4)

                if not aom_entity_api.path_reachable(self) then
                    self._aom_to_pos = nil
                end
                -- no target or target pos
                if (not self._aom_to_pos) and (not self._aom_target) then
                    return "idle"
                end

                -- found target, so chase them
                if self._aom_target then
                    return "charge"
                end

                aom_entity_api.rotate_to_path(self, dtime*8)
            end,
        },
        charge = {
            on_state_start = function(self)
                self._aom_time_since_target = 0
            end,
            -- animation = "charge",
            step = function(self, dtime, moveresult)
                if not self._aom_attack_cooldown then
                    if math.random() < 0.3 then
                        self._aom_attack_cooldown = 0.3
                    else
                        self._aom_attack_cooldown = math.random() * 4 + 1
                    end
                end
                if self._aom_attack_cooldown > 0 then self._aom_attack_cooldown = self._aom_attack_cooldown - dtime end
                local dist = aom_entity_api.get_target_dist(self)


                local tmp_vel = self.object:get_velocity()
                tmp_vel.y = 0
                if vector.length(tmp_vel) > 1 then
                    aom_entity_api.set_my_animation(self, "charge")
                else
                    aom_entity_api.set_my_animation(self, "idle")
                end

                if self._aom_combat_bias == 0 then -- MELEE FOCUS, CHARGE AT ENEMY
                    if dist and dist > 8 then
                        aom_entity_api.get_and_follow_target(self, 2.5)
                    else
                        aom_entity_api.get_and_follow_target(self, 1)
                    end
                elseif dist and (self._aom_combat_bias == 1) then -- RANGED ONLY, KEEP DISTANCE
                    if dist > 20 then
                        aom_entity_api.get_and_follow_target(self, 2.5)
                    elseif (not self._aom_to_pos) then
                        if math.random() < 0.5 then
                            aom_entity_api.find_roam_target(self, 10)
                            -- aom_entity_api.run_from(self, self._aom_target:get_pos(), {angle_deviation = math.pi*0.5, distance = 12})
                        else
                            aom_entity_api.find_roam_target(self, 10)
                            -- aom_entity_api.run_from(self, self._aom_target:get_pos(), {angle = (math.random(0,1)*2-1)*2, distance = 6})
                        end
                    end
                end

                if (not self._aom_to_pos) then
                    aom_entity_api.get_path(self)
                else
                    aom_entity_api.do_path(self)
                end

                aom_entity_api.check_jump(self, 4)

                -- no target or target pos
                if (not self._aom_target) or not dist then
                    return "roam"
                end

                aom_entity_api.rotate_to_path(self, dtime*3)

                local has_los = aom_entity_api.has_los_to_object(self, self._aom_target)
                if has_los and dist and dist < 3 and math.random() < 0.02 then
                    return "punch"
                end

                if self._aom_target then
                    self._aom_time_since_target = 0
                end

                if (dist and (dist > 50) or (not has_los)) then
                    if self._aom_time_since_target > ((self._aom_combat_bias == 0 and 8) or 3) then
                        self._aom_target = nil
                        aom_entity_api.get_target(self)
                    else
                        self._aom_time_since_target = self._aom_time_since_target + dtime
                    end
                end

                if (math.random() < 0.4) and (self._aom_combat_bias == 1) and (not has_los)
                and (not self._aom_to_pos) and (self._aom_attack_cooldown <= 0) and (dist > 6) then
                    self._aom_attack_cooldown = 2
                    if math.random() < 0.5 then
                        aom_entity_api.find_roam_target(self, 10)
                        -- aom_entity_api.run_from(self, self._aom_target:get_pos(), {angle_deviation = math.pi*0.5, distance = 12})
                    else
                        aom_entity_api.find_roam_target(self, 10)
                        -- aom_entity_api.run_from(self, self._aom_target:get_pos(), {angle = (math.random(0,1)*2-1), distance = 6})
                    end
                end
                if (self._aom_attack_cooldown <= 0) and (dist and dist < 30) then
                    if has_los and (dist < 12) and (math.random() < ((self._aom_combat_bias == 0 and 0.7) or 0.1)) then
                        self._aom_attack_cooldown = nil
                        return "pre_lunge"
                    elseif (has_los) or (self._aom_combat_bias == 1) then
                        self._aom_attack_cooldown = nil
                        return "throw"
                    elseif (math.random() < 0.1) and (not has_los) and (self._aom_combat_bias == 0) then
                        self._aom_combat_bias = 1
                    end
                end
            end,
        },
        throw = {
            animation = "throw",
            on_state_start = function(self)
                local vel = self.object:get_velocity()
                vel.x = 0
                vel.z = 0
                self.object:set_velocity(vel)
                self._aom_throwing = true
            end,
            step = function(self, dtime, moveresult)
                if not self._aom_target then
                    aom_entity_api.get_target(self)
                    if not self._aom_target then return "charge" end
                end
                aom_entity_api.rotate_to_target(self, dtime*3)
                aom_entity_api.decelerate(self, 0.9)

                if self._aom_throwing and self._aom_state_time > (1 / aom_entity_api.get_current_animation_length(self)) * 30 then repeat
                    self._aom_throwing = false
                    local pos = vector.offset(self.object:get_pos(), 0, 1, 0)
                    local dir
                    local tpos = self._aom_target and self._aom_target:get_pos()
                    tpos.y = tpos.y + 1
                    if tpos then
                        local tvel = self._aom_target:get_velocity()
                        local dist = vector.distance(pos, tpos)
                        tvel = vector.multiply(tvel, (dist / 20) * (math.random()/2 + 0.5))

                        tpos = vector.add(tpos, tvel)

                        local has_los = aom_entity_api.has_los_to_object(self, self._aom_target)
                        local inaccuracy = (has_los and 1) or 6
                        tpos = vector.offset(tpos,
                            ((math.random() - 0.5) * inaccuracy),
                            ((math.random() - 0.5) * inaccuracy),
                            ((math.random() - 0.5) * inaccuracy))
                        if not has_los then
                            tpos.y = tpos.y + math.random() * 6
                        end

                        dir = vector.direction(pos, tpos)
                        dir.y = dir.y + 0.017 * (dist) + 0.05
                    else break end

                    local ent = aom_combat.projectile.add_projectile(self.object,
                        "aom_kelar:rock_ENTITY",
                        pos, dir, 20
                    )
                    core.sound_play(("aom_whap"), {
                        gain = 1.4,
                        pos = self.object:get_pos(),
                        object = self.object,
                        pitch = (math.random()*0.2) + 0.9,
                        max_hear_distance = 20,
                    })
                until true end

                if self._aom_state_time > 45 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_lunge_time = nil
                    return "charge"
                end
            end,
        },
        punch = {
            animation = "punch",
            on_state_start = function(self)
                local vel = self.object:get_velocity()
                vel.x = 0
                vel.z = 0
                self.object:set_velocity(vel)
            end,
            step = function(self, dtime, moveresult)
                if not self._aom_target then
                    aom_entity_api.get_target(self)
                    if not self._aom_target then return "charge" end
                end
                local dist = aom_entity_api.get_target_dist(self)
                aom_entity_api.rotate_to_target(self, dtime*3)
                aom_entity_api.decelerate(self, 0.9)

                if self._aom_since_attack > 1 and self._aom_state_time > 0.6 and dist and dist < 3 then
                    self._aom_since_attack = 0
                    send_attack(self, self._aom_target, {
                        blunt = 2,
                    })
                end
                if self._aom_state_time > 25 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    return "charge"
                end
            end,
        },
        pre_lunge = {
            animation = "pre_lunge",
            on_state_start = function(self)
                local vel = self.object:get_velocity()
                vel.x = 0
                vel.z = 0
                self.object:set_velocity(vel)
            end,
            step = function(self, dtime, moveresult)
                if not self._aom_target then
                    aom_entity_api.get_target(self)
                end
                if not self._aom_path then
                    aom_entity_api.get_path(self, aom_entity_api.min_cost)
                end
                aom_entity_api.rotate_to_target(self, dtime*3)
                if not self._aom_lunge_time then self._aom_lunge_time = 0.5 end
                if self._aom_lunge_time > 0 then self._aom_lunge_time = self._aom_lunge_time - dtime end

                if self._aom_lunge_time <= 0 then
                    self._aom_lunge_time = nil
                    return "lunge"
                end
            end,
        },
        lunge = {
            animation = "lunge",
            on_state_start = function(self)
                aom_kelar.do_sound(self, "lunge_"..(math.random(0,3)), 1)
                self._aom_path = nil
                self._aom_to_pos = nil

                if not self._aom_target then
                    return "idle"
                end
                local target_pos = self._aom_target:get_pos()
                local pos = self.object:get_pos()
                local dir = vector.direction(pos, target_pos)
                local yaw = core.dir_to_yaw(dir)
                self.object:set_yaw(yaw)
                dir = vector.multiply(dir, self._aom_speed * 2.5)
                dir.y = 4
                self.object:set_velocity(dir)
                self._aom_lunge_jump = true
            end,
            step = function(self, dtime, moveresult)

                if self._aom_state_time >= 34 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    local vel = self.object:get_velocity() * 0.5
                    self.object:set_velocity(vel)
                    return "charge"
                end
                if self._aom_state_time > 0.7 then
                    local vel = self.object:get_velocity() * 0.99
                    self.object:set_velocity(vel)
                end
                if self._aom_lunge_jump and self._aom_state_time > 3 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_lunge_jump = false
                    local vel = self.object:get_velocity()
                    self.object:set_velocity(vector.offset(vel, 0, 1.8, 0))
                end

                local dist = aom_entity_api.get_target_dist(self, 1.5)
                if self._aom_since_attack >= 1 and dist and dist < 2
                and aom_entity_api.has_los_to_object(self, self._aom_target) then
                    send_attack(self, self._aom_target, {
                        blunt = 2,
                    })
                    self._aom_since_attack = 0
                end
            end,
        },
        death = {
            animation = "death",
            on_state_start = function(self)
                aom_entity_api.do_drops(self)
                aom_entity_api.rotate_to_target(self, 1)
                self._aom_detectable = false
                self.object:set_properties({
                    pointable = false,
                })
            end,
            step = function(self, dtime, moveresult)
                aom_entity_api.decelerate(self, 0.5)
                local vel = self.object:get_velocity()
                vel.y = vel.y - self._aom_gravity * dtime
                self.object:set_velocity(vel)
                if self._aom_state_time > 4 then
                    local pos = self.object:get_pos()
                    local part = {
                        amount = 10,
                        time = 0.000001,
                        vertical = false,
                        texpool = {
                            "aom_kelar_particle.png^[multiply:#ccc",
                            "aom_kelar_particle.png^[multiply:#aaa",
                            "aom_kelar_particle.png^[multiply:#999",
                        },
                        collisiondetection = true,
                        drag = vector.new(2.5, 0, 2.5),
                        pos = pos,
                        minvel = vector.new(-3, 0.5, -3),
                        maxvel = vector.new( 3, 4,  3),
                        acc = vector.new(0, -9, 0),
                        minexptime = 1,
                        maxexptime = 2,
                        minsize = 0.7,
                        maxsize = 3,
                    }
                    core.add_particlespawner(part)
                    self.object:remove()
                end
            end,
        },
    },
    _default_state = "idle",
    on_activate = function(self, staticdata, dtime_s)
        self.object:set_armor_groups({
            pierce=200,
            slash=100,
            blunt=100,
            magic=200,
            poison=100,
        })
        self.object:set_hp(self._aom_max_health)
        aom_entity_api.on_activate(self, staticdata, dtime_s)

        local pos = self.object:get_pos()
        if self._age ~= nil and (not self._no_despawn) and pos and not aom_entity_api.has_mobs_in_radius(pos, 60, {self.name}, 0, 5) then
            return self.object:remove()
        end
    end,
    get_staticdata = function(self)
        return aom_entity_api.get_staticdata(self)
    end,
    on_deactivate = function(self, removal)
        aom_entity_api.on_deactivate(self, removal)
    end,
    on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
        alert_nearby_kelar(self, puncher, 30)
        if damage and damage > 0 then
            aom_kelar.do_sound(self, "hurt_0", 1)

            local hp = self.object:get_hp() - damage
            if hp <= 0 then
                aom_entity_api.set_state(self, "death")
                self.object:set_hp(self._aom_max_health)
                self.object:set_armor_groups({fleshy=0})
                return true
            end

            if puncher and puncher ~= self.object and not self._aom_target then
                self._aom_target = puncher
                aom_entity_api.set_state(self, "charge")
            end
        end
        return aom_entity_api.damage.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
    end,
    _aom_staticdata_load_list = {
        "_aom_speed",
        "_aom_damage_groups",
        "_aom_hostile",
    },
    _aom_speed = 3,
    _aom_acceleration = 20,
    _aom_gravity = 9,
    _aom_max_health = 20,
    _aom_detectable = true,
    _aom_hostile = {player=1},
    _eye_height = 1.3,
    _aom_damage_groups = {
        pierce=2,
        slash=2,
        blunt=1,
    },
    _animations = {
        idle = {frames={x=0, y=39}, blend=0.2},
        charge = {frames={x=40, y=57}, blend=0.2},
        pre_lunge = {frames={x=110, y=114}, blend=0.2, speed = 24, loop = false},
        lunge = {frames={x=115, y=149}, blend=0.2, speed = 32, loop = false},
        throw = {frames={x=60, y=106}, blend=0.2, speed = 24, loop = false},
        punch = {frames={x=80, y=106}, blend=0.2, speed = 24, loop = false},
        death = {frames={x=150, y=169}, blend=0.2, speed = 24, loop = false},
    },
    _aom_wander = 3,
    _aom_range = 20,
    _aom_statusfx_enable = true,
	_mobcap_groups = {"hostile"},
    _aom_is_mob = true,
}


core.register_entity("aom_kelar:kelar", kelar)

local animspeedmult = 0.7
local underkelar
underkelar = table.copy(kelar)
underkelar.initial_properties.textures = {"aom_kelar.png^[multiply:#ffb9b780"}
underkelar.initial_properties.glow = 2
underkelar._aom_speed = underkelar._aom_speed / animspeedmult
underkelar._aom_did_variant_check = true
underkelar._drop = {
    minmax = {
        {min = 1, max = 4,  item = "aom_underworld:ash"},
        {min = 2, max = 8,  item = "aom_items:ignis"},
        {min = -5, max = 2, item = "aom_underworld:mythril_strand"},
    },
    items = {
        {rarity = 8, items = {"aom_spawnnodes:burning_ash"}},
    },
}
for i, anim in pairs(underkelar._animations) do
    underkelar._animations[i] = table.copy(anim)
    underkelar._animations[i].speed = (underkelar._animations[i].speed or 24) / animspeedmult
end
core.register_entity("aom_kelar:kelar_underworld", underkelar)

core.register_craftitem("aom_kelar:kelar_spawn", {
    description = S("Kelar spawn egg"),
    inventory_image = "aom_kelar.png",
    on_place = function(itemstack, placer, pointed_thing)
        local ent = core.add_entity(vector.offset(core.get_pointed_thing_position(pointed_thing), 0, 1, 0), "aom_kelar:kelar")
    end,
})

core.register_craftitem("aom_kelar:kelar_underworld_spawn", {
    description = S("Kelar Underworld spawn egg"),
    inventory_image = "aom_kelar.png^[multiply:#ffb9b780",
    on_place = function(itemstack, placer, pointed_thing)
        local ent = core.add_entity(vector.offset(core.get_pointed_thing_position(pointed_thing), 0, 1, 0), "aom_kelar:kelar_underworld")
    end,
})

-- get rid of unused vars
underkelar = nil
kelar = nil
