import { Player, EntityInventoryComponent, EntityEquippableComponent, Entity, ItemStack, EquipmentSlot, ItemLockMode, Dimension, Block, BlockPermutation, BlockVolume, system, Effect, world, System } from "@minecraft/server";
import { getScoreUtility, operationScoreUtility, setScoreUtility } from "./scoreboard";
import { skillAllNameReset, skillFormNameRemove } from "./skill_list";
import { ReplaceableBlocks, UnbreakableBlocks } from "./blocks";


/**
 * @param {Entity} entity
 * @param {String} tag
 */
export function hasMainhandItemTag(entity,tag){
    /** @type {EntityEquippableComponent}  */
    const inventory = entity.getComponent("minecraft:equippable");
    if(inventory.getEquipment("Mainhand") != null){
        const mainhandItemTag = inventory.getEquipment("Mainhand").hasTag(tag);
        return mainhandItemTag;
    }
}
/**
 * @param {Player} player
 * @param {String} equipmentSlot
 * @param {String} itemName
 */
export function hasEquipmentItem(player,equipmentSlot,itemName){
    /** @type {EntityEquippableComponent}  */
    const equippableComponent = player.getComponent("minecraft:equippable");
    if(equippableComponent.getEquipment(equipmentSlot)!=undefined){
        return equippableComponent.getEquipment(equipmentSlot).typeId == itemName;
    }else{
        return false;
    }
}
/**
 * @param {Player} player
 * @param {String} itemName
 */
function hasItemAmountInInventory(player,itemName){
    const inventoryComp = player.getComponent("minecraft:inventory");
	let count = 0;
    for(let slotNumber=0; slotNumber<=35; slotNumber++ ){
        const itemStack = inventoryComp.container.getItem(slotNumber);
        if(itemStack!=undefined){
            if(itemStack.typeId == itemName){
                count = count + itemStack.amount;
            }
        }
    }
	return count;
}
/**
 * @param {Player} player
 * @param {EquipmentSlot} equipmentSlot 
 * @param {String} replaceItemName 
 * @param {Number} amount 
 * @param {Boolean} keepOnDeath
 * @param {ItemLockMode} itemLockMode 
 */
export function replaceEquipment(player,equipmentSlot,replaceItemName,amount,keepOnDeath,itemLockMode){
    const equipComp = player.getComponent("equippable");
    const replaceitemStack = new ItemStack(replaceItemName,amount);
    replaceitemStack["keepOnDeath"] = keepOnDeath;
    replaceitemStack.lockMode = itemLockMode;
    equipComp.setEquipment(equipmentSlot,replaceitemStack);
}
/**
 * @param {Player} player
 * @param {String} itemName
 * @param {String} replaceItemName
 */
function replaceItemInInventory(player,itemName,replaceItemName){
    /**@type {EntityInventoryComponent} */
    const inventory = player.getComponent("minecraft:inventory");
    for(let slotNumber=0; slotNumber<=35; slotNumber++ ){
        const itemStack = inventory.container.getItem(slotNumber);
        if(itemStack!=undefined){
            if(itemStack.typeId == itemName){
                const replaceItemStack = new ItemStack(replaceItemName,itemStack.amount);
                replaceItemStack["keepOnDeath"] = itemStack.keepOnDeath;
                replaceItemStack["lockMode"] = itemStack.lockMode;
                inventory.container.setItem(slotNumber,replaceItemStack);
            }
        }
    }
}
/**
 * @param {Player} player
 * @param {String} itemName
 * @param {String} replaceItemName
 */
export function replaceItemInInventoryCombine(player,itemName,replaceItemName){
    const inventoryComp = player.getComponent("minecraft:inventory");
    const hasitem = () => {
        let bool = replaceItemName == "undefined" ? true : false;
        
        if(bool == false){
            for(let slotNumber=0; slotNumber<=35; slotNumber++ ){

                if(bool == true) break;

                const itemStack = inventoryComp.container.getItem(slotNumber);
                if(itemStack!=undefined && itemStack.typeId == replaceItemName){
                    bool = true;
                }
            }
        }

        return bool;
    };
    for(let slotNumber=0; slotNumber<=35; slotNumber++ ){
        const itemStack = inventoryComp.container.getItem(slotNumber);
        if(itemStack!=undefined){
            if(itemStack.typeId == itemName){
                if(hasitem()==false){
                    const replaceItemStack = new ItemStack(replaceItemName,itemStack.amount);
                    replaceItemStack["keepOnDeath"] = itemStack.keepOnDeath;
                    replaceItemStack["lockMode"] = itemStack.lockMode;
                    inventoryComp.container.setItem(slotNumber,replaceItemStack);
                }
                else{
                    inventoryComp.container.setItem(slotNumber);
                }
            }
        }
    }
}
/**
 * @param {Player} player
 * @param {String} equipmentSlot
 * @param {String} replaceItemName
 */
export function setEquipmentLockAndKeep(player,equipmentSlot,replaceItemName){

    /**@type {EntityEquippableComponent} */
	const equipment = player.getComponent("minecraft:equippable");

    const replaceItemStack = new ItemStack(replaceItemName,1);
    replaceItemStack["keepOnDeath"] = true;
    replaceItemStack["lockMode"] = "inventory";
    equipment.setEquipment(equipmentSlot,replaceItemStack);
}
/**
 * @param {Player} player
 * @param {Number} slotNumber
 * @param {String} setItemId
 * @param {Number} amount
 * @param {Boolean} keepOnDeath
 * @param {ItemLockMode} itemLockMode
 */
export function setItemInInventory(player,slotNumber,setItemId,amount,keepOnDeath,itemLockMode){

    /**@type {EntityInventoryComponent} */
    const inventory = player.getComponent("minecraft:inventory");

    const setItemStack = new ItemStack(setItemId,amount);
    setItemStack["keepOnDeath"] = keepOnDeath;
    setItemStack["lockMode"] = itemLockMode;
    inventory.container.setItem(slotNumber,setItemStack);
}
/**
 * @param {Player} player
 * @param {Number} amount
 * @param {Boolean} ignoreGameMode
 */
export function decrementStack(player,amount,ignoreGameMode){
    const equippableComp = player.getComponent("minecraft:equippable");
    const itemStack = equippableComp.getEquipment("Mainhand");
    const exeAmount = amount == null ? 1 : amount;
    const ignoreGm = ignoreGameMode == null ? false : ignoreGameMode;
    if(ignoreGm==true || player.getGameMode()!="Creative"){
        for(let exeCount=0; exeCount<exeAmount; exeCount++){
            if(equippableComp.getEquipment("Mainhand")==undefined) break;
            const remainingAmount = equippableComp.getEquipment("Mainhand").amount;
            if(remainingAmount>1){
                const updateItemStack = new ItemStack(itemStack.typeId,remainingAmount-1);
                updateItemStack["keepOnDeath"] = itemStack.keepOnDeath;
                updateItemStack["lockMode"] = itemStack.lockMode;
                equippableComp.setEquipment("Mainhand",updateItemStack);
            }else{
                equippableComp.setEquipment("Mainhand",undefined);
            }
        }
    }
}
/**
 * @param {Player} player
 * @param {String} itemId
 * @param {Number} amount
 * @param {Boolean} keepOnDeath
 * @param {ItemLockMode} lookMode
 */
export function giveCommand(player,itemId,amount,keepOnDeath,lookMode){
    const inventoryComp = player.getComponent("minecraft:inventory");
    if(inventoryComp.container.emptySlotsCount > 0){
        const giveItemStack = new ItemStack(itemId,amount);
        giveItemStack["keepOnDeath"] = keepOnDeath;
        giveItemStack["lockMode"] = lookMode;

        inventoryComp.container.addItem(giveItemStack);
    }
    else{
        if(keepOnDeath==false && lookMode=="none"){
            const spawnitemStack = new ItemStack(itemId,amount);
            player.dimension.spawnItem(spawnitemStack,player.location);
        }
        else{
            player.sendMessage({rawtext:[{translate:"rawtext.inventory_full"}]});
        }
    }

}
/**
 * @param {Player} player
 * @param {String} itemId
 * @param {Number} amount
 */
export function clearCommand(player,itemId,amount){
    const inventoryComp = player.getComponent("minecraft:inventory");
    const equippableComp = player.getComponent("minecraft:equippable");
    if(amount==null){
        for(let slotNumber=0; slotNumber<=35; slotNumber++){
            const itemStack = inventoryComp.container.getItem(slotNumber);
            if(itemStack!=undefined && itemStack.typeId == itemId){
                inventoryComp.container.setItem(slotNumber,undefined);
            }
        }
        for(const equipmentSlot of Object.values(EquipmentSlot)){
            const itemStack = equippableComp.getEquipment(equipmentSlot);
            if(itemStack!=undefined && itemStack.typeId == itemId){
                equippableComp.setEquipment(equipmentSlot,undefined);
            }
        }
    }else{
        let exeCount=0;
        for(let slotNumber=0; slotNumber<=35 && exeCount<amount; slotNumber++){
            const itemStack = inventoryComp.container.getItem(slotNumber);
            if(itemStack!=undefined && itemStack.typeId == itemId){
                for(; exeCount<amount && inventoryComp.container.getItem(slotNumber)!=undefined;){
                    exeCount++
                    const remainingAmount = inventoryComp.container.getItem(slotNumber).amount;
                    if(remainingAmount>1){
                        inventoryComp.container.setItem(slotNumber,new ItemStack(itemStack.typeId,remainingAmount-1));
                    }else{
                        inventoryComp.container.setItem(slotNumber,undefined);
                    }
                }
            }
        }
        for(const equipmentSlot of Object.values(EquipmentSlot)){
            if(exeCount>=amount){
                break;
            }else{
                const itemStack = equippableComp.getEquipment(equipmentSlot);
                if(itemStack!=undefined && itemStack.typeId == itemId){
                    
                    for(; exeCount<amount && equippableComp.getEquipment(equipmentSlot)!=undefined;){
                        exeCount++
                        const remainingAmount = equippableComp.getEquipment(equipmentSlot).amount;
                        if(remainingAmount>1){
                            equippableComp.setEquipment(equipmentSlot,new ItemStack(itemStack.typeId,remainingAmount-1));
                        }else{
                            equippableComp.setEquipment(equipmentSlot,undefined);
                        }
                    }
                }
            }
        }
    }
}
/**
 * @param {Entity} user
 * @param {Entity} target
 * @param {Number} strength
 */
export function attract(user,target,strength){
    const userLoc = user.location;
    const targetLoc = target.location;

    const distanceXZ = Math.sqrt(Math.pow((targetLoc.x - userLoc.x),2) + Math.pow((targetLoc.z - userLoc.z),2));
    const distanceXYZ = Math.sqrt(Math.pow((targetLoc.x - userLoc.x),2) + Math.pow((targetLoc.y - userLoc.y),2) + Math.pow((targetLoc.z - userLoc.z),2));
    const directionX = strength * (targetLoc.x - userLoc.x)/distanceXZ;
    const directionY = strength * (targetLoc.y - userLoc.y)/distanceXYZ;
    const directionZ = strength * (targetLoc.z - userLoc.z)/distanceXZ;

    target.applyKnockback({x:-directionX,z:-directionZ},-directionY);
}
/**
 * @param {Entity} entity
 */
export function convertRotYtoPosXZ(entity){
    const entityRotationY = entity.getRotation().y;
    const positionX = -Math.floor(100000*Math.sin(entityRotationY*(Math.PI/180)))/100000;
    const positionZ = Math.floor(100000*Math.cos(entityRotationY*(Math.PI/180)))/100000;
    return {x:positionX,z:positionZ};
};
/**
 * @param {Player} player
 * @param {String} projectileId
 * @param {Number} power
 * @param {[number,number,number]} offset
 * @param {String} eventId
 */
export function shootProjectile(player,projectileId,power,offset,eventId){

    /**@param {import("@minecraft/server").Vector3} location */
    function common(location){
        const projectile = player.dimension.spawnEntity(projectileId,location);
        if(eventId != null){
            projectile.triggerEvent(eventId);
        }
        const projectileComp = projectile.getComponent("projectile");
        projectileComp.owner = player;
        projectileComp.shoot(
            {
                x:player.getViewDirection().x * power,
                y:player.getViewDirection().y * power,
                z:player.getViewDirection().z * power
            }
        );
    }

    if(offset != null){
        const location = caret(player.getHeadLocation(),player.getRotation(),offset);
        common(location);
    }else{
        const location = player.getHeadLocation();
        common(location);
    }
}
/**
 * @param {Player} player
 * @param {String} projectileId
 * @param {Number} power
 * @param {[number,number,number]} offset
 * @param {String} propertyId
 * @param {Boolean | Number | String} propertyValue
 * @param {String} eventId
 */
export function shootProjectileProp(player,projectileId,power,offset,propertyId,propertyValue,eventId){

    /**@param {import("@minecraft/server").Vector3} location */
    function common(location){
        const projectile = player.dimension.spawnEntity(projectileId,location);
        if(eventId != null){
            projectile.triggerEvent(eventId);
        }
        projectile.setProperty(propertyId,propertyValue);
        const projectileComp = projectile.getComponent("projectile");
        projectileComp.owner = player;
        projectileComp.shoot(
            {
                x:player.getViewDirection().x * power,
                y:player.getViewDirection().y * power,
                z:player.getViewDirection().z * power
            }
        );
    }

    if(offset != null){
        const location = caret(player.getHeadLocation(),player.getRotation(),offset);
        common(location);
    }else{
        const location = player.getHeadLocation();
        common(location);
    }
}
/**
 * @param {Player} player
 * @param {String} projectileId
 * @param {Number} power
 * @param {[number,number,number]} offset
 * @param {String} propertyId
 * @param {Boolean | Number | String} propertyValue
 */
export function shootProjectilePropH(player,projectileId,power,offset,propertyId,propertyValue){

    /**@param {import("@minecraft/server").Vector3} location */
    function common(location){
        const projectile = player.dimension.spawnEntity(projectileId,location);
        projectile.setProperty(propertyId,propertyValue);
        const projectileComp = projectile.getComponent("projectile");
        projectileComp.owner = player;
        projectileComp.shoot(
            {
                x:-(player.getHeadLocation().x - caretZ(player.getHeadLocation(),player.getRotation(),[0,0,1]).x) * power,
                y:0,
                z:-(player.getHeadLocation().z - caretZ(player.getHeadLocation(),player.getRotation(),[0,0,1]).z) * power
            }
        );
    }

    if(offset != null){
        const location = caretZ(player.getHeadLocation(),player.getRotation(),offset);
        common(location);
    }else{
        const location = player.getHeadLocation();
        common(location);
    }
}
/**
 * @param {import("@minecraft/server").Vector3} location
 * @param {Number} x
 * @param {Number} y
 * @param {Number} z
 */
export function shiftLocation(location,x,y,z){
    const newX = location.x + x;
    const newY = Math.min(location.y + y,255);
    const newZ = location.z + z;
    return {x:newX,y:newY,z:newZ}
}
/**
 * @param {import("@minecraft/server").Vector3} location
 * @param {import("@minecraft/server").Vector2} rotation
 * @param {[number,number,number]} offset
 */
export function caret(location, rotation, offset) {
    const yaw = rotation.y * (Math.PI / 180);
    const pitch = rotation.x * (Math.PI / 180);
    const forward = {
        x: -Math.sin(yaw) * Math.cos(pitch),
        y: -Math.sin(pitch),
        z:  Math.cos(yaw) * Math.cos(pitch)
    };
    const right = {
        x:  Math.cos(yaw),
        y:  0,
        z:  Math.sin(yaw)
    };
    const up = {
        x: forward.y * right.z - forward.z * right.y,
        y: forward.z * right.x - forward.x * right.z,
        z: forward.x * right.y - forward.y * right.x
    };

    return {
        x: location.x + right.x * offset[0] + up.x * offset[1] + forward.x * offset[2],
        y: location.y + right.y * offset[0] + up.y * offset[1] + forward.y * offset[2],
        z: location.z + right.z * offset[0] + up.z * offset[1] + forward.z * offset[2]
    };
}
/**
 * @param {import("@minecraft/server").Vector3} location
 * @param {import("@minecraft/server").Vector2} rotation
 * @param {[number,number,number]} offset
 */
export function caretZ(location, rotation, offset) {
    const yaw = rotation.y * (Math.PI / 180);
    const forward = {
        x: -Math.sin(yaw),
        y: 0,
        z:  Math.cos(yaw)
    };
    const right = {
        x:  Math.cos(yaw),
        y:  0,
        z:  Math.sin(yaw)
    };
    const up = { x: 0, y: 1, z: 0 };
    return {
        x: location.x + right.x * offset[0] + up.x * offset[1] + forward.x * offset[2],
        y: location.y + right.y * offset[0] + up.y * offset[1] + forward.y * offset[2],
        z: location.z + right.z * offset[0] + up.z * offset[1] + forward.z * offset[2]
    };
}

/**
 * 
 * @param {String} particleId 
 * @param {Dimension} dimension 
 * @param {import("@minecraft/server").Vector3} startLocation 
 * @param {import("@minecraft/server").Vector3} stopLocation 
 */
export function spawnParticleTrail(particleId,dimension,startLocation,stopLocation){
    const startLocX = startLocation.x;
    const startLocY = startLocation.y;
    const startLocZ = startLocation.z;
    const stopLocX = stopLocation.x;
    const stopLocY = stopLocation.y;
    const stopLocZ = stopLocation.z;
    const moveDist = Math.round(Math.sqrt(Math.pow((stopLocX - startLocX),2) + Math.pow((stopLocY - startLocY),2) + Math.pow((stopLocZ - startLocZ),2)));
    const coordX = startLocX + (stopLocX - startLocX) * (m/moveDist);
    const coordY = startLocY + (stopLocY - startLocY) * (m/moveDist);
    const coordZ = startLocZ + (stopLocZ - startLocZ) * (m/moveDist);
    const coord = {x:coordX,y:coordY,z:coordZ};
    dimension.spawnParticle(particleId,coord);
}
/**
 * @param {Block} block 
 */
export function replaceAir(block){
    block.setPermutation(BlockPermutation.resolve("air"));
}
/**
 * @param {Player} player 
 * @param {EquipmentSlot} equipmentSlot
 */
export function damageItemDurability(player,equipmentSlot){
    const equippableComp = player.getComponent("equippable");
    const itemStack = equippableComp.getEquipment(equipmentSlot);
	if(itemStack==undefined) return;
    const durabilityComp = itemStack.getComponent("durability");
	if(durabilityComp==undefined) return;
	const maxDurability = durabilityComp.maxDurability;
	const damagedDurability = durabilityComp.damage;
	const remainingDurability = maxDurability - damagedDurability;
	if(remainingDurability > 0){
		durabilityComp.damage = damagedDurability + 1;
		equippableComp.setEquipment(equipmentSlot,itemStack);
	}
	else{
		equippableComp.setEquipment(equipmentSlot,undefined);
	}
}
/**
 * @param {import("@minecraft/server").Vector3} location1 
 * @param {import("@minecraft/server").Vector3} location2 
 * @returns 
 */
export function getDistance(location1,location2){
    return Math.round(Math.sqrt(Math.pow((location2.x - location1.x),2) + Math.pow((location2.y - location1.y),2) + Math.pow((location2.z - location1.z),2)));
}
/**
 * @param {Entity} entity
 * @param {import("@minecraft/server").Vector3} location
 * @param {import("@minecraft/server").Vector3} volume
 * @param {Boolean} exceptSelf
 */
export function getEntitiesInRange(entity,location,volume,exceptSelf=true){
    const targets = entity.dimension.getEntities({excludeFamilies:["damage"],excludeTypes:["item","xp_orb"],volume:volume,location:location});

    if(exceptSelf==true){
        const targetsExceptSelf = targets.filter(function(target){
            return target.id != entity.id;
        });
        return targetsExceptSelf;
    }else{
        return targets;
    }
}
/**
 * @param {Entity} entity
 * @param {import("@minecraft/server").Vector3} location
 * @param {Number} maxDistance
 * @param {Boolean} exceptSelf
 */
export function getEntitiesDistance(entity,location,maxDistance,exceptSelf=true){
    const targets = entity.dimension.getEntities({excludeFamilies:["damage"],excludeTypes:["item","xp_orb"],maxDistance:maxDistance,location:location});
    if(exceptSelf==true){
        const targetsExceptSelf = targets.filter(function(target){
            return target.id != entity.id;
        });
        return targetsExceptSelf;
    }else{
        return targets;
    }
}
/**
 * @param {Entity} entity
 * @param {Number[]} range
 * @param {Number} distance
 * @param {boolean} ignoreXRotation
 * @returns {Entity[]}
 */
export function getEntitiesInFront(entity, range, distance, ignoreXRotation = false) {
    const headLoc = entity.getHeadLocation();
    const rot = ignoreXRotation 
        ? { x: 0, y: entity.getRotation().y }
        : entity.getRotation();
    const viewD = entity.getViewDirection();
    const raycastOptions = { excludeFamilies: ["damage"], excludeTypes: ["xp_orb", "item"], maxDistance: distance };
    const Loc = (xyz) => caret(headLoc, rot, xyz);
    const targetsAll = [];
    for (let x = -range[0] + 1; x < range[0]; x++) {
        for (let y = -range[1] / 2 + 0.5; y < range[1] / 2; y++) {
            const rayTargets = entity.dimension.getEntitiesFromRay(Loc([x, y, 0]), viewD, raycastOptions)
                ?.map(ray => ray.entity);
            if (rayTargets) targetsAll.push(...rayTargets);
        }
    }
    const uniqueTargets = [];
    const seenIds = new Set();
    for (const target of targetsAll) {
        if (!seenIds.has(target.id)) {
            seenIds.add(target.id);
            uniqueTargets.push(target);
        }
    }
    return uniqueTargets.filter(target => target.id !== entity.id);
}
/**
 * @param {Entity} entity
 * @param {Number} baseDamage
 * @param {Number} damageMultiplier
 */
export function damageByEffectStrength(entity,baseDamage,damageMultiplier){
    let damage = baseDamage;
    const multiplier = damageMultiplier==null ? 1.3 : damageMultiplier;
    const effectAmplifier = entity.getEffect("strength") == undefined ? 0 : Math.min(entity.getEffect("strength").amplifier + 1,20);
    /**@param {Number} n  */
    function exe(n){
        damage = damage*multiplier + 1;
        if(n > 1){
            exe(n-1);
        }
    }
    if(effectAmplifier > 0){
        exe(effectAmplifier);
    }
    return damage;
}
/**
 * @param {import("@minecraft/server").Vector3} exeLoc 
 * @param {import("@minecraft/server").Vector3} targetLoc 
 * @param {Number} baseDamage 
 * @param {Number} damageMultiplier 
 */
export function damageByDistance(exeLoc,targetLoc,baseDamage,damageMultiplier){
    const targetDist = getDistance(exeLoc,targetLoc);
    return Math.max(1,baseDamage - damageMultiplier*targetDist);
}
/**
 * @param {Entity} entity 
 * @param {import("@minecraft/server").Vector3} location 
 * @param {Number[]} start 
 * @param {Number[]} stop 
 * @param {BlockPermutation} blockPermutation 
 * @param {String[]} replaceableBLockTypes 
 * @param {Boolean} ignoreGameRule 
 */
export function fillCommand(entity,location,start,stop,blockPermutation,replaceableBLockTypes=REPLACEABLE_BLOCKS,ignoreGameRule=false){
    if(ignoreGameRule==false && world.getDynamicProperty("block_replaceable")==false) return;

    const locX = location.x;
    const locY = location.y;
    const locZ = location.z;
    
    const fromLoc = {
        x:locX + start[0],
        y:locY + start[1],
        z:locZ + start[2]
    };
    const toLoc = {
        x:locX + stop[0],
        y:locY + stop[1],
        z:locZ + stop[2]
    };

    const blockVolume = new BlockVolume(
        fromLoc,
        toLoc
    );

    if(replaceableBLockTypes.length==0){
        entity.dimension.fillBlocks(
            blockVolume,
            blockPermutation,
            {
                ignoreChunkBoundErrors:true
            }
        );
    }else{
        entity.dimension.fillBlocks(
            blockVolume,
            blockPermutation,
            {
                blockFilter:{
                    includeTypes:replaceableBLockTypes
                },
                ignoreChunkBoundErrors:true
            }
        );
    }
}
/**
 * @param {Entity} entity
 * @param {import("@minecraft/server").Vector3} location
 * @param {Number[]} start
 * @param {Number[]} stop
 * @param {BlockPermutation} blockPermutation
 * @param {Boolean} ignoreGameRule
 */
export function fillCommandReplaceAir(entity,location,start,stop,blockPermutation){

    const locX = location.x;
    const locY = location.y;
    const locZ = location.z;
    
    const fromLoc = {
        x:locX + start[0],
        y:locY + start[1],
        z:locZ + start[2]
    };
    const toLoc = {
        x:locX + stop[0],
        y:locY + stop[1],
        z:locZ + stop[2]
    };

    const blockVolume = new BlockVolume(
        fromLoc,
        toLoc
    );

    entity.dimension.fillBlocks(
        blockVolume,
        blockPermutation,
        {
            blockFilter:{
                includeTypes:["air"]
            },
            ignoreChunkBoundErrors:true
        }
    );
}
/**
 * @param {Entity} entity
 * @param {import("@minecraft/server").Vector3} exeLoc
 * @param {Number[]} startPos
 * @param {Number[]} stopPos
 */
export function fillCommandDestroy(entity,exeLoc,startPos,stopPos,ignoreGameRule=false){
    if(ignoreGameRule==false && world.getDynamicProperty("block_breakable")==false) return;
    const locX = exeLoc.x;
    const locY = exeLoc.y;
    const locZ = exeLoc.z;
    
    const startX = Math.min(startPos[0],stopPos[0]);
    const stopX = Math.max(startPos[0],stopPos[0]);
    const startY = Math.min(startPos[1],stopPos[1]);
    const stopY = Math.max(startPos[1],stopPos[1]);
    const startZ = Math.min(startPos[2],stopPos[2]);
    const stopZ = Math.max(startPos[2],stopPos[2]);
    for(let y=startY; y<=stopY; y++){
        for(let z=startZ; z<=stopZ; z++){
            for(let x=startX; x<=stopX; x++){
                const block = entity.dimension.getBlock({x:locX+x,y:locY+y,z:locZ+z});
                destroyBlock(block);
            }
        }
    }
}
/**
 * @param {Block} block 
 * @param {Boolean} ignoreGameRule
 */
export function destroyBlock(block,ignoreGameRule=false){
    if(ignoreGameRule==false && world.getDynamicProperty("block_breakable")==false) return;
    const blockId = block.typeId;
    if(block==undefined || block.isAir || UnbreakableBlocks.includes(blockId) || block.getComponent("inventory")) return;
    if(blockId.includes("grass")){
        block.dimension.playSound("dig.grass",block.location);
    }
    else if(blockId.includes("dirt")){
        block.dimension.playSound("break.dirt_with_roots",block.location);
    }
    else if(blockId.includes("sand")){
        block.dimension.playSound("dig.sand",block.location);
    }
    else if(blockId.includes("wood") || blockId.includes("planks")){
        block.dimension.playSound("dig.wood",block.location);
    }
    else if(blockId.includes("glass") || blockId.includes("ice")){
        block.dimension.playSound("random.glass",block.location);
    }
    else if(true){
        block.dimension.playSound("dig.stone",block.location);
    }
    const item = block.getItemStack(1,false);
    if(item!=undefined){
        block.dimension.spawnItem(item,block.location);
    }
    block.setPermutation(BlockPermutation.resolve("air"));
}
/**
 * @param {Entity} entity
 * @param {String[]} blockTypes
 */
export function removeProjectileInBLock(entity,blockTypes){
    let exe = false;
    if(blockTypes==null){
        const block = entity.dimension.getBlock(entity.location);
        if(!block.isAir){
            const blockRay = block.dimension.getBlockFromRay(block.location,{x:0,y:0.001,z:0},{includePassableBlocks:false});
            if(blockRay!=undefined){
                const loc1 = block.location;
                const loc2 = blockRay.block.location;
                if(Math.sqrt(Math.pow((loc1.x - loc2.x),2) + Math.pow((loc1.y - loc2.y),2) + Math.pow((loc1.z - loc2.z),2))==0){
                    entity.remove();
                    exe = true;
                }
            }
        }
    }
    else{
        if(blockTypes.includes(entity.dimension.getBlock(entity.location).typeId)){
            entity.remove();
            exe = true;
        }
    }

    return exe;
}
class itemCooldown {
    /**
     * 
     * @param {String} cooldownCategory 
     * @param {Number} tickDuration 
     */
    constructor(cooldownCategory,tickDuration) {
        this.cooldownCategory = "hatsu";
        this.tickDuration = 0;
    }
}
/**
 * @param {Player} player 
 * @param {Boolean} stopCondition 
 * @param {itemCooldown[]} cooldownList 
 */
export function stopComboMove(player,stopCondition,cooldownList){
    if(stopCondition){
        const equipComp = player.getComponent("equippable");
        for(const cooldown of cooldownList){
            player.startItemCooldown(cooldown.cooldownCategory,cooldown.tickDuration);
        }
        const useItemId = equipComp.getEquipment("Mainhand").typeId;
        const replaceitemStack = new ItemStack("hxc:dummy_item",1);
        replaceitemStack["keepOnDeath"] = true;
        replaceitemStack.lockMode = "inventory";
        equipComp.setEquipment("Mainhand",replaceitemStack);
        system.runTimeout(()=>{
            replaceItemInInventoryCombine(player,"hxc:dummy_item",useItemId);
        },1);
    }
}
/**
 * @param {Entity} owner 
 * @param {String} spawnEntityId 
 * @param {import("@minecraft/server").Vector3} location 
 * @param {String} eventId 
 */
export function spawnProjectileHasOwner(owner,spawnEntityId,location,eventId){
    const entity = owner.dimension.spawnEntity(spawnEntityId,location);
    if(eventId!=null){
        entity.triggerEvent("hxc:entity_summoned");
    }
    entity.getComponent("projectile").owner = owner;
}
/**
 * @param {Player} player 
 * @param {String} spawnEntityId 
 * @param {import("@minecraft/server").Vector3} location 
 * @param {String} eventId 
 */
export function spawnTamedEntity(player,spawnEntityId,location,eventId){
    const entity = player.dimension.spawnEntity(spawnEntityId,location);
    if(eventId!=null){
        entity.triggerEvent("hxc:entity_summoned");
    }
    entity.getComponent("tameable").tame(player);
    return entity;
}
/**
 * @param {Entity} rider 
 * @param {String} rideEnityId
 * @param {import("@minecraft/server").Vector3} location 
 * @param {String} eventId 
 */
export function spawnRideEntity(rider,rideEnityId,location,eventId){
    const rideEnity = rider.dimension.spawnEntity(rideEnityId,location);
    rideEnity.getComponent("rideable").addRider(rider);
    rideEnity.setRotation(rider.getRotation());
    if(eventId!=null){
        rideEnity.triggerEvent("hxc:entity_summoned");
    }
    return rideEnity;
}
/**
 * @param {Entity} rideEnity
 * @param {String} riderEntityId 
 * @param {import("@minecraft/server").Vector3} location 
 * @param {String} eventId 
 */
export function spawnRiderEntity(rideEnity,riderEntityId,location,eventId){
    const riderEnity = rideEnity.dimension.spawnEntity(riderEntityId,location);
    rideEnity.getComponent("rideable").addRider(riderEnity);
    riderEnity.setRotation(rideEnity.getRotation());
    if(eventId!=null){
        riderEnity.triggerEvent("hxc:entity_summoned");
    }
    return riderEnity;
}
/**
 * @param {Player} player 
 * @param {String} cooldownCategor
 * @param {Number} maxTickDuration
 * @param {Number} minTickDuration
 */
export function startItemCooldownDependingOnchargeRate(player,cooldownCategory,maxTickDuration,minTickDuration){
    const charge = getScoreUtility(player,"charge");
    player.startItemCooldown(cooldownCategory,Math.max(minTickDuration,maxTickDuration*(1 - charge/20)));
    setScoreUtility(player,"charge",0);
}
/**
 * @param {Player} player
 * @param {String} cooldownCategor
 * @param {Number} maxTickDuration
 * @param {Number} minTickDuration
 */
export function startItemCooldownDependingOncharge(player,cooldownCategory,maxTickDuration,minTickDuration){
    const charge = getScoreUtility(player,"charge");
    player.startItemCooldown(cooldownCategory,Math.max(minTickDuration,maxTickDuration*(charge/20)));
    setScoreUtility(player,"charge",0);
}
/**
 * @param {Entity} entity 
 * @param {Entity} projectile 
 */
function isOwnerOfProjectile(entity,projectile){
    const projectileComp = projectile.getComponent("projectile");
    if(projectileComp==undefined){
        return false;
    }else{
        return projectileComp.owner==entity
    }
}
/**
 * @param {Player} player 
 * @param {Entity} entity 
 */
export function isTamedToPlayer(player,entity){
    const tameableComp = entity.getComponent("tameable");
    if(tameableComp==undefined){
        return false;
    }else{
        return tameableComp.tamedToPlayer == player;
    }
}
/**
 * @param {Player} player
 * @param {String[]} tags
 */
export function clearItemHasTag(player,tags){
    const inventory = player.getComponent("minecraft:inventory");
    for(let slotNumber=0; slotNumber<=35; slotNumber++){
        const itemStack = inventory.container.getItem(slotNumber);
        if(itemStack!=undefined){
            const tagList = itemStack.getTags();
            if(tags.every(element => tagList.includes(element))){
                inventory.container.setItem(slotNumber,undefined);
            }
        }
    }
}
/**
 * @param {Object} params
 * @param {Entity} params.target
 * @param {Entity} params.damaging
 * @param {Object} [params.damage]
 * @param {number} [params.damage.amount=1]
 * @param {string} [params.damage.cause="entityAttack"]
 * @param {boolean} [params.damage.swordDamage=false]
 * @param {Object} [params.knockback]
 * @param {"front"|"sphere"|"right"|"up"} [params.knockback.type="front"]
 * @param {number} [params.knockback.horizontalStrength=0]
 * @param {number} [params.knockback.verticalStrength=0]
 * @param {Vector3} [params.knockback.knockbackLocation]
 * @param {Object} [params.effect]
 * @param {string} [params.effect.typeId="slowness"]
 * @param {number} [params.effect.duration=0]
 * @param {number} [params.effect.amplifier=0]
 * @param {boolean} [params.effect.showParticles=true]
 * @param {boolean} [params.ignoreCreative=true]
 */
export function applyVarious({
    target,
    damaging,
    damage,
    knockback,
    effect,
    ignoreCreative = true
}) {
    if (!target || !damaging) return;
    const isCreative = isPlayerInCreative(target);
    if (damage && target.getComponent("health") && (!ignoreCreative || !isCreative)) {
        const amount = damage.amount ?? 1;
        const cause = damage.cause ?? "entityAttack";
        const swordDamage = damage.swordDamage ?? false;
        target.applyDamage(amount, { cause, damagingEntity: damaging });
    }
    if (effect && (!ignoreCreative || !isCreative)) {
        const effects = Array.isArray(effect) ? effect : [effect];
        for (const eff of effects) {
            const typeId = eff.typeId ?? "slowness";
            const duration = eff.duration ?? 0;
            const amplifier = eff.amplifier ?? 0;
            const showParticles = eff.showParticles ?? true;
            target.addEffect(typeId, duration, { amplifier, showParticles });
        }
    }
    if (knockback && (!ignoreCreative || !isCreative)) {
        const sensor = target.getProperty("hxc:damage_sensor");
        if (sensor === "invincible") return;
        const horizontalStrength = knockback.horizontalStrength ?? 0;
        const verticalStrength = knockback.verticalStrength ?? 0;
        let directionX = 0;
        let directionZ = 0;
        switch (knockback.type) {
            case "sphere": {
                const targetLoc = target.location;
                const exeLoc = knockback.knockbackLocation ?? damaging.location;
                const dx = targetLoc.x - exeLoc.x;
                const dz = targetLoc.z - exeLoc.z;
                const distanceXZ = Math.sqrt(dx * dx + dz * dz) || 0.001;
                directionX = (dx / distanceXZ);
                directionZ = (dz / distanceXZ);
                break;
            }
            case "right": {
                const rot = damaging.getRotation();
                const rotX = rot.x;
                const rotY = rot.y + 90;
            
                const cosX = Math.cos(rotX * (Math.PI/180));
            
                const sinY = Math.sin(rotY * (Math.PI/180));
                const cosY = Math.cos(rotY * (Math.PI/180));
            
                directionX = -sinY*cosX;
                directionZ = cosX*cosY;
                break;
            }
            case "up": {
                break;
            }
            case "front":
            default: {
                const view = damaging.getViewDirection();
                directionX = view.x;
                directionZ = view.z;
                break;
            }
        }
        target.applyKnockback(
            { x: directionX * horizontalStrength, z: directionZ * horizontalStrength },
            verticalStrength
        );
    }
}
/**
 * @param {Entity} entity
 * @param {number} [horizontalStrength=0]
 * @param {number} [verticalStrength=0]
 */
export function applySelfKnockback(entity, horizontalStrength = 0, verticalStrength = 0) {
    let knockbackX = 0;
    let knockbackZ = 0;

    if (horizontalStrength !== 0) {
        const view = entity.getViewDirection();
        knockbackX = view.x * horizontalStrength;
        knockbackZ = view.z * horizontalStrength;
    }

    entity.applyKnockback({ x: knockbackX, z: knockbackZ }, verticalStrength);
}
/**
 * @param {Entity} entity
 */
export function cancelKnockback(entity) {
    entity.applyKnockback({x:0,z:0},0);
}
/**
 * @param {Object} params
 * @param {Entity} params.damaging
 * @param {Vector3} params.startLocation
 * @param {Vector3} params.stopLocation
 * @param {Vector3} params.volume
 * @param {Object} [params.damage]
 * @param {number} [params.damage.amount=1]
 * @param {string} [params.damage.cause="entityAttack"]
 * @param {boolean} [params.damage.swordDamage=false]
 * @param {Object} [params.knockback]
 * @param {"front"|"sphere"|"right"|"up"} [params.knockback.type="front"]
 * @param {number} [params.knockback.horizontalStrength=0]
 * @param {number} [params.knockback.verticalStrength=0]
 * @param {Vector3} [params.knockback.knockbackLocation]
 * @param {Object} [params.effect]
 * @param {string} [params.effect.typeId="slowness"]
 * @param {number} [params.effect.duration=0]
 * @param {number} [params.effect.amplifier=0]
 * @param {boolean} [params.effect.showParticles=true]
 * @param {boolean} [params.ignoreCreative]
 * @param {Object} [params.particle]
 * @param {Object} [params.particle.hitParticleId]
 * @param {Object} [params.particle.trailParticleId]
 */
export function rushAttack({
    damaging,
    startLocation,
    stopLocation,
    volume,
    damage,
    knockback,
    effect,
    ignoreCreative,
    particle
}) {
    const startLocX = startLocation.x;
    const startLocY = startLocation.y;
    const startLocZ = startLocation.z;
    const stopLocX = stopLocation.x;
    const stopLocY = stopLocation.y;
    const stopLocZ = stopLocation.z;
    const dx = stopLocX - startLocX;
    const dy = stopLocY - startLocY;
    const dz = stopLocZ - startLocZ;
    const moveDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
    const coords = moveDist === 0
        ? [{ x: stopLocX, y: stopLocY, z: stopLocZ }]
        : Array.from({ length: moveDist + 1 }, (_, m) => ({
            x: startLocX + (dx * m) / moveDist,
            y: startLocY + (dy * m) / moveDist,
            z: startLocZ + (dz * m) / moveDist
        }));
    for (const coord of coords) {
        for (const target of getEntitiesInRange(
            damaging,
            shiftLocation(coord, -volume.x / 2, -volume.y / 2, -volume.z / 2),
            volume
        )) {
            if (particle?.hitParticleId) {
                target.dimension.spawnParticle(
                    particle.hitParticleId,
                    shiftLocation(target.location, 0, 1, 0)
                );
            }
            applyVarious({
                target,
                damaging,
                damage,
                knockback,
                effect,
                ignoreCreative,
                particle
            });
        }
        if (particle?.trailParticleId) {
            damaging.dimension.spawnParticle(particle.trailParticleId, coord);
        }
    }
}
/**
 * @param {Object} params
 * @param {Entity} params.damaging
 * @param {Number[]} params.range
 * @param {Number} params.distance
 * @param {Object} [params.damage]
 * @param {number} [params.damage.amount=1]
 * @param {string} [params.damage.cause="entityAttack"]
 * @param {boolean} [params.damage.swordDamage=false]
 * @param {Object} [params.knockback]
 * @param {"front"|"sphere"|"right"|"up"} [params.knockback.type="front"]
 * @param {number} [params.knockback.horizontalStrength=0]
 * @param {number} [params.knockback.verticalStrength=0]
 * @param {Vector3} [params.knockback.knockbackLocation]
 * @param {Object} [params.effect]
 * @param {string} [params.effect.typeId="slowness"]
 * @param {number} [params.effect.duration=0]
 * @param {number} [params.effect.amplifier=0]
 * @param {boolean} [params.effect.showParticles=true]
 * @param {boolean} [params.ignoreCreative]
 */
export function rangedAttackForward({
    damaging,
    range,
    distance,
    damage,
    knockback,
    effect,
    ignoreCreative,
    ignoreXRotation = false
}) {
    const targets = getEntitiesInFront(damaging, range, distance, ignoreXRotation);

    for (const target of targets) {
        applyVarious({
            target,
            damaging,
            damage,
            knockback,
            effect,
            ignoreCreative
        });
    }
}
/**
 * @param {Entity} entity
 * @param {String} blockId
 * @param {(entity: Entity) => void} exeFunc
 */
export function ifStandingOn(entity, blockId, exeFunc) {
    const block = entity.dimension.getBlock(shiftLocation(entity.location,0,-1,0));

    if (block && block.typeId === blockId) {
        exeFunc(entity);
    }
}
/**
 * @param {Entity} entity 
 */
export function isPlayer(entity) {
   return entity.typeId == "minecraft:player";
}
/**
 * @param {Entity} entity 
 */
export function isPlayerInCreative(entity) {
   return entity.typeId == "minecraft:player" && entity.getGameMode() === "Creative";
}
/**
 * @param {Player} player
 * @param {import("@minecraft/server").Vector3} location
 * @param {Number} maxDistance
 * @param {String} type
 * @returns 
 */
export function getTamedEntity(player,location,maxDistance,type){
    const entities = player.dimension.getEntities({type,location,maxDistance});
    for(const entity of entities){
        if(isTamedToPlayer(player,entity)){
            return entity;
        }
    }
}
/**
 * @param {Player} player
 * @param {number} speedFactor
 * @param {boolean} [useAccel=true]
 */
export function exeFlying(player, speedFactor, useAccel = true){
    const pitchRad = player.getRotation().x * (Math.PI / 180);
    const viewDir = player.getViewDirection();
    let accelCharge = 1;
    if(useAccel){
        let charge = player.getDynamicProperty("accelCharge") ?? 0;
        charge = Math.min(charge + 0.05, 1);
        player.setDynamicProperty("accelCharge", charge);
        accelCharge = charge;
    }
    const horizontalStrength = 2 * Math.cos(pitchRad) * speedFactor * accelCharge;
    const horizontalForce = {
        x: viewDir.x * horizontalStrength,
        z: viewDir.z * horizontalStrength,
    };
    let verticalStrength = -Math.sin(pitchRad) * speedFactor * accelCharge;
    if(verticalStrength > 0) {
        verticalStrength *= 1.5;
    }
    else if(verticalStrength < 0) {
        verticalStrength *= 0.4;
    }
    player.applyKnockback(horizontalForce, verticalStrength);
}
/**
 * @param {Player} player 
 */
export function exeStayInAir(player){

    player.addEffect("slow_falling",10,{amplifier:0,showParticles:false});

    if(system.currentTick%20 === 0){
        player.addEffect("levitation",16,{amplifier:0,showParticles:false});
    }
}
/**
 * @param {Player} player
 * @param {string} effectId
 * @param {number} duration
 * @param {{amplifier?: number, showParticles?: boolean}} options
 * @param {number} [minRemaining=2]
 */
export function ensureEffect(player, effectId, duration, options, minRemaining = 2){
    const effect = player.getEffect(effectId);

    if(!effect || effect.duration <= minRemaining){
        player.addEffect(effectId, duration, options);
    }
}
/**
 * @param {Player} player
 */
export function isInputIdle(player){
    return player.inputInfo.getMovementVector().y == 0 && player.inputInfo.getMovementVector().x == 0
}
/**
 * @param {Player} player 
 */
function consumeHunger(player){
    const hungerComp = player.getComponent("player.hunger");
    const saturationComp = player.getComponent("player.saturation");
    const satuValue = saturationComp.currentValue;
    if(satuValue>=1){
        saturationComp.setCurrentValue(satuValue-1);
    }
    else{
        hungerComp.setCurrentValue(Math.max(0,hungerComp.currentValue-1));
    }
}
/**
 * @param {Player} player 
 */
export function comboConsume(player){
    const charge = getScoreUtility(player, "charge");
    if(charge > 0){
        operationScoreUtility(player, "charge", -1);
        return;
    }
    let counter = player.getDynamicProperty("consumeHungerCounter") ?? 0;
    counter++;
    player.setDynamicProperty("consumeHungerCounter", counter);
    if (counter % 3 === 0) {
        consumeHunger(player);
    }
}
/**
 * @param {Entity} damagingEntity
 * @param {Entity} hitEntity
 * @param {number} horizontalStrength
 * @param {number} verticalStrength
 */
export function meleeComboKnockback(damagingEntity, hitEntity, horizontalStrength, verticalStrength) {
    let count = damagingEntity.getDynamicProperty("attack_count") ?? 0;
    count++;
    if (count >= 3) {
        count = 0;
        applyVarious({
            target: hitEntity,
            damaging: damagingEntity,
            knockback: { type: "front", horizontalStrength, verticalStrength }
        });
    }
    damagingEntity.setDynamicProperty("attack_count", count);
}
/**
 * 
 * @param {Entity|Block} entity
 * @param {import("@minecraft/server").Vector3} location
 * @param {string|number} event
 * @param {boolean} [knockback=true]
 */
export function spawnExplosion(entity, location, event, knockback=true) {
    const entityId = knockback 
        ? "hxc:explosion_nodamage" 
        : "hxc:explosion_nodamage_nonockback";
    const spawned = entity.dimension.spawnEntity(entityId, location);
    if (typeof event === "number") {
        spawned.triggerEvent(`hxc:power.${event}`);
    } else if (typeof event === "string") {
        spawned.triggerEvent(event);
    }
}
/**
 * @param {Player} player 
 */
export function clearAllSkill(player){
    const inventoryComp = player.getComponent("inventory");
    for(let slotNumber=0; slotNumber<=35; slotNumber++ ){
        const itemStack = inventoryComp.container.getItem(slotNumber);
        if(itemStack!=undefined){
            if(skillAllNameReset.includes(itemStack.typeId.split(":")[1])){
                inventoryComp.container.setItem(slotNumber,undefined);
            }
        }
    }
    const equipComp = player.getComponent("equippable");
    const equipmentSlotAll = Object.keys(EquipmentSlot).filter((v) => isNaN(Number(v)));
    for(const equipmentSlot of equipmentSlotAll){
        const itemStack = equipComp.getEquipment(equipmentSlot);
        if(itemStack != undefined){
            if(skillAllNameReset.includes(itemStack.typeId.split(":")[1])){
                equipComp.setEquipment(equipmentSlot,undefined);
            }
        }
    }
}
/**
 * @param {Player} player 
 */
export function clearFormSkill(player){
    const inventoryComp = player.getComponent("inventory");
    for(let slotNumber=0; slotNumber<=35; slotNumber++ ){
        const itemStack = inventoryComp.container.getItem(slotNumber);
        if(itemStack!=undefined){
            if(skillFormNameRemove.includes(itemStack.typeId.split(":")[1])){
                inventoryComp.container.setItem(slotNumber,undefined);
            }
        }
    }
    const equipComp = player.getComponent("equippable");
    const equipmentSlotAll = Object.keys(EquipmentSlot).filter((v) => isNaN(Number(v)));
    for(const equipmentSlot of equipmentSlotAll){
        const itemStack = equipComp.getEquipment(equipmentSlot);
        if(itemStack != undefined){
            if(skillFormNameRemove.includes(itemStack.typeId.split(":")[1])){
                equipComp.setEquipment(equipmentSlot,undefined);
            }
        }
    }
}