var _a;
import * as mc from "@minecraft/server";
import * as log from './log';
import { end, nether, overworld } from "./dimension";
import { addName, GlobalNamespace } from "./namespace";
import { Vector3 } from "./vector3";
import { getScore, modifyScore, setScore } from "./scores";
import { waitForWorldLoad } from "./events";
import { readDynamicProperty, writeDynamicProperty } from "./properties";
// #region Safety Wrappers
function emptyObjectCreator(obj) {
    let newObj = {};
    Object.getOwnPropertyNames(obj).forEach(property => {
        newObj[property] = function () { };
    });
    return newObj;
}
class SoundManager {
    static registerSound(sound, emitter, listeners) {
        var _b, _c;
        if (!listeners)
            listeners = getAllPlayersSafe().map(player => player.id);
        for (const listener of listeners) {
            const listenerData = (_b = this.soundRegister.get(listener)) !== null && _b !== void 0 ? _b : new Map();
            const emitterData = (_c = listenerData.get(emitter)) !== null && _c !== void 0 ? _c : [];
            emitterData.push(sound);
            listenerData.set(emitter, emitterData);
            this.soundRegister.set(listener, listenerData);
        }
    }
    static stopSound(emitter, listeners) {
        if (!listeners)
            listeners = getAllPlayersSafe().map(player => player.id);
        for (const listener of listeners) {
            const listenerData = this.soundRegister.get(listener);
            const listenerEntity = getEntitySafe(listener);
            if (listenerData && (listenerEntity === null || listenerEntity === void 0 ? void 0 : listenerEntity.isValid())) {
                const emitterData = listenerData.get(emitter);
                if (emitterData) {
                    for (const sound of emitterData) {
                        listenerEntity.runCommand(`stopsound @s ${sound}`);
                    }
                }
            }
        }
    }
}
_a = SoundManager;
SoundManager.soundRegister = new Map();
(() => {
    mc.world.afterEvents.playerLeave.subscribe(event => {
        _a.soundRegister.delete(event.playerId);
    });
    mc.world.afterEvents.entityRemove.subscribe(event => {
        for (const entry of _a.soundRegister) {
            entry[1].delete(event.removedEntityId);
            _a.soundRegister.set(entry[0], entry[1]);
        }
    });
})();
/**
 * @remarks A safety wrapper for {@link mc.Entity}, performs a validation check before accessing.
 */
export class SafetyCheckedEntity {
    get UnsafeEntity() {
        return this.unsafeEntity;
    }
    // #region Custom Getters
    /**
     * @remarks Gets a {@link mc.EntityHealthComponent} or undefined if the entity doesn't have the component.
     */
    get Health() {
        return this.getComponent("health");
    }
    /**
     * @remarks Gets a {@link mc.Container} or undefined if the entity doesn't have an inventory component.
     */
    get Inventory() {
        const inventory = this.getComponent("inventory");
        return inventory === null || inventory === void 0 ? void 0 : inventory.container;
    }
    /**
     * @remarks Gets a {@link mc.EntityEquippableComponent} or undefined if the entity doesn't have the component.
     */
    get Equipment() {
        return this.getComponent("equippable");
    }
    /**
     * @remarks Gets an {@link mc.ItemStack} or undefined if no equipment is present or no item is held.
     */
    get MainHand() {
        var _b;
        return (_b = this.Equipment) === null || _b === void 0 ? void 0 : _b.getEquipment(mc.EquipmentSlot.Mainhand);
    }
    /**
     * @remarks Sets an {@link mc.ItemStack} or undefined to the main hand if the entity has an equippable component.
     */
    set MainHand(v) {
        var _b;
        (_b = this.Equipment) === null || _b === void 0 ? void 0 : _b.setEquipment(mc.EquipmentSlot.Mainhand, v);
    }
    /**
     * @remarks Gets the relative up vector based on an entity's rotation, useful for providing as the cross axis in {@link Vector3.rotateYaw}.
     */
    get RelativeUp() {
        const rotation = this.getRotation();
        const pitch = (-rotation.x + 90) * (Math.PI / 180);
        const yaw = (rotation.y + 90) * (Math.PI / 180);
        let x = Math.cos(yaw) * Math.cos(pitch);
        let y = Math.sin(pitch);
        let z = Math.sin(yaw) * Math.cos(pitch);
        return new Vector3({ x, y, z });
    }
    // #endregion
    // #region Custom Methods
    /**
     * @remarks Gets the level of an effect on this entity.
     * @param effectId The effect identifier to check.
     * @returns The level of the effect (where "Strength II" would return 3 and "Strength" would return 1). 0 if the effect is not present.
     */
    getEffectLevel(effectId) {
        if (!this.isValid())
            return 0;
        try {
            const effect = this.getEffect(effectId);
            if (effect)
                return effect.amplifier + 1;
            else
                return 0;
        }
        catch (error) {
            log.mcerror(`${effectId} is not a valid effect.`);
            return 0;
        }
    }
    /**
     * @remarks Plays a sound at the entity's head location.
     * @param soundId The sound to play.
     * @param soundOptions Options for the sound, the pitch, volume, and offset from the entity head location.
     */
    playSoundAt(soundId, soundOptions) {
        if (!this.isValid())
            return;
        let location = this.getHeadLocation();
        if (soundOptions === null || soundOptions === void 0 ? void 0 : soundOptions.offset)
            location = location.add(soundOptions.offset);
        if (soundOptions === null || soundOptions === void 0 ? void 0 : soundOptions.players) {
            for (const player of soundOptions.players) {
                player.playSound(soundId, Object.assign({ location }, soundOptions));
            }
            SoundManager.registerSound(soundId, this.id, soundOptions.players.map(player => player.id));
            return;
        }
        this.dimension.playSound(soundId, location, soundOptions);
        SoundManager.registerSound(soundId, this.id);
    }
    /**
     * @remarks Stops a playing sound. If no sound ID is provided, all sounds for the entity will be stopped. If no players are provided, the sound will be stopped for all players.
     * @param data Optional arguments for what sound should be stopped and for what players.
     */
    stopSound(data) {
        var _b;
        if (!data) {
            SoundManager.stopSound(this.id);
            return;
        }
        if ((data === null || data === void 0 ? void 0 : data.players) && data.soundId) {
            data.players.forEach(player => player.runCommand(`stopsound @s ${data.soundId}`));
            return;
        }
        const players = (_b = data.players) === null || _b === void 0 ? void 0 : _b.map(player => player.id);
        SoundManager.stopSound(this.id, players);
    }
    /**
     * @remarks Gets this entity as a safety checked player. May be undefined if the entity is not a player.
     * @returns A safety checked player, or undefined if the entity is not a player.
     */
    getPlayer() {
        if (this.UnsafeEntity instanceof mc.Player) {
            return new SafetyCheckedPlayer(this.UnsafeEntity);
        }
    }
    /**
     * @remarks Sets the entity's score. This will automatically initialize the objective and add the entity to the scoreboard if needed.
     * @param objectiveID The objective name to set.
     * @param value The value the scoreboard should be set to.
     */
    setScore(objectiveID, value) {
        if (this.isValid()) {
            setScore(this.UnsafeEntity, objectiveID, value);
        }
    }
    /**
     * @remarks Modifies the entity's score by the value provided. This will automatically initialize the objective and add the entity to the scoreboard if needed.
     * @param objectiveID The objective name to set.
     * @param value The amount to modify the score by.
     * @returns The value of the target's objective score.
     */
    modifyScore(objectiveID, value) {
        if (this.isValid()) {
            modifyScore(this.UnsafeEntity, objectiveID, value);
        }
    }
    /**
     * @remarks Checks the entity's score. This will automatically initialize the objective and add the entity to the scoreboard if needed.
     * @param objectiveID The objective name to test.
     * @returns The value of the target's objective score.
     */
    getScore(objectiveID) {
        if (this.isValid()) {
            return getScore(this.UnsafeEntity, objectiveID);
        }
        else {
            return 0;
        }
    }
    /**
     * @remarks Gets the dot product view difference on the y axis between this entity and a location. This can be used to approximate whether or not the entity is looking at the location.
     * @param location The location to check the dot product against.
     * @returns The dot product view difference where 1 = facing the location, 0 = perpendicular to the location, -1 = facing away from the location.
     */
    getViewDotProduct(location) {
        const testPoint = location.subtract(this.location).normalize();
        return this.getViewDirection().dot(testPoint);
    }
    /**
     * @remarks Tallies instances of the specified item in this entity's inventory.
     * @param itemName Identifier of the item or block.
     * @param states Applicable only for blocks. An optional set of states to compare against. If states is not specified, matches checks against the set of types more broadly.
     * @returns The amount of the item this entity is carrying.
     * @example
     * Block Resolution
     * ```typescript
     * player.getItemCount("minecraft:shulker_box", {color: 0});
     * ```
     * Item Resolution
     * ```typescript
     * player.getItemCount("minecraft:white_shulker_box");
     * ```
     */
    getItemCount(itemName, states) {
        let blockItem;
        try {
            blockItem = mc.BlockPermutation.resolve(itemName, states).getItemStack();
        }
        catch (error) { }
        if (this.isValid()) {
            let count = 0;
            const inventory = this.Inventory;
            if (!inventory)
                return 0;
            for (let index = 0; index < inventory.size; index++) {
                const element = inventory.getItem(index);
                if (element && ((blockItem === null || blockItem === void 0 ? void 0 : blockItem.typeId) === element.typeId || itemName === element.typeId)) {
                    count += element.amount;
                }
            }
            return count;
        }
        else {
            return 0;
        }
    }
    /**
     * @remarks Analog to `^ ^ ^` in traditional Minecraft commands. Gets the local coordinates relative to this entity.
     * @param offset Relative coordinate to this entity.
     * @param useHeadAsOrigin Use the entity's head as the origin instead of the feet. Defaults to true
     * @returns Local coordinate relative the entity.
     */
    toLocalOffset(offset, useHeadAsOrigin = true) {
        if (useHeadAsOrigin) {
            return this.getHeadLocation().toLocal(this.getViewDirection(), offset);
        }
        else {
            return this.location.toLocal(this.getViewDirection(), offset);
        }
    }
    // #endregion
    get dimension() {
        if (this.isValid()) {
            return this.UnsafeEntity.dimension;
        }
        else {
            log.mcerror(`dimension Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return overworld;
        }
    }
    get id() {
        return this.UnsafeEntity.id;
    }
    get isClimbing() {
        if (this.isValid()) {
            return this.UnsafeEntity.isClimbing;
        }
        else {
            log.mcerror(`isClimbing Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isFalling() {
        if (this.isValid()) {
            return this.UnsafeEntity.isFalling;
        }
        else {
            log.mcerror(`isFalling Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isInWater() {
        if (this.isValid()) {
            return this.UnsafeEntity.isInWater;
        }
        else {
            log.mcerror(`isInWater Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isOnGround() {
        if (this.isValid()) {
            return this.UnsafeEntity.isOnGround;
        }
        else {
            log.mcerror(`isOnGround Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isSleeping() {
        if (this.isValid()) {
            return this.UnsafeEntity.isSleeping;
        }
        else {
            log.mcerror(`isSleeping Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isSneaking() {
        if (this.isValid()) {
            return this.UnsafeEntity.isSneaking;
        }
        else {
            log.mcerror(`isSneaking Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isSprinting() {
        if (this.isValid()) {
            return this.UnsafeEntity.isSprinting;
        }
        else {
            log.mcerror(`isSprinting Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isSwimming() {
        if (this.isValid()) {
            return this.UnsafeEntity.isSwimming;
        }
        else {
            log.mcerror(`isSwimming Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get location() {
        if (this.isValid()) {
            return new Vector3(this.UnsafeEntity.location);
        }
        else {
            log.mcerror(`location Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return new Vector3(0);
        }
    }
    get nameTag() {
        if (this.isValid()) {
            return this.UnsafeEntity.nameTag;
        }
        else {
            log.mcerror(`nameTag Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return '';
        }
    }
    set nameTag(v) {
        if (this.isValid()) {
            this.UnsafeEntity.nameTag = v;
        }
        else {
            log.mcerror(`nameTag Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    get scoreboardIdentity() {
        if (this.isValid()) {
            return this.UnsafeEntity.scoreboardIdentity;
        }
        else {
            log.mcerror(`scoreboardIdentity Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    get typeId() {
        return this.UnsafeEntity.typeId;
    }
    /**
     * @remarks Creates a safety checked entity.
     * @param entity The entity to wrap.
     * @example
     * ```typescript
     * new SafetyCheckedEntity(entity);
     * ```
     */
    constructor(entity) {
        if (entity instanceof SafetyCheckedEntity) {
            entity = entity.UnsafeEntity;
        }
        this.unsafeEntity = entity;
    }
    addEffect(effectType, duration, options) {
        if (this.isValid()) {
            return this.UnsafeEntity.addEffect(effectType, duration, options);
        }
        else {
            log.mcerror(`addEffect Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    addTag(tag) {
        if (this.isValid()) {
            if (!tag.includes(":"))
                tag = addName(tag);
            return this.UnsafeEntity.addTag(tag);
        }
        else {
            log.mcerror(`addTag Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    applyDamage(amount, options) {
        if (this.isValid()) {
            if ((options === null || options === void 0 ? void 0 : options.damagingEntity) instanceof SafetyCheckedEntity) {
                options.damagingEntity = options.damagingEntity.unsafeEntity;
            }
            return this.UnsafeEntity.applyDamage(amount, options);
        }
        else {
            log.mcerror(`applyDamage Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    applyImpulse(vector) {
        if (this.isValid()) {
            return this.UnsafeEntity.applyImpulse(vector);
        }
        else {
            log.mcerror(`applyImpulse Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    applyKnockback(directionX, directionZ, horizontalStrength, verticalStrength) {
        if (this.isValid()) {
            return this.UnsafeEntity.applyKnockback(directionX, directionZ, horizontalStrength, verticalStrength);
        }
        else {
            log.mcerror(`applyKnockback Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    clearDynamicProperties() {
        if (this.isValid()) {
            return this.UnsafeEntity.clearDynamicProperties();
        }
        else {
            log.mcerror(`clearDynamicProperties Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    clearVelocity() {
        if (this.isValid()) {
            return this.UnsafeEntity.clearVelocity();
        }
        else {
            log.mcerror(`clearVelocity Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    extinguishFire() {
        if (this.isValid()) {
            return this.unsafeEntity.extinguishFire();
        }
        else {
            log.mcerror(`extinguishFire Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    getBlockFromViewDirection(options) {
        if (this.isValid()) {
            return this.UnsafeEntity.getBlockFromViewDirection(options);
        }
        else {
            log.mcerror(`getBlockFromViewDirection Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getComponent(componentId) {
        if (this.isValid()) {
            return this.UnsafeEntity.getComponent(componentId);
        }
        else {
            log.mcerror(`getComponent Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getComponents() {
        if (this.isValid()) {
            return this.UnsafeEntity.getComponents();
        }
        else {
            log.mcerror(`getComponents Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return [];
        }
    }
    getDynamicProperty(identifier) {
        if (this.isValid()) {
            return readDynamicProperty(this.UnsafeEntity, identifier);
        }
        else {
            log.mcerror(`getDynamicProperty Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getDynamicPropertyIds() {
        if (this.isValid()) {
            return this.UnsafeEntity.getDynamicPropertyIds();
        }
        else {
            log.mcerror(`getDynamicPropertyIds Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return [];
        }
    }
    getDynamicPropertyTotalByteCount() {
        if (this.isValid()) {
            return this.UnsafeEntity.getDynamicPropertyTotalByteCount();
        }
        else {
            log.mcerror(`getDynamicPropertyTotalByteCount Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    getEffect(effectType) {
        if (this.isValid()) {
            return this.UnsafeEntity.getEffect(effectType);
        }
        else {
            log.mcerror(`getEffect Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getEffects() {
        if (this.isValid()) {
            return this.UnsafeEntity.getEffects();
        }
        else {
            log.mcerror(`getEffects Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return [];
        }
    }
    getEntitiesFromViewDirection(options) {
        if (this.isValid()) {
            return this.UnsafeEntity.getEntitiesFromViewDirection(options);
        }
        else {
            log.mcerror(`getEntitiesFromViewDirection Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return [];
        }
    }
    getHeadLocation() {
        if (this.isValid()) {
            return new Vector3(this.UnsafeEntity.getHeadLocation());
        }
        else {
            log.mcerror(`getHeadLocation Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return new Vector3(0);
        }
    }
    getProperty(identifier) {
        if (this.isValid()) {
            if (!identifier.includes(':'))
                identifier = GlobalNamespace.Namespace + identifier;
            return this.UnsafeEntity.getProperty(identifier);
        }
        else {
            log.mcerror(`getProperty Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getRotation() {
        if (this.isValid()) {
            return this.UnsafeEntity.getRotation();
        }
        else {
            log.mcerror(`getRotation Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return { x: 0, y: 0 };
        }
    }
    getTags() {
        if (this.isValid()) {
            return this.UnsafeEntity.getTags().map(tag => tag.replace(GlobalNamespace.Namespace, ''));
        }
        else {
            log.mcerror(`getTags Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return [];
        }
    }
    getVelocity() {
        if (this.isValid()) {
            return new Vector3(this.UnsafeEntity.getVelocity());
        }
        else {
            log.mcerror(`getVelocity Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return new Vector3(0);
        }
    }
    getViewDirection() {
        if (this.isValid()) {
            return new Vector3(this.UnsafeEntity.getViewDirection());
        }
        else {
            log.mcerror(`getViewDirection Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return new Vector3(0);
        }
    }
    hasComponent(componentId) {
        if (this.isValid()) {
            return this.UnsafeEntity.hasComponent(componentId);
        }
        else {
            log.mcerror(`hasComponent Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    hasTag(tag) {
        if (this.isValid()) {
            if (!tag.includes(":"))
                tag = addName(tag);
            return this.UnsafeEntity.hasTag(tag);
        }
        else {
            log.mcerror(`hasTag Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    isValid() {
        return this.UnsafeEntity.isValid();
    }
    kill() {
        if (this.isValid()) {
            return this.UnsafeEntity.kill();
        }
        else {
            log.mcerror(`kill Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    matches(options) {
        if (this.isValid()) {
            return this.UnsafeEntity.matches(options);
        }
        else {
            log.mcerror(`matches Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    playAnimation(animationName, options) {
        if (this.isValid()) {
            return this.UnsafeEntity.playAnimation(animationName, options);
        }
        else {
            log.mcerror(`playAnimation Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    remove() {
        if (this.isValid()) {
            return this.UnsafeEntity.remove();
        }
        else {
            log.mcerror(`remove Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    removeEffect(effectType) {
        if (this.isValid()) {
            return this.UnsafeEntity.removeEffect(effectType);
        }
        else {
            log.mcerror(`removeEffect Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    removeTag(tag) {
        if (this.isValid()) {
            if (!tag.includes(":"))
                tag = addName(tag);
            return this.UnsafeEntity.removeTag(tag);
        }
        else {
            log.mcerror(`removeTag Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    resetProperty(identifier) {
        if (this.isValid()) {
            return this.UnsafeEntity.resetProperty(identifier);
        }
        else {
            log.mcerror(`resetProperty Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    runCommand(commandString) {
        if (this.isValid()) {
            return this.UnsafeEntity.runCommand(commandString);
        }
        else {
            log.mcerror(`runCommand Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return mc.CommandResult.prototype;
        }
    }
    runCommandAsync(commandString) {
        if (this.isValid()) {
            return this.UnsafeEntity.runCommandAsync(commandString);
        }
        else {
            log.mcerror(`runCommandAsync Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return new Promise(resolve => resolve(mc.CommandResult.prototype));
        }
    }
    setDynamicProperty(identifier, value) {
        if (this.isValid()) {
            return writeDynamicProperty(this.UnsafeEntity, identifier, value);
        }
        else {
            log.mcerror(`setDynamicProperty Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    setOnFire(seconds, useEffects) {
        if (this.isValid()) {
            return this.unsafeEntity.setOnFire(seconds, useEffects);
        }
        else {
            log.mcerror(`setOnFire Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    setProperty(identifier, value) {
        if (this.isValid()) {
            if (!identifier.includes(':'))
                identifier = GlobalNamespace.Namespace + identifier;
            return this.UnsafeEntity.setProperty(identifier, value);
        }
        else {
            log.mcerror(`setProperty Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    setRotation(rotation) {
        if (this.isValid()) {
            return this.UnsafeEntity.setRotation(rotation);
        }
        else {
            log.mcerror(`setRotation Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    teleport(location, teleportOptions) {
        if (this.isValid()) {
            return this.UnsafeEntity.teleport(location, teleportOptions);
        }
        else {
            log.mcerror(`teleport Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    triggerEvent(eventName) {
        if (this.isValid()) {
            return this.UnsafeEntity.triggerEvent(eventName);
        }
        else {
            log.mcerror(`triggerEvent Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    tryTeleport(location, teleportOptions) {
        if (this.isValid()) {
            return this.UnsafeEntity.tryTeleport(location, teleportOptions);
        }
        else {
            log.mcerror(`tryTeleport Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
}
/**
 * @remarks A safety wrapper for {@link mc.Player}, performs a validation check before accessing.
 */
export class SafetyCheckedPlayer extends SafetyCheckedEntity {
    get UnsafeEntity() {
        return this.unsafeEntity;
    }
    get inputInfo() {
        if (this.isValid()) {
            return this.UnsafeEntity.inputInfo;
        }
        else {
            return emptyObjectCreator(mc.InputInfo.prototype);
        }
    }
    get clientSystemInfo() {
        if (this.isValid()) {
            return this.UnsafeEntity.clientSystemInfo;
        }
        else {
            return emptyObjectCreator(mc.ClientSystemInfo.prototype);
        }
    }
    get inputPermissions() {
        if (this.isValid()) {
            return this.UnsafeEntity.inputPermissions;
        }
        else {
            return emptyObjectCreator(mc.PlayerInputPermissions.prototype);
        }
    }
    get camera() {
        if (this.isValid()) {
            return this.UnsafeEntity.camera;
        }
        else {
            log.mcerror(`camera Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return emptyObjectCreator(mc.Camera.prototype);
        }
    }
    get isEmoting() {
        if (this.isValid()) {
            return this.UnsafeEntity.isEmoting;
        }
        else {
            log.mcerror(`isEmoting Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isFlying() {
        if (this.isValid()) {
            return this.UnsafeEntity.isFlying;
        }
        else {
            log.mcerror(`isFlying Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isGliding() {
        if (this.isValid()) {
            return this.UnsafeEntity.isGliding;
        }
        else {
            log.mcerror(`isGliding Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get isJumping() {
        if (this.isValid()) {
            return this.UnsafeEntity.isJumping;
        }
        else {
            log.mcerror(`isJumping Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return false;
        }
    }
    get level() {
        if (this.isValid()) {
            return this.UnsafeEntity.level;
        }
        else {
            log.mcerror(`level Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    get name() {
        if (this.isValid()) {
            return this.UnsafeEntity.name;
        }
        else {
            log.mcerror(`name Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return '';
        }
    }
    get onScreenDisplay() {
        return this.UnsafeEntity.onScreenDisplay;
    }
    get totalXpNeededForNextLevel() {
        if (this.isValid()) {
            return this.UnsafeEntity.totalXpNeededForNextLevel;
        }
        else {
            log.mcerror(`totalXpNeededForNextLevel Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    get xpEarnedAtCurrentLevel() {
        if (this.isValid()) {
            return this.UnsafeEntity.xpEarnedAtCurrentLevel;
        }
        else {
            log.mcerror(`xpEarnedAtCurrentLevel Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    get selectedSlotIndex() {
        if (this.isValid()) {
            return this.UnsafeEntity.selectedSlotIndex;
        }
        else {
            log.mcerror(`selectedSlotIndex Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    set selectedSlotIndex(v) {
        if (this.isValid()) {
            this.UnsafeEntity.selectedSlotIndex = v;
        }
        else {
            log.mcerror(`selectedSlotIndex Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    /**
     * @remarks Creates a safety checked player.
     * @param entity The player to wrap.
     * @example
     * ```typescript
     * new SafetyCheckedPlayer(player);
     * ```
     */
    constructor(entity) {
        if (entity instanceof SafetyCheckedPlayer) {
            entity = entity.UnsafeEntity;
        }
        super(entity);
    }
    addExperience(amount) {
        if (this.isValid()) {
            return this.UnsafeEntity.addExperience(amount);
        }
        else {
            log.mcerror(`addExperience Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    addLevels(amount) {
        if (this.isValid()) {
            return this.UnsafeEntity.addLevels(amount);
        }
        else {
            log.mcerror(`addLevels Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    getSpawnPoint() {
        if (this.isValid()) {
            return this.UnsafeEntity.getSpawnPoint();
        }
        else {
            log.mcerror(`getSpawnPoint Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return;
        }
    }
    getTotalXp() {
        if (this.isValid()) {
            return this.UnsafeEntity.getTotalXp();
        }
        else {
            log.mcerror(`getTotalXp Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    playMusic(trackId, musicOptions) {
        if (this.isValid()) {
            return this.UnsafeEntity.playMusic(trackId, musicOptions);
        }
        else {
            log.mcerror(`playMusic Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    playSound(soundId, soundOptions) {
        if (this.isValid()) {
            return this.UnsafeEntity.playSound(soundId, soundOptions);
        }
        else {
            log.mcerror(`playSound Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    resetLevel() {
        if (this.isValid()) {
            return this.UnsafeEntity.resetLevel();
        }
        else {
            log.mcerror(`resetLevel Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    sendMessage(message) {
        if (this.isValid()) {
            return this.UnsafeEntity.sendMessage(message);
        }
        else {
            log.mcerror(`sendMessage Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    setSpawnPoint(spawnPoint) {
        if (this.isValid()) {
            return this.UnsafeEntity.setSpawnPoint(spawnPoint);
        }
        else {
            log.mcerror(`setSpawnPoint Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    spawnParticle(effectName, location, molangVariables) {
        if (this.isValid()) {
            return this.UnsafeEntity.spawnParticle(effectName, location, molangVariables);
        }
        else {
            log.mcerror(`spawnParticle Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    stopMusic() {
        if (this.isValid()) {
            return this.UnsafeEntity.stopMusic();
        }
        else {
            log.mcerror(`stopMusic Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    queueMusic(trackId, musicOptions) {
        if (this.isValid()) {
            return this.UnsafeEntity.playSound(trackId, musicOptions);
        }
        else {
            log.mcerror(`playSound Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getGameMode() {
        if (this.isValid()) {
            return this.UnsafeEntity.getGameMode();
        }
        else {
            log.mcerror(`getGameMode Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return mc.GameMode.survival;
        }
    }
    setGameMode(gameMode) {
        if (this.isValid()) {
            this.UnsafeEntity.setGameMode(gameMode);
        }
        else {
            log.mcerror(`getGameMode Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
    getItemCooldown(cooldownCategory) {
        if (this.isValid()) {
            return this.UnsafeEntity.getItemCooldown(cooldownCategory);
        }
        else {
            log.mcerror(`getItemCooldown Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
            return 0;
        }
    }
    startItemCooldown(cooldownCategory, tickDuration) {
        if (this.isValid()) {
            this.UnsafeEntity.startItemCooldown(cooldownCategory, tickDuration);
        }
        else {
            log.mcerror(`startItemCooldown Failed on ${this.typeId} Id: ${this.id}\n${new Error().stack}`);
        }
    }
}
/**
 * @remarks Returns a safety checked entity based on the provided id.
 * @param id The id of the entity.
 * @returns The requested safety checked object.
 * @example
 * ```typescript
 * const id = utils.readDynamicProperty<string>(mc.world, 'targetId');
 * if (id) {
 * 	utils.getEntitySafe(id); // Returns the safety checked entity if it exists
 * }
 * ```
 */
export function getEntitySafe(id) {
    const entity = mc.world.getEntity(id);
    if (entity) {
        return new SafetyCheckedEntity(entity);
    }
}
/**
 * @remarks Returns an array of all active players within the world.
 * @example
 * ```typescript
 * getAllPlayersSafe().forEach(safePlayer => {
 * 	safePlayer.getEffects(); // Returns the list of effects on the player. The array will be empty if the player is invalid.
 * })
 * ```
 */
export function getAllPlayersSafe() {
    return mc.world.getAllPlayers().map(player => new SafetyCheckedPlayer(player));
}
/**
 * @remarks Returns a set of safety checked players based on a set of conditions defined via the EntityQueryOptions set of filter criteria.
 * @param options Additional options that can be used to filter the set of safety checked players returned.
 * @returns A safety checked player array.
 * @example
 * ```typescript
 * getPlayersSafe({tags: ['target']}).forEach(safePlayer => {
 * 	safePlayer.getEffects(); // Returns the list of effects on the player. The array will be empty if the player is invalid.
 * });
 * ```
 */
export function getPlayersSafe(options, dimension) {
    const source = dimension !== null && dimension !== void 0 ? dimension : mc.world;
    return source.getPlayers(options).map(player => new SafetyCheckedPlayer(player));
}
/**
 * @remarks Returns a set of safety checked entities based on a set of conditions defined via the EntityQueryOptions set of filter criteria.
 * @param options Additional options that can be used to filter the set of safety checked entities returned.
 * @returns A safety checked entity array.
 * @example
 * ```typescript
 * getEntitiesSafe({tags: ['target']}).forEach(safeEntity => {
 * 	safeEntity.getEffects(); // Returns the list of effects on the entity. The array will be empty if the entity is invalid.
 * });
 * ```
 */
export function getEntitiesSafe(dimension, options) {
    return dimension.getEntities(options).map(entity => new SafetyCheckedEntity(entity));
}
/**
 * @remarks Returns a set of safety checked entities at a particular location.
 * @param location The location at which to return safety checked entities.
 * @returns Zero or more safety checked entities at the specified location.
 * @example
 * ```typescript
 * // Gets all the entities at 0 0 0 in the overworld.
 * getEntitiesAtBlockLocationSafe(overworld, new Vector3(0)).forEach(safeEntity => {
 * 	safeEntity.getEffects(); // Returns the list of effects on the entity. The array will be empty if the entity is invalid.
 * });
 * ```
 */
export function getEntitiesAtBlockLocationSafe(dimension, location) {
    return dimension.getEntitiesAtBlockLocation(location).map(entity => new SafetyCheckedEntity(entity));
}
/**
 * @remarks Gets safety checked entities that intersect with a specified vector emanating from a location.
 * @param dimension The dimension in which to raycast.
 * @param location The location from which the raycast should start.
 * @param direction The direction the raycast should travel in.
 * @param options Additional raycast options {@link mc.EntityRaycastOptions}.
 * @returns An array of hits {@link SafetyCheckedEntityRaycastHit}.
 * @example
 * ```typescript
 * const player = getAllPlayersSafe()[0];
 * getEntitiesFromRaySafe(player.dimension, player.getHeadLocation(), player.getViewDirection(), {maxDistance: 10}); // Returns an array of hits for entities in the raycast
 * ```
 */
export function getEntitiesFromRaySafe(dimension, location, direction, options) {
    return dimension.getEntitiesFromRay(location, direction, options).map(hit => {
        return {
            distance: hit.distance,
            entity: new SafetyCheckedEntity(hit.entity)
        };
    });
}
/**
 * @remarks Gets the entities that this entity is looking at by performing a ray cast from the view of this entity.
 * @param entity The entity performing the raycast.
 * @param options Additional raycast options {@link mc.EntityRaycastOptions}.
 * @returns An array of hits {@link SafetyCheckedEntityRaycastHit}.
 * @example
 * ```typescript
 * const player = getAllPlayersSafe()[0];
 * getEntitiesFromViewDirectionSafe(player, {maxDistance: 10}); // Returns an array of hits for entities in the raycast
 * ```
 */
export function getEntitiesFromViewDirectionSafe(entity, options) {
    return entity.getEntitiesFromViewDirection(options).map(hit => {
        return {
            distance: hit.distance,
            entity: new SafetyCheckedEntity(hit.entity)
        };
    });
}
/**
 * @remarks Spawns a safe entity.
 * @param dimension The dimension in which to spawn the entity.
 * @param identifier The identifier of the entity to summon.
 * @param location The location at which to summon the entity.
 * @param options Additional options for configure how the entity should be summoned.
 * @returns A {@link SafetyCheckedEntity}.
 * @example
 * ```typescript
 * utils.getAllPlayersSafe().forEach(player => {
 *		const cow = utils.spawnEntitySafe(player.dimension, "minecraft:cow", player.location.south(5), {facing: player, nameTag: "Bruce"});
 *		utils.waitForEvent(60, () => cow.remove())
 *	});
 * ```
 */
export function spawnEntitySafe(dimension, identifier, location, options) {
    var _b, _c;
    if (options) {
        let command = `summon ${identifier} ${location}`;
        if (options.rotation)
            command += ` ${options.rotation.y} ${options.rotation.x}`;
        else if (options.facing) {
            if (options.facing instanceof Vector3)
                command += ` facing ${options.facing}`;
            else
                command += ` facing ${options.facing.location}`;
        }
        else
            command += ` 0 0`;
        command += ` ${(_b = options.spawnEvent) !== null && _b !== void 0 ? _b : "minecraft:spawn_entity"}`;
        command += ` ${(_c = options.nameTag) !== null && _c !== void 0 ? _c : ""}`;
        const cachedEntityIds = dimension.getEntitiesAtBlockLocation(location).map(entity => entity.id);
        dimension.runCommand(command);
        const newEntities = dimension.getEntitiesAtBlockLocation(location).filter(entity => !cachedEntityIds.includes(entity.id));
        if (newEntities.length) {
            return new SafetyCheckedEntity(newEntities[0]);
        }
        else {
            log.mcerror(`Failed to find ${identifier} at ${location}`);
        }
    }
    return new SafetyCheckedEntity(dimension.spawnEntity(identifier, location));
}
// #endregion
/**
 * @deprecated This function is no longer useful.
 * @remarks Gets the name of the given entity via entity query options.
 * @param target The entity query options
 * @returns The name of the entity.
 * @throws This function can throw errors.
 */
export function getTargetName(target) {
    return overworld.getEntities(target).concat(nether.getEntities(target)).concat(end.getEntities(target))[0].nameTag;
}
/**
 * @deprecated This function was created before {@link mc.Entity.isValid} or {@link SafetyCheckedEntity} existed.
 * @remarks Returns true if the given entity is valid, false otherwise.
 * @param entity The entity who's validity we want to check.
 * @returns True if valid, false otherwise.
 */
export function isEntityValid(entity) {
    try {
        entity.location;
        return true;
    }
    catch (error) {
        log.mcerror(`Entity Is Invalid`, 'verbose');
        return false;
    }
}
/**
 * @remarks returns an inventory container from an entity.
 * @param entity The entity who's inventory to get.
 * @returns The inventory container or undefined if they don't have one.
 * @example
 * ```typescript
 * const player = getAllPlayersSafe()[0];
 * const container = getInventoryContainer(player);
 * ```
 */
export function getInventoryContainer(entity) {
    const inventory = entity.getComponent(mc.EntityInventoryComponent.componentId);
    if (inventory && inventory.isValid()) {
        return inventory.container;
    }
}
/**
 * @deprecated This functionality is now built directly into {@link SafetyCheckedEntity.addTag}
 * @remarks Adds a specified tag to an entity. Applies the global namespace automatically.
 * @param entity The entity the tag should be added to.
 * @param tag Content of the tag to add. The tag must be less than 256 characters.
 * @returns Returns true if the tag was added successfully. This can fail if the tag already exists on the entity.
 */
export function addTag(entity, tag) {
    if (entity.isValid()) {
        return entity.addTag(GlobalNamespace.Namespace + tag);
    }
    else {
        log.mcwarn(`Tried to add tag: ${tag} on invalid entity of type: ${entity.typeId}`);
        return false;
    }
}
/**
 * @deprecated This functionality is now built directly into {@link SafetyCheckedEntity.removeTag}
 * @remarks Removes a specified tag from an entity. Applies the global namespace automatically.
 * @param entity The entity the tag should be removed from.
 * @param tag Content of the tag to remove.
 * @returns Returns whether the tag existed on the entity.
 */
export function removeTag(entity, tag) {
    if (entity.isValid()) {
        return entity.removeTag(GlobalNamespace.Namespace + tag);
    }
    else {
        log.mcwarn(`Tried to remove tag: ${tag} on invalid entity of type: ${entity.typeId}`);
        return false;
    }
}
/**
 * @deprecated This functionality is now built directly into {@link SafetyCheckedEntity.hasTag}
 * @remarks Returns whether an entity has a particular tag. Applies the global namespace automatically.
 * @param entity The entity for whom the tag should be checked.
 * @param tag Identifier of the tag to test for.
 * @returns Returns whether an entity has a particular tag.
 */
export function hasTag(entity, tag) {
    if (entity.isValid()) {
        return entity.hasTag(GlobalNamespace.Namespace + tag);
    }
    else {
        log.mcwarn(`Tried to read tag: ${tag} on invalid entity of type: ${entity.typeId}`);
        return false;
    }
}
/**
 * @deprecated This functionality is now built directly into {@link SafetyCheckedEntity.getTags}
 * @remarks Returns all tags associated with an entity. Removes the global namespace automatically.
 * @param entity The entity who's tags should be returned.
 * @returns Returns all tags associated with an entity.
 */
export function getTags(entity) {
    if (entity.isValid()) {
        return entity.getTags().map(tag => tag.replace(GlobalNamespace.Namespace, ''));
    }
    else {
        log.mcwarn(`Tried to get tags on invalid entity of type: ${entity.typeId}`);
        return [];
    }
}
/**
 * @remarks Despawns an entity if they are reloaded, useful for temporary entities that should despawn but failed to do so normally do to closing the world.
 * @param entity The entity who should despawn if they are loaded a second time.
 */
export function despawnOnReload(entity) {
    if (entity.isValid()) {
        entity.addTag("despawn_on_reload");
    }
}
function initializeDespawnOnReload() {
    mc.world.afterEvents.entityLoad.subscribe(event => {
        const entity = new SafetyCheckedEntity(event.entity);
        if (entity.hasTag("despawn_on_reload")) {
            entity.remove();
        }
    });
    waitForWorldLoad().then(() => {
        getEntitiesSafe(overworld, { tags: [GlobalNamespace.Namespace + "despawn_on_reload"] }).forEach(entity => entity.remove());
        getEntitiesSafe(nether, { tags: [GlobalNamespace.Namespace + "despawn_on_reload"] }).forEach(entity => entity.remove());
        getEntitiesSafe(end, { tags: [GlobalNamespace.Namespace + "despawn_on_reload"] }).forEach(entity => entity.remove());
    });
}
initializeDespawnOnReload();
