import {
    world,
    system,
    EntityDamageCause
} from "@minecraft/server";

import { applyElementDamage, clampValue, getCurrentMana, getDirection, protEnchantLvl, getMaxMana, makeVector } from "./main.js";

export function runSpell(source, spell, level) {
    source.runCommand(`scriptevent mm:triggered_${spell} ${level}`)
    switch (spell) {
        case "mm:launch":
            launch(source, level)
            return;
        case "mm:dash":
            dash(source, level)
            return;
        case "mm:burst":
            burst(source, level)
            return;
        case "mm:bolt":
            bolt(source, level)
            return;
        case "mm:icicle":
            icicle(source, level)
            return;
        case "mm:terraform":
            terraform(source, level)
            return;
        case "mm:supersonic":
            supersonic(source, level)
            return;
        case "mm:retrieval":
            retrieval(source, level)
            return;
        case "mm:cauterise":
            cauterise(source, level)
            return;
        case "mm:overclock":
            overclock(source, level)
            return;
        case "mm:siphoning":
            siphoning(source, level)
            return;
        case "mm:swap":
            swap(source, level)
            return;
        case "mm:glaciate":
            glaciate(source, level)
            return;
        case "mm:overheat":
            overheat(source, level)
            return;
        case "mm:teleport":
            teleport(source, level)
            return;
        case "mm:spark":
            spark(source, level)
            return;
        case "mm:quake":
            quake(source, level)
            return;
        case "mm:hellfire":
            hellfire(source, level)
            return;
        case "mm:toxic":
            toxic(source, level)
            return;
        case "mm:roots":
            roots(source, level)
            return;
        case "mm:thunderbolt":
            thunderbolt(source, level)
            return;
        case "mm:chilling":
            chilling(source, level)
            return;
        case "mm:snowflake":
            snowflake(source, level)
            return;
        case "mm:shadow":
            shadow(source, level)
            return;
        case "mm:purify":
            purify(source, level)
            return;
        default:
            source.runCommand(`scriptevent mm:${spell} ${level}`)
            return;
    }
}

import "./spellcasting.js";

function launch(player, level) {
    const vd = player.getViewDirection()
    const force = 1.5 + (level / 4)
    player.applyKnockback({ x: vd.x * level + 1, z: vd.z * level + 1 }, force)
    player.addEffect("slow_falling", 100)
    player.dimension.playSound("mob.bat.takeoff", player.location)
}
function dash(player, level) {
    const vd = player.getViewDirection()
    const force = 2 + (level / 1.33)
    player.applyKnockback({ x: vd.x * force, z: vd.z * force }, clampValue(vd.y, -5, 5) * (0.75 + (level / 4) * 1.3) + 0.2)
    player.addEffect("slow_falling", 10)
    if (vd.y >= 0) player.addEffect("levitation", 10)
    player.dimension.playSound("mob.breeze.shoot", player.location)
}
function burst(player, level) {
    const loc = player.location;
    const water = player.isInWater;

    if (water == true) {
        player.dimension.playSound("random.fizz", loc)
        player.dimension.spawnParticle("minecraft:ice_evaporation_emitter", loc)
    } else {
        player.dimension.playSound("mob.blaze.shoot", loc, { pitch: 1.5, volume: 3 })
        player.dimension.spawnParticle("mm:burst_aura", loc)
        player.dimension.spawnParticle("mm:burst_black_smoke", loc)
        system.runTimeout(() => {
            player.dimension.playSound("random.explode", loc, { pitch: 0.3, volume: 3 })
            player.dimension.spawnParticle("mm:burst_explosion", loc)
            player.dimension.spawnParticle("mm:burst_flames", loc)
            const block = player.dimension.getBlock(loc)
            if (block.above().isAir) block.above().setType("mm:flickerwick_light")
            const targets = player.dimension.getEntities({ location: loc, maxDistance: 4.33 })
            for (const target of targets) {
                if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                    target.setOnFire(1 + level)
                    applyElementDamage(5 + level * 4, target, player, EntityDamageCause.fire, "fire")
                }
            }
        }, 10)
    }
}
function bolt(player, level) {
    const vd = player.getViewDirection()
    const force = 4
    player.applyKnockback({ x: vd.x * force, z: vd.z * force }, -3)
    player.addEffect("speed", 20 * level, { amplifier: 4 })
    player.addEffect("slow_falling", 2)
    player.dimension.spawnParticle("mm:bolt_spell", player.location)
    player.dimension.playSound("mob.breeze.shoot", player.location, { pitch: 0.6 })
    player.dimension.playSound("beacon.power", player.location, { pitch: 3 })
}
function icicle(player, level) {
    const loc = player.dimension.getTopmostBlock({ x: player.location.x, z: player.location.z }, player.location.y).above().location
    const dim = player.dimension;
    system.runTimeout(() => {
        const icicle = dim.spawnEntity("mm:icicles", loc)
        icicle.setProperty("mm:level", level)
        icicle.getComponent("minecraft:tameable").tame(player)
    }, 20)
}
function terraform(player, level) {
    const loc = player.dimension.getTopmostBlock({ x: player.location.x, z: player.location.z }, player.location.y).above().location
    const dim = player.dimension;
    var launchVar = true
    if (loc.y + 3 < player.location.y) launchVar = false
    if (player.isOnGround) launchVar = true
    player.dimension.playSound("mob.breeze.death", player.location, { pitch: 0.4 })
    if (launchVar == false) {
        const velocity = player.getVelocity()
        player.applyKnockback({ x: 0, z: 0 }, 0.65 + level / 20)
        var toAdd = 0
        if (velocity.y < 0) toAdd = velocity.y * 1.6
        system.runTimeout(() => {
            player.dimension.spawnParticle("mm:stone_platform_emerge", { x: player.location.x, y: loc.y, z: player.location.z })
            player.dimension.playSound("mob.creaking.attack", player.location, { pitch: 0.6 })
            if (velocity.y < 0) player.addEffect("slow_falling", 4, { showParticles: false })
            const stonePlatform = dim.spawnEntity("mm:stone_platform", { x: player.location.x, y: player.location.y - 1.2 + toAdd, z: player.location.z })
            stonePlatform.setProperty("mm:level", level)
            stonePlatform.getComponent("minecraft:tameable").tame(player)
            stonePlatform.triggerEvent("mm:scale_" + level)
        }, 2)
    } else {
        player.applyKnockback({ x: 0, z: 0 }, 1.175 + level / 20)
        player.dimension.spawnParticle("mm:stone_platform_launch", player.location)
        const spawnLoc = { x: player.location.x, y: player.location.y + 6, z: player.location.z }
        system.runTimeout(() => {
            player.dimension.spawnParticle("mm:stone_platform_emerge", { x: player.location.x, y: loc.y, z: player.location.z })
            player.dimension.playSound("mob.creaking.attack", player.location, { pitch: 0.6 })
            const stonePlatform = dim.spawnEntity("mm:stone_platform", spawnLoc)
            stonePlatform.setProperty("mm:level", level)
            stonePlatform.getComponent("minecraft:tameable").tame(player)
            stonePlatform.triggerEvent("mm:scale_" + level)
        }, 8)
    }
}
function supersonic(player, level) {
    const loc = player.location;


    player.dimension.playSound("mob.warden.sonic_charge", loc, { pitch: 2.5, volume: 1.5 })
    player.dimension.spawnParticle("mm:super_sonic_outside", loc)
    system.runTimeout(() => {
        player.dimension.playSound("random.explode", loc, { pitch: 2.3, volume: 1.5 })
        player.dimension.playSound("mob.warden.sonic_boom", loc, { pitch: 1, volume: 1.5 })
        player.dimension.spawnParticle("mm:super_sonic_inside", loc)
        const targets = player.dimension.getEntities({ location: loc, maxDistance: 5.5 })
        for (const target of targets) {
            if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                target.addEffect("slowness", 50 + (level * 10), { amplifier: 3 + level })
                target.addEffect("weakness", 50 + (level * 10), { amplifier: 0 + level })
                const direction = getDirection(loc, target.location);
                target.applyKnockback(makeVector(direction, 1.5 + level / 2), 0.45);
            }
            if (target.typeId == "mm:sanctuary_keeper") target.triggerEvent("mm:stun_no_target")
        }
    }, 10)

}

function retrieval(player, level) {
    const retrieval = player.getEntitiesFromViewDirection({
        maxDistance: 32,
        includePassableBlocks: false
    })
    if (retrieval.length == 0) {
        player.dimension.spawnParticle("minecraft:wind_explosion_emitter", player.location)
        player.dimension.playSound("mob.breeze.jump", player.location, { pitch: 0.5 })
        const targets = player.dimension.getEntities({ location: player.location, maxDistance: 2 })
        for (const target of targets) {
            if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                applyElementDamage(level * 4, target, player, EntityDamageCause.entityAttack, "air")
            }
        }
        return;
    }
    for (const target of retrieval) {
        if (target !== player && (target.entity.matches({ families: ["monster"] }) || target.entity.matches({ families: ["mob"] }) || target.entity.matches({ families: ["animal"] }) || target.entity.matches({ families: ["player"] }))) {
            const entity = target.entity;
            const xDif = entity.location.x - player.location.x;
            const zDif = entity.location.z - player.location.z;
            const yDif = player.location.y - entity.location.y;
            if (entity.typeId === 'minecraft:player' && entity.isFlying) return;
            entity.dimension.spawnParticle("minecraft:wind_explosion_emitter", entity.location)
            entity.dimension.playSound("mob.breeze.jump", entity.location)
            applyElementDamage(level * 1, entity, player, EntityDamageCause.entityAttack, "air")
            entity.applyKnockback({ x: -xDif / (3 - (level * 1 / 2.2)), z: -zDif / (3 - (level * 1 / 2.2)) }, clampValue(0.05 + yDif * 0.25, -1, yDif / 10) + 0.1);
            return;
        }
    }

}

function cauterise(player, level) {
    var damage = 9 - level
    const damageDealt = clampValue(applyElementDamage(damage, player, player, EntityDamageCause.fire, "fire", true), 2, 10)
    const fireRes = player.getEffect("fire_resistance")
    if (fireRes) {
        player.addEffect("regeneration", 250 / 5, { amplifier: 1 })
    } else {
        player.applyDamage(1, { cause: EntityDamageCause.fire })
        player.addEffect("regeneration", 250, { amplifier: 1 })
        let hp = player.getComponent("health")
        if (player.getGameMode !== "Creative") hp.setCurrentValue(clampValue(hp.currentValue - damageDealt, 0, hp.defaultValue))
    }
    player.dimension.spawnParticle("mm:burst_flames", player.location)
    player.dimension.playSound("fire.ignite", player.location)
    player.dimension.playSound("beacon.power", player.location, { pitch: 4 })
}
function overclock(player, level) {
    const damageDealt = applyElementDamage(6, player, player, EntityDamageCause.lightning, "electric", true)
    player.applyDamage(1, { cause: EntityDamageCause.lightning })
    let hp = player.getComponent("health")
    if (player.getGameMode !== "Creative") hp.setCurrentValue(clampValue(hp.currentValue - damageDealt, 0, hp.defaultValue))
    player.addEffect("speed", 100 + (level * 60), { amplifier: 1 })
    player.addEffect("strength", 100 + (level * 60), { amplifier: 0 })
    player.dimension.playSound("beacon.power", player.location, { pitch: 2 })
    player.dimension.spawnParticle("mm:spark_hit_electricity", player.location)

}

function siphoning(player, level) {
    player.dimension.playSound("mob.vex.charge", player.location, { pitch: 0.85, volume: 1.5 })
    player.dimension.playSound("beacon.power", player.location, { pitch: 1.5, volume: 1.5 })
    var siphonScore = world.scoreboard.getObjective("mm:mana_siphon_duration")
    if (!siphonScore) siphonScore = world.scoreboard.addObjective("mm:mana_siphon_duration", "Mana Siphon Duration")
    siphonScore.addScore(player, 0)
    if (world.scoreboard.getObjective("mm:mana_siphon_duration").getScore(player) > 0) {
        siphonScore.addScore(player, 25)
    } else {
        siphonScore.addScore(player, 100)
    }
    var siphonLevel = world.scoreboard.getObjective("mm:mana_siphon_level")
    if (!siphonLevel) siphonLevel = world.scoreboard.addObjective("mm:mana_siphon_level", "Mana Siphon Level")
    siphonLevel.addScore(player, 0)
    if (world.scoreboard.getObjective("mm:mana_siphon_level").getScore(player) < level) siphonLevel.setScore(player, level)
}

function swapConnectionVisual(targetLoc, hitLoc, dim) {
    const xDif = targetLoc.x - hitLoc.x
    const yDif = targetLoc.y - hitLoc.y
    const zDif = targetLoc.z - hitLoc.z
    const distanceBetween = Math.hypot(hitLoc.x - targetLoc.x, hitLoc.y - targetLoc.y, hitLoc.z - targetLoc.z)
    for (let i = 1; i < distanceBetween; i++) {
        system.runTimeout(() => {
            dim.spawnParticle("mm:siphon_idle", { x: hitLoc.x + (xDif * (i / distanceBetween)), y: 1 + hitLoc.y + (yDif * (i / distanceBetween)), z: hitLoc.z + (zDif * (i / distanceBetween)) })
        }, i / 2)
    }
}

function swap(player, level) {
    const swapTarget = player.getEntitiesFromViewDirection({
        maxDistance: level * 12,
        includePassableBlocks: false
    })
    if (swapTarget.length == 0) {
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        return;
    }
    for (const target of swapTarget) {
        if (target !== player && (target.entity.matches({ families: ["monster"] }) || target.entity.matches({ families: ["mob"] }) || target.entity.matches({ families: ["animal"] }) || target.entity.matches({ families: ["player"] }))) {
            const targetLoc = target.entity.location;
            const playerLoc = player.location
            target.entity.tryTeleport(playerLoc)
            player.tryTeleport(targetLoc)
            const playerRot = player.getRotation()
            const targetRot = target.entity.getRotation()
            player.setRotation(targetRot)
            target.entity.setRotation(playerRot)
            player.dimension.spawnParticle("mm:swap", targetLoc)
            player.dimension.spawnParticle("mm:swap", playerLoc)
            player.dimension.playSound("mob.endermen.portal", targetLoc)
            player.dimension.playSound("mob.endermen.portal", playerLoc)
            swapConnectionVisual(targetLoc, playerLoc, player.dimension)
            swapConnectionVisual(playerLoc, targetLoc, player.dimension)
            return;
        }
    }
}

function glaciate(player, level) {
    player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 0.85, volume: 1.5 })
    player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 1.5, volume: 1.5 })
    var frigidScore = world.scoreboard.getObjective("mm:glaciate_duration")
    if (!frigidScore) frigidScore = world.scoreboard.addObjective("mm:glaciate_duration", "Glaciate Duration")
    frigidScore.addScore(player, 0)
    if (world.scoreboard.getObjective("mm:glaciate_duration").getScore(player) > 0) {
        frigidScore.addScore(player, 25 + (level * 15))
    } else {
        frigidScore.addScore(player, 100 + (level * 40))
    }
}

function teleport(player, level) {
    const raycast = player.getBlockFromViewDirection(
        {
            maxDistance: 16 * level,
            includePassableBlocks: false,
            includeLiquidBlocks: true
        });
    if (!raycast) {
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
        player.dimension.spawnParticle("mm:siphon_idle", player.location)
    } else {
        const playerLoc = player.location
        const block = raycast.block.location;
        const faceLocation = raycast.faceLocation;
        const face = raycast.face;
        player.dimension.spawnParticle("mm:swap", {
            x: block.x + faceLocation.x,
            y: block.y + faceLocation.y + 1,
            z: block.z + faceLocation.z
        })
        player.dimension.playSound("mob.endermen.portal", block)
        player.dimension.spawnParticle("mm:swap", playerLoc)
        player.dimension.playSound("mob.endermen.portal", playerLoc)
        if ((face === "Up" || face === "Down") || !raycast.block.above().isAir) {
            player.tryTeleport(
                {
                    x: block.x + faceLocation.x,
                    y: block.y + faceLocation.y + 1,
                    z: block.z + faceLocation.z
                },
                {
                    checkForBlocks: false
                });
        }
        else {
            player.tryTeleport(
                {
                    x: block.x + 0.5,
                    y: block.y + 1,
                    z: block.z + 0.5
                },
                {
                    checkForBlocks: false
                });
        }
    }
}

function spark(player, level) {
    const ammo = player.dimension.spawnEntity('mm:spark', player.getHeadLocation());
    const proj = ammo.getComponent('projectile');
    proj.owner = player;
    ammo.setProperty("mm:level", level)
    proj.shoot(player.getViewDirection())
    player.dimension.playSound("mob.blaze.hit", player.location, { pitch: 1.5 })
    player.dimension.playSound("beacon.power", player.location, { pitch: 3 })
}

function createEarthquake(player, level, loc, dim) {
    dim.playSound("mob.warden.sonic_charge", loc, { pitch: 1.5, volume: 1 })
    dim.playSound("mob.creaking.death", loc, { volume: 1, pitch: 0.3 })
    dim.spawnParticle("mm:earthquake_smoke", loc)
    const shakeTargets = dim.getPlayers({ location: loc, maxDistance: 7.5 })
    for (const target of shakeTargets) {
        target.runCommand("camerashake add @s 0.75 1.7 ")
    }
    const slowTargets = dim.getEntities({ location: loc, maxDistance: 6.5 })
    for (const target of slowTargets) {
        if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
            if (target.isOnGround) {
                target.addEffect("slowness", 40, { amplifier: 2, showParticles: false })
            }
        }
    }
    system.runTimeout(() => {
        dim.spawnParticle("mm:earthquake_hit", loc)
        dim.spawnParticle("mm:earthquake_hit_smoke", loc)
        dim.playSound("random.explode", loc, { pitch: 1, volume: 4 })
        dim.playSound("mace.heavy_smash_ground", loc, { pitch: 0.5, volume: 3 })
        const quakeTargets = dim.getEntities({ location: loc, maxDistance: 6.5 })
        for (const target of quakeTargets) {
            if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                if (target.isOnGround) {
                    const damageDealt = applyElementDamage(8 + (level * 4), target, player, EntityDamageCause.fall, "earth")
                    system.runTimeout(() => {
                        if (damageDealt == true) target.applyKnockback({ x: 0, z: 0 }, 0.65)
                    }, 1)
                }
            }
        }
    }, 20)
}

function quake(player, level) {
    const dim = player.dimension;
    const quakeLoc = player.dimension.getTopmostBlock({ x: player.location.x, z: player.location.z }, player.location.y).above()
    if (quakeLoc.y + 8 < player.location.y) {
        return;
    } else {
        dim.spawnParticle("mm:earthquake_smoke", quakeLoc)
        dim.playSound("mob.creaking.death", quakeLoc, { volume: 1, pitch: 0.6 })
        const offset1 = Math.floor(Math.random() * 25) + 15
        const offset2 = Math.floor(Math.random() * 25) + offset1 + 25
        const offset3 = Math.floor(Math.random() * 25) + offset2 + 25

        system.runTimeout(() => {
            createEarthquake(player, level, quakeLoc, dim)
        }, offset1)
        system.runTimeout(() => {
            createEarthquake(player, level, quakeLoc, dim)
        }, offset2)
        system.runTimeout(() => {
            createEarthquake(player, level, quakeLoc, dim)
        }, offset3)
    }
}

function overheat(player, level) {
    player.dimension.playSound("mob.blaze.shoot", player.location, { pitch: 1, volume: 1 })
    var overheatScore = world.scoreboard.getObjective("mm:overheat_duration")
    if (!overheatScore) overheatScore = world.scoreboard.addObjective("mm:overheat_duration", "Overheat Duration")
    overheatScore.addScore(player, 0)
    if (world.scoreboard.getObjective("mm:overheat_duration").getScore(player) > 0) {
        overheatScore.addScore(player, 50 + (level * 20))
    } else {
        overheatScore.addScore(player, 200 + (level * 30))
    }
}

function hellfire(player, level) {
    const hp = player.getComponent("minecraft:health")
    const pct = hp.currentValue / hp.defaultValue
    const power = (Math.ceil(pct * 4) / 4)
    const ammo = player.dimension.spawnEntity('mm:hellfire', player.getHeadLocation());
    const proj = ammo.getComponent('projectile');
    proj.owner = player;
    ammo.setProperty("mm:level", level)
    var heat = 0
    if (power == 1) heat = 0
    if (power == 0.75) heat = 1
    if (power == 0.5) heat = 2
    if (power == 0.25) heat = 3
    ammo.setProperty("mm:heat", heat)
    proj.shoot({ x: player.getViewDirection().x, y: player.getViewDirection().y * 1.2, z: player.getViewDirection().z })
    player.dimension.playSound("mob.blaze.hit", player.location, { pitch: 0.5 + (heat / 4) })
    player.dimension.playSound("mob.vex.charge", player.location, { pitch: 1.15 - (heat / 8), volume: 1.5 })
    player.dimension.spawnParticle(`mm:hellfire_sonicboom_${heat}`, player.location)
}

function toxicParticle(loc, dim, count) {
    if (count < 1) return;
    dim.spawnParticle("mm:toxic_spore", loc)
    system.runTimeout(() => {
        toxicParticle(loc, dim, count - 1)
    }, 1)
}

function spawnToxic(loc, dim, owner, level, power) {
    if (power > level * 4 + 3) {
        return;
    }
    dim.spawnParticle("mm:toxic_loop", loc)
    const numSpawn = Math.ceil(power / 2) - 2
    if (numSpawn >= 1) {
        toxicParticle(loc, dim, numSpawn)
    }
    dim.playSound("random.potion.brewed", loc, { pitch: (Math.random() * 0.4 + 0.2) })
    system.runTimeout(() => {
        dim.playSound("mob.witch.drink", loc, { pitch: (Math.random() * 0.4 + 0.2) })
        const targets = dim.getEntities({ location: loc, maxDistance: 3.5 + level / 3 })
        for (const target of targets) {
            if (target !== owner && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                var mult = 1
                if (target.matches({ families: ["undead"] })) mult = 0.33
                var damageAmt = (power * mult)
                if (target.typeId == "minecraft:player") {
                    const protection = target.getComponent("minecraft:equippable").totalArmor
                    damageAmt = clampValue(damageAmt - protection / 5, power / 3 + 1, damageAmt)
                }
                const damageDealt = applyElementDamage(damageAmt, target, owner, EntityDamageCause.wither, "earth")
                system.runTimeout(() => {

                    if (damageDealt == true) {
                        target.applyKnockback({ x: 0, z: 0 }, 0.05)
                        target.addEffect("poison", 10)
                    }
                }, 1)


            }
        }

        system.runTimeout(() => {
            spawnToxic(loc, dim, owner, level, power + 1)
        }, 10)
    }, 10)
}

function toxic(player, level) {
    const spawnLoc = player.location
    const dim = player.dimension;
    dim.playSound("mob.witch.drink", spawnLoc, { pitch: (Math.random() * 0.4 + 0.8) })
    dim.spawnParticle("mm:toxic_start", spawnLoc)
    spawnToxic(spawnLoc, dim, player, level, 1)
}

function roots(player, level) {
    if (player.hasTag("mm:roots_active")) return;
    const health = player.getComponent("minecraft:health")
    if (health.currentValue < health.defaultValue) {
        player.addTag("mm:roots_active")
        player.addTag("mm:roots_active_" + level)
        player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 0.2, volume: 1.5 })
    }
}

function thunderboltChain(damager, origin, target, prevTargets, damage, range, pitch) {
    if (damage < 1) return;
    const dim = origin.dimension;
    const targetLoc = target.location
    const hitLoc = origin.location;
    const xDif = targetLoc.x - hitLoc.x
    const yDif = targetLoc.y - hitLoc.y
    const zDif = targetLoc.z - hitLoc.z
    const distanceBetween = Math.hypot(hitLoc.x - targetLoc.x, hitLoc.y - targetLoc.y, hitLoc.z - targetLoc.z)
    for (let i = 1; i < distanceBetween; i++) {
        system.runTimeout(() => {
            dim.spawnParticle("mm:discharge_hit", { x: hitLoc.x + (xDif * (i / distanceBetween)), y: 1 + hitLoc.y + (yDif * (i / distanceBetween)), z: hitLoc.z + (zDif * (i / distanceBetween)) })
            dim.spawnParticle("mm:discharge_electricity", { x: hitLoc.x + (xDif * (i / distanceBetween)), y: 1 + hitLoc.y + (yDif * (i / distanceBetween)), z: hitLoc.z + (zDif * (i / distanceBetween)) })
        }, i / 2)
    }
    system.runTimeout(() => {
        const damageDealt = applyElementDamage(damage, target, damager, EntityDamageCause.lightning, "electric")
        if (!damageDealt) return;
        target.dimension.playSound("item.trident.thunder", target.location, { pitch: pitch * 5, volume: 1 })
        target.dimension.spawnParticle("mm:discharge_big_electricity", target.location)
        const newTargets = prevTargets
        newTargets.push(target)
        const newchaintargets = dim.getEntities({ location: targetLoc, maxDistance: range })
        for (const newchaintarget of newchaintargets) {
            if (!prevTargets.includes(newchaintarget)) {
                if ((newchaintarget.matches({ families: ["monster"] }) || newchaintarget.matches({ families: ["mob"] }) || newchaintarget.matches({ families: ["animal"] }) || newchaintarget.matches({ families: ["player"] }))) {
                    thunderboltChain(damager, target, newchaintarget, newTargets, damage * 0.9, clampValue(range * 1.2, range, 16), pitch * 0.9)
                    return;
                }
            }
        }
    }, 1 + distanceBetween / 2)
}

function thunderbolt(player, level) {
    const dim = player.dimension;
    const loc = player.location
    const targets = dim.getEntities({ location: loc, maxDistance: 6 })
    dim.playSound("item.trident.thunder", loc)
    dim.spawnParticle("mm:discharge_big_electricity", loc)
    const damage = level * 3 + 3
    for (const target of targets) {
        if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
            thunderboltChain(player, player, target, [player], damage, 7, 1)
            return;
        }
    }
}

function chilling(player, level) {
    player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 0.85, volume: 1.5 })
    player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 1.5, volume: 1.5 })
    var chillingScore = world.scoreboard.getObjective("mm:chilling_duration")
    if (!chillingScore) chillingScore = world.scoreboard.addObjective("mm:chilling_duration", "Chilling Duration")
    chillingScore.addScore(player, 0)
    if (world.scoreboard.getObjective("mm:chilling_duration").getScore(player) > 0) {
        chillingScore.addScore(player, 50 + (level * 25))
    } else {
        chillingScore.addScore(player, 200 + (level * 100))
    }
}

function snowflake(player, level) {
    if (player.hasTag("mm:snowflake_active")) return;
    player.addTag("mm:snowflake_active")
    player.addTag("mm:snowflake_active_" + level)
    player.dimension.playSound("mob.player.hurt_freeze", player.location, { pitch: 2, volume: 1.5 })
}

function healPlayer(player, amount) {
    const hp = player.getComponent("minecraft:health");
    if (!hp) return;
    if (hp.currentValue + amount > hp.defaultValue) {
        hp.setCurrentValue(hp.defaultValue)
    } else {
        hp.setCurrentValue(hp.currentValue + amount)
    }
}

function shadow(player, level) {
    const dim = player.dimension;
    const loc = player.location;
    const damage = 4 + level * 4
    dim.spawnParticle("mm:shadow", loc)
    dim.playSound("mob.warden.death", loc, { pitch: 0.66, volume: 2 })
    system.runTimeout(() => {
        dim.spawnParticle("mm:shadow_smoke", loc)
        dim.playSound("mob.warden.hurt", loc, { pitch: 0.5, volume: 8 })
        dim.playSound("mob.warden.clicking", loc, { pitch: 0.5 })
        const targets = player.dimension.getEntities({ location: loc, maxDistance: 4.5 })
        for (const target of targets) {
            if (target !== player && (target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
                const damageDealt = applyElementDamage(damage, target, player, EntityDamageCause.entityAttack, "mana")
                if (damageDealt) {
                    target.addEffect("blindness", 10 + level * 10)
                    healPlayer(player, damage * 0.05)
                }
            }
        }
    }, 8)
}

function purifyEffect(loc, dim, owner, level) {
    const targets = dim.getEntities({ location: loc, maxDistance: 2.25 })
    for (const target of targets) {
        if ((target.matches({ families: ["monster"] }) || target.matches({ families: ["mob"] }) || target.matches({ families: ["animal"] }) || target.matches({ families: ["player"] }))) {
            var manaRestore = 0.01
            if (target == owner) manaRestore += 0.01
            if (target.typeId == "minecraft:player") {
                target.runCommand("scriptevent mm:remove_custom_effects")
                target.setDynamicProperty("mm:mana", clampValue(getCurrentMana(target) + manaRestore, 0, getMaxMana(target)))
            }
            const targetEffects = target.getEffects()
            for (const effect of targetEffects) {
                if (effect.duration !== -1) {
                    target.removeEffect(effect.typeId)
                    target.dimension.spawnParticle("mm:purify_clear", target.location)
                    target.dimension.playSound("beacon.deactivate", target.location, { pitch: 2 })
                }
            }
        }
    }
}

function purifyPulse(loc, dim, owner, level, countdown) {
    if (countdown - 1 < 0) return;
    dim.spawnParticle("mm:purify_ring", loc)
    dim.spawnParticle("mm:purify_sparkles", loc)
    dim.spawnParticle("mm:purify_sparkles", loc)
    system.runTimeout(() => {
        purifyEffect(loc, dim, owner, level)
    }, 2)
    system.runTimeout(() => {
        purifyEffect(loc, dim, owner, level)
    }, 4)
    system.runTimeout(() => {
        purifyEffect(loc, dim, owner, level)
    }, 6)
    system.runTimeout(() => {
        purifyEffect(loc, dim, owner, level)
    }, 8)
    system.runTimeout(() => {
        purifyEffect(loc, dim, owner, level)
        purifyPulse(loc, dim, owner, level, countdown - 1)
    }, 10)
}

function purify(player, level) {
    const spawnLoc = { x: player.location.x, y: player.location.y + 0.5, z: player.location.z }
    const dim = player.dimension;
    purifyPulse(spawnLoc, dim, player, level, level * 4 + 5)
    dim.playSound("beacon.activate", spawnLoc, { pitch: 0.9 })
}
