import { world, system, ItemStack, EquipmentSlot, EntityComponentTypes, MinecraftDimensionTypes, MolangVariableMap } from '@minecraft/server';
import "./gui/index.js"
import "./dpsTracker.js"
import {delayMap, customItemSet, customProjectiles, hitEnt, rangeMap, passiveMobs, mobs, ignoreList, replaceBlocks, simExplosion} from "./data.js"
import {lootTable} from "lootingDB.js";

//settings for dash attack 
const DASH_DURATION = 120; // 6 seconds at 20 ticks per second
const DASH_STRENGTH = 1; // Adjust this for desired speed
const HOVER_STRENGTH = 0.04; // Constant hover strength
const ASCEND_SPEED = 0.2; // Upward speed when jumping
const DESCEND_SPEED = 0.1; // Downward speed when not jumping
const DASH_KNOCKBACK_STRENGTH = 2;
let directionX = 0;
let directionZ = 0;
let dashInterval = null;
const cooldowns = new Map();
const COOLDOWN_DURATION = 500;

//settings for the fire breath attack
const FIRE_INTERVAL = 5;
let fireBreathCounter = 0;
// Create a map to store fire breath intervals for each mob in case more than one are in the world attacking at the same time
const attackIntervals = new Map();

let players;
let captureOrbs = [];
let markedMobs = [];
let markers = [];
let tempOrbs = [];

let playerMobs = new Map;
let poisonInterval = 0;
const petMap = new Map;

// world.afterEvents.itemUse.subscribe(event => {
//   if(event.itemStack.typeId.includes(`maca_evolvingmobs:mob_orb`))
//   {
//     let storedLore = event.itemStack.getLore()
//     let storedLevel
//     for(let i = 0; i < storedLore.length; i++)
//     {
//       if(storedLore[i].includes(`§r§6Level`))
//       {
//         storedLevel = storedLore[i].replace(`§r§6Level: `, "")
//       }
//     }
//     system.runTimeout(()=>{
      
//     let spawnedMob = world.getDimension(event.source.dimension.id).getEntities({location:event.source.location,closest:1,excludeTags:['maca_evolvingmobs:level_stored'],maxDistance:10,families:['evolutions']})
    
//     for(let j = 0; j < spawnedMob.length; j++)
//       {
//         if(storedLevel != null){
//             if(spawnedMob[j].typeId.includes('maca_evolvingmobs')){
//               spawnedMob[j].setProperty('maca_evolvingmobs:level',parseInt(storedLevel))
//               spawnedMob[j].runCommandAsync('tag @e[family=evolutions] add maca_evolvingmobs:level_stored')
            
//           }
//         }
//       }

//     },1)
//   }
// });

world.afterEvents.itemUse.subscribe(data => {
  
  let item = data.itemStack.typeId;
  if(!customItemSet.has(item) && !item.includes(`mob_orb`)) return;
  if (!checkAndSetCooldown(data.source.id)) return;
  if (item.includes(`mob_orb`)) {
    item = item.split(".")[0]
  }
  switch (item) {
    case 'maca_evolvingmobs:capture_orb':
      handleCaptureOrb(data);
      spawnCaptureOrb(data);
      break;
    case 'maca_evolvingmobs:mob_orb':
      mobOrb(data)
      break;
    case 'maca_evolvingmobs:empty_mob_orb':
      emptyMobOrb(data)
      break;    
    case 'minecraft:stick':
      debugStick(data)
      break;
    case 'maca_evolvingmobs:mark_target':
      markEnemies(data)
      break;
    default:
      break;
  }   
});

world.afterEvents.entitySpawn.subscribe((e) => {
  const {entity: projectile} = e;
  try {
    const dim = projectile?.dimension;
    if (projectile.typeId === "maca_evolvingmobs:shroombull") {
      poisonInt(projectile);
    };
    if (!customProjectiles.has(projectile?.typeId)) return;
    if (projectile?.typeId === "maca_evolvingmobs:arcancluck.projectile") {
      const entity = dim.getEntities({
        location: projectile.location,
        maxDistance: 2
      }).filter(entity => entity.typeId === "maca_evolvingmobs:arcancluck" || entity.typeId === "maca_evolvingmobs:pyrovulpis")[0];
      eggLauncher(entity, projectile)
    } else {
      const nearbyMob = dim.getEntities({
        location: projectile.location,
        maxDistance: 3
      }).filter(entity => 
        entity.typeId.includes("maca_evolvingmobs:")
      )[0];

      switch (nearbyMob.typeId) {
        case "maca_evolvingmobs:turtle":
          bubbleAttack(nearbyMob);
          break;
        default:
          break;
      }
      
    }
  } catch (e){}
});

world.afterEvents.entityHitEntity.subscribe((e) => {
  const {damagingEntity, hitEntity} = e;
  if (!hitEnt.has(damagingEntity.typeId)) return;

  if (damagingEntity.typeId.includes("bee")) {
    hitEntity.addEffect('poison', 100);
  } else if (damagingEntity.typeId.includes("golem") || damagingEntity.typeId === "maca_evolvingmobs:vurulfur") {
    secondHit(damagingEntity, hitEntity);
  } else if (damagingEntity.typeId === "maca_evolvingmobs:taurorix") {
    const dmg = getKey(damagingEntity);
    KnockbackAOE(damagingEntity, dmg, hitEntity.location)
  } else {
    const range = getKey(damagingEntity);
    system.runTimeout(() => {
      if (hitEntity && hitEntity.isValid()) {
      knockbackDir(damagingEntity, range, hitEntity);
    }
  },1)
  }
});

world.afterEvents.projectileHitEntity.subscribe((event) => {
  const {projectile, source} = event;
  const projectileType = projectile.typeId;
  
  // Early exit if the projectile is not one of the custom types
  if (!source) return;
  if (!customProjectiles.has(projectileType) || !source.typeId.includes("maca_evolvingmobs:")) return;
  if (!checkAndSetCooldown(source.id, 40)) return;
  const hitEntity = event.getEntityHit().entity;
  if (!hitEntity) return;
  try {
    const ownerId = source.getDynamicProperty(`maca_evolvingmobs:owner_id`);
    const targetOwner = hitEntity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
    if (targetOwner === ownerId) return;
    const dmg = getKey(source);
    if (simExplosion.has(projectileType)) {
      KnockbackAOE(source, dmg, hitEntity.location, false)
      let particle;
      switch (projectileType) {
        case "maca_evolvingmobs:vexor.projectile":
          particle = "maca_evolvingmobs:vexor_on_hit";
          break;
        case "maca_evolvingmobs:wythern.projectile":
          particle = "maca_evolvingmobs:wythern_impact";
          hitEntity.addEffect('levitation', 40, { amplifier: 2, showParticles: true });
          hitEntity.addEffect('wither', 40, { amplifier: 3, showParticles: true });
          break;
        default:
          particle = false;
          break;
      }
      if (particle) {
        source.dimension.spawnParticle(particle, hitEntity.location)
      }
    } else {
      hitEntity.applyDamage(dmg, {cause: "entityAttack", damagingEntity: source});
    }
  } catch {}
});

let projectileMap = new Map ();
world.afterEvents.projectileHitBlock.subscribe((event) => {
  const {projectile, source, location} = event;
  const projectileType = projectile.typeId;
  if (!projectileMap.has(projectile.id)) {
    projectileMap.set(projectile.id, projectile);
    
    // Early exit if the projectile is not one of the custom types
    if (!simExplosion.has(projectileType) || !source) return;
    const dmg = getKey(source);
    KnockbackAOE(source, dmg, location, true)
    system.runTimeout(() => {
      projectileMap.delete(projectile.id);
    },20)
  }
});

system.afterEvents.scriptEventReceive.subscribe((event) => {
  const mob = event.sourceEntity;
  try {
    switch (event.id) {
      case "maca_evolvingmobs:endoritharian_select_target": {
        if (!mob) return;

        var picked_entity = mob.dimension.getEntities({
          tags: ["grab_target"],
          closest: 1,
          location: mob.location,
          maxDistance: 10,
        });
        if (picked_entity.length > 0) {
          picked_entity[0].addTag(`Enemy:${mob.id}`);
          picked_entity[0].removeTag("grab_target");
          picked_entity[0].runCommand("inputpermission set @s camera disabled");
          return;
        }
        break;
      }
      case "maca_evolvingmobs:endoritharian_deselect_target": {
        if (!mob) return;

        var picked_entity = mob.dimension.getEntities({
          tags: [`Enemy:${mob.id}`],
        });
        if (picked_entity.length > 0) {
          picked_entity[0].removeTag(`Enemy:${mob.id}`);
          picked_entity[0].runCommand("inputpermission set @s camera enabled");
          return;
        }

        break;
      }
      case "maca_evolvingmobs:endoritharian_grab": {
        if (!mob) return;
        const picked_entity = mob.dimension.getEntities({
          tags: [`Enemy:${mob.id}`],
          closest: 1,
          location: mob.location,
          maxDistance: 10,
        });
          const endoritharianRot = mob.getRotation();
          let distance = 1.5;
          let targetX =
            mob.location.x +
            distance * Math.cos(((endoritharianRot.y + 90) * Math.PI) / 180.0);
          let targetZ =
            mob.location.z +
            distance * Math.sin(((endoritharianRot.y + 90) * Math.PI) / 180.0);

          if (picked_entity.length > 0) {
            picked_entity[0].teleport({
              x: targetX,
              y: mob.location.y + 3,
              z: targetZ,
            });
            return;
          }
        break;
      }
      // Trigger on evolve and add name as tag
      case "maca_evolvingmobs:transformed": {
        let mobId = mob.nameTag;
        mob.nameTag = ``;
        mob.addTag(`${mobId}`);
        // world.sendMessage(`I am evolved my name is ${mobId}`)
        break;
      }
      // Trigger on evolve and add name as tag
      case "maca_evolvingmobs:transform": {
        let tags = mob.getTags();
        for (let i = 0; i < tags.length; i++) {
          // world.sendMessage(`${tags[i]}`)
          if (tags[i].includes(`-`)) {
            var mobId = tags[i].toString();
          }
        }
        // mob.runCommand(`say I am evolving! ${mobId}.`);
        mob.nameTag = `${mobId}`;
        break;
      }

      // Simulate explosion and AOE style knockback
      case "maca_evolvingmobs:explode": {
        if (mob.typeId === "maca_evolvingmobs:mooshroom") {
          startSporeAOE(mob)
        } else if (mob.typeId === "maca_evolvingmobs:shroombull") {
          const delay = delayMap.get(mob.typeId.split(":")[1])
          petMap.set(mob.id, mob);
          system.runTimeout(() => {
            shroombullAttack(mob);
          },delay)
        } else {
          const range = getKey(mob);
          KnockbackAOE(mob, range);
        }
        break;
      }

      // Simulate directional knockback for just one target
      case "maca_evolvingmobs:knockback": {
        let range = getKey(mob);
        if (mob.typeId === "maca_evolvingmobs:spidox") {
          range = 6
        }
        knockbackDir(mob, range);
        break;
      }

      case "maca_evolvingmobs:spidox": {
        const range = getKey(mob);
        const ownerId = mob.getDynamicProperty(`maca_evolvingmobs:owner_id`);
          const nearbyMobs = mob.dimension.getEntities({
          location: mob.location,
          maxDistance: 6
          }).filter(e => e.id !== mob.id && e.id !== ownerId && !ignoreList.has(e.typeId) && !e.typeId.includes("projectile"));
          nearbyMobs.forEach((entity) => {
            const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
            if (ownerId !== targetOwner) {
              entity.applyDamage(range, {cause: "entityAttack", damagingEntity: mob});
            }
          }) 
        break;
      }

      case "maca_evolvingmobs:turtle": {
        if (!checkAndSetCooldown(mob.id)) {
          mob.triggerEvent("maca_evolvingmobs:goltarras_attack_deactivated");
          mob.triggerEvent("maca_evolvingmobs:switch_to_melee");
          return;
        }
        dashAttack(mob);
        break;
      }

      case "maca_evolvingmobs:chicken": {
        furyFeathers(mob);
        break;
      }

      case "maca_evolvingmobs:fairbee": {
        let currentTick = 0;
        const loc = mob.location;
        const dim = mob.dimension;
        const ownerId = mob.getDynamicProperty(`maca_evolvingmobs:owner_id`)

        const raycastResult = dim.getBlockFromRay(
          loc,
          { x: 0, y: -1, z: 0 },
          {
              maxDistance: 5,
              includeLiquidBlocks: false,
              includePassableBlocks: false
          }
        );

        dim.getEntities({
          location: raycastResult.block.location,
          maxDistance: 5
        }).filter(entity => 
          !ignoreList.has(entity.typeId) &&
          entity.id !== mob.id && 
          entity.id !== ownerId
        ).forEach(entity => {
          try {
            const ownerId = mob.getDynamicProperty(`maca_evolvingmobs:owner_id`);
            const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
            if (targetOwner === ownerId) return;
            fairbee(mob, entity);
          } catch {}
        });
        
        poisonInterval = system.runInterval(() => {
          if (currentTick % 10 == 0) {
            //try 5 times to poison surrounding entities
            try{
              poisonEntitiesInRadius(loc, dim, mob);
            } catch{
              system.clearRun(poisonInterval);
            }
          }
          if (currentTick === 50) {
            system.clearRun(poisonInterval);
          }
          currentTick++;
        }, 1);
        break;
      }

      case "maca_evolvingmobs:firebreath_on": {
        
        // Check if the mob already has an active interval
        if (!attackIntervals.has(mob.id)) {
          mob.dimension.playSound("evolvingmobs.fire", mob.location);
          fireBreath(mob, true);
        }
        break;
      }

      case "maca_evolvingmobs:firebreath_off": {
        fireBreath(mob, false);
        break;
      }

      default:
        break;
    }
    return;
  } catch {}
});

loadPlayerMobs();

system.runInterval(()=>{
  players = world.getPlayers()
},100);

//check for the tags in one place
system.runInterval(()=> {
  if ( players == null) return;
  if ( players.length > 0 ) {
    for(let i = 0; i < players.length; i++) {
      // GET the marked mobs for attack. AND an array of the actual markers. And the tempOrbs array
      markedMobs = world.getDimension(players[i].dimension.id).getEntities({location:players[i].location,maxDistance:100,tags:['maca_evolvingmobs:entity_marked_for_attack']})
      markers = world.getDimension(players[i].dimension.id).getEntities({location:players[i].location,maxDistance:100,type:'maca_evolvingmobs:entity_marked'})
      tempOrbs = world.getDimension(players[i].dimension.id).getEntities({location:players[i].location,maxDistance:40,type:`maca_evolvingmobs:capture_orb`})
      if (markers.length || markedMobs.length) {
        spawnMarker(markedMobs,markers);
      }
      if (tempOrbs.length) 
        try {
        tempOrbsFunction(tempOrbs);
      } catch (e) {
        return;
      }
    }
  }
},1);

system.runInterval(()=> {
  if (markers.length == 0) return;
  checkMarkers(markers);
},4);

world.afterEvents.entityDie.subscribe(e => {
  if (ignoreList.has(e.deadEntity.typeId)) return;
  try {
    removeTarget(e.deadEntity.dimension, e.deadEntity.location)
  } catch {
    return;
  }
  const {damageSource, deadEntity: entity} = e;
    if (!damageSource.damagingEntity) return; //Check that its another entity that caused damage
    if (damageSource.damagingEntity.typeId !== "maca_evolvingmobs:fox") return; //Check if the entity damaging is in the set
    
    const loot = lootTable[entity.typeId];
    if (!loot || loot.length === 0) return; // No loot table or items defined for this mob
    const dimension = entity.dimension;
    
    loot.forEach(drop => {
        if (Math.random() <= drop.chance) {
            const qty = Math.floor(Math.random() * (drop.max_qty - drop.min_qty + 1)) + drop.min_qty;
            if (qty !== 0){
            const itemStack = new ItemStack(drop.item, qty);
            dimension.spawnItem(itemStack, entity.location);
          }
        }
    });
});

function poisonInt(mob) {
  if (mob && mob.isValid() && !attackIntervals.has(mob.id)) {
    const interval = system.runInterval(() => {
      if (!mob || !mob.isValid()) {
        attackIntervals.delete(mob.id);
        system.clearRun(interval);
        return;
      }
      poisonEntitiesInRadius(mob.location, mob.dimension, mob, 4)
    }, 20);
    attackIntervals.set(mob.id, interval);
  }
}

function shroombullAttack(source) {
  if (!source || !source.isValid()) return;
  try {
    
  source.triggerEvent("maca_evolvingmobs:xp_up.one");
    const dim = source.dimension;
    const loc = source.location;
    petMap.forEach((petId, pet) => {
      if (petId.id === source.id){
        system.runTimeout(() => {
          petMap.delete(source.id);
          return;
        },45)
        let counter =0;
        let runID = system.runInterval(() => {
          counter ++
          if (!petMap.has(source.id)) {
            system.clearRun(runID)
            return;
          }
          const key = getKey(source);
          const range = key / counter;
          const ownerId = source.getDynamicProperty(`maca_evolvingmobs:owner_id`);
          const nearbyMobs = dim.getEntities({
          location: loc,
          maxDistance: range
          }).filter(e => e.id !== source.id && e.id !== ownerId && !ignoreList.has(e.typeId) && !e.typeId.includes("projectile"));
          nearbyMobs.forEach((entity) => {
            const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
            if (ownerId !== targetOwner) {
              entity.addEffect("poison", 200);
              entity.applyDamage(range);
            }
          }) 
        },5) 
      }
    });
  } catch {}  
}

function startSporeAOE(mob) {
  const dim = mob.dimension;
  const loc = mob.location;
  dim.playSound("mob.cow.say", loc);
  
  mob.triggerEvent("maca_evolvingmobs:xp_up.one");
  const range = getKey(mob);
  KnockbackAOE(mob, range);
  const delay = delayMap.get(mob.typeId.split(":")[1])
  system.runTimeout(() => {
    
    dim.spawnParticle("maca_evolvingmobs:poison_explosion", loc);
    dim.spawnParticle("maca_evolvingmobs:mooshroom_attack", loc);
  }, delay)
  
}

function fairbee(fairbee,entity) {
  entity.dimension.spawnParticle("maca_evolvingmobs:fairbee_downward_drag", entity.location)
  entity.applyKnockback(0, 0, 0, 1);
  const range = getKey(fairbee);
  fairbee.triggerEvent("maca_evolvingmobs:xp_up.one");
  system.runTimeout(() => {
    entity.applyDamage(range, {cause: "entityAttack", damagingEntity: fairbee});
  }, 3);
  
  system.runTimeout(() => {
    knockbackDir(fairbee, range, entity)
  }, 20);
};

function fireBreath(pet, action) {
  if (!pet || !action) {
    const Fireinterval = attackIntervals.get(pet.id);
        if (Fireinterval !== undefined) {
          system.clearRun(Fireinterval);
          attackIntervals.delete(pet.id);
        }
        return;
  }
  const petID = pet.id
  const interval = system.runInterval(() => {
    attackIntervals.set(petID, interval);
    if (!pet) {
            system.clearRun(interval);
            attackIntervals.delete(petID);
          return;
    }
    try {
      const petPosition = pet.getHeadLocation();
    const viewDirection = pet.getViewDirection();
    const dimension = pet.dimension

    // Constants for the cone effect
    const FIRE_RANGE = 16;
    const CONE_ANGLE = 0.05;
    const PARTICLES_PER_RING = 3;

    // Play sound every 4th time the function is called
    fireBreathCounter++;
    if (fireBreathCounter % 8 === 0) {
        const petSoundOptions = { pitch: 1.0, volume: 8.0 };
        pet.dimension.playSound("evolvingmobs.fire", pet.location, petSoundOptions);
        fireBreathCounter = 0;
    }

    // Apply fire damage to entities
    const entityHits = pet.getEntitiesFromViewDirection({ maxDistance: FIRE_RANGE });
    for (const hit of entityHits) {
        const entity = hit.entity;
        const ownerId = pet.getDynamicProperty(`maca_evolvingmobs:owner_id`);
        const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
        if (entity.id !== pet.id && targetOwner !== ownerId) {
            const dmg = getKey(pet);
            entity.setOnFire(5, true);
            entity.applyDamage(dmg, {cause: "entityAttack", damagingEntity: pet});
            pet.triggerEvent("maca_evolvingmobs:xp_up.one");
        }
    }

    // Spawn particles in a cone shape that rotates with the pet's view direction
    for (let i = 2; i <= FIRE_RANGE; i++) {
        const radius = Math.tan(CONE_ANGLE) * i;

        for (let j = 0; j < PARTICLES_PER_RING; j++) {
            const angle = (j / PARTICLES_PER_RING) * Math.PI * 2;
            const offsetX = radius * Math.cos(angle) + Math.random();
            const offsetY = radius * Math.sin(angle);

            // Calculate rotated offsets based on view direction using cross product for perpendicular vectors
            const perpVector1 = {
                x: -viewDirection.z,
                y: 0,
                z: viewDirection.x
            };

            const perpVector2 = {
                x: -viewDirection.y * viewDirection.x,
                y: viewDirection.x * viewDirection.x + viewDirection.z * viewDirection.z,
                z: -viewDirection.y * viewDirection.z
            };

            // Normalize perpendicular vectors
            const length1 = Math.sqrt(perpVector1.x ** 2 + perpVector1.y ** 2 + perpVector1.z ** 2);
            perpVector1.x /= length1;
            perpVector1.y /= length1;
            perpVector1.z /= length1;

            const length2 = Math.sqrt(perpVector2.x ** 2 + perpVector2.y ** 2 + perpVector2.z ** 2);
            perpVector2.x /= length2;
            perpVector2.y /= length2;
            perpVector2.z /= length2;

            // Calculate final particle position
            const particlePosition = {
                x: petPosition.x + viewDirection.x * i + offsetX * perpVector1.x + offsetY * perpVector2.x,
                y: petPosition.y + viewDirection.y * i + offsetX * perpVector1.y + offsetY * perpVector2.y,
                z: petPosition.z + viewDirection.z * i + offsetX * perpVector1.z + offsetY * perpVector2.z,
            };
            try {
                dimension.spawnParticle("minecraft:basic_flame_particle", particlePosition);
                dimension.spawnParticle("maca_evolvingmobs:fire.trail.1", particlePosition);
                dimension.spawnParticle("maca_evolvingmobs:fire.trail.2", particlePosition);
                dimension.spawnParticle("maca_evolvingmobs:fire.trail.3", particlePosition);

                // Check if we can set fire
                const targetBlock = dimension.getBlock(particlePosition);
                let firePos;

                const fireImmuneBlocks = new Set ([
                  "minecraft:air",
                  "minecraft:lava",
                  "minecraft:water",
                  "minecraft:running_lava",
                  "minecraft:running_water"
                ]);
                

                if (targetBlock) {
                    if (replaceBlocks.has(targetBlock.typeId)) {
                        firePos = particlePosition;
                        
                    }  else if (!fireImmuneBlocks.has(targetBlock.typeId)) {
                        const abovePos = {
                            x: particlePosition.x,
                            y: particlePosition.y + 1,
                            z: particlePosition.z
                        };
                        const aboveBlock = dimension.getBlock(abovePos);
                        
                        // Only place fire if above position is air or replaceable vegetation
                        if (aboveBlock.typeId === "minecraft:air" || replaceBlocks.has(aboveBlock.typeId)) {
                            firePos = abovePos;
                        }
                    } else {
                        const belowPos = {
                            x: particlePosition.x,
                            y: particlePosition.y - 1,
                            z: particlePosition.z
                        };
                        const belowBlock = dimension.getBlock(belowPos);
                        const secondBelowPos = {
                          x: particlePosition.x,
                          y: particlePosition.y - 2,
                          z: particlePosition.z
                        };
                        const secondBelowBlock = dimension.getBlock(secondBelowPos);

                        if (belowBlock && replaceBlocks.has(belowBlock.typeId)){
                          firePos = belowPos;
                        } else if (belowBlock.typeId === "minecraft:air" && !fireImmuneBlocks.has(secondBelowBlock.typeId) && !replaceBlocks.has(secondBelowBlock.typeId)) {
                          firePos = particlePosition;
                        }
                    }
                    
                    if (firePos) {
                      dimension.runCommand(`setblock ${firePos.x} ${firePos.y} ${firePos.z} fire`);
                      break;
                    } // Stop after first valid placement
                }
            } catch {}
        }
    }
    } catch {
      system.clearRun(interval);
      attackIntervals.delete(petID);
      return;
    }
  }, FIRE_INTERVAL);
}

function bubbleAttack(turtle) {
  const dim = turtle.dimension;
  const loc = turtle.location;
  let baseVelocity = turtle.getViewDirection();
  let dir = normalizeVector(baseVelocity);
  turtle.triggerEvent("maca_evolvingmobs:xp_up.one");
  const entities = getEntitiesInfront(turtle, dir) || false;
  if (!entities || entities === undefined) return;
  if (entities.length > 0) {
    let hitCount = 0;
    let hitInterval = system.runInterval(() => {
      if (!turtle) {
        system.clearRun(hitInterval);
        hitCount = 0;
        return;
      }
      try {
        baseVelocity = turtle.getViewDirection();
        dir = normalizeVector(baseVelocity);
        const dirY = (dir.y / 1.2) * 90;

        const targetLocation = {
          x: loc.x,
          y: loc.y + 0.5,
          z: loc.z,
        };
        const molangVars = new MolangVariableMap();
        molangVars.setFloat("variable.direction_x", dir.x);
        molangVars.setFloat("variable.direction_y", dirY);
        molangVars.setFloat("variable.direction_z", dir.z);
        
        entities.forEach((entity) => {
          if (hitCount < 4) {
            try {
              dim.spawnParticle(
                "maca_evolvingmobs:turtle_bubbles",
                targetLocation,
                molangVars
              );
              const ownerId = turtle.getDynamicProperty(
                `maca_evolvingmobs:owner_id`
              );
              const targetOwner = entity?.getDynamicProperty(
                `maca_evolvingmobs:owner_id`
              );
              if (targetOwner === ownerId) return;
              if (!ignoreList.has(entity.typeId)) {
                const distance = Math.sqrt(
                  Math.pow(entity.location.x - targetLocation.x, 2) +
                    Math.pow(entity.location.y - targetLocation.y, 2) +
                    Math.pow(entity.location.z - targetLocation.z, 2)
                );
                const maxDamage = getKey(turtle);
                const damage = Math.max(2, maxDamage - distance / 8); // damage scales, the further away the less damage they take
                system.runTimeout(() => {
                  entity.applyDamage(damage, {
                    cause: "entityAttack",
                    damagingEntity: turtle,
                  });
                }, distance)
                
              }
              hitCount++;
            } catch (e) {
            }
          } else {
            system.clearRun(hitInterval);
            hitCount = 0;
          }
        });
      } catch {
        system.clearRun(hitInterval);
        hitCount = 0;
        return;
      }
    }, 10);
  }
}

function normalizeVector(v) {
  const length = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
  return { x: v.x / length, y: v.y / length, z: v.z / length };
}

function getEntitiesInfront(mob, direction) {
  if (!mob) return;
  try {
    const dimension = mob?.dimension;
    const maxDistance = 20;
    const entitiesInfront = [];
    const ownerId = mob.getDynamicProperty(`maca_evolvingmobs:owner_id`)
    const start = {x: mob.location.x, y: mob.location.y, z: mob.location.z}
    const maxHits = 5;
    let hitCount = 0;

    for (let i = 0; i < maxDistance && hitCount < maxHits; i++) {
      const width = 2
      const forwardPoint = {
        x: start.x + direction.x * (i),
        y: start.y + direction.y * (i),
        z: start.z + direction.z * (i),
      };

      const nearbyEntities = dimension.getEntities({
        location: forwardPoint,
        maxDistance: width,
      });

      for (const entity of nearbyEntities) {
        if (!entitiesInfront.includes(entity) && !ignoreList.has(entity.typeId) && entity.id !== ownerId && entity.id !== mob.id) {
          entitiesInfront.push(entity);
          hitCount++;
          if (hitCount >= maxHits) break;
        }
      }
    }

    return entitiesInfront;
  } catch {}
  
}

function getKey(mob) {
  try {
    const LVL = Math.floor(mob.getProperty('maca_evolvingmobs:level') / 5) * 5; //rounds down to the nearest multiple of 5
    const key = `${mob.typeId.split(":")[1]}${LVL}`;
    const range = rangeMap.get(key) || 4;
  return range;
  } catch(e) {
    return;
  }
}

function secondHit(golem, entity) {
  if (!golem || !entity) return;
  let delay = 20;
  if (golem.typeId.includes("gold") || golem.typeId === "maca_evolvingmobs:vurulfur") {
    delay = 10
  }
  const range = getKey(golem);
  system.runTimeout(() => {
    
  golem.triggerEvent("maca_evolvingmobs:xp_up.one");
    knockbackDir(golem, range, entity)
  }, delay)
}

function loadPlayerMobs() {
  const data = world.getDynamicProperty("LoadedMobs");
  if (data) {
      try {
          const parsedData = JSON.parse(data);
          playerMobs.clear();

          // Populate map from parsed data
          for (const key in parsedData) {
              if (parsedData.hasOwnProperty(key)) {
                  playerMobs.set(key, parsedData[key]);
              }
          }

      } catch (error) {

      }
  } 
}

function poisonEntitiesInRadius(loc, dim, mob, rad) {
  const radius = rad ? rad : 12;
  const ownerId = mob.getDynamicProperty(`maca_evolvingmobs:owner_id`)
  dim.getEntities({
    location: loc,
    maxDistance: radius
  }).filter(entity => 
    !ignoreList.has(entity.typeId) &&
    entity.id !== mob.id && 
    entity.id !== ownerId
  ).forEach(entity => {
    try {
      const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`)
      if (targetOwner === ownerId) return;
      if (mob.typeId === "maca_evolvingmobs:shroombull") {
        if (passiveMobs.has(entity.typeId)) return;
      }
      entity.addEffect("poison", 200);
    } catch {}
  });
}

function saveLoadedMob(ownerId, limit) {
  playerMobs.set(ownerId, limit);

  // Convert Map to plain JavaScript object before saving, json and maps do not get along 
  const obj = Object.fromEntries(playerMobs);
  const jsonString = JSON.stringify(obj);
  world.setDynamicProperty("LoadedMobs", jsonString);
}

function KnockbackAOE(source, range, block, hitblock) {
  const strength = Math.max(1, Math.min(4, range /4))
  const dist = Math.max(5, Math.min(10, range))
  const loc = block ? block : source.location;
  const delay = delayMap.get(source.typeId.split(":")[1]) || 1;
  const ownerId = source.getDynamicProperty(`maca_evolvingmobs:owner_id`)
  
  source.triggerEvent("maca_evolvingmobs:xp_up.one");
  
  const nearbyMobs = source.dimension.getEntities({
    location: loc,
    maxDistance: dist
  }).filter(e => e.id !== source.id && e.id !== ownerId && !ignoreList.has(e.typeId) && !customProjectiles.has(e.typeId)); 
  system.runTimeout(() => {
    nearbyMobs.forEach((entity) => {
      
      try {
        const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
        if (ownerId !== targetOwner){
          if (source.typeId === "maca_evolvingmobs:mooshroom" || source.typeId === "maca_evolvingmobs:shroombull") {
            source.dimension.playSound("evolvingmobs.poison_explosion", loc);
            entity.addEffect('poison', 200);
            range = strength
          } 

          if (source.typeId === "maca_evolvingmobs:vexor" && hitblock) {
            entity.setOnFire(5)
          }
          
          const direction = calculateDirection(loc, entity.location);
          if (direction.x === null || direction.z === null) return;
          applyKnockback(entity, direction.x, direction.z, strength, 0.5)
          system.runTimeout(() => {
            try {
              entity.applyDamage(range, {cause: "entityAttack", damagingEntity: source});
            } catch(e){
            }
            
          },2)
        }
      } catch (error) {
      }
  });
  },delay)
};

function knockbackDir(source, range, target) {
  try {
    let loc;
    if (source.typeId === "maca_evolvingmobs:endoritharian") {
      loc = {x: source.location.x, y: source.location.y +3, z: source.location.z}
    } else {
      loc = source.location
    }
    let hitCount = 0;
    const strength = range /3;
    const delay = delayMap.get(source.typeId.split(":")[1])
    const ownerId = source.getDynamicProperty(`maca_evolvingmobs:owner_id`);
    const rotation = source.getRotation();
    const rad = rotation.y * Math.PI / 180;
    directionX = -Math.sin(rad) * DASH_STRENGTH;
    directionZ = Math.cos(rad) * DASH_STRENGTH;
    const nearbyMobs = target ? target : source.dimension.getEntities({
      location: loc,
      maxDistance: range
    }).filter(e => e.id !== source.id && e.id !== ownerId && !ignoreList.has(e.typeId));
    system.runTimeout(() => {
      if (target && nearbyMobs && nearbyMobs.isValid()) {
        const targetOwner = nearbyMobs?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
        try {
          if (targetOwner !== ownerId){
            hitCount ++
            nearbyMobs.applyDamage(range, {cause: "entityAttack", damagingEntity: source});
            applyKnockback(nearbyMobs, directionX, directionZ, strength, 0.5)
          }
        } catch (error) {}
      } else if (nearbyMobs.length){
        nearbyMobs.forEach((entity) => {
          if (hitCount == 0) {
            const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
            try {
              if (targetOwner !== ownerId){
                hitCount ++
                entity.applyDamage(range, {cause: "entityAttack", damagingEntity: source});
                applyKnockback(entity, directionX, directionZ, strength, 0.5)
              }
            } catch (error) {}  
          }
        });
      }
      source.triggerEvent("maca_evolvingmobs:xp_up.one");
    }, delay);
  } catch {}
}

function applyKnockback(entity, directionX, directionZ, horizontalStrength, verticalStrength) {
  entity.applyKnockback(
    directionX,
    directionZ,
    horizontalStrength,
    verticalStrength
  );
}

// Function to calculate a normalized direction vector
function calculateDirection(from, to) {
  const dx = to.x - from.x;
  const dz = to.z - from.z;
  const length = Math.sqrt(dx * dx + dz * dz);
  return { x: dx / length, z: dz / length };
}

function checkMarkers(markers) {
  for (let i = 0; i < markers.length; i++) {
    let mobLocation = markers[i].location
    let mobDimension = markers[i].dimension
    const marker = mobDimension.getEntities({location:mobLocation,maxDistance:2}).filter(e => e.typeId === 'maca_evolvingmobs:entity_marked')[0];
    const ID = marker.getTags();
    const ent = mobDimension.getEntities({location:mobLocation,maxDistance:2}).filter(e => e.typeId !== 'maca_evolvingmobs:entity_marked')[0];
    if (ent?.id == ID) return;
    removeTarget(mobDimension, mobLocation)
  }
}

function handleCaptureOrb(data) {
  let dimension = data.source.dimension;
  let thrownOwnerId = data.source.id;
  let nearbyCaptureOrb = dimension.getEntities({location:data.source.location,maxDistance:30,type:'maca_evolvingmobs:capture_orb',closest:1,excludeTags:['maca_evolvingmobs:owner_marked']})
  for (let i = 0; i < nearbyCaptureOrb.length; i++) {
    let orb = nearbyCaptureOrb[i];
    orb.setDynamicProperty(`maca_evolvingmobs:thrown_owner_id`, thrownOwnerId); 
    orb.addTag('maca_evolvingmobs:owner_marked');
  }
};

function spawnCaptureOrb(data) {
  const player = data.source;
  const item = data.itemStack;
  if (item.amount > 1) {
    item.amount--;
    player.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, item);
  } else {
    player.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, undefined);
  }

  let sourceLocation = player.location;
  let dimension = player.dimension;
  var spawnedEntity = dimension.spawnEntity(`maca_evolvingmobs:capture_orb<maca_evolvingmobs:capture_orb.projectile>`, {
    x: sourceLocation.x,
    y: sourceLocation.y + 1.5,
    z: sourceLocation.z,
  });
  
  let projectileComponent = spawnedEntity.getComponent('minecraft:projectile');
  projectileComponent.owner = player

  let ownerId = player.id
  let ownerName = player.name
  spawnedEntity.setDynamicProperty(`maca_evolvingmobs:thrown_owner_id`, ownerId)
  spawnedEntity.setDynamicProperty(`maca_evolvingmobs:thrown_owner_name`, ownerName)

  const velocity = player.getViewDirection();
  spawnedEntity.applyImpulse(velocity);   
};

function mobOrb(data) {
  const player = data.source;
  const item = data.itemStack;
  let ownerId = player.id;
  let limit = playerMobs.get(ownerId) || 0;
  limit ++;
  if (limit >= 6) {
    player.onScreenDisplay.setActionBar("Maximum number of mob pets has been reached")
    return;
  }
    
  // Store Level
  let storedLore = item.getLore();
  // world.sendMessage(`Set storedLore to ${storedLore}`);
  let storedLevel
  for(let i = 0; i < storedLore.length; i++)
    {
      if(storedLore[i].includes(`§r§6Level`))
      {
        storedLevel = storedLore[i].replace(`§r§6Level: `, "")
      }
    }
    // world.sendMessage(`${storedLevel}`)
  // world.sendMessage(`Set storedLevel to ${storedLevel}`);

  // Store mob type
  let storedName = item.typeId.replace(`mob_orb.`, "");
  // world.sendMessage(`Set storedName to ${storedName}`);

  // Spawn Entity
  // world.sendMessage("item used")
  let sourceLocation = player.location;
  let dimension = player.dimension;
  var spawnedEntity = dimension.spawnEntity(`${storedName}<maca_evolvingmobs:spawn_as_projectile>`, {
    x: sourceLocation.x,
    y: sourceLocation.y + 2,
    z: sourceLocation.z,
  });

  // Drop Normal Capture Orb
  // const captureOrb = new ItemStack('maca_evolvingmobs:capture_orb', 1);
  // dimension.spawnItem(captureOrb, sourceLocation);

  // Set Owner
  let projectileComponent = spawnedEntity.getComponent('minecraft:projectile');
  projectileComponent.owner = player

  // DATA
  
  let ownerName = player.name
  // world.sendMessage(`Owner ID = ${ownerId}`);
  spawnedEntity.setDynamicProperty(`maca_evolvingmobs:owned`, true);
  spawnedEntity.setDynamicProperty(`maca_evolvingmobs:owner_id`, ownerId)

  // Drop Empty Capture Orb: 
  let newName = item.typeId.replace(`maca_evolvingmobs:`, `maca_evolvingmobs:empty_`);
  let strippedMobName = item.typeId.replace(`maca_evolvingmobs:mob_orb.`, ``);
  //example of shorter way to strip name, item.typeId.split(".")[1] 
  let mobNameBaseLevel = strippedMobName + `BaseLevel`;
  let mobId = spawnedEntity.id;
  spawnedEntity.setDynamicProperty(`maca_evolvingmobs:id`, mobId)
  saveLoadedMob(ownerId, limit)
  
  // world.sendMessage(`${newName}`)
  // world.sendMessage(`${strippedMobName}`)
  // world.sendMessage(`${mobNameBaseLevel}`)
  if ( storedLevel != null ) {
    // world.sendMessage("Stored level is defined")
    let emptyCaptureOrb = new ItemStack(`${newName}`);
    emptyCaptureOrb.keepOnDeath = true;
    emptyCaptureOrb.setLore([`§r§6Level: ${storedLevel}`, `§r§6Owner: ${ownerName}`, `§r§6ID: ${mobId}`])
    player.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, emptyCaptureOrb);
    player.runCommand(`titleraw @s actionbar {"rawtext":[{"text":"§bGo ${strippedMobName}!"}]}`);
  } else if ( storedLevel == undefined ) {
    // world.sendMessage("Stored level is undefined")
    let storedLevel = mobs[mobNameBaseLevel]
    let emptyCaptureOrb = new ItemStack(`${newName}`);
    emptyCaptureOrb.keepOnDeath = true;
    emptyCaptureOrb.setLore([`§r§6Level: ${storedLevel}`, `§r§6Owner: ${ownerName}`, `§r§6ID: ${mobId}`])
    player.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, emptyCaptureOrb);
    player.runCommand(`titleraw @s actionbar {"rawtext":[{"text":"§bGo ${strippedMobName}!"}]}`);
  }        

  // Velocity
  const velocity = player.getViewDirection();
  spawnedEntity.applyImpulse(velocity);
  spawnedEntity.addTag(`${mobId}`);
  spawnedEntity.triggerEvent('maca_evolvingmobs:caught')
  if (storedLevel != undefined) {
    spawnedEntity.triggerEvent(`maca_evolvingmobs:set_level_${storedLevel}`)
  }
  
  
  system.runTimeout(() => {
    spawnedEntity.triggerEvent('maca_evolvingmobs:remove_projectile')
  }, 4)
};

function debugStick(event) {
  const player = event.source;
  
  const ray = player.getEntitiesFromViewDirection({maxDistance:10, excludeTypes:["minecraft:player"]});

  if(!ray) return;

  ray.forEach((hit, index) => {
      const entity = hit.entity;
      player.sendMessage(
          `Hit Debug Entity ${index}:
          Entity ID: ${entity.id}
          Entity Name: ${entity.nameTag}
          DP: ${entity.getDynamicProperty("maca_evolvingmobs:id")} `
      )
  });
};

function emptyMobOrb(event) {
  // world.sendMessage('use empty orb test')

    // Data Extraction
    let storedLore = event.itemStack.getLore();
    for (let i = 0; i < storedLore.length; i++) {
      if (storedLore[i].includes(`§r§6Level`)) {
        var storedLevel = storedLore[i].replace(`§r§6Level: `, "")
      }
      if (storedLore[i].includes(`§r§6ID`)) {
        var storedID = storedLore[i].replace(`§r§6ID: `, "")
      }
      if (storedLore[i].includes(`§r§6Owner`)) {
        var storedOwner = storedLore[i].replace(`§r§6Owner: `, "")
      }
    }
    // world.sendMessage(`${storedLevel} is level, ${storedID} is ID, ${storedOwner} is Owner`);
    let dimension = event.source.dimension;
    let linkedMobArray = dimension.getEntities({tags:[`${storedID}`]})
    for (let i = 0; i < linkedMobArray.length; i++) {
      var linkedMob = linkedMobArray[0]
    }
    // GET info from mob
    let newLevel = linkedMob.getProperty('maca_evolvingmobs:level').toString()
   
    let mobName = linkedMob.typeId.replace(`maca_evolvingmobs:`, ``)
    let itemMobName = event.itemStack.typeId.replace(`maca_evolvingmobs:empty_mob_orb.`, ``)

    if ( mobName == itemMobName ) {
      // world.sendMessage(`Item matches mob`)
      // Create NEW item
      let newItemName = event.itemStack.typeId.replace(`maca_evolvingmobs:empty_mob_orb`, `maca_evolvingmobs:mob_orb`)
      // world.sendMessage(`${newItemName}`);
      let newCaptureOrb = new ItemStack(`${newItemName}`)
      newCaptureOrb.keepOnDeath = true;
      newCaptureOrb.setLore([`§r§6Level: ${newLevel}`])
      event.source.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, newCaptureOrb);
      event.source.runCommand(`titleraw @s actionbar {"rawtext":[{"text":"§bRecalling ${mobName}"}]}`);

    } else if ( mobName != itemMobName) { 
      // world.sendMessage(`Item doesn't match mob, but evolved mob exists`)
      // Create new item
      let newItemName = `maca_evolvingmobs:mob_orb.` + mobName 
      let newCaptureOrb = new ItemStack(`${newItemName}`)
      newCaptureOrb.keepOnDeath = true;
      newCaptureOrb.setLore([`§r§6Level: ${newLevel}`])
      event.source.getComponent('equippable').setEquipment(EquipmentSlot.Mainhand, newCaptureOrb);
    };
    if (dashInterval !== null && linkedMob.typeId === "maca_evolvingmobs:goltarras") {
      cancelDash(linkedMob)
      console.warn("cancelling dash")
    }
    // Despawn Mob
    linkedMob.triggerEvent('maca_evolvingmobs:is_being_captured')
    linkedMob.triggerEvent('maca_evolvingmobs:white.on')
    try {
      linkedMob.triggerEvent('maca_evolvingmobs:attack')
    } catch{}
    let limit = playerMobs.get(event.source.id);
    limit = Math.max(0,limit -1)
    saveLoadedMob(event.source.id, limit)
};

function markEnemies(event) {
  const player = event.source;
    const target_entity = player.getEntitiesFromViewDirection({maxDistance: 30});
    if (target_entity.length) {
      const ent = target_entity[0].entity;
      const health = ent.getComponent(EntityComponentTypes.Health);
      if (!health) return;
      if (ignoreList.has(ent.typeId)) return;
      if (ent.hasTag(`maca_evolvingmobs:entity_marked_for_attack`)) {
        ent.removeTag(`maca_evolvingmobs:entity_marked_for_attack`)
        removeTarget(ent.dimension, ent.location)
        return;
      }
      world.sendMessage(`Target entity is ${ent.typeId}`)
      ent.addTag(`maca_evolvingmobs:entity_marked_for_attack`)
    }
};

function removeTarget(dim, loc) {
  try {
    let deadMarkers = world.getDimension(dim.id).getEntities({location:loc,maxDistance:2,type:'maca_evolvingmobs:entity_marked'})
    if (deadMarkers.length) {
      deadMarkers[0].triggerEvent('maca_evolvingmobs:despawn');
    }
  } catch (error) {
  }
}

function spawnMarker(markedMobs,markers) {
  try {
  // For each marked mob. Store data.
    for( let j = 0; j < markedMobs.length; j++ ) {
      let markedMobId = markedMobs[j].id
      let mobLocation = markedMobs[j].location
      let mobDimension = markedMobs[j].dimension
      for ( let k = 0; k < markers.length; k++ ) {
        if ( markers[k].hasTag(`${markedMobId}`)) {
          markers[k].teleport(mobLocation);
          break;
        } 
      } 
      if (markers.some(m => m.hasTag(markedMobId))) return;
      var entityMarker = mobDimension.spawnEntity(`maca_evolvingmobs:entity_marked`, mobLocation)
      entityMarker.addTag(`${markedMobId}`)
      //world.sendMessage('Spawning marker')        
    }
  } catch {}
 
};

function tempOrbsFunction(tempOrbs) {
  for(let j = 0; j < tempOrbs.length; j++){
    if(!captureOrbs.includes(tempOrbs[j])) {
      captureOrbs.push(tempOrbs[j])
    }
  }
  
  if(captureOrbs.length > 0) {
    for(let k = 0; k < captureOrbs.length; k++)
      {
        if(captureOrbs[k].isValid())
        {
          
          let captureDim = world.getDimension(captureOrbs[k].dimension.id)
          let nearbyCapturables = captureDim.getEntities({location:captureOrbs[k].location,maxDistance:1.5,excludeTypes:['item','player','maca_evolvingmobs:capture_orb']})
          let thrownOwnerId = captureOrbs[k].getDynamicProperty(`maca_evolvingmobs:thrown_owner_id`);
          let thrownOwnerName = captureOrbs[k].getDynamicProperty(`maca_evolvingmobs:thrown_owner_name`);
          if (nearbyCapturables[0] != null) {
            var capturableOwnerId = nearbyCapturables[0].getDynamicProperty(`maca_evolvingmobs:owner_id`);
          }          
          // world.sendMessage(`${capturableOwnerId} equals ${thrownOwnerId}`);
          
          if(nearbyCapturables.length > 0){
            if(!nearbyCapturables[0].hasTag('maca_evolvingmobs:capture_initiated') && !captureOrbs[k].hasTag('maca_evolvingmobs:instant_despawn')){
              if ( nearbyCapturables[0].getDynamicProperty(`maca_evolvingmobs:owned`) === true && capturableOwnerId != thrownOwnerId) {
                nearbyCapturables[0].runCommand(`titleraw @a[name=${thrownOwnerName}] actionbar {"rawtext":[{"text":"§cThis is not your mob"}]}`)
              }
              if ( nearbyCapturables[0].getDynamicProperty(`maca_evolvingmobs:owned`) === true && capturableOwnerId === thrownOwnerId ) {
                nearbyCapturables[0].runCommand(`titleraw @a[name=${thrownOwnerName}] actionbar {"rawtext":[{"text":"§cYou own this mob"}]}`)
              }
              else if (nearbyCapturables[0].typeId.includes('maca_evolvingmobs') && nearbyCapturables[0].getDynamicProperty(`maca_evolvingmobs:owned`) != true)
              {
              if( nearbyCapturables[0].getProperty('maca_evolvingmobs:level') != null)
                {
                  let tempLevel = nearbyCapturables[0].getProperty('maca_evolvingmobs:level').toString()
                  let tempName = nearbyCapturables[0].typeId
                  captureOrbs[k].addTag(`captureLevel_${tempLevel}`)
                  captureOrbs[k].addTag(`captureMob_${tempName}`)
                }
                captureOrbs[k].clearVelocity()
                nearbyCapturables[0].triggerEvent('maca_evolvingmobs:is_being_captured')
                nearbyCapturables[0].triggerEvent('maca_evolvingmobs:white.on')
                captureOrbs[k].setProperty('maca_evolvingmobs:capturing', true)
                nearbyCapturables[0].addTag('maca_evolvingmobs:capture_initiated')
              }
              else if (nearbyCapturables[0].typeId.includes('minecraft'))
              {
                if(nearbyCapturables[0].typeId.includes('creeper') ||nearbyCapturables[0].typeId.includes('blaze') || nearbyCapturables[0].typeId.includes('enderman') || nearbyCapturables[0].typeId.includes('ghast') || nearbyCapturables[0].typeId.includes('piglin_brute') || nearbyCapturables[0].typeId.includes('slime') || nearbyCapturables[0].typeId.includes('spider') || nearbyCapturables[0].typeId.includes('vex') || nearbyCapturables[0].typeId.includes('warden') || nearbyCapturables[0].typeId.includes('wither') || nearbyCapturables[0].typeId.includes('wither_skeleton') || nearbyCapturables[0].typeId.includes('zombie') || nearbyCapturables[0].typeId.includes('skeleton') || nearbyCapturables[0].typeId.includes('chicken') || nearbyCapturables[0].typeId.includes('bee') || nearbyCapturables[0].typeId.includes('iron_golem') || nearbyCapturables[0].typeId.includes('fox') || nearbyCapturables[0].typeId.includes('pig') || nearbyCapturables[0].typeId.includes('mooshroom') || nearbyCapturables[0].typeId.includes('cow') || nearbyCapturables[0].typeId.includes('wolf') || nearbyCapturables[0].typeId.includes('turtle'))
                  {
                    let tempName = nearbyCapturables[0].typeId
                    if(tempName == 'minecraft:blaze'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:creeper'){captureOrbs[k].addTag(`captureLevel_25`)}
                    if(tempName == 'minecraft:enderman'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:ghast'){captureOrbs[k].addTag(`captureLevel_18`)}
                    if(tempName == 'minecraft:piglin_brute'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:skeleton'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:slime'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:spider'){captureOrbs[k].addTag(`captureLevel_22`)}
                    if(tempName == 'minecraft:vex'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:warden'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:wither'){captureOrbs[k].addTag(`captureLevel_34`)}
                    if(tempName == 'minecraft:wither_skeleton'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:zombie'){captureOrbs[k].addTag(`captureLevel_18`)}
                    if(tempName == 'minecraft:chicken'){captureOrbs[k].addTag(`captureLevel_10`)}
                    if(tempName == 'minecraft:bee'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:iron_golem'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:fox'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:pig'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:mooshroom'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:cow'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:wolf'){captureOrbs[k].addTag(`captureLevel_1`)}
                    if(tempName == 'minecraft:turtle'){captureOrbs[k].addTag(`captureLevel_1`)}
                    
                    captureOrbs[k].addTag(`captureMob_${tempName}`)
                    
                    captureOrbs[k].clearVelocity()
                    nearbyCapturables[0].addEffect('slowness',1000,{amplifier:20,showParticles:false})
                    captureOrbs[k].setProperty('maca_evolvingmobs:capturing', true)
                    nearbyCapturables[0].runCommandAsync('playanimation @s animation.maca_evolvingmobs.evolutions.is_being_captured')
                    nearbyCapturables[0].addTag('maca_evolvingmobs:capture_initiated')
                  }
                else
                  {
                    nearbyCapturables[0].runCommand(`titleraw @a[r=20] actionbar {"rawtext":[{"text":"§cYou can't capture this mob"}]}`)
                  }
              }
          }
          }
          if(captureOrbs[k].hasTag('maca_evolvingmobs:instant_despawn'))
          {
            let storedLevel
            let storedName
            let hasTag = false
            for(let o = 0; o < captureOrbs[k].getTags().length; o++)
            {
              storedName = captureOrbs[k].getTags()[o]
              if(storedName.includes('captureLevel_'))
              {
                storedLevel = storedName.split('_').pop()
              }
              if(storedName.includes('captureMob_'))
              {
                if(!captureOrbs[k].hasTag('maca_evolvingmobs:capture_locked'))
                {
                  
                if(storedName.includes('maca_evolvingmobs') && !storedName.includes('instant_despawn'))//custom mobs
                {
                storedName = storedName.replace("captureMob_", "")
                storedName = storedName.replace("maca_evolvingmobs:", "maca_evolvingmobs:mob_orb.")
                hasTag = true
                if(hasTag == true)
                {
                  let newCaptureOrb = new ItemStack(`${storedName}`,1)
                  newCaptureOrb.setLore([`§r§6Level: ${storedLevel}`])
      
                  captureDim.spawnItem(newCaptureOrb,captureOrbs[k].location)
                  captureOrbs[k].triggerEvent('maca_evolvingmobs:despawn_confirm')
                  captureOrbs[k].addTag('maca_evolvingmobs:capture_locked')
                }
                }
                else if(storedName.includes('minecraft'))//vanilla mobs
                {
                  storedName = storedName.replace("captureMob_", "")
                  storedName = storedName.replace("minecraft:", "maca_evolvingmobs:mob_orb.")
                  hasTag = true
                  if(hasTag == true)
                  {
                    let newCaptureOrb = new ItemStack(`${storedName}`,1)
                    newCaptureOrb.setLore([`§r§6Level: ${storedLevel}`])
        
                    captureOrbs[k].runCommandAsync('kill @e[r=3,c=1,family=!evolutions,type=!item,type=!player,type=!maca_evolvingmobs:capture_orb]')
                    captureOrbs[k].runCommandAsync('event entity @s maca_evolvingmobs:despawn_confirm')
                    captureDim.spawnItem(newCaptureOrb,captureOrbs[k].location)
                    captureOrbs[k].addTag('maca_evolvingmobs:capture_locked')
                  }
                  
                }
                }
              }
            }
            if(storedName == 'maca_evolvingmobs:instant_despawn'){
                
              let newCaptureOrb = new ItemStack('maca_evolvingmobs:capture_orb',1)
              captureOrbs[k].triggerEvent('maca_evolvingmobs:despawn_confirm')
              captureDim.spawnItem(newCaptureOrb,captureOrbs[k].location)
              captureOrbs[k].addTag('maca_evolvingmobs:capture_locked')
              
            }
          }
        } else {
          captureOrbs.splice(k,1)
        }
      }
  }
};

function dashAttack(turtle) {
  if (dashInterval !== null) return;
  let dashTicks = 0;
  dashInterval = system.runInterval(() => {
      if (turtle && dashTicks < DASH_DURATION) {
          updateDashMovement(turtle);
          damageEntitiesInFront(turtle);
          if (dashTicks % 2 === 0) {
              spawnDashParticles(turtle);
          }
          if (dashTicks % 60 === 0 || dashTicks === 0) {
            turtle.triggerEvent("maca_evolvingmobs:xp_up.one");
        }
          dashTicks++;
      } else {
        cancelDash(turtle)
      }
  }, 1);
}

function updateDashMovement(turtle) {
  
  if (!turtle || !turtle.isValid()) {
    return;
  }
  const rotation = turtle.getRotation();
  const rad = rotation.y * Math.PI / 180;
  directionX = -Math.sin(rad) * DASH_STRENGTH;
  directionZ = Math.cos(rad) * DASH_STRENGTH;

  const isJumping = turtle.isJumping;

  let verticalKnockback = HOVER_STRENGTH;
  let blockBelowPos;
  if (Math.floor(turtle.location.y) -2 <= dimensionToHeight(turtle.dimension.id)) {
    // Check for block 3 blocks below the turtle
    blockBelowPos = {
      x: Math.floor(turtle.location.x),
      y: Math.floor(turtle.location.y) -1,
      z: Math.floor(turtle.location.z)
  };
  } else if (Math.floor(turtle.location.y) -1 <= dimensionToHeight(turtle.dimension.id)) {
    // Check for block 3 blocks below the turtle
    blockBelowPos = {
      x: Math.floor(turtle.location.x),
      y: Math.floor(turtle.location.y),
      z: Math.floor(turtle.location.z)
  };
  } else {
    // Check for block 3 blocks below the turtle
    blockBelowPos = {
      x: Math.floor(turtle.location.x),
      y: Math.floor(turtle.location.y) - 2,
      z: Math.floor(turtle.location.z)
    };
  }
  
  const blockBelow = turtle.dimension.getBlock(blockBelowPos);
  const isAirBelow = blockBelow.typeId === "minecraft:air";

  if (isJumping && !isAirBelow) {
      verticalKnockback = ASCEND_SPEED;
  } else if (!isJumping) {
      if (!isAirBelow) {
          // Descend if there's air below and we're above the minimum height
          verticalKnockback = -DESCEND_SPEED;
      } else {
          // Slowly descend if there's a block below but we're still above minimum height
          verticalKnockback = -DESCEND_SPEED * 0.5;
      }
  }
  turtle.applyKnockback(directionX, directionZ, DASH_STRENGTH, verticalKnockback)
}

function damageEntitiesInFront(turtle) {
  const rotation = turtle.getRotation();
  const rad = rotation.y * Math.PI / 180;
  const dmg = getKey(turtle);
  directionX = -Math.sin(rad) * DASH_STRENGTH;
  directionZ = Math.cos(rad) * DASH_STRENGTH;
  const checkPosition = {
      x: turtle.location.x + directionX,
      y: turtle.location.y,
      z: turtle.location.z + directionZ
  };

  const ownerId = turtle.getDynamicProperty(`maca_evolvingmobs:owner_id`);

  const entities = turtle.dimension.getEntities({
      location: checkPosition,
      maxDistance: 2 // Radius to check around the check position
  }).filter(e => e.id !== ownerId);

  for (const entity of entities) {
    const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
      if (entity.id !== turtle.id && !ignoreList.has(entity.typeId) && targetOwner !== ownerId) {
          try {
            system.runTimeout(() => {
              turtle.triggerEvent("maca_evolvingmobs:xp_up.one");
              entity.applyDamage(dmg, {cause: "entityAttack", damagingEntity: turtle});
            },2)
            entity.applyKnockback(directionX, directionZ, DASH_KNOCKBACK_STRENGTH, 1)
              turtle.dimension.spawnParticle("minecraft:critical_hit_emitter", entity.location);
              cancelDash(turtle)
          } catch (error) {
            cancelDash(turtle)
          }
      }
  }
}

function cancelDash(turtle) {
  system.clearRun(dashInterval);
  system.runTimeout(() => {
    dashInterval = null;
  },1)
  turtle.triggerEvent("maca_evolvingmobs:goltarras_attack_deactivated")
  turtle.triggerEvent("maca_evolvingmobs:switch_to_melee")
  checkAndSetCooldown(turtle.id)
}

function spawnDashParticles(entity) {
  const rotation = entity.getRotation();
  const rad = rotation.y * Math.PI / 180;
  const directionX = -Math.sin(rad);
  const directionZ = Math.cos(rad);
  const particleOffset = 1; // Spawn particles slightly in front of the entity
  const particlePosition = {
      x: entity.location.x + directionX * particleOffset,
      y: entity.location.y + 1, // Adjust height as needed
      z: entity.location.z + directionZ * particleOffset
  };
  entity.dimension.spawnParticle("minecraft:large_explosion", particlePosition);
}

// Function to check and set cooldown
function checkAndSetCooldown(entityId, cooldown) {
  const currentTime = Date.now();
  if (cooldowns.has(entityId)) {
      const lastUsedTime = cooldowns.get(entityId);
      const cool = cooldown ? cooldown : COOLDOWN_DURATION;
      const timeRemaining = cool - (currentTime - lastUsedTime);
      if (timeRemaining > 0) {
          return false;
      }
  }
  cooldowns.set(entityId, currentTime);
  return true;
}

function dimensionToHeight(dimension) {
  const heights = [
      {
          id: MinecraftDimensionTypes.overworld,
          minHeight: -64
      },
      {
          id: MinecraftDimensionTypes.nether,
          minHeight: 0
      },
      {
          id: MinecraftDimensionTypes.theEnd,
          minHeight: 0
      }
  ];
  const data = heights.find((f)=>f.id == dimension);
  if (data != undefined) {
      return data.minHeight;
  } else return undefined;
}

function eggLauncher(entity, projectile) {
  const targetLocation = getTargetLocation(entity);
  const dist = getDistance(entity.location, targetLocation);
  if (targetLocation && dist >= 10) {
    // Launch the egg with the function if the distance is greater than 10
    system.runTimeout(() => {
      launchEgg(projectile, projectile.location, targetLocation, dist);
    }, 1);
  }
}
  

function launchEgg(projectile, startPosition, targetLocation, dist) {
  const totalFlightTime = dist ? dist : 60;
  const maxArcHeight = Math.min(6,totalFlightTime/3);
  let currentTick = 0;

  const flightInterval = system.runInterval(() => {
      currentTick++;
      if (currentTick >= totalFlightTime) {
          system.clearRun(flightInterval);
          return;
      }
        if (!projectile.isValid()) return;

        const progress = currentTick / totalFlightTime;
        const verticalProgress = 4 * progress * (1 - progress);

        // Calculate curved path without initial angle
        const curvedPath = calculateCurvedPath(startPosition, targetLocation, progress, verticalProgress, maxArcHeight);

        // Apply velocity to reach the next point on the path
          const velocity = calculateVelocity(projectile.location, curvedPath, 0.5);
          try {
              projectile.applyImpulse(velocity);
          } catch (error) {}
  },1);
}

function getDistance(pos1, pos2) {
  const dx = pos1.x - pos2.x;
  const dy = pos1.y - pos2.y;
  const dz = pos1.z - pos2.z;
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

function calculateCurvedPath(start, end, progress, verticalProgress, maxArcHeight) {
  return {
      x: start.x + (end.x - start.x) * progress,
      y: start.y + (end.y - start.y) * progress + maxArcHeight * verticalProgress,
      z: start.z + (end.z - start.z) * progress
  };
}

function getTargetLocation(entity) {
    //need to add a check, for each entity if entity.typeId in passiveMobs set AND entity does NOT have tag "maca_evolvingmobs:entity_marked_for_attack". We're looking to take the first entity that passes these conditions as our target entity

  const raycastOptions = {
      maxDistance: 60,
      includeLiquidBlocks: false,
      ignoreBlockCollision: true,
  };
  const rayDirection = entity.getViewDirection();
  const headLoc = entity.getHeadLocation();
  const raycastResults = entity.dimension.getEntitiesFromRay(headLoc, rayDirection, raycastOptions);
  const filteredResults = raycastResults.filter(result => 
      !ignoreList.has(result.entity.typeId) && entity.id !== result.entity.id && (!passiveMobs.has(result.entity.typeId) && result.entity.typeId !== "maca_evolvingmobs:arcancluck.projectile" || result.entity.hasTag("maca_evolvingmobs:entity_marked_for_attack"))
    );
    if (filteredResults.length > 0) {
      const closestResult = filteredResults[0];
      const hitEntity = closestResult.entity;
      return hitEntity.location;
      
    } else {
      // If no entity is hit, allow the mob to throw the projectile on their own
      return false;
    }
}

function calculateVelocity(currentPos, targetPos, speed) {
  return {
      x: (targetPos.x - currentPos.x) * speed,
      y: (targetPos.y - currentPos.y) * speed,
      z: (targetPos.z - currentPos.z) * speed
  };
}

function furyFeathers(chicken) {
  const loc = chicken.location
  const centerX = loc.x;
  const centerY = loc.y + 0.4;
  const centerZ = loc.z;
  const radius = 3;
  const particleCount = 25;
  const maxDistance = 15;
  const damageAmount = getKey(chicken);
  const ownerId = chicken.getDynamicProperty(`maca_evolvingmobs:owner_id`);
  chicken.dimension.playSound("pines", loc);
  chicken.triggerEvent("maca_evolvingmobs:xp_up.one");

  for (let i = 0; i < particleCount; i++) {
    const angle = (i / particleCount) * 2 * Math.PI;
    const x = centerX + radius * Math.cos(angle);
    const z = centerZ + radius * Math.sin(angle);

    const dirX = (x - centerX) / radius;
    const dirZ = (z - centerZ) / radius;

    const molangVars = new MolangVariableMap();
    molangVars.setFloat("variable.direction_x", dirX);
    molangVars.setFloat("variable.direction_z", dirZ);
    chicken.dimension.spawnParticle(
      "maca_evolvingmobs:fury_feathers_particle",
      { x, y: centerY, z },
      molangVars
    );

    const raycastResult = chicken.dimension.getEntities({
      location: loc,
      maxDistance: maxDistance,
    });
    system.runTimeout(() => {
      chicken.setProperty('maca_evolvingmobs:chicken_activated', false)
    }, 40);

    raycastResult.forEach((entity, index) => {
      const targetOwner = entity?.getDynamicProperty(`maca_evolvingmobs:owner_id`);
      if (entity.id !== chicken.id && !ignoreList.has(entity.typeId) && entity.id !== ownerId && targetOwner !== ownerId) {
        const distance = Math.sqrt(
          Math.pow(entity.location.x - x, 2) +
            Math.pow(entity.location.z - z, 2)
        );
        const delay = Math.floor(distance * 1.2);

        system.runTimeout(() => {
          try {
            const distance = Math.sqrt(
              Math.pow(entity.location.x - loc.x, 2) +
              Math.pow(entity.location.y - loc.y, 2) +
              Math.pow(entity.location.z - loc.z, 2)
            );
            const damage = Math.max(1, damageAmount - (distance)); // damage scales, the further away the less damage they take
            entity.applyDamage(damage, {cause: "entityAttack", damagingEntity: chicken});
            const direction = calculateDirection(loc, entity.location);
            applyKnockback(entity, direction.x, direction.z, 0.5, 0.5);
          } catch (error) {}
        }, delay);
      }
    });
  }
}