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

function aom_goblins.alert_nearby(self, target, dist)
    if not dist then dist = 10 end
    local nearby_objects = minetest.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_goblins:goblin" then
            aom_entity_api.set_state(ent, "charge")
            ent._aom_target = target
        end
    end
end

function aom_goblins.do_sound(self, spec, gain)
    minetest.sound_play(spec, {
        gain = gain,
        object = self.object,
        pitch = 0.9 + math.random()*0.2,
        max_hear_distance = 20,
    })
end

function aom_goblins.choose_attack(self)
    if math.random() < 0.4 then
        return "caution"
    elseif math.random() < 0.4 then
        return "lunge"
    elseif math.random() < 0.7 then
        return "swing"
    else
        return "block"
    end
end

function aom_goblins.send_attack(self, target, base_damage)
    if minetest.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

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


    on_step = aom_entity_api.do_states(),
    _drop = {
        max_items = 5,
        items = {
            {
                rarity = 1,
                items = {"aom_items:stick"},
            },
            {
                rarity = 2,
                items = {"aom_items:stick 2"},
            },
            {
                rarity = 2,
                items = {"aom_items:gold_nugget"},
            },
        }
    },

    _is_blocking = function(self, atk)
        if atk and atk.base_damage.projectile then return false end
        if self._state == "block" then return true end
        if math.random() < 0.7 then
            local states = {caution=true,}
            if states[self._state] then
                aom_entity_api.set_state(self, "block")
                return true
            end
        end
    end,

    _states = {
        on_step = function(self, dtime, moveresult)
            aom_entity_api.test_fall_damage(self, dtime, false)
            if not self.object:get_pos() then
                return "die"
            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
                        if math.random() < 0.8 then
                            return "cautionwalk"
                        else
                            return "charge"
                        end
                    else
                        return "roam"
                    end
                end
            end,
        },
        block = {
            animation = "block",
            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 "charge"
                end
            end,
        },
        roam = {
            -- animation = "walk",
            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_goblins:goblin"})
                    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, "walk")
                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, self._aom_speed * 0.4)
                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 "caution"
                end

                aom_entity_api.rotate_to_path(self, dtime*8)
            end,
        },
        charge = {
            -- animation = "run",
            on_state_start = function(self)
                self._aom_time_since_target = 0
            end,
            step = function(self, dtime, moveresult)
                if not self._aom_attack_cooldown then self._aom_attack_cooldown = 1 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, "run")
                else
                    aom_entity_api.set_my_animation(self, "idle")
                end

                if dist and dist > 5 then
                    aom_entity_api.get_and_follow_target(self, 2.5)
                else
                    aom_entity_api.get_and_follow_target(self, 1)
                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 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 > 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 has_los and (self._aom_attack_cooldown <= 0) and (dist and dist < 15) then
                    return aom_goblins.choose_attack(self)
                end
            end,
        },
        caution = {
            -- animation = "run",
            on_state_start = function(self)
                self._aom_time_since_target = 0
                if math.random() < 0.4 then
                    self._aom_wait_time = math.random() * 6
                else
                    self._aom_wait_time = math.random() + 0.2
                end
            end,
            step = function(self, dtime, moveresult)
                if not self._aom_attack_cooldown then self._aom_attack_cooldown = 1 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)

                aom_entity_api.rotate_to_target(self, 0.1, false)

                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, "cautionwalk")
                else
                    aom_entity_api.set_my_animation(self, "cautionidle")
                end

                if dist and dist > 7 then
                    -- self._aom_path = nil
                    -- self._aom_to_pos = nil
                    aom_entity_api.get_and_follow_target(self, 1, {no_do_path=true})
                elseif dist and dist < 7 then
                    if not self._aom_to_pos then
                        aom_entity_api.find_encircle(self, self._aom_target, math.random(0,1)*2-1, 6)
                        -- aom_entity_api.find_roam_target(self, 10)
                    end
                end

                if self._aom_to_pos then
                    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

                local has_los = aom_entity_api.has_los_to_object(self, self._aom_target)

                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 > 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 (self._aom_state_time > self._aom_wait_time) and has_los and (self._aom_attack_cooldown <= 0) then
                    if dist and (dist < 15) then
                        self._aom_wait_time = nil
                        return aom_goblins.choose_attack(self)
                    end
                end
            end,
        },
        lunge = {
            animation = "lunge",
            on_state_start = function(self)
                aom_goblins.do_sound(self, "aom_goblins_attack", 0.6)
                self._aom_path = nil
                self._aom_to_pos = nil
                self._aom_substate = "init"

                if not self._aom_target then
                    return "idle"
                end
                local dir = vector.normalize(self._aom_target:get_pos() - self.object:get_pos())
                dir.y = 0.2
                self.object:add_velocity(dir * 8)
            end,
            step = function(self, dtime, moveresult)
                aom_entity_api.rotate_to_target(self, dtime*20)
                local dist = aom_entity_api.get_target_dist(self)
                if (self._aom_state_time >= 4 * (1 / aom_entity_api.get_current_animation_length(self)))
                and (self._aom_state_time <= 22 * (1 / aom_entity_api.get_current_animation_length(self)))
                and dist and (dist > 2) then
                    aom_entity_api.move_toward_target(self, self._aom_speed*3)
                else
                    aom_entity_api.decelerate(self, 0.90)
                end

                if self._aom_state_time >= 29 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_substate = nil
                    return "caution"
                end
                if self._aom_substate ~= "winddown"
                and self._aom_state_time >= 17 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_substate = "winddown"
                    if dist and dist < 3
                    and aom_entity_api.has_los_to_object(self, self._aom_target) then
                        aom_goblins.send_attack(self, self._aom_target, {
                            blunt=2,
                            slash=4,
                        })
                        minetest.sound_play("aom_whap", {
                            gain = 1,
                            object = self.object,
                            max_hear_distance = 15,
                        })
                        self._aom_since_attack = 0
                    end
                end
            end,
        },
        swing = {
            animation = "swing",
            on_state_start = function(self)
                aom_goblins.do_sound(self, "aom_goblins_attack", 0.6)
                self._aom_path = nil
                self._aom_to_pos = nil
                self._aom_substate = "init"
                aom_entity_api.move_toward_target(self, 20)

                if not self._aom_target then
                    return "idle"
                end
                local dir = vector.normalize(self._aom_target:get_pos() - self.object:get_pos())
                dir.y = 0.2
                self.object:add_velocity(dir * 8)
            end,
            step = function(self, dtime, moveresult)
                aom_entity_api.rotate_to_target(self, dtime*20)
                local dist = aom_entity_api.get_target_dist(self)
                if dist and (dist > 4) then
                    aom_entity_api.move_toward_target(self, self._aom_speed*2)
                else
                    aom_entity_api.decelerate(self, 0.95)
                end

                if self._aom_state_time >= 39 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_substate = nil
                    return "caution"
                end
                if self._aom_substate ~= "winddown"
                and self._aom_state_time >= 22 * (1 / aom_entity_api.get_current_animation_length(self)) then
                    self._aom_substate = "winddown"
                    local dist = aom_entity_api.get_target_dist(self, 1.5)
                    if dist and dist < 3
                    and aom_entity_api.has_los_to_object(self, self._aom_target) then
                        aom_goblins.send_attack(self, self._aom_target, {
                            blunt=1,
                            slash=3,
                        })
                        minetest.sound_play("aom_whap", {
                            gain = 1,
                            object = self.object,
                            max_hear_distance = 15,
                        })
                        self._aom_since_attack = 0
                    end
                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.9)
                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_goblins_particle.png^[multiply:#bbb",
                            "aom_goblins_particle.png^[multiply:#fff",
                        },
                        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.6,
                        maxsize = 4,
                    }
                    minetest.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, 50, {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)
        self._on_damage_received(self, puncher, damage)
        if damage and damage > 0 then
            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
        end
        return aom_entity_api.damage.on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
    end,
    _on_damage_received = function(self, attacker_entity, maxdmg)
        if maxdmg < 1 then return end
        aom_goblins.alert_nearby(self, attacker_entity, 15)
        aom_goblins.do_sound(self, "aom_goblins_hurt", 0.5)
        if attacker_entity and attacker_entity ~= self.object and not self._aom_target then
            self._aom_target = attacker_entity.object
            aom_entity_api.set_state(self, "charge")
            return
        end
        -- chance to retaliate
        if math.random() < 0.3 then
            local states = {idle=true,roam=true,caution=true,}
            if states[self._state] then
                aom_entity_api.set_state(self, "charge")
            end
        end
    end,
    _aom_staticdata_load_list = {
        "_aom_speed",
        "_aom_damage_groups",
    },
    _aom_speed = 5,
    _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,
    },
    _animation = nil,
    _animations = {
        idle = {frames={x=10, y=99}, blend=0.2},
        walk = {frames={x=110, y=139}, blend=0.2},
        run = {frames={x=140, y=159}, blend=0.1},
        lunge = {frames={x=160, y=189}, blend=0.2, loop = false},
        swing = {frames={x=200, y=239}, blend=0.3, loop = false},
        block = {frames={x=240, y=261}, blend=0.2, loop = false},
        cautionwalk = {frames={x=270, y=290}, blend=0.2},
        cautionidle = {frames={x=295, y=316}, blend=0.2},
        death = {frames={x=317, y=339}, blend=0.2, loop = false},
        sit = {frames={x=340, y=374}, blend=0.5, speed = 12},
        climb = {frames={x=375, y=395}, blend=0.2},
        climbend = {frames={x=396, y=440}, blend=0.2, loop = false},
    },
    _aom_wander = 3,
    _aom_range = 20,
    _aom_statusfx_enable = true,
}


minetest.register_entity("aom_goblins:goblin", aom_goblins.goblin_default)

minetest.register_craftitem("aom_goblins:goblin_spawn", {
    description = S("Kelar spawn egg"),
    inventory_image = "aom_goblins_goblin.png",
    on_place = function(itemstack, placer, pointed_thing)
        local ent = minetest.add_entity(vector.offset(minetest.get_pointed_thing_position(pointed_thing), 0, 1, 0), "aom_goblins:goblin")
    end,
})
