// Script to manage updating horses' equipment properties
// Includes dumping chest contents when un-chesting horses,
// Updating Armor and Carpets, 
// and checking for burning in daylight for Zombie and Skeleton Horses
// Author: InnerTri





// Imports
// Important Global Variables
import { system, world } from "@minecraft/server";



// Global Variables

// projOriginForward is the distance in front of sourceHorse that the projectile spawns at
const projOriginForward = 2.5;
// projOriginY is the distance from the ground projectiles should spawn at via horse abilities
const projOriginY = 1;
// creepBlastBack is the distance the Creeper Horse's explosion will blast it backwards
const creepBlastBack = 0.5;
// enderTeleDist is pretty self-explanatory; the distance ender horse teleports
const enderTeleDist = 32;



// Subscription For Blaze Horse melee attack setting fire
world.afterEvents.entityHitEntity.subscribe(
	event => {
		if (event.damagingEntity.typeId == "cyn_hao:blaze_horse") {
			event.hitEntity.setOnFire(5);
		}
	}
);

// Horse Ability Event Subscription
system.afterEvents.scriptEventReceive.subscribe(
    event => {
        // first, check if the message is for us
        if (event.id === "cyn:hao") {
            // if it is, save entity for reference and check which "command" is being run
            let sourceHorse = event.sourceEntity;
            switch (event.message) {

				// Horse Special Abilities
				// cooldown checks are done in the horses themselves,
				// in the event that calls this scriptEvent,
				// and via a BP AC
				


				// Skeleton Horses sustained-fire bones for a little bit
				// However, most of this is done via BP AC, 
				// which has anims that call this scriptEvent periodically for a few seconds
				// all the ACTUAL SE does is fire a single arrow, with no variance
				
				// REPLACED with Breath Attack script, called via ScriptEvent when ability is requested
				// case "ability_skeletonHorse":
				// 	sprayProjectile(sourceHorse, "minecraft:arrow", 1, 0, 0.25);
				// return;

				// Spider Horses spray one big blast of cobwebs
				// REPLACED with Breath Attack script, called via ScriptEvent when ability is requested
				// case "ability_spiderHorse":
				// 	// spray 25 projectiles every call,
				// 	// since Spider Horse does one big shot,
				// 	// instead of sustained fire
				// 	for(let i = 0; i < 25; i++) {
				// 		// sprayImpulse of 0.5, since they don't need to fly far
				// 		// sprayModifier of 0.35 to get a decently-wide cone
				// 		sprayProjectile(sourceHorse, "cyn_hao:projectile_web", 0.5, 0.35, 0.5);
				// 	}
				// return;

				// Dragon Horses spray waves of dragon breath
				// REPLACED with Breath Attack script, called via ScriptEvent when ability is requested
				// case "ability_dragonHorse":
				// 	for(let i = 0; i < 3; i++) {
				// 		// sprayImpulse of 0.5, since they don't need to fly far
				// 		// sprayModifier of 0.35 to get a decently-wide cone
				// 		sprayProjectile(sourceHorse, "cyn_hao:projectile_dragon", 1, 0.175, 0.375);
				// 	}
				// return;



				// Creeper Horses give their rider brief Resistance (since there is no blast resist effect, only enchantment), 
				// then cause an explosion at their location
				case "ability_creeperHorse":
					creeperHorseExplosion(sourceHorse);
				return;

				// Ender Horses teleport forward
				case "ability_enderHorse":
					enderHorseTeleport(sourceHorse);
				return;

				// Blaze Horses AoE set enemies on fire
				case "ability_blazeHorse":
					blazeHorseBlast(sourceHorse);
				return;
            }
        }
    }
);





// Function to spray a bunch of projectile-entities the direction the sourceHorse is looking

// ALLLLL THIS HAS BEEN REPLACED BY THE BREATH ATTACK SCRIPT
// Should probably delete it once it's verified not-in-use

// IMPORTANT INFORMATION

// sprayImpulse is the INITIAL impulse that the projectile has
// this is then modified by the below sprayMod
// NOTE that this is ***NOT*** the default number of blocks that the projectile will fly!
// how far it will travel varies a LOT on the path itself (ie height and such)
// for reference, at a height of 2.5 off the ground,
// an impulse of 1 will make the arrow fly a downward curve of 8 blocks forward before hitting the ground,
// provided nothing is in it's way



// sprayModifier is the max IMPULSE deviation of any axis for projectile spraying
// AGAIN, REMINDER, THIS IS ***NOT*** IN UNITS OF BLOCKS!!!
// the getImpulseMod function turns this into a random number between -sprayMod and +sprayMod
// then that mod is added to the above initial impulse, to result in a random "cone"



//  NOW THIS IS IMPORTANT!!!
// sprayModifier MUST be < sprayImpulse, otherwise the projectiles can end up with a negetive total impulse,
// making them fly back towards the source!!!

// TODO: THIS MIGHT NOT HAVE THE INTENDED EFFECT, since I'm pretty sure sprayModifier also changes the width ***AND*** the DISTANCE

// it looks like they're tied together; kind of hard to explain, but:
// the larger the sprayImpulse is, the larger sprayModifier needs to be to get higher spread
// the smaller sprayImpulse is, the smaller sprayModifier needs to be to NOT get a 180-degree spread
// unfortunately, this interaction means that it's kind of hard to get a short, wide spread
// but hopefully we can get it with enough tweaking

// high sprayImpulse, low sprayModifier = long, thin cone
// high sprayImpulse, high sprayModifier = HUGE, wide cone
// low sprayImpulse, high sprayModifier = long, VERY WIDE cone (we're talking approaching 360-degrees, so the projs fly backwards!)
// low sprayImpulse, low sprayModifier = short, thin cone
// equal-high sprayImpulse, equal-high sprayModifier = HUGE, 180-degree cone
// equal-low sprayImpulse, equal-low sprayModifier = shorter, 180-degree cone still

// So, after some fiddling with numbers, I think this is how the relationship goes:
// I THINK, for a given X sprayImpulse:
// sprayMod of 1.00 = 180 Degree Cone
// sprayMod of 0.75 = 90(ish) Degree Cone
// sprayMod of 0.50 = 45(ish) Degree Cone
// sprayMod of 0.25 = 27.5(probably) Degree Cone
// sprayMod of 0.00 = 0 Degree Cone

// Neat! BUT KEEP IN MIND that sprayModifier ***WILL*** affect the DISTANCE as well!!!
// (since it also applies impulse in the forward direction, not just left-right and up-down)



// so it seems like the closer the values are to each other, the closer the cone approaches 180 degrees
// therefore, probably decide on the range you want first, then work on the degree of cone you want from there,
// KEEPING IN MIND that sprayModifier will impact the distance as well!

function sprayProjectile(sourceHorse, projectileId, sprayImpulse = 1, sprayModifier = 0.35, yImpulse = 0.5 ) {
	// save the location to spawn the projectile
	// this loc is horseLoc + projOriginForward blocks in the direction the horse is looking
	// as well as projOriginY blocks up
	// unfortunately, minecraft-math "needs a separate install" or something,
	// which I can't be bothered with right now
	// so just put in the vector multiplication manually
	let viewDir = sourceHorse.getViewDirection();
	let spawnOffset = {
		x: viewDir.x * projOriginForward,
		y: projOriginY,
		z: viewDir.z * projOriginForward
	}



	// spawn the projectile
	// the projectile's ID is defined when this function is called
	// the spawn location is the direction the sourceHorse is looking, 
	// times a few blocks so as to not spawn IN the horse
	let newProjectile = sourceHorse.dimension.spawnEntity(
		projectileId,
		
		// add the horseLocation and viewDir vectors
		// also manually, for the minecraft-math reason stated above
		// y axis + 2.5 since it seems to spawn the entity slightly too low
		// most likely because it's falling right away when it spawns
		
		{
			x: sourceHorse.location.x + spawnOffset.x,
			y: sourceHorse.location.y + spawnOffset.y,
			z: sourceHorse.location.z + spawnOffset.z
		}
	)

	

	// define the initial impulse of the projectile
	// this is done so that if impulseMod happens to roll 0, 0, 0,
	// the projectile won't just drop dead in the water;
	// it'll fly the "default path", so to speak
	let spawnImpulse = {
		x: viewDir.x * sprayImpulse,
		y: yImpulse,
		z: viewDir.z * sprayImpulse
	}



	// apply a random impulse to the spawned projectile to get the "spray" effect
	// static value for Z, since we want them all to fly forward with the same impulse
	newProjectile.applyImpulse({

	// use the below-defined getImpulseMod to get an impulse modifier between
	//  -2.5, -2.5, -2.5 and 2.5, 2.5, 2.5

	// next, add that impulse modifier to the pre-existing impulse of
	// 5 blocks in the direction the sourceHorse is looking
	// we do NOT directly use the impulseMod,
	// since that could result in an impuse that would shoot backwards into the sourceHorse
	// if we add it to 5, all it will do is reduce the distance it travels (still forward!) in half

		x: spawnImpulse.x + getImpulseMod(sprayModifier),
		y: spawnImpulse.y + getImpulseMod(sprayModifier),
		z: spawnImpulse.z + getImpulseMod(sprayModifier)
	});

}



// function to get a random value between -sprayMod and sprayMod
// adding this to the static impuslse of 5 blocks in the direction the sourceHorse is looking
// results in a random "spray" of the projectile
function getImpulseMod(sprayModifier = 0.35) {
	// define the "cone" for the spray
	let impulseMod = 
	// first, Math.random returns between 0 and 1,
	// so * 2 to make it generate a number between 0 and 2,
	((Math.random() * 2)
	// then - 1 to make it generate a number between -1 and 1
	// saved to a variable so we're not calling it 3 times per call
	- 1)
	// next, now that we have a number between -1 and 1,
	// multiply it by sprayMod, which is the max distance
	// any axis can deviate (in blocks)
	* sprayModifier;

	return impulseMod;
}









function creeperHorseExplosion(creeperHorse) {
	// give all riders and give them all resistance for a brief moment 
	// there will most likely only ever be 1 rider, but getRiders() returns an array so we'll be proper about it
	for(let rider of creeperHorse.getComponent("minecraft:rideable").getRiders()) {
		rider.addEffect("resistance", 10, { amplifier: 5, showParticles: false } );
	}

	creeperHorse.dimension.createExplosion(creeperHorse.location, 2.50, 
		{
			allowUnderwater: true,
			breaksBlocks: false,
			causesFire: false,
			source: creeperHorse
		}
	);

	// Make the Creeper Horse get "blasted back" a little bit
	let viewDir = creeperHorse.getViewDirection();
	creeperHorse.applyImpulse(
		{
			x: viewDir.x * -creepBlastBack,
			y: creepBlastBack,
			z: viewDir.z * -creepBlastBack
		}
	);
}



// function to teleport the Ender Horse forward
function enderHorseTeleport(enderHorse) {
	// save the start location, to spawn particles and play the teleporting sound
	let startLoc = enderHorse.location;

	// get the direction the ender horse is facing
	let viewDir = enderHorse.getViewDirection();

	// do a raycast to find the nearest non-passable block
	let tpRaycast = enderHorse.dimension.getBlockFromRay(
		startLoc,
		{
			x: viewDir.x,
			y: 0,
			z: viewDir.z
		},
		{
			includeLiquidBlocks: true,
			includePassableBlocks: false,
			maxDistance: enderTeleDist
		}
	);

	// save the end location, to spawn particles and play the teleporting sound
	let endLoc = enderHorse.location;

	// if the raycast did not encounter a blocking block,
	// tp to the furthest point
	if (tpRaycast === undefined) {
		endLoc =
		{
			x: startLoc.x + (viewDir.x * enderTeleDist),
			y: startLoc.y,
			z: startLoc.z + (viewDir.z * enderTeleDist)
		};
	}

	// otherwise, the raycast was blocked (get it?),
	// so tp to that block instead
	else {
		endLoc = tpRaycast.block.location;
	}

	// once we know where we're going, teleport there!
	enderHorse.tryTeleport(endLoc);

	// after the teleport is done, play the teleport sound and spawn particles at both the start and end locations
	enderHorse.dimension.playSound("mob.endermen.portal", startLoc);
	enderHorse.dimension.playSound("mob.endermen.portal", endLoc);
	enderHorse.dimension.spawnParticle("cyn_hao:particle_teleport_in", {x: startLoc.x, y: startLoc.y + 1, z: startLoc.z})
	enderHorse.dimension.spawnParticle("cyn_hao:particle_teleport_out", {x: endLoc.x, y: endLoc.y + 1, z: endLoc.z})
	
	return;
}



// function to set enemies near the blaze horse on fire
function blazeHorseBlast(blazeHorse) {

	// first, get nearby entities
	// since this ability can only be used with a tame horse,
	// ignore players
	let nearbyEntities = blazeHorse.dimension.getEntities({
		excludeTypes: [ "minecraft:player", "minecraft:blaze", "cyn_hao:blaze_horse", "minecraft:item", "minecraft:xp_orb", "minecraft:armor_stand" ],
		location: blazeHorse.location,
		maxDistance: 5
	});


	// check if any entities were found
	if (nearbyEntities.length < 1) {
		// console.warn("Blaze Horse attack did not find any nearby entities. Aborting.");
		return;
	}

	// if so, go through them and deal damage, set on fire, and deal knockback
	for (let nearbyEntity of nearbyEntities) {
		nearbyEntity.applyDamage(8, {cause: "fire", damagingEntity: blazeHorse});
		nearbyEntity.setOnFire(8);
		let knockVec = {x: nearbyEntity.location.x - blazeHorse.location.x, z: nearbyEntity.location.z - blazeHorse.location.z}
		nearbyEntity.applyKnockback({x: knockVec.x, z: knockVec.z}, 0.5);
	}
}