import * as mc from "@minecraft/server";
import * as utils from "./utils/index";
/**
 * The tag that is detected to represent a player is performing an attack
 */
const attackTag = 'using_item';
/**
 * The default attack data
 */
const attackDataDefaults = { attackID: '', damage: 10, timeInTicks: 4, minAngle: -45, maxAngle: 45, degreeCheck: 5, range: 5, piercing: false, mobEffects: [], functionList: [], callback: () => { } };
/**
 * The class that uses the IAttackData interface
 */
class AttackData {
    /**
     * Creates a new AttackData from the interface options
     * @param data The IAttackData to be used by this attack
     */
    constructor(data) {
        this.data = data;
    }
}
/**
 * This list of custom attacks associated with the project
 */
const attackDataList = [
    new AttackData(Object.assign(Object.assign({}, attackDataDefaults), { attackID: 'using_magnet', damage: 0, timeInTicks: 1, minAngle: 0, maxAngle: 0, callback: (hitEntity, player) => {
            if (hitEntity.typeId.includes('breeze')) {
                const loot_table = hitEntity.typeId.replace(/.+:/, 'entities/genes/');
                utils.runCommand(`loot spawn ${hitEntity.location.x} ${hitEntity.location.y} ${hitEntity.location.z} loot "${loot_table}"`, hitEntity);
            }
        } })),
];
/**
 * Starts the attack detection system
 */
export function runAttacks() {
    utils.waitForLoopEvent(1, () => {
        for (const player of mc.world.getPlayers({ tags: [attackTag] })) {
            for (const attack of attackDataList) {
                if (player.hasTag(attack.data.attackID)) {
                    performAttack(player, attack);
                    break;
                }
            }
        }
        return false;
    });
}
/**
 * Causes a player to perform an attack
 * @param player The player who is performing an attack
 * @param attack The attack that is being performed
 */
async function performAttack(player, attack) {
    player.removeTag(attackTag);
    player.removeTag(attack.data.attackID);
    const angleIncrementAmount = Math.round((Math.abs(attack.data.minAngle) + Math.abs(attack.data.maxAngle)) / attack.data.timeInTicks);
    let currentAngle = attack.data.maxAngle;
    for (let tick = 0; tick < attack.data.timeInTicks; tick++) {
        let entities = [];
        for (let angle = currentAngle; angle >= currentAngle - angleIncrementAmount; angle -= attack.data.degreeCheck) {
            const direction = new utils.Vector3(player.getViewDirection()).rotateByAngle(angle).normalize();
            entities.push(...getEntitiesFromRaycast(player, attack.data.range, direction, { stopAtFirstHit: !attack.data.piercing }));
        }
        entities.forEach(entity => {
            var _a, _b, _c;
            entity.applyDamage(attack.data.damage, { cause: mc.EntityDamageCause.entityAttack, damagingEntity: player });
            if (utils.isEntityValid(entity)) {
                for (const effect of attack.data.mobEffects) {
                    utils.runCommand(`effect @s ${effect.effect} ${(_a = effect.duration) !== null && _a !== void 0 ? _a : 5} ${(_b = effect.amplifier) !== null && _b !== void 0 ? _b : 0} ${(_c = effect.hideParticles) !== null && _c !== void 0 ? _c : false}`, entity);
                }
                for (const func of attack.data.functionList) {
                    utils.runCommand(`function ${func}`, entity);
                }
            }
            attack.data.callback(entity, player);
        });
        currentAngle -= angleIncrementAmount;
        await utils.waitForEvent(1, () => { });
    }
}
/**
 * Performs a raycast from an entity and getting entities hit by the ray
 * @param entity The entity from whom the raycast originates
 * @param distance The distance from the entity the raycast should travel (in blocks)
 * @param direction The normalized Vector3 representing the direction the raycast travels in
 * @param options Additional options for how the raycast should interact with the world
 * @returns An array of entities hit by the raycast
 */
function getEntitiesFromRaycast(entity, distance, direction, options) {
    var _a;
    const origin = new utils.Vector3(entity.getHeadLocation());
    let entitiesOnRay = entity.dimension.getEntitiesFromRay(origin, direction, { maxDistance: distance });
    if ((_a = options.ignoreOwner) !== null && _a !== void 0 ? _a : true) {
        if (entitiesOnRay.length > 0)
            entitiesOnRay.shift();
    }
    if (!options.passThroughSolidBlocks) {
        const blockHit = entity.dimension.getBlockFromRay(origin, direction, { maxDistance: distance });
        if (blockHit) {
            const blockHitLocation = new utils.Vector3(blockHit.block.location).add(blockHit.faceLocation);
            const blockHitDistance = blockHitLocation.distance(origin);
            entitiesOnRay = entitiesOnRay.filter(entityHit => entityHit.distance < blockHitDistance);
        }
    }
    if (options.stopAtFirstHit && entitiesOnRay.length > 0) {
        entitiesOnRay.length = 1;
    }
    return entitiesOnRay.map(entityHit => entityHit.entity);
}
