enum BaseIwad
{
	IWAD_DOOM = 0,
	IWAD_HERETIC = 1,
}

enum GunPos
{
	WEAP_BR = 0,
	WEAP_BL = 1,
	WEAP_TR = 2,
	WEAP_TL = 3,
}

Class GHAGGardevoir : PlayerPawn
{
	BaseIwad iwadClass;
	BaseIwad iwadGame;

	int RageTimer;
	int killInterupt;
	int RageConstant;
	int RageValue;
	
	bool OverTheEdge;
	Bool MegaEvolved;
	bool charonmode;
	bool chargingBlackHole;
	bool hiding;
	//property charonmode: charonmode;
	
	PointLight glowy;
	Name prevWeap;
	Array<Actor> FloatGuns;
	
	Actor IcePunchTarget;
	
	bool updateWeapons;
	
	default {
		Health 350;
		PainChance 255;
		Player.MaxHealth 350;
		
		Player.ForwardMove 2, 1;
		
		xscale 0.325;
		yscale 0.325;
		
		DamageFactor "Slime", 2.0;
		DamageFactor "Poison", 2.0;
		DamageFactor "PoisonCloud", 2.0;
		DamageFactor "Melee", 0.25;
	}
	
	States
	{
		Spawn:
			GARD BCBA 16;
			Loop;
		See:
			GARD BCBA 6;
			TNT1 A 0 A_GetSwayDirection();
			Loop;
		see.SwayForward:
			GARD BCBA 6;
			TNT1 A 0 A_GetSwayDirection();
			goto See;
		See.SwayLeft:
			GARD A 6;
			TNT1 A 0 A_GetSwayDirection();
			goto See;
		See.SwayRight:
			GARD C 6;
			TNT1 A 0 A_GetSwayDirection();
			goto See;
		Pain:
			GARD P 8;
			Goto Spawn;
		Missile:
		Melee:
			GARD F 10
			{
				if (!invoker.glowy)
				{
					Vector3 lightpos = (self.pos.x, self.pos.y, self.pos.z + 50);
					invoker.glowy = Pointlight(self.spawn("PointLight", lightpos));
					invoker.glowy.args[0] = 0x6A;
					invoker.glowy.args[1] = 0x06;
					invoker.glowy.args[2] = 0xAD;
					invoker.glowy.args[3] = 16;
				}
			}
			TNT1 A 0
			{
				invoker.glowy.destroy();
			}
			goto spawn;
		Death:
			GARD A 1
			{
				A_SetRenderStyle(1, STYLE_Stencil);
				SetShade("FF0000");
				
				for (int i = 0; i < 4; ++i)
				{
					floatGuns[i].bNoGravity = false;
					floatGuns[i].roll = 90;
					floatGuns[i].angle = random(0, 360);
					floatGuns[i].Thrust(5);
				}
				
			}
		Death.Shrink:
			GARD A 4 A_JumpIf(A_Shrink(), "Death.Loop");
			loop;
		Death.Loop:
			TNT1 A 0 A_SetScale(1);
			PBAL B 16;
			TNT1 A 0 A_StartSound("pokeball/Shake", CHAN_BODY, CHANF_OVERLAP);
			PBAL BFB 4;
			PBAL B 16;
			TNT1 A 0 A_StartSound("pokeball/Shake", CHAN_BODY, CHANF_OVERLAP);
			PBAL BEB 4;
			loop;
	}
	
	Action bool A_Shrink()
	{
		if (scale.x > 0.1)
		{
			A_SetScale(scale.x - 0.05);
			return false;
		} else {
			A_SetRenderStyle(1, STYLE_Normal);
			return true;
		}
	}
	
	void initFloatWeapons()
	{
		FloatGuns.clear();
		for (int i = 0; i < 4; ++i)
		{
			FloatGuns.push(Actor.spawn('GHAGFloatingGun', self.pos));
		}
	}
	
	Action state A_GetSwayDirection()
	{
		double thresh = 3;
	
		double x_dir = self.vel.x * cos(self.angle) - self.vel.y * sin(angle);
		double y_dir = self.vel.x * sin(self.angle) + self.vel.y * cos(angle);
		
		if (y_dir >= thresh) return resolveState('see.SwayLeft');
		else if (y_dir <= -thresh) return resolveState('see.SwayRight');
		else if (x_dir > thresh || x_dir < -thresh) return resolveState('see.SwayForward');
		
		return resolveState('see');
	}
	
	override void BeginPlay()
	{
		Super.BeginPlay();
		
		RageValue = 0;
		Ragetimer = 0;
		RageConstant = 200;
		
		OverTheEdge = false;
		charonmode = false;
		
		self.iwadClass = IWAD_DOOM;
	}
	
	void subtractAnger(Int value, bool drainage = false)
	{
		if (OverTheEdge) return;
		
		RageValue -= value * (rageValue < 0 ? 2 : 1);
		if (RageValue < 0 && !drainage) RageValue = 0;
	}
	
	void addAnger(Int value)
	{
		if (OverTheEdge) return;
		
		RageValue += value;
		if (RageValue > RageConstant) RageValue = RageConstant;
	}
	
	int rollCounterAnger;
	void IncreaseAngerRange(Int value)
	{
		rollCounterAnger += value;
	}
	
	void GetLivid()
	{
		if (!OverTheEdge) RageValue = RageConstant;
		RageValue += 750;
	}
	
	int GetRageValue() {
		return RageValue;
	}
	
	double GetRageModifier()
	{
		return GetRageValue() * 0.001 * (v_dashed ? 3 : 1) * (rageValue < 0 ? 2 : 1);
	}
	
	int GetTicModifier(int tic, bool allowZero = false, int weight = -1) 
	{
		int tics = weight == -1 ? tic : weight;
		double value = tics * (1 - GetRageModifier());
		if (!allowZero) {
			if (value < 1) value = 1;
		}
		value = round(value);
		if (weight != -1) value = min(tic, value);
		return int(Max(value, 0));
	}
	
	double GetDamageModifier(double damage)
	{
		return damage + (damage * GetRageModifier());
	}
	
	double GetAccuracyModifier(double accuracy)
	{
		return min(max(1, accuracy * (1 - GetRageModifier())), 20);
	}
	
	override int DamageMobj(Actor inflictor, Actor source, int damage, Name mod, int flags, double angle)
	{
		int _locDamage = damage;
		int _locFlags = flags;
		
		if (RageValue <= RageConstant) {
		
			RageValue += int(damage / 5);
		
			RageTimer = 0;
			if (RageValue > RageConstant) RageValue = RageConstant;
		}
		
		if (GHAGGardevoir(source)) _locDamage = Int(_locDamage * 0.25);
		
		if (_locDamage >= 5)
		{
			A_StartSound("gardie/Pain", CHAN_AUTO, 0, min(3, (double(_locdamage / 10.0))));
		}
		
		return Super.DamageMobj(inflictor, source, _locDamage, mod, _locFlags, angle);
	}
	
	override void Tick()
	{
		if (self.glowy)
		{
			Vector3 lightpos = (self.pos.x, self.pos.y, self.pos.z + 50);
			self.glowy.setOrigin(lightpos, true);
		}
	
		////////////////////////////////////////////////////////////////////////////////////////////////////
		//Rage cooldown
		////////////////////////////////////////////////////////////////////////////////////////////////////
		RageTimer++;
		
		if (rollCounterAnger > 0 && gametic % 2 == 0) {
			RageConstant += 1;
			rollCounterAnger -= 1;
		}
		
		if (RageValue > RageConstant) OverTheEdge = true;
		else if (RageValue <= RageConstant) OverTheEdge = false;
		
		if (rageValue != 0)
		{
			if (!overTheEdge)
			{
				int draintic = int(100 / (rageValue / 10. / 2.));
				int drainrate = 1;
				
				if (ragevalue < 0)
				{
					if (gametic % 3 == 0)
					{
						ragevalue += drainrate;
						RageTimer = 0;
					}
				}
				else
				{
					if (ragevalue > 1000 && !overTheEdge)
					{
						int rate = int((ragevalue - 1000) / 100 / 2);
						drainrate += rate;
					}
					if (killInterupt > 0)
					{
						--killInterupt;
					}
					else if (RageTimer >= draintic) 
					{
						ragevalue -= drainrate;
						RageTimer = 0;
					}
				}
			} 
			else if (overTheEdge) 
			{
				if (RageTimer >= 10) {
					ragevalue -= 3;
					RageTimer = 0;
				}
			}
		}
		
		////////////////////////////////////////////////////////////////////////////////////////////////////
		//Dash handler
		////////////////////////////////////////////////////////////////////////////////////////////////////
		
		if (player)
		{
			let buttons = player.cmd.buttons;
			double theta = 0;
			double speed = 45;
			
			if (buttons & BT_Crouch)
			{
				double velspeed = sqrt((vel.x * vel.x) + (vel.y * vel.y) + (vel.z * vel.z));
				self.friction = 1 + (velspeed / 22 / 12);
			}
			else
			{
				self.friction = 1;
			}
			
			if (buttons & BT_Speed && shift_tic == -1) {
				shift_tic = gametic;
				dashed = false;
			}
			else if (!(buttons & BT_Speed) && shift_tic != -1) {
				shift_tic = -1;
			}
			
			if (buttons & BT_Jump) {
				if (!self.bFly && buttons & BT_Crouch)
				{
					self.vel.z += 32;
				}
			}
			
			if (player.onGround && buttons & BT_Jump) jumped = true;
			else if (player.onGround) {
				jumped = false;
				v_dashed = false;
				A_StopSound(CHAN_BODY);
			}
			
			if (shift_tic == -1) dash_timer = 0;
			else
			{
				if (!dashed)
				{
					dashed = true;
				
					if (shift_tic != -1)
					{
						theta = 0;
					}
					
					if (!v_dashed)
					{
						if (buttons & BT_MoveLeft) {
							theta = 90;
							if (buttons & BT_Forward && !(buttons & BT_Back)) theta -= 45;
							else if (buttons & BT_Back && !(buttons & BT_Forward)) theta += 45;
						}
						else if (buttons & BT_MoveRight) {
							theta = 270;
							if (buttons & BT_Forward && !(buttons & BT_Back)) theta += 45;
							else if (buttons & BT_Back && !(buttons & BT_Forward)) theta -= 45;
						}
						else if (buttons & BT_Back && !(buttons & (BT_MoveRight | BT_MoveLeft))) theta = 180;
						
						theta += (self.angle % 360);
						
						double thrust_x = speed * cos(theta);
						double thrust_y = speed * sin(theta);
						double thrust_z = speed * sin(self.pitch) * (buttons & BT_Back ? 1 : -1);
						
						self.vel.x = thrust_x;
						self.vel.y = thrust_y;
						self.vel.z = thrust_z;
						
						A_StartSound("dash/Start", CHAN_AUTO);
					}
				} 
			}
			
			double p_speed = sqrt((self.vel.x * self.vel.x) + (self.vel.y * self.vel.y));
			
			if (p_speed >= 20)
			{
				double r_speed = (p_speed - 20.0) / 20;
				
				FLineTraceData bash;
				if (self.LineTrace(self.angle, 64, self.pitch, 0, self.height - 12, 0, 0, bash))
				{
					double x = bash.HitLocation.x - self.pos.x;
					double y = bash.HitLocation.y - self.pos.y;
					double dist = sqrt((x * x) + (y * y));
					if (bash.hitline)
					{
						
					}
				}
			}
		}
		
		Super.Tick();
		
		if (FloatGuns.size() == 0) initFloatWeapons();
		
		for (int i = 0; i < 4; ++i)
		{
			double sinoffset = sin((gametic + (i * 250)) * 0.25);
			double cosoffset = sin((gametic + (i * 250)) * 0.25);
			Double p_angle = self.angle;
			Double p_pitch = self.pitch;
			double p_height= (player.viewz - pos.z) - 3 + sinoffset;
			
			double spring = 0.7;
			double dampening = 1.0;
			double distance = 18;
			double spread = 35;
			
			if (player.readyweapon is "GHAGRocketLauncher")
			{
				distance = 12;
			}
			else if (player.ReadyWeapon is 'GHAGPsychoCutter')
			{
				let cutter = GHAGPsychoCutter(player.readyweapon);
				if (cutter.firing)
				{
					distance = 30;
					spread = 15 + (i < 2 ? -7 : 0);
					p_pitch += 20;
				}
			}
			
			switch (i)
			{
				case WEAP_BR:
					p_angle -= spread + cosoffset;
					break;
				case WEAP_BL:
					p_angle += spread + cosoffset;
					break;
				case WEAP_TR:
					p_angle -= spread + 10 + cosoffset;
					p_pitch += 20;
					p_height += 10;
					break;
				case WEAP_TL:
					p_angle += spread + 10 + cosoffset;
					p_pitch += 20;
					p_height += 10;
					break;
				default:
					break;
			}
			
			if (player.ReadyWeapon)
			{
				if (hiding)
				{
					p_angle = self.angle - 180 + (i % 2 == 0 ? 20 : -20);
					spring = 0.1;
					dampening = 1;
				}
			}
			
			if (self.health > 0)
			{
				FLineTraceData ray;
				Self.LineTrace(p_angle, distance, (p_pitch / 2), TRF_THRUBLOCK | TRF_THRUHITSCAN | TRF_NOSKY | TRF_SOLIDACTORS, p_height, 0, 0, ray);
				
				let activefloat = FloatGuns[i];
			
				double velx = DampedSpring(activefloat.pos.x, ray.hitlocation.x, activefloat.vel.x, spring, dampening);
				double vely = DampedSpring(activefloat.pos.y, ray.hitlocation.y, activefloat.vel.y, spring, dampening);
				double velz = DampedSpring(activefloat.pos.z, ray.hitlocation.z, activefloat.vel.z, spring, dampening);
			
				activefloat.vel += (velx, vely, velz);
				
				if (countInv('PowerInvisibility'))
				{
					FloatGuns[i].A_SetRenderStyle(0.25, STYLE_Fuzzy);
				} 
				else
				{
					FloatGuns[i].A_SetRenderStyle(0.25, STYLE_Normal);
				}
			}
		}
		
		if (player.ReadyWeapon && self.health > 0)
		{
			FLineTraceData PlayerAim;
			Self.LineTrace(self.angle, 128, self.pitch, TRF_THRUACTORS | TRF_THRUBLOCK | TRF_THRUHITSCAN | TRF_NOSKY | TRF_SOLIDACTORS, 41, 0, 0, PlayerAim);
			
			let GunTarget = Actor.spawn('GHAGGunTarget', PlayerAim.HitLocation);
			
			for (int i = 0; i < 4; ++i)
			{
				let gun = GHAGFloatingGun(FloatGuns[i]);
			
				if (gun.targetOverride) gun.A_Face(gun.targetOverride, 0, 270, 0, 0, FAF_MIDDLE);
				else FloatGuns[i].A_Face(guntarget, 0, 270, 0, 0, FAF_MIDDLE);
				
				Vector3 dirTo = FloatGuns[i].vec3To(GunTarget).unit();
				FloatGuns[i].pitch = -asin(dirTo.z);
				
				if (player.readyweapon is "GHAGRocketLauncher")
				{
					FloatGuns[i].pitch += 16 * cos(gametic);
					FloatGuns[i].roll += 4;
				}
				else if (player.readyweapon is 'GHAGPsychoCutter')
				{
					let cutter = GHAGPsychoCutter(player.readyweapon);
					if (cutter.firing)
					{
						FloatGuns[i].roll = (i % 2 == 0 ? 20 : -20);
					} 
					else 
					{
						FloatGuns[i].roll = 0;
					}
				}
				else 
				{
					FloatGuns[i].roll = 0;
				}
			}
		
			Name wepname = player.ReadyWeapon.getClassName();
			
			if (prevWeap != wepname || updateWeapons)
			{
				updateWeapons = false;
			
				prevWeap = wepname;
				int count = 0;
			
				StateLabel wepstate = 'Hidden';
		
				if (player.readyWeapon is "GHAGPokemonMove")
				{
					count = -1;
				}
				else
				{
					switch (wepname)
					{
						case 'GHAGPsychoCutter' :
							count = self.CountInv("GHAGPsychoCutterOverlay");
							break;
						case 'GHAGPistol':
							count = self.CountInv("GHAGPistolOverlay");
							wepstate = 'PistolIdle';
							break;
						case 'GHAGShotgun' :
							count = self.CountInv("GHAGShotgunOverlay");
							wepstate = 'ShotgunIdle';
							break;
						case 'GHAGNailgun' :
							count = self.CountInv("GHAGNailgunOverlay");
							wepstate = 'NailgunIdle';
							break;
						case 'GHAGPlasmaRifle' :
							count = self.CountInv("GHAGPlasmaOverlay");
							wepstate = 'PlasmaIdle';
							break;
						case 'GHAGRocketLauncher' :
							count = -1;
							break;
						case 'GHAGPsychoCutter' :
							count = self.CountInv("GHAGPsychoCutterOverlay");
							wepstate = 'ChainsawIdle';
							break;
						case 'GHAGSuperRailgun' :
							count = self.CountInv("GHAGSuperRailgunOverlay");
							wepstate = 'RailgunIdle';
							break;
						Default:
							break;
					}
				}
				
				for (int i = 0; i < 4; ++i)
				{
					if (i < count) 
					{
						FloatGuns[i].SetStateLabel(wepstate);
						if (player.readyweapon is 'GHAGShotgun')
						{
							if (GHAGShotgun(player.readyWeapon).batmode && i == 0) FloatGuns[i].SetStateLabel('Hidden');
						}
					}
					else FloatGuns[i].SetStateLabel('Hidden');
				}
			}
		}
		else if (self.health > 0)
		{
			for (int i = 0; i < 4; ++i)
			{
				FloatGuns[i].SetStateLabel('Hidden');
			}
		}
		
	}
	
	//Thank you Cali
	double DampedSpring(double p, double r, double v, double k, double d) {
		return -(d * v) - (k * (p - r));
	}
	
	
	override void CheckCrouch(bool totallyfrozen)
	{
		let player = self.player;
		UserCmd cmd = player.cmd;

		if (cmd.buttons & BT_JUMP)
		{
			if (cmd.buttons & BT_Crouch && player.onground)
			{
				double velspeed = sqrt((vel.x * vel.x) + (vel.y * vel.y));
				vel.z = velspeed;
			}
			cmd.buttons &= ~BT_CROUCH;
		}
		if (CanCrouch() && player.health > 0)
		{
			if (!totallyfrozen)
			{
				int crouchdir = player.crouching;

				if (crouchdir == 0)
				{
					crouchdir = (cmd.buttons & BT_CROUCH) ? -1 : 1;
				}
				else if (cmd.buttons & BT_CROUCH)
				{
					player.crouching = 0;
				}
				if (crouchdir == 1 && player.crouchfactor < 1 && pos.Z + height < ceilingz)
				{
					CrouchMove(1);
				}
				else if (crouchdir == -1 && player.crouchfactor > 0.5)
				{
					CrouchMove(-1);
				}
			}
		}
		else
		{
			player.Uncrouch();
		}

		player.crouchoffset = -(ViewHeight) * (1 - player.crouchfactor);
	}
	
	//From GZDoom.pk3, just removes the check to see if the level turns of jumping
	override void CheckJump()
	{
		let player = self.player;
		if (player.cmd.buttons & BT_JUMP)
		{
			if (player.crouchoffset != 0)
			{
				player.crouching = 1;
			}
			else if (waterlevel >= 2)
			{
				Vel.Z = 4 * Speed;
			}
			else if (player.onground && player.jumpTics == 0)
			{
				double jumpvelz = JumpZ * 35 / TICRATE;
				double jumpfac = 0;
				
				for (let p = Inv; p != null; p = p.Inv)
				{
					let pp = PowerHighJump(p);
					if (pp)
					{
						double f = pp.Strength;
						if (f > jumpfac) jumpfac = f;
					}
				}
				if (jumpfac > 0) jumpvelz *= jumpfac;

				Vel.Z += jumpvelz;
				bOnMobj = false;
				player.jumpTics = -1;
				if (!(player.cheats & CF_PREDICTING)) A_StartSound("*jump", CHAN_BODY);
			}
		}
	}
	
	Vector3 holdpos;
	bool dashed;
	bool v_dashed;
	bool jumped;
	int dash_timer;
	int shift_tic;
	
	override void CheatGive(String _name, int _amount)
	{
		if (_name == 'all' || _name == 'weapons')
		{
			if (RageConstant < 1000) RageConstant = 1000;
			else RageConstant += 100;
			
			RageValue = RageConstant;
			
			GiveInventory("GHAGPistol", 1);
			GiveInventory("GHAGPsychocutter", 1);
			GiveInventory("GHAGShotgun", 1);
			GiveInventory("GHAGNailgun", 1);
			GiveInventory("GHAGRocketLauncher", 1);
			GiveInventory("GHAGPlasmarifle", 1);
			GiveInventory("GHAGMoonBlastGun", 1);
			GiveInventory("GHAGBlackhole", 1);
			GiveInventory("GHAGBackpack", 1);
			for (int i = 0; i < 4; ++i)
			{
				GiveInventory("GHAGPistolOverlay", 1);
				GiveInventory("GHAGShotgunOverlay", 1);
				GiveInventory("GHAGNailgunOverlay", 1);
				GiveInventory("GHAGPlasmaOverlay", 1);
				GiveInventory("GHAGPsychocutterOverlay", 1);
				GiveInventory("GHAGSuperRailgunOverlay", 1);
			}
			
			if (_name == "Backpack")
			{
				super.CheatGive("GHAGBackpack", _amount);
			}
			else
			{
				super.CheatGive(_name, _amount);
			}
			
			return;
		}
		
		super.CheatGive(_name, _amount);
		
	}
	
	//ty Cali: https://gist.github.com/caligari87/ca20c1547c76fb6f1999a2bfa952463f
	bool KeyCheck(int toCheck, int checkType, PlayerPawn pointer) {
	int b = pointer.player.cmd.buttons; int ob = pointer.player.oldbuttons;
	switch (checkType) {
		case KPT_PRESSING: return (b&toCheck);break;
		case KPT_JUSTPRESSED: return ((b&toCheck) && !(ob&toCheck));break;
		case KPT_JUSTRELEASED: return (!(b&toCheck) && (ob&toCheck));break;
		case KPT_HELD: return ((b&toCheck) && (ob&toCheck));break;
	}return false;
}
}

enum KeyPressTypes {
	KPT_PRESSING,
	KPT_JUSTPRESSED,
	KPT_JUSTRELEASED,
	KPT_HELD,
}