Mixin Class GHAGIcePunch
{

	Array<Actor> Affected;
	property Affected: Affected;
	
	//Can't figure out how to override A_ExplosivePunch, so this will have to do for now
	Action void A_ExplosivePunch(int damage, bool norandom = false, int flags = CPF_USEAMMO, class<Actor> pufftype = "BulletPuff", double range = 0, double lifesteal = 0, int lifestealmax = 0, class<BasicArmorBonus> armorbonustype = "ArmorBonus", sound MeleeSound = 0, sound MissSound = "")
	{
		let player = self.player;
		if (!player) return;

		let weapon = player.ReadyWeapon;

		double angle;
		double pitch;
		FTranslatedLineTarget t;
		int			actualdamage;

		if (!norandom)
			damage *= random[cwpunch](1, 8);

		angle = self.Angle + random2[cwpunch]() * (5.625 / 256);
		if (range == 0) range = DEFMELEERANGE;
		pitch = AimLineAttack (angle, range, t, 0., ALF_CHECK3D);

		// only use ammo when actually hitting something!
		if ((flags & CPF_USEAMMO) && t.linetarget && weapon && stateinfo != null && stateinfo.mStateType == STATE_Psprite)
		{
			if (!weapon.DepleteAmmo(weapon.bAltFire, true))
				return;	// out of ammo
		}

		if (pufftype == NULL)
			pufftype = 'BulletPuff';
		int puffFlags = LAF_ISMELEEATTACK | ((flags & CPF_NORANDOMPUFFZ) ? LAF_NORANDOMPUFFZ : 0);

		Actor puff;
		[puff, actualdamage] = LineAttack (angle, range, pitch, damage, 'Melee', pufftype, puffFlags, t);

		A_BlastLOS(BF_AFFECTBOSSES | BF_DONTWARN | 16, 255, 256 * GHAGGardevoir(self).GetRageModifier(), 20 * GHAGGardevoir(self).GetRageModifier(), pufftype, damage);

		if (!t.linetarget)
		{
			if (MissSound) A_StartSound(MissSound, CHAN_WEAPON);
		}
		else
		{
			if (lifesteal > 0 && !(t.linetarget.bDontDrain))
			{
				if (flags & CPF_STEALARMOR)
				{
					if (armorbonustype == NULL)
					{
						armorbonustype = 'ArmorBonus';
					}
					if (armorbonustype != NULL)
					{
						let armorbonus = BasicArmorBonus(Spawn(armorbonustype));
						if (armorbonus)
						{
							armorbonus.SaveAmount *= int(actualdamage * lifesteal);
							if (lifestealmax > 0) armorbonus.MaxSaveAmount = lifestealmax;
							armorbonus.bDropped = true;
							armorbonus.ClearCounters();

							if (!armorbonus.CallTryPickup(self))
							{
								armorbonus.Destroy ();
							}
						}
					}
				}
				else
				{
					GiveBody (int(actualdamage * lifesteal), lifestealmax);
				}
			}
			if (weapon != NULL)
			{
				if (MeleeSound) A_StartSound(MeleeSound, CHAN_WEAPON);
				else			A_StartSound(weapon.AttackSound, CHAN_WEAPON);
			}

			if (!(flags & CPF_NOTURN))
			{
				// turn to face target
				self.Angle = t.angleFromSource;
			}

			if (flags & CPF_PULLIN) self.bJustAttacked = true;
			if (flags & CPF_DAGGER) t.linetarget.DaggerAlert (self);
		}
	}
	
	action void A_BlastLOS(int blastflags = 0, double strength = 255, double radius = 255, double speed = 20, class<Actor> blasteffect = "BlastEffect", int _damage = 0, sound blastsound = "BlastRadius")
	{

		if (player && (blastflags & BF_USEAMMO) && invoker == player.ReadyWeapon && stateinfo != null && stateinfo.mStateType == STATE_Psprite)
		{
			Weapon weapon = player.ReadyWeapon;
			if (weapon != null && !weapon.DepleteAmmo(weapon.bAltFire))
			{
				return;
			}
		}

		A_StartSound (blastsound, CHAN_AUTO);

		if (!(blastflags & BF_DONTWARN))
		{
			SoundAlert (self);
		}
		ThinkerIterator it = ThinkerIterator.Create("Actor");
		Actor mo;
		while ( (mo = Actor(it.Next ())) )
		{
			if (mo == self || (mo.bBoss && !(blastflags & BF_AFFECTBOSSES)) || mo.bDormant || mo.bDontBlast)
			{ // Not a valid monster: originator, boss, dormant, or otherwise protected
				continue;
			}
			if (mo.bIceCorpse || mo.bCanBlast)
			{
				// Let these special cases go
			}
			else if (mo.bIsMonster && mo.health <= 0)
			{
				continue;
			}
			else if (!mo.player && !mo.bMissile && !mo.bIsMonster && !mo.bCanBlast && !mo.bTouchy && !mo.bVulnerable)
			{	// Must be monster, player, missile, touchy or vulnerable
				continue;
			}
			if (Distance2D(mo) > radius)
			{ // Out of range
				continue;
			}
			if (mo.CurSector.PortalGroup != CurSector.PortalGroup && !CheckSight(mo))
			{
				// in another region and cannot be seen.
				continue;
			}
			
			if ((blastflags & 16) && !isVisible(mo, true)) 
			{
				//only blast if target can bee seen by calling actor
				continue;
			}
			
			Actor victim = mo;
			
			double ang = AngleTo(victim);
			Vector2 move = AngleToVector(ang, speed);
			victim.Vel.XY = move;

			ang -= 180.;
			Vector3 spawnpos = victim.Vec3Offset(
				(victim.radius + 1) * cos(ang),
				(victim.radius + 1) * sin(ang),
				(victim.Height / 2) - victim.Floorclip);
			Actor puff = blasteffect? Spawn (blasteffect, spawnpos, ALLOW_REPLACE) : null;
			if (puff)
			{
				puff.target = victim;
			}
			if (victim.bMissile)
			{
				// [RH] Floor and ceiling huggers should not be blasted vertically.
				if (!victim.bFloorHugger && !victim.bCeilingHugger)
				{
					victim.Vel.Z = 8;
					if (puff != null) mo.Vel.Z = 8;
				}
			}
			else
			{
				victim.Vel.Z = 1000. / victim.Mass;
			}
			if (victim.bTouchy)
			{
				victim.bArmed = false;
				victim.DamageMobj(self, self, victim.health, 'Melee', DMG_FORCED|DMG_EXPLOSION);
			}
			victim.DamageMobj(self, self, int(_damage * (2. / 3.)), Puff.damageType, DMG_FORCED|DMG_EXPLOSION);
		}
	}
	
	Double IcePunchGetSpeed()
	{
		let gardie = GHAGGardevoir(players[consoleplayer].mo);
		double x = gardie.vel.x * gardie.vel.x;
		double y = gardie.vel.y * gardie.vel.y;
		double z = gardie.vel.z * gardie.vel.z;
		return sqrt(x + y + z);
	}
	
	line hitLine;
	
	Action void A_IcePunchPrecalc()
	{
		FLineTraceData shot;
	
		let gardie = GHAGGardevoir(players[consoleplayer].mo);
		gardie.LineTrace(gardie.angle, 64 + (128 * gardie.GetRageValue()), gardie.pitch, TRF_THRUHITSCAN, gardie.height - 12, 0, 0, shot);
		{
			if (shot.HitActor && shot.HitActor.bIsMonster)
			{
				if (gardie.distance3d(shot.HitActor) > 128)
				{
					gardie.IcePunchTarget = shot.HitActor;
					
					double spring = Max(0.1, 0.3 * gardie.getRageModifier());
					double dampening = gardie.getRageValue() / 1000;
			
					double velx = DampedSpring(gardie.pos.x, shot.HitActor.pos.x, gardie.vel.x, spring, dampening);
					double vely = DampedSpring(gardie.pos.y, shot.HitActor.pos.y, gardie.vel.y, spring, dampening);
					double velz = DampedSpring(gardie.pos.z, shot.HitActor.pos.z, gardie.vel.z, spring, dampening);
					
					gardie.vel = (velx, vely, velz);
					A_StartSound("dash/Start", CHAN_AUTO, 0, 1.2);
				}
			}
		}
	}
	
	Action void A_IcePunch() 
	{
		let gardie = GHAGGardevoir(players[consoleplayer].mo);
		A_ExplosivePunch(15 + Int(60 * gardie.GetRageModifier()), 0, true, "IcePuff", 32 + Int(16 * gardie.GetRageModifier()), 0, 0, "", "");
		invoker.hitline = null;
	}
	
	Action void A_OverlayIcePunch()
	{
		A_Overlay(3, "IcePunch.Overlay", true);
		A_OverlayScale(3, 0.8, 0.8); 
	}
	
	//Thank you Cali
	Action double DampedSpring(double p, double r, double v, double k, double d) {
		return -(d * v) - (k * (p - r));
	}
}

Class BasePunchPuff : BulletPuff
{
	int chance;
	property chance: chance;

	Default {
		+PUFFONACTORS;
		+HITTARGET;
		-RANDOMIZE;
		VSpeed 0;
		Alpha 1;
	}
	
	override void tick() {
	
		if (target) {
		
			Vector3 follow = target.pos;
			if (target.health > 0 || target.bIceCorpse)
			{
				follow.z += (target.height) - (target.height / 3.);
			} else {
				follow.z += 32;
			}
			SetOrigin(follow, false);

		}
		
		Super.tick();
	}
}

Class IcePuff : BasePunchPuff
{
	Default
	{
		DamageType "Ice";
	}
	
	States
	{
		Spawn :
		Melee :
			TNT1 A 0 A_StartSound("attack/Contact", CHAN_AUTO);
			IPFA ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 Bright;
			IPFB ABCDEFGHIJKLM 1 Bright;
			IPFB K 1 Bright /*A_Inflict(chance, FROZEN)*/;
			IPFB NO 1 Bright;
			IPFB K 1 Bright;
			TNT1 A 1 Bright;
			IPFB K 1 Bright;
			IPFB L 1 Bright;
			IPFB K 1 Bright;
			TNT1 A 2 Bright;
			IPFB K 5 Bright;
		Death:
			TNT1 A 0;
			stop;
			
	}
}