/*
hello dear add-on maker. want to use my code? sure. but it would probably help if you understand how it works instead of
just copy pasting it and changing ids...

the code shouldnt be too hard to understand. should be easy to configure for your own things

but if you're mostly just using the same code, would be nice if you credit

otherwise, have fun removing hit immunity :)

note that this version of my hit immunity removal code is edited quite abit for this add-on and does not have gun durability

- JuliusScizzor
*/


import {
    world,
    Player,
    system,
    Vector,
    EntityDamageCause,
    EntityEquipmentInventoryComponent,
    EquipmentSlot,
    ItemStack,
    ItemEnchantsComponent,
    EnchantmentTypes,
    ItemDurabilityComponent,
    EntityHealthComponent,
    Entity,
  } from "@minecraft/server";
  



// return totalD + (100 - (totalD * 4)) * totalEpf * 0.04 * 0.0075;
const defPoints = {
    turtle_helmet: 2,
    leather_helmet: 1,
    golden_helmet: 2,
    chainmail_helmet: 2,
    iron_helmet: 2,
    diamond_helmet: 3,
    netherite_helmet: 3,
    "js:netherite_boots": 3,
    "js:scarlet_boots": 3,
  
    leather_chestplate: 3,
    golden_chestplate: 5,
    chainmail_chestplate: 5,
    iron_chestplate: 6,
    diamond_chestplate: 8,
    netherite_chestplate: 8,
    "js:netherite_chestplate": 8,
    "js:scarlet_chestplate": 8,
  
    leather_leggings: 2,
    golden_leggings: 3,
    chainmail_leggings: 4,
    iron_leggings: 5,
    diamond_leggings: 6,
    netherite_leggings: 6,
    "js:netherite_leggings": 6,
    "js:scarlet_leggings": 6,
  
    leather_boots: 1,
    golden_boots: 1,
    chainmail_boots: 1,
    iron_boots: 2,
    diamond_boots: 3,
    netherite_boots: 3,
    "js:miner_helmet": 3,
    "js:pirate_hat": 3,
    "js:netherite_helmet": 3,
    "js:scarlet_helmet": 3,
  };
  
export const projectileDef = {
    "js:flamethrower_bullet": {
      damage: {
        entity: {
          falloff: [4, 15],
          damage: [0, 0],
        },
        armor: {
          falloff: [4, 15],
          damage: [1, 1],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "Flamethrower",
      },
    },
    "js:svd_bullet": {
      damage: {
        entity: {
          falloff: [12, 24],
          damage: [19, 22],
        },
        armor: {
          falloff: [16, 32],
          damage: [1, 1],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "SVD",
      },
    },
    "js:sh_plasma_cannon_bullet": {
      damage: {
        entity: {
          falloff: [32, 32],
          damage: [1, 1],
        },
        armor: {
          falloff: [32, 32],
          damage: [1, 1],
        },
      },
      deathMessage: {
        messages: ["blasted"],
        weapon: "Plasma Cannon",
      },
    },
    "js:sh_pistol_bullet": {
      damage: {
        entity: {
          falloff: [32, 32],
          damage: [20, 20],
        },
        armor: {
          falloff: [32, 32],
          damage: [2, 2],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "Serpent",
      },
    },
    "js:p250_bullet": {
      damage: {
        entity: {
          falloff: [8, 16],
          damage: [4, 5],
        },
        armor: {
          falloff: [8, 16],
          damage: [0, 1],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "P250",
      },
    },
    "js:oblivion_bullet": {
        damage: {
          entity: {
            falloff: [12, 24],
            damage: [4, 4],
          },
          armor: {
            falloff: [12, 24],
            damage: [1, 1],
          },
        },
        deathMessage: {
          messages: ["shot"],
          weapon: "Oblivion",
        },
    },
    "js:hk417_bullet": {
      damage: {
        entity: {
          falloff: [12, 24],
          damage: [10, 11],
        },
        armor: {
          falloff: [12, 24],
          damage: [1, 1],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "HK-417",
      },
    },
    "js:zombie_bullet": {
      damage: {
        entity: {
          falloff: [12, 24],
          damage: [5, 5],
        },
        armor: {
          falloff: [12, 24],
          damage: [1, 1],
        },
      },
      deathMessage: {
        messages: ["shot"],
        weapon: "Zombie",
      },
    },
    "js:fp6_bullet": {
        damage: {
          entity: {
            falloff: [24, 24],
            damage: [3, 3],
          },
          armor: {
            falloff: [24, 24],
            damage: [2, 2],
          },
        },
        deathMessage: {
          messages: ["shot"],
          weapon: "FP6",
        },
    },
};
  
const ARMOUR_POINTS = {
}

const IGNORED_ENTITIES = {
  "js:frag": {
  },
  "js:scatter_frag": {
  },
  "js:fp6_sound": {},
  "js:hk417_sound": {},
  "js:knight_zombie_sound": {},
  "js:p250_sound": {},
  "js:oblivion_sound": {},
  "js:sh_pistol_sound": {},
  "js:sh_plasma_pistol_sound": {},
  "js:svd_sound": {},
  "js:chair_invis": {},
  "js:chuck": {},
  "js:duck": {},
  "js:johnny_chainsaw": {},
  "js:lock": {},
  "js:plasma_blast": {},
  "js:pistol_booster": {},
  "js:weakness_booster": {},
  "js:xp_booster": {}
}


export const AFFECTED_MOBS = {
  "minecraft:zombie": {},
}

const PROJECTILE_STATS = {
  "js:flamethrower_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:svd_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:sh_plasma_cannon_bullet": {
    "defenceNegation": 1,
    "damageAmplifier": 1,
    "command": "",
    "effect": {
      "id": "slowness", 
      "duration": 3 * 20,
      "amplifier": 0,
      "showParticles": true
    }
  },
  "js:sh_pistol_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:p250_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:oblivion_bullet": {
    "defenceNegation": 0.25,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:hk417_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:zombie_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
  "js:fp6_bullet": {
    "defenceNegation": 0,
    "damageAmplifier": 1,
    "command": "",
    "effect": ""
  },
}

  const unknownDefenseData = { dps: 0, epf: 0 };
  
  function calcEPF(lvl, modifier) {
    return Math.floor((6 + Math.pow(lvl, 2) * modifier) / 3);
  }
  
  function calcItem(item) {
    if (!item) return unknownDefenseData;
  
    const dps = defPoints[item.typeId.replace("minecraft:", "")];
    if (!dps) return unknownDefenseData;
  
    const enchs = item.getComponent("enchantments").enchantments;
    const prot = enchs.getEnchantment(EnchantmentTypes.get("protection"));
    const projProt = enchs.getEnchantment(EnchantmentTypes.get("projectile_protection"));
  
    let epf = 0;
    if (prot) {
      epf = calcEPF(prot.level, 0.75);
    } else if (projProt) {
      epf = calcEPF(projProt.level, 1.25);
    }
  
    return { dps, epf };
  }
  
  function getArmor(entity) {
    const equipment = entity.getComponent("equipment_inventory");
    if (!equipment) return [];
    return [
      equipment.getEquipmentSlot(EquipmentSlot.head),
      equipment.getEquipmentSlot(EquipmentSlot.chest),
      equipment.getEquipmentSlot(EquipmentSlot.legs),
      equipment.getEquipmentSlot(EquipmentSlot.feet),
    ].filter((v) => v.typeId);
  }
  

  function calcArmor(entity) {
    const armorPieces = getArmor(entity);
  
    let totalD = 0;
    let totalEpf = 0;
    for (const piece of armorPieces) {
      const itemCalc = calcItem(piece.getItem());
      totalD += itemCalc.dps;
      totalEpf += itemCalc.epf;
    }
    const armorTypeId = entity.typeId;
    if (armorTypeId in ARMOUR_POINTS) {
      totalD = ARMOUR_POINTS[armorTypeId];
    }

    totalD *= 0.04;
    if (totalD <= 0) return 0;
    return totalD + (100 - totalD * 4) * totalEpf * 0.04 * 0.0075;
  }
  
  function damageArmor(entity, dmg) {
    if (entity.hasTag("projectileArmourDamageFalse")) return;
    const armorPieces = getArmor(entity);
    for (const piece of armorPieces) {
      const item = piece.getItem();
      const dur = item.getComponent("durability");
      if (!dur) continue;
  
      const enchs = item.getComponent("enchantments")?.enchantments;
      const unbreak = enchs?.getEnchantment(EnchantmentTypes.get("unbreaking"))?.level ?? 0;
      if (Math.round(Math.random() * 100) + 1 > dur.getDamageChance(unbreak)) return;
  
      dur.damage = Math.min(Math.round(dur.damage + dmg), dur.maxDurability);
  
      if (dur.maxDurability == dur.damage) {
        piece.setItem();
        entity.runCommand("playsound random.break @a ~~~");
        world.playSound("random.break", entity.location, soundOptions)
      } else piece.setItem(item);
    }
  }
  
  function calcDamage(dmgDef, dist) {
    const [fmin, fmax] = dmgDef.falloff;
    const [dmin, dmax] = dmgDef.damage;
    let dmg = dmax;
    if (dist > fmin) {
      dmg = Math.round(Math.max(dmin, dmin + (dmax - dmin) * (1 - (dist - fmin) / (fmax - fmin))));
    }
    return dmg;
  }


const LastHitThreshold = 300;
let lastProcessedTick = -1;

const callback = world.afterEvents.projectileHit.subscribe((ev) => {
        
        //world.afterEvents.projectileHit.unsubscribe(callback)
        const eHitI = ev.getEntityHit();
        const proj = ev.projectile;
        
      //  if (!eHitI || !(proj === entity)) return
      if (!eHitI || !proj) return

        const source = ev.source;
        const hitEntity = eHitI.entity;
       // const projId = projectileId
        const projId = proj.typeId ?? proj
        const projDef = projectileDef[projId];
      
        
        if (eHitI instanceof Player) {
          const gamemode = getGamemode(hitEntity);
          if (gamemode == "creative" || gamemode == "spectator") return;
        }
        if (!eHitI || hitEntity.hasTag("projectileDamageFalse") || hitEntity.hasTag("projectileShield")) return;
      
        if (!projDef) return;
      
        if (source instanceof Player && hitEntity instanceof Player) return
      
        const entityTypeId = hitEntity.typeId;
        if (entityTypeId in IGNORED_ENTITIES) {
          const eventCommand = IGNORED_ENTITIES[entityTypeId]?.event;
          if (eventCommand) {
            hitEntity.runCommandAsync(eventCommand);
          }
          return;
        }
      
        const dist = Vector.subtract(source.location, ev.location).length();
      
        const dmgDefs = projDef.damage;
        const entDmg = calcDamage(dmgDefs.entity, dist);
        const armorDmg = calcDamage(dmgDefs.armor, dist);
      
        let finalDefencePoints = calcArmor(hitEntity);
      
        damageArmor(hitEntity, armorDmg);
      
        const sourceEquipment = (source.getComponent("equipment_inventory"));
        const sourceEquipmentSlot = sourceEquipment.getEquipmentSlot(EquipmentSlot.mainhand);
        const sourceItem = sourceEquipmentSlot.typeId ? sourceEquipmentSlot.getItem() : undefined;
        const sourceEnchs = (sourceItem?.getComponent("enchantments"))?.enchantments;
        const fireAspect = sourceEnchs?.getEnchantment(EnchantmentTypes.get("fire_aspect"))?.level ?? 0;
        const sharpness = sourceEnchs?.getEnchantment(EnchantmentTypes.get("sharpness"))?.level ?? 0;
      

        let damageAmplifier = 1
        let defenceNegation = 0;

        const projTypeId = proj?.typeId;
        if (projTypeId in PROJECTILE_STATS) {
          const projectileStats = PROJECTILE_STATS[projTypeId];
          defenceNegation = projectileStats.defenceNegation;
          damageAmplifier = projectileStats.damageAmplifier;
        }

        if (!(hitEntity.getEffect("fire_resistance"))) {
          hitEntity.setOnFire(3 * fireAspect, true);
        }

        let resistanceDefence = 0
        if (hitEntity.getEffect("resistance")) {
          resistanceDefence = (0.1 * (hitEntity.getEffect("resistance").amplifier + 1));
        }
        
        if (hitEntity.hasTag("armour")) {
          damageAmplifier = 0.25
        }
        if (hitEntity.hasTag("armour2")) {
          damageAmplifier = 0.5
        }
        if (hitEntity.hasTag("armour3")) {
          damageAmplifier = 0.1
        }

        if (proj.typeId == "js:flamethrower_bullet") {
          defenceNegation = 0
          damageAmplifier = 0
        }
        if (proj.typeId == "js:p250_bullet" && proj.hasTag("cheat") && !hitEntity.hasTag("armour3")) {
          defenceNegation = 1
          damageAmplifier = 25
        }
        if (proj.typeId == "js:p250_bullet" && proj.hasTag("cheat") && hitEntity.hasTag("armour3")) {
          defenceNegation = 1
          damageAmplifier = 3
        }
        if (proj.typeId == "js:oblivion_bullet" && source.typeId == "minecraft:zombie") {
          defenceNegation = 0.25
          damageAmplifier = 0.5
        }

        const defenceNegationMultipler = 1 + sharpness * 0.05
        defenceNegation *= defenceNegationMultipler
        const finalDmg = (Math.max(entDmg * (1 - (finalDefencePoints - defenceNegation + resistanceDefence)), 1)) * damageAmplifier
      
        hitEntity.applyDamage(finalDmg, {
          cause: EntityDamageCause.suicide,
          damagingProjectile: proj,
        });
        
        
      
        const hitCommand = PROJECTILE_STATS[projTypeId]?.command;
        const hitEffect = PROJECTILE_STATS[projTypeId]?.effect;
        if (hitCommand) {
          hitEntity.runCommandAsync(hitCommand);
        }
        if (hitEffect) {
          hitEntity.addEffect(hitEffect.id, hitEffect.duration, {amplifier: hitEffect.amplifier, showParticles: hitEffect.showParticles});
        }

        if (projTypeId == "js:flamethrower_bullet") {
          hitEntity.setOnFire((5 + (1 * fireAspect)), true);
        }
      });