import * as mc from "@minecraft/server";
import * as vector from "../utils/vector";
import * as utils from "../utils/generalUtils";

const ROAM_IMPULSE_STRENGTH = 0.65;
const ROAM_DISTANCE = 18;

const SPIT_COOLDOWN = 120;
const SWOOP_COOLDOWN = 90;
const ROAR_COOLDOWN = 400;
const TIME_ON_GROUND = 90;

const STATES = {
    ROAMING: 0,
    HOVERING: 1,
    ATTACKING: 2,
    FALLING: 3,
    FALLEN: 4,
    DEAD: 5
}
const ATTACK_STATES = {
    NONE: 0,
    SPIT_ATTACK: 1,
    SWOOP_ATTACK: 2
}

let data = new Map();

export function eventBehavior(event) {
    if (event.eventId === 'register') {
        data.set(event.entity.id, {
            spawnPosition: {
                x: event.entity.getDynamicProperty("hf_mzs:spawn_pos_x"),
                y: 0,
                z: event.entity.getDynamicProperty("hf_mzs:spawn_pos_z")
            },
            anchorPosition: event.entity.location,

            oldPosition: event.entity.location,

            targetCooldown: 0,
            targetPosition: undefined,
            attackTarget: undefined,
            rotationFromAttackTarget: undefined,
            oldAttackTargetPosition: undefined,
            swoopAnchor: undefined,

            state: STATES.ROAMING,
            attackState: ATTACK_STATES.NONE,

            ticks: 0,
            flapTick: 0,
            beginFlapTick: 0,
            dodgeCooldown: 0,
            spitCooldown: SPIT_COOLDOWN,
            swoopCooldown: SWOOP_COOLDOWN,
            roarCooldown: 400
        });
        //mc.world.sendMessage("reg");
        event.entity.triggerEvent("pick_roam_target");
        event.entity.setProperty("hf_mzs:y_rot", 0);
        event.entity.setProperty("hf_mzs:on_ground", false);
        event.entity.setProperty("hf_mzs:hovering", false);
        event.entity.setProperty("hf_mzs:swooping", false);
        event.entity.setProperty("hf_mzs:spitting", false);
        event.entity.setProperty("hf_mzs:falling", false);
        return;
    }
    if (event.eventId === "minecraft:entity_spawned" || event.eventId === "spawned_naturally") {
        const pos = event.entity.location;
        event.entity.setDynamicProperty("hf_mzs:spawn_pos_x", pos.x);
        event.entity.setDynamicProperty("hf_mzs:spawn_pos_z", pos.z);
        return;
    }
    if (event.eventId === "pick_roam_target") {
        if (data.get(event.entity.id).state === STATES.ROAMING)
            pickRoamTarget(event.entity);
        return;
    }
    if (event.eventId === "drop") {
        if (data.get(event.entity.id)) {
            data.delete(event.entity.id);
        }
    }
}
export function hitByProjectile(entity, projectile) {
    if (projectile.isValid() && projectile.typeId === "hf_mzs:poison_ball")
        return;
    if (entity.getComponent("health").currentValue === 1)
        return;
    if (data.get(entity.id) === undefined || !((data.get(entity.id).attackState === ATTACK_STATES.SWOOP_ATTACK && data.get(entity.id).ticks < 46) || data.get(entity.id).attackState === ATTACK_STATES.SPIT_ATTACK))
        return;
    //mc.world.sendMessage("HIT BY ARROW");
    data.get(entity.id).attackState = ATTACK_STATES.NONE;
    data.get(entity.id).ticks = 0;
    data.get(entity.id).spitCooldown = SPIT_COOLDOWN;
    entity.setProperty("hf_mzs:spitting", false);
    entity.setProperty("hf_mzs:swooping", false);
    entity.setProperty("hf_mzs:x_rot", 0);

    data.get(entity.id).state = STATES.FALLING;
    entity.setProperty("hf_mzs:falling", true);
    entity.applyImpulse(vector.up);
}
export function hurtBehavior(event) {
    const entity = event.hurtEntity;
    if (data.get(entity.id).state !== STATES.FALLEN || event.damage < 5)
        return;
    if (entity.getComponent("health").currentValue === 1)
        return;
    data.get(entity.id).state = STATES.HOVERING;
    data.get(entity.id).ticks = 0;
    data.get(entity.id).beginFlapTick = 10;
    data.get(entity.id).spitCooldown = SPIT_COOLDOWN;
    data.get(entity.id).swoopCooldown = SWOOP_COOLDOWN / 3;
    data.get(entity.id).dodgeCooldown = 0;
    entity.setProperty("hf_mzs:on_ground", false);
    entity.setProperty("hf_mzs:falling", false);
    entity.clearVelocity();
    pickAttackPosition(entity, true);
}

export function tick20(entity) {
    if (entity.getProperty("hf_mzs:death")) {
        return;
    }
    if (data.get(entity.id).state === STATES.HOVERING || data.get(entity.id).state === STATES.ROAMING)
        getAttackTarget(entity);
    if (data.get(entity.id).state === STATES.ROAMING) {
        //mc.world.sendMessage(`${data.get(entity.id).roarCooldown}`);
        if (data.get(entity.id).roarCooldown >= 1)
            data.get(entity.id).roarCooldown -= 20;
        if (data.get(entity.id).roarCooldown === 0) {
            entity.runCommand("/playsound hf_mzs:mob.naga.roar @a[r=64] ~~~");
            data.get(entity.id).roarCooldown = ROAR_COOLDOWN;
        }
    }
}
// movement
export function tick1(entity) {
    //mc.world.sendMessage(`${entity.getProperty("hf_mzs:y_rot")}`)
    if (entity.getProperty("hf_mzs:death")) {
        if (entity.isOnGround) {
            entity.setProperty("hf_mzs:on_ground", true);
        }
        return;
    }

    if (data.get(entity.id).dodgeCooldown >= 1)
        data.get(entity.id).dodgeCooldown--;
    if (data.get(entity.id).targetCooldown >= 1)
        data.get(entity.id).targetCooldown--;
    // sync flap animation with actual flap - does not work well for some reason
    if (data.get(entity.id).state === STATES.HOVERING || data.get(entity.id).attackState === ATTACK_STATES.SPIT_ATTACK) {
        if (data.get(entity.id).beginFlapTick < 10) {
            data.get(entity.id).beginFlapTick++;
            return;
        }
        if (data.get(entity.id).flapTick == 0) {
            entity.setProperty("hf_mzs:flap", true);
        }
        // flap up
        if (data.get(entity.id).flapTick == 1) {
            //mc.world.sendMessage("flap");
            entity.applyImpulse({ x: 0, y: 0.7, z: 0 });
            if (Math.random() <= 0.1) {
                pickAttackPosition(entity, false, false, Math.PI / 4 + Math.random() * Math.PI / 4);
            }
            else
                pickAttackPosition(entity, false, false, (Math.random() < 0.5 ? 1 : -1) * Math.PI / 16);
        }
        if (data.get(entity.id).flapTick > 3 && data.get(entity.id).flapTick <= 12) {
            if (data.get(entity.id).dodgeCooldown === 0)
                moveThowardsTarget(entity, 0.05);
        }
        if (data.get(entity.id).flapTick == 25) {
            entity.setProperty("hf_mzs:flap", false);
        }

        //mc.world.sendMessage(`${data.get(entity.id).flapTick}`);
        data.get(entity.id).flapTick++;
        if (data.get(entity.id).flapTick >= 30)
            data.get(entity.id).flapTick = 0;
    }
    else {
        entity.setProperty("hf_mzs:flap", false);
        data.get(entity.id).flapTick = 0;
        data.get(entity.id).beginFlapTick = 0;
    }
    //mc.world.sendMessage(`${data.get(entity.id).state}   ${data.get(entity.id).attackState}`);
    // hovering over player
    if (data.get(entity.id).state === STATES.HOVERING) {
        const arrows = entity.dimension.getEntities({
            type: "arrow",
            maxDistance: 18,
            location: entity.location
        });
        for (const arrow of arrows) {
            if (vector.magnitude(arrow.getVelocity()) > 0 &&
                data.get(entity.id).dodgeCooldown === 0) 
            {
                data.get(entity.id).dodgeCooldown = 15;
                dodgeArrow(entity, arrow.getViewDirection(), arrow.location);
                return;
            }
        }

        const delta = vector.magnitude(vector.subtract(data.get(entity.id).oldAttackTargetPosition,
            data.get(entity.id).attackTarget.location));
        if (delta <= 1) {
            entity.setProperty("hf_mzs:hovering", true);
        }
        else if (delta >= 9) {
            entity.setProperty("hf_mzs:hovering", false);
            data.get(entity.id).state = STATES.ROAMING;
        }
        else {
            pickAttackPosition(entity, true);
        }
        //mc.world.sendMessage(`${mc.system.currentTick}`);

        if (data.get(entity.id).flapTick > 12 && data.get(entity.id).dodgeCooldown === 0) {
            entity.clearVelocity();
            if (hasReachedTarget(entity, 4))
                moveThowardsTarget(entity, 0.2);
            else {
                moveThowardsTarget(entity);
            }
        }
        rotateToTarget(entity, data.get(entity.id).attackTarget.location);

        if (Math.floor(Math.random() * 80) === 0 && data.get(entity.id).spitCooldown === 0
            && data.get(entity.id).attackState === ATTACK_STATES.NONE) {
            data.get(entity.id).state = STATES.ATTACKING;
            data.get(entity.id).attackState = ATTACK_STATES.SPIT_ATTACK
            //doSpitAttack(entity);
        }
        else if (Math.floor(Math.random() * 80) === 0 && data.get(entity.id).swoopCooldown === 0
            && data.get(entity.id).attackState === ATTACK_STATES.NONE) {
            data.get(entity.id).state = STATES.ATTACKING;
            data.get(entity.id).attackState = ATTACK_STATES.SWOOP_ATTACK
            //doSpitAttack(entity);
        }

        //if (vector.magnitude(vector.subtract(data.get(entity.id).oldPosition, entity.location)) > 1.5)
        //    entity.clearVelocity();
        data.get(entity.id).oldPosition = entity.location;

        if (data.get(entity.id).spitCooldown >= 1)
            data.get(entity.id).spitCooldown--;
        if (data.get(entity.id).swoopCooldown >= 1)
            data.get(entity.id).swoopCooldown--;
        return;
    }
    // falling
    if (data.get(entity.id).state === STATES.FALLING) {
        entity.applyImpulse({ x: 0, y: -0.2, z: 0 });
        if (entity.isOnGround) {
            data.get(entity.id).state = STATES.FALLEN;
        }
        return;
    }
    // fallen
    if (data.get(entity.id).state === STATES.FALLEN) {
        if (data.get(entity.id).ticks === 0) {
            entity.clearVelocity();
            entity.setProperty("hf_mzs:on_ground", true)
            //mc.world.sendMessage("Fallen");
        }
        if (data.get(entity.id).ticks >= TIME_ON_GROUND) {
            data.get(entity.id).state = STATES.HOVERING;
            data.get(entity.id).ticks = 0;
            data.get(entity.id).beginFlapTick = 10;
            data.get(entity.id).spitCooldown = SPIT_COOLDOWN;
            data.get(entity.id).swoopCooldown = SWOOP_COOLDOWN / 3;
            data.get(entity.id).dodgeCooldown = 0;
            entity.setProperty("hf_mzs:on_ground", false);
            entity.setProperty("hf_mzs:falling", false);
            entity.clearVelocity();
            pickAttackPosition(entity, true);
            return;
        }
        data.get(entity.id).ticks++;
        return;
    }

    // doing spit attack
    if (data.get(entity.id).attackState === ATTACK_STATES.SPIT_ATTACK) {
        //mc.world.sendMessage("test2");
        if (data.get(entity.id).flapTick > 12) {
            entity.clearVelocity();
            if (hasReachedTarget(entity, 4))
                moveThowardsTarget(entity, 0.2);
            else
                moveThowardsTarget(entity);
        }
        rotateToTarget(entity, data.get(entity.id).attackTarget.location);

        if (data.get(entity.id).ticks === 0) {
            if (Math.random() <= 0.5) {
                pickAttackPosition(entity, false, false, Math.PI / 4);
            }
            entity.setProperty("hf_mzs:spitting", true);
            //mc.world.sendMessage("Begin spit");
        }
        if (data.get(entity.id).ticks === 25) {
            //mc.world.sendMessage("Spit");
            const dist = vector.magnitude(vector.flatten(vector.subtract(entity.location,
                data.get(entity.id).attackTarget.location
            ), 'y', false));
            const spit = entity.dimension.spawnEntity("hf_mzs:poison_ball", vector.add(
                entity.getHeadLocation(), vector.multiply(entity.getViewDirection(), 2.5)));
            spit.applyImpulse(vector.add(vector.multiply(vector.flatten(entity.getViewDirection(), 'y'),
                dist * 0.09 + 0.4), { x: 0, y: 0.5, z: 0 }));
        }
        if (data.get(entity.id).ticks >= 50) {
            data.get(entity.id).state = STATES.HOVERING;
            data.get(entity.id).attackState = ATTACK_STATES.NONE
            data.get(entity.id).ticks = 0;
            data.get(entity.id).spitCooldown = SPIT_COOLDOWN;
            entity.setProperty("hf_mzs:spitting", false);
            //mc.world.sendMessage("End spit");
            return;
        }
        data.get(entity.id).ticks++;
        return;
    }
    // doing swoop attack
    if (data.get(entity.id).attackState === ATTACK_STATES.SWOOP_ATTACK) {
        //mc.world.sendMessage("test2");
        if (data.get(entity.id).ticks === 0) {
            entity.clearVelocity();
            entity.setProperty("hf_mzs:swooping", true);
            //mc.world.sendMessage("Begin spit");
        }
        if (data.get(entity.id).ticks === 13) {
            entity.clearVelocity();

            let impulseDir = vector.flatten(vector.cross(entity.getViewDirection(), vector.up), 'y');
            impulseDir = vector.multiply(impulseDir, 2);

            data.get(entity.id).swoopAnchor = vector.add(entity.location, impulseDir);
            //rotateToTarget(entity, vector.add(entity.location, impulseDir));
        }

        if (data.get(entity.id).ticks >= 13 && data.get(entity.id).ticks <= 26) {
            entity.clearVelocity();
            let impulseDir = vector.normalize(vector.cross(vector.subtract(entity.location, data.get(entity.id).swoopAnchor), vector.up));
            entity.applyImpulse(vector.multiply(impulseDir, 1.5))
            rotateToTarget(entity, vector.add(entity.location, impulseDir));
            //mc.world.sendMessage("Begin spit");
        }
        if (data.get(entity.id).ticks < 13 || (data.get(entity.id).ticks > 26 && data.get(entity.id).ticks < 30)) {
            entity.clearVelocity();
            lerpRotateToTarget(entity, data.get(entity.id).attackTarget.location);
            //mc.world.sendMessage("Begin spit");
        }
        if (data.get(entity.id).ticks >= 30 && data.get(entity.id).ticks < 36) {
            entity.clearVelocity();
            rotateToTarget(entity, data.get(entity.id).attackTarget.location);
            //mc.world.sendMessage("Begin spit");
        }

        if (data.get(entity.id).ticks == 36) {
            data.get(entity.id).targetPosition = vector.add(data.get(entity.id).attackTarget.location, vector.up);
            rotateToTarget(entity, data.get(entity.id).attackTarget.location);
        }

        // damaging part
        if (data.get(entity.id).ticks >= 36 && data.get(entity.id).ticks < 54) {
            const targetPosition = vector.add(data.get(entity.id).targetPosition, vector.up);
            const deltaPos = vector.subtract(targetPosition, entity.location);
            entity.clearVelocity();
            entity.applyImpulse(vector.multiply(vector.normalize(deltaPos), 2));

            const dist = vector.magnitude(vector.subtract(entity.location, data.get(entity.id).attackTarget.location));

            if (dist < 3) {
                data.get(entity.id).ticks = data.get(entity.id).ticks >= 50 ? data.get(entity.id).ticks : 54;
                entity.clearVelocity();
                data.get(entity.id).attackTarget.applyDamage(4, {
                    cause: "entityAttack",
                    damagingEntity: entity
                });
                data.get(entity.id).attackTarget.applyKnockback(entity.getViewDirection().x, entity.getViewDirection().z, 0.4, 0.5);
                entity.applyImpulse(vector.flatten(entity.getViewDirection(), 'y'));
            }
        }
        if (data.get(entity.id).ticks >= 54 && data.get(entity.id).ticks < 64) {
            entity.clearVelocity();
            entity.setProperty("hf_mzs:x_rot", -30);
            entity.applyImpulse(vector.add(vector.multiply(vector.flatten(entity.getViewDirection(), 'y'), 1), { x: 0, y: 0.4, z: 0 }));
        }
        if (data.get(entity.id).ticks === 64) {
            entity.clearVelocity();
        }
        if (data.get(entity.id).ticks > 64) {
            entity.clearVelocity();
            lerpRotateToTarget(entity, data.get(entity.id).attackTarget.location);
        }

        if (data.get(entity.id).ticks >= 76) {
            entity.clearVelocity();
            data.get(entity.id).state = STATES.HOVERING;
            data.get(entity.id).attackState = ATTACK_STATES.NONE
            data.get(entity.id).ticks = 0;
            data.get(entity.id).beginFlapTick = 10;
            if (data.get(entity.id).spitCooldown < 20)
                data.get(entity.id).spitCooldown = 20;
            data.get(entity.id).swoopCooldown = SWOOP_COOLDOWN
            entity.setProperty("hf_mzs:swooping", false);
            //mc.world.sendMessage("End spit");
            //entity.applyImpulse(vector.up);
            pickAttackPosition(entity, false, true);
            return;
        }
        data.get(entity.id).ticks++;
        return;
    }

    // roaming through the sky
    if (data.get(entity.id).state === STATES.ROAMING) {
        entity.setProperty("hf_mzs:hovering", false);
        if (hasReachedTarget(entity)) {
            entity.clearVelocity();
            entity.applyImpulse(vector.multiply(entity.getViewDirection(), 0.4));
            pickRoamTarget(entity);
            return;
        }
        entity.clearVelocity();
        // push naga up when too close to the ground or if obstacle is ahead
        const ground = entity.dimension.getBlockFromRay(entity.location, vector.down, {
            maxDistance: 2,
            includeLiquidBlocks: true,
            includePassableBlocks: false
        })?.block;
        if (ground)
            entity.applyImpulse(vector.multiply(vector.up, 1));
        const obstacle = entity.dimension.getBlockFromRay(entity.location, entity.getViewDirection(), {
            maxDistance: 5,
            includeLiquidBlocks: true,
            includePassableBlocks: false
        })?.block;
        if (obstacle)
            entity.applyImpulse(vector.multiply(vector.up, 1));
        // push naga back when stuck under a roof
        const roof = entity.dimension.getBlockFromRay(vector.add(entity.location, vector.multiply(entity.getViewDirection(), 3.5)), vector.up, {
            maxDistance: 5,
            includeLiquidBlocks: true,
            includePassableBlocks: false
        })?.block;
        if (roof)
            entity.applyImpulse(vector.multiply(entity.getViewDirection(), -1));


        // the tangent would equal the naga's intended direction
        const tangent = vector.cross(vector.flatten(vector.subtract(entity.location, data.get(entity.id).anchorPosition), 'y'), vector.up);
        let dir = {
            x: tangent.x,
            y: -vector.normalize(vector.subtract(entity.location, data.get(entity.id).targetPosition)).y,
            z: tangent.z
        }
        dir = vector.normalize(dir);
        const target = vector.add(dir, entity.location);
        //mc.world.sendMessage(`${vector.string(dir)}`)
        lerpRotateToTarget(entity, target);
        entity.applyImpulse(vector.multiply(entity.getViewDirection(), ROAM_IMPULSE_STRENGTH));
        //moveThowardsTarget(entity)
    }
}

// outline of the roaming behaviour:
// naga attempts to stay 15-20 block above ground, staying around where it spawned
// each tick, we rotate it to face the target, we record its rotation to create 
// smooth animations, and we apply an impulse forward, until it reaches its target
// after that, we choose a new target
function legacyPickRoamTarget(entity, attempt = 0) {
    if (data.get(entity.id).attackTarget !== undefined)
        return;
    data.get(entity.id).state = 0;
    const angle = Math.random() * 2 * Math.PI;
    const xzPosition = {
        x: entity.location.x - Math.sin(angle) * ROAM_DISTANCE,
        y: 300,
        z: entity.location.z + Math.cos(angle) * ROAM_DISTANCE
    };
    if (attempt > 10) {
        // fallback - pick random point relative to it's position
        data.get(entity.id).targetPosition = {
            x: entity.location.x - Math.sin(angle) * ROAM_DISTANCE * (3 / 2),
            y: entity.location.y,
            z: entity.location.z + Math.cos(angle) * ROAM_DISTANCE * (3 / 2)
        };
        return;
    }
    const blockAtXZ = entity.dimension.getBlockFromRay(xzPosition, vector.down, {
        maxDistance: 400,
        includeLiquidBlocks: true,
        includePassableBlocks: false
    })?.block;
    // normal behaviour
    if (blockAtXZ !== undefined) {
        const targetPosition = {
            x: xzPosition.x,
            y: blockAtXZ.location.y + 15 + Math.random() * 5,
            z: xzPosition.z
        };
        const dist = vector.magnitude(vector.subtract(data.get(entity.id).spawnPosition, targetPosition));
        if (dist >= 64) {
            pickRoamTarget(entity, attempt + 1);
            return;
        }
        const ray = entity.dimension.getBlockFromRay(entity.location,
            vector.subtract(targetPosition, entity.location), {
            maxDistance: vector.magnitude(vector.subtract(targetPosition, entity.location)),
            includeLiquidBlocks: false,
            includePassableBlocks: false
        });
        if (ray !== undefined) {
            pickRoamTarget(entity, attempt + 1);
            return;
        }
        data.get(entity.id).targetPosition = targetPosition;
        return;
    }
    pickRoamTarget(entity, attempt + 1);
    return;
}
// pick an anchor A some distance R away from the naga N, and then pick another point T which is
// R away from A; <TNR e [90, 180]
// we are essentially rotating the naga around an anchor, creating an arc
function pickRoamTarget(entity, attempt = 0) {
    if (data.get(entity.id).attackTarget !== undefined)
        return;
    data.get(entity.id).state = 0;
    const rlDir = vector.cross(vector.flatten(entity.getViewDirection(), 'y'), Math.random() < 0.5 ? vector.up : vector.down);
    const xzPosition = {
        x: entity.location.x + rlDir.x * ROAM_DISTANCE,
        y: 300,
        z: entity.location.z + rlDir.z * ROAM_DISTANCE
    };
    if (attempt > 10) {
        const angle = Math.random() * Math.PI * 2;
        // fallback - pick random point relative to it's position
        data.get(entity.id).targetPosition = {
            x: entity.location.x - Math.sin(angle) * ROAM_DISTANCE * (3 / 2),
            y: entity.location.y,
            z: entity.location.z + Math.cos(angle) * ROAM_DISTANCE * (3 / 2)
        };
        return;
    }
    const blockAtXZ = entity.dimension.getBlockFromRay(xzPosition, vector.down, {
        maxDistance: 800,
        includeLiquidBlocks: true,
        includePassableBlocks: false
    })?.block;
    // normal behaviour
    if (blockAtXZ === undefined) {
        pickRoamTarget(entity, attempt + 1);
        return;
    }
    const angle = Math.PI / 2 + Math.random() * Math.PI / 2;
    const targetPosition = {
        x: xzPosition.x - Math.sin(angle) * ROAM_DISTANCE,
        y: blockAtXZ.location.y + 15 + Math.random() * 5,
        z: xzPosition.z + Math.cos(angle) * ROAM_DISTANCE
    };
    const dist = vector.magnitude(vector.flatten(vector.subtract(data.get(entity.id).spawnPosition, targetPosition), 'y', false));
    if (dist >= 48) {
        pickRoamTarget(entity, attempt + 1);
        return;
    }
    const ray = entity.dimension.getBlockFromRay(entity.location,
        vector.subtract(targetPosition, entity.location), {
        maxDistance: vector.magnitude(vector.subtract(targetPosition, entity.location)),
        includeLiquidBlocks: false,
        includePassableBlocks: false
    });
    if (ray !== undefined) {
        pickRoamTarget(entity, attempt + 1);
        return;
    }
    //mc.world.sendMessage(`${vector.string(xzPosition)}    ${vector.string(targetPosition)}`)
    data.get(entity.id).anchorPosition = {
        x: xzPosition.x,
        y: targetPosition.y,
        z: xzPosition.z
    };
    data.get(entity.id).targetPosition = targetPosition;
    return;
}


function lerpRotateToTarget(entity, targetPosition) {
    try {
        const direction = vector.normalize(vector.subtract(targetPosition, entity.location));
        let targetY = -Math.atan2(direction.x, direction.z) / Math.PI * 180 + 360;
        let currY = entity.getProperty("hf_mzs:y_rot");
        //mc.world.sendMessage(`${targetY-currY}`);
        if (targetY >= 538 || entity.getProperty("hf_mzs:y_rot") >= 538) {
            targetY -= 358;
            currY -= 358;
        }

        const delta = utils.clamp(targetY - currY, -8, 8) * 0.2;
        const rotation = {
            x: utils.lerp(entity.getRotation().x, Math.acos(direction.y) / Math.PI * 180 - 90, 0.1),
            y: currY + delta
        };
        //mc.world.sendMessage(`${rotation.y}    ${currY}    ${targetY}`);
        entity.setProperty("hf_mzs:x_rot", rotation.x);
        entity.setProperty("hf_mzs:y_rot", Math.max(180, rotation.y));
        entity.setRotation(rotation);
    }
    catch (e) { }
}
function rotateToTarget(entity, targetPosition) {
    const direction = vector.normalize(vector.subtract(targetPosition, entity.location));
    let targetY = -Math.atan2(direction.x, direction.z) / Math.PI * 180 + 360;
    if (targetY >= 538) {
        targetY -= 358;
    }
    //mc.world.sendMessage(`${targetY}`);

    const rotation = {
        x: Math.acos(direction.y) / Math.PI * 180 - 90,
        y: targetY
    };

    entity.setProperty("hf_mzs:x_rot", rotation.x);
    entity.setProperty("hf_mzs:y_rot", rotation.y);
    entity.setRotation(rotation);
}
function hasReachedTarget(entity, range = 2.5) {
    const targetPosition = data.get(entity.id).targetPosition;
    const deltaPos = vector.subtract(targetPosition, entity.location);
    //mc.world.sendMessage(`${vector.magnitude(deltaPos)}`)
    if (vector.magnitude(deltaPos) <= range) {
        //mc.world.sendMessage(`Target reached`)
        return true;
    }
    return false;
}



function getAttackTarget(entity) {
    if (entity.getProperty("hf_mzs:peaceful")) {
        data.get(entity.id).state = 0;
        data.get(entity.id).attackTarget = undefined;
        return;
    }
    const players = entity.dimension.getEntities({
        families: ["player"],
        maxDistance: 128,
        location: entity.location
    });
    for (let i = 0; i < players.length; i++) {
        const player = players[i];
        if (player.getGameMode() === "creative" || player.getGameMode() === "spectator")
            continue;
        if (vector.magnitude(vector.flatten(vector.subtract(player.location, entity.location), 'y', false)) > 24)
            continue;
        data.get(entity.id).oldAttackTargetPosition = player.location;
        if (data.get(entity.id).attackTarget !== player) {
            data.get(entity.id).attackTarget = player;
            if (data.get(entity.id).state === 0)
                pickAttackPosition(entity, true);
            data.get(entity.id).state = 1;
        }
        return;
    }
    data.get(entity.id).state = 0;
    data.get(entity.id).attackTarget = undefined;
    return;
}
function pickAttackPosition(entity, isNew = false, isFromCurrentPos = false, addedAngle = 0) {
    let angle;
    if (isFromCurrentPos) {
        const direction = vector.subtract(data.get(entity.id).attackTarget.location, entity.location);
        angle = Math.atan2(direction.x, direction.z);
        data.get(entity.id).rotationFromAttackTarget = angle;
    }
    else if (!isNew) {
        angle = data.get(entity.id).rotationFromAttackTarget + addedAngle;
        data.get(entity.id).rotationFromAttackTarget = angle;
    }
    else {
        angle = Math.random() * 2 * Math.PI;
        data.get(entity.id).rotationFromAttackTarget = angle;
    }
    const player = data.get(entity.id).attackTarget.location;
    let targetPosition = {
        x: player.x - (Math.sin(angle) * (11)),
        y: player.y + 9,
        z: player.z + (Math.cos(angle) * (11))
    };
    //mc.world.sendMessage("test");
    //targetPosition = vector.add(targetPosition, vector.flatten(vector.random(6), 'y', false));
    data.get(entity.id).targetPosition = targetPosition;
    return;
}
function moveThowardsTarget(entity, speed = ROAM_IMPULSE_STRENGTH) {
    const targetPosition = data.get(entity.id).targetPosition;
    const deltaPos = vector.subtract(targetPosition, entity.location);
    entity.applyImpulse(vector.multiply(vector.normalize(deltaPos), speed));
}
function dodgeArrow(entity, arrowDirection, arrowPosition) {
    const dir = vector.flatten(vector.subtract(entity.location, arrowPosition), 'y');
    let arrow = vector.flatten(arrowDirection, 'y');
    arrow.x *= -1;
    //mc.world.sendMessage(`Arrow Direction: x = ${arrow.x}, z = ${arrow.z}`);
    //mc.world.sendMessage(`Entity Direction: x = ${dir.x}, z = ${dir.z}`);
    const angle = (arrow.z * dir.x) - (arrow.x * dir.z);
    //mc.world.sendMessage(`Cross Product Value: ${angle}`);
    //mc.world.sendMessage(`${angle>0 ? "right" : "left"} `)

    const impulseDir = vector.flatten(vector.cross(entity.getViewDirection(), vector.up), 'y');
    entity.applyImpulse(vector.multiply(impulseDir, angle >= 0 ? 1 : -1));
}