

class Hellportal_ScriptFunctions play
{
	static void ActivatePortals(int lineId1, int lineId2)
	{
		int line1 = Level.CreateLineIdIterator(lineId1).Next();
		int line2 = Level.CreateLineIdIterator(lineId2).Next();
		
		if (line1 == -1 || line2 == -1)
		{
			console.Printf("ActivatePortals: Lines not found");
			return;
		}
		
		Hellportal_PortalManager manager = Hellportal_PortalManager.Instance();
		manager.ShootPortalAtLine(Level.Lines[line1], Hellportal_PortalManager.COLOR_BLUE);
		manager.ShootPortalAtLine(Level.Lines[line2], Hellportal_PortalManager.COLOR_ORANGE);
	}
	
	static void KillBothPortals(Actor activator)
	{
		if (activator.player != null)
		{
			Weapon readyWeapon = activator.player.ReadyWeapon;
			if (readyWeapon is "Hellportal_PortalShotgun")
				Hellportal_PortalShotgun(readyWeapon).forceReload = true;
		}
	
		Hellportal_PortalManager.Instance().KillBoth();
	}
}

class Hellportal_VectorUtils
{
	static Vector3 CenterOfLine(Line l)
	{
		Vector2 middle = (l.v1.p + l.v2.p) / 2;
		double floorHeight = l.frontsector.floorplane.ZatPoint(middle);
		double halfHeight = (l.frontsector.ceilingplane.ZatPoint(middle) - floorHeight) / 2;
		return (middle, floorHeight + halfHeight);
	}
	
	static Vector3 CenterOfFloor(Sector s)
	{
		return (s.centerspot, s.floorplane.ZatPoint(s.centerspot));
	}
	
	static Vector3 CenterOfCeiling(Sector s)
	{
		return (s.centerspot, s.ceilingplane.ZatPoint(s.centerspot));
	}
	
	static double VectorToAngle(Vector2 vec)
	{
		return ATan2(vec.Y, vec.X);
	}
	
	static double VectorToPitch(Vector3 vec)
	{
		double len = vec.Length();
		if (len == 0)
			return 0;
		return ASin(-vec.Z / len);
	}
	
	static Vector3 AngleAndPitchToVector(double angle, double pitch)
	{
		double z = -Sin(pitch);
		double angleLen = Sqrt(1 - z*z);
		return (Actor.AngleToVector(angle, angleLen), z);
	}
}

class Hellportal_Portal play
{
	Line pline;
	Sector floor;
	Sector ceiling;
	Vector3 center;
	Vector3 normal;
	Vector3 sideways;
	Vector3 up;
	TextureID oldTexture;
	double oldXOffset;
	double oldYOffset;
	TextureID camtex;
	TextureID placetex;
	Hellportal_PortalCamera cam;
	Hellportal_Portal other;
	
	bool IsPlaced()
	{
		return pline != null || floor != null || ceiling != null;
	}
	
	void Kill()
	{
		if (pline != null)
		{
			Side side = pline.sidedef[Line.front];
			
			side.SetTexture(Side.mid, oldTexture);
			side.SetTextureXOffset(Side.mid, oldXOffset);
			side.SetTextureYOffset(Side.mid, oldYOffset);
			
			pline = null;
		}
		else if (floor != null)
		{
			myKillSectorPortal(floor, Sector.floor);
			floor = null;
		}
		else if (ceiling != null)
		{
			myKillSectorPortal(ceiling, Sector.ceiling);
			ceiling = null;
		}
	}
	
	void myKillSectorPortal(Sector s, int pos)
	{
		s.SetTexture(pos, oldTexture);
		s.SetXOffset(pos, oldXOffset);
		s.SetYOffset(pos, oldYOffset);
	}
	
	void MakeLinePortal(Line l)
	{
		pline = l;
		
		Side side = l.sidedef[Line.front];
		
		oldTexture = side.GetTexture(Side.mid);
		oldXOffset = side.GetTextureXOffset(Side.mid);
		oldYOffset = side.GetTextureYOffset(Side.mid);
		
		side.SetTexture(Side.mid, myPortalTexture());
		side.SetTextureXOffset(Side.mid, 0);
		side.SetTextureYOffset(Side.mid, 0);
		
		center = Hellportal_VectorUtils.CenterOfLine(l);
		
		//Vector2 sectorCenter = l.sidedef[Line.front].sector.centerspot;
		//Vector2 n = sectorCenter - center.Xy; // This probably not the right way to do this.
		
		Vector2 vertOffset = l.v2.p - l.v1.p;
		Vector2 n = (vertOffset.Y, -vertOffset.X);
		
		normal = (n.Unit(), 0);
		sideways = (-(normal.Y), normal.X, 0);
		up = (0, 0, 1);
		
		myPlaceCam();
	}
	
	void MakeFloorPortal(Sector s)
	{
		floor = s;
		myMakeSectorPortal(s, Sector.floor);
		
		center = Hellportal_VectorUtils.CenterOfFloor(s);
		normal = s.floorplane.Normal;
		
		myPlaceCam();
	}
	
	void MakeCeilingPortal(Sector s)
	{
		ceiling = s;
		myMakeSectorPortal(s, Sector.ceiling);
		
		center = Hellportal_VectorUtils.CenterOfCeiling(s);
		normal = s.ceilingplane.Normal;
		
		myPlaceCam();
	}
	
	void myMakeSectorPortal(Sector s, int pos)
	{
		oldTexture = s.GetTexture(pos);
		oldXOffset = s.GetXOffset(pos);
		oldYOffset = s.GetYOffset(pos);
		
		double XWonkiness = s.centerspot.X % 128;
		double YWonkiness = s.centerspot.Y % 128;
		
		//console.printf("%f %f", s.centerspot.X, s.centerspot.Y);
		//console.printf("%f %f", XWonkiness, YWonkiness);
		
		s.SetTexture(pos, myPortalTexture());
		s.SetXOffset(pos, 64 - XWonkiness);
		s.SetYOffset(pos, YWonkiness + 64);
		
		//console.printf("%f %f", s.GetXOffset(pos), s.GetYOffset(pos));
		
		// TODO: Derive this from the normal.
		sideways = (-1, 0, 0);
		up = (0, 1, 0);
	}
	
	TextureID myPortalTexture()
	{
		if (other.IsPlaced())
			return camtex;
		else
			return placetex;
	}
	
	void myPlaceCam()
	{
		cam.SetOrigin(center, false);
		cam.Angle = Hellportal_VectorUtils.VectorToAngle(normal.Xy);
		cam.Pitch = Hellportal_VectorUtils.VectorToPitch(normal);
		other.myFixTexture();
	}
	
	void myFixTexture()
	{
		if (pline != null)
			pline.sidedef[pline.front].SetTexture(Side.mid, myPortalTexture());
		else if (floor != null)
			floor.SetTexture(Sector.floor, myPortalTexture());
		else if (ceiling != null)
			ceiling.SetTexture(Sector.ceiling, myPortalTexture());
	}
	
	Vector3 WorldToPortal(Vector3 vec)
	{
		return (vec dot sideways, vec dot normal, vec dot up);
	}
	
	Vector3 PortalToWorld(Vector3 vec)
	{
		return (vec.X * sideways) + (vec.Y * normal) + (vec.Z * up);
	}
}

class Hellportal_PortalManager : Thinker
{
	const COLOR_BLUE = 0;
	const COLOR_ORANGE = 1;
	const CAMTEX_1 = "PORTAL_CAMTEX_1";
	const CAMTEX_2 = "PORTAL_CAMTEX_2";
	const PLACETEX_1 = "OWARPE01";
	const PLACETEX_2 = "OWARPE21";
	const LINETAG_PORTALABLE = 990;
	const SECTAG_FLOOR_PORTALABLE = 991;
	const SECTAG_CEILING_PORTALABLE = 992;
	const ACTORTAG_PORTAL_USER = 99;
	const ACTORTAG_ENEMY_WATCHER = 98;

	Hellportal_Portal portals[2];

	Hellportal_PortalManager Init()
	{
		portals[COLOR_BLUE] = new("Hellportal_Portal");
		portals[COLOR_ORANGE] = new("Hellportal_Portal");
		
		portals[COLOR_BLUE].other = portals[COLOR_ORANGE];
		portals[COLOR_ORANGE].other = portals[COLOR_BLUE];
	
		portals[COLOR_BLUE].cam = Hellportal_PortalCamera(Actor.Spawn("Hellportal_PortalCamera"));
		portals[COLOR_BLUE].cam.myPortal = portals[COLOR_BLUE];
		portals[COLOR_BLUE].cam.particleColor = "Purple";
		
		portals[COLOR_ORANGE].cam = Hellportal_PortalCamera(Actor.Spawn("Hellportal_PortalCamera"));
		portals[COLOR_ORANGE].cam.myPortal = portals[COLOR_ORANGE];
		portals[COLOR_ORANGE].cam.particleColor = "Gold";
	
		TexMan.SetCameraToTexture(portals[COLOR_BLUE].cam, CAMTEX_1, 45);
		portals[COLOR_ORANGE].camtex = TexMan.CheckForTexture(CAMTEX_1);
		
		TexMan.SetCameraToTexture(portals[COLOR_ORANGE].cam, CAMTEX_2, 45);
		portals[COLOR_BLUE].camtex = TexMan.CheckForTexture(CAMTEX_2);
		
		portals[COLOR_BLUE].placetex = TexMan.CheckForTexture(PLACETEX_1);
		portals[COLOR_ORANGE].placetex = TexMan.CheckForTexture(PLACETEX_2);
		
		ChangeStatNum(STAT_USER);
		
		return self;
	}

	static Hellportal_PortalManager Instance()
	{
		ThinkerIterator iter = ThinkerIterator.Create("Hellportal_PortalManager", STAT_USER);
		Thinker instance = iter.Next();
		if (instance != null)
			return Hellportal_PortalManager(instance);
		else
			return new("Hellportal_PortalManager").Init();
	}
	
	bool ShootPortalAtLine(Line l, int portalColor)
	{
		if (IsLinePortalable(l) && !LineHasPortal(l))
		{
			Hellportal_Portal p = portals[portalColor];
			p.Kill();
			p.MakeLinePortal(l);
			return true;
		}
		else
		{
			return false;
		}
	}
	
	bool ShootPortalAtFloor(Sector s, int portalColor)
	{
		if (IsSectorFloorPortalable(s) && !FloorHasPortal(s))
		{
			Hellportal_Portal p = portals[portalColor];
			p.Kill();
			p.MakeFloorPortal(s);
			return true;
		}
		else
		{
			return false;
		}
	}
	
	bool ShootPortalAtCeiling(Sector s, int portalColor)
	{
		if (IsSectorCeilingPortalable(s) && !CeilingHasPortal(s))
		{
			Hellportal_Portal p = portals[portalColor];
			p.Kill();
			p.MakeCeilingPortal(s);
			return true;
		}
		else
		{
			return false;
		}
	}
	
	bool LineHasPortal(Line l)
	{
		return portals[COLOR_BLUE].pline == l || portals[COLOR_ORANGE].pline == l;
	}
	
	bool FloorHasPortal(Sector s)
	{
		return portals[COLOR_BLUE].floor == s || portals[COLOR_ORANGE].floor == s;
	}
	
	bool CeilingHasPortal(Sector s)
	{
		return portals[COLOR_BLUE].ceiling == s || portals[COLOR_ORANGE].ceiling == s;
	}
	
	Hellportal_Portal PortalOfLine(Line l)
	{
		if (portals[COLOR_BLUE].pline == l)
			return portals[COLOR_BLUE];
		else if (portals[COLOR_ORANGE].pline == l)
			return portals[COLOR_ORANGE];
		else
			return null;
	}
	
	Hellportal_Portal PortalOfFloor(Sector s)
	{
		if (portals[COLOR_BLUE].floor == s)
			return portals[COLOR_BLUE];
		else if (portals[COLOR_ORANGE].floor == s)
			return portals[COLOR_ORANGE];
		else
			return null;
	}
	
	Hellportal_Portal PortalOfCeiling(Sector s)
	{
		if (portals[COLOR_BLUE].ceiling == s)
			return portals[COLOR_BLUE];
		else if (portals[COLOR_ORANGE].ceiling == s)
			return portals[COLOR_ORANGE];
		else
			return null;
	}
	
	bool BothPortalsArePlaced()
	{
		return portals[COLOR_BLUE].IsPlaced() && portals[COLOR_ORANGE].IsPlaced();
	}
	
	void KillBoth()
	{
		portals[COLOR_BLUE].Kill();
		portals[COLOR_ORANGE].Kill();
	}
	
	static bool IsLinePortalable(Line l)
	{
		return DoesLineHaveTag(l, LINETAG_PORTALABLE);
	}
	
	static bool IsSectorFloorPortalable(Sector s)
	{
		return DoesSectorHaveTag(s, SECTAG_FLOOR_PORTALABLE);
	}
	
	static bool IsSectorCeilingPortalable(Sector s)
	{
		return DoesSectorHaveTag(s, SECTAG_CEILING_PORTALABLE);
	}
	
	static bool DoesLineHaveTag(Line l, int tag)
	{
		for (int i = 0; i < l.CountIDs(); i++)
		{
			if (l.GetID(i) == tag)
				return true;
		}
		return false;
	}
	
	static bool DoesSectorHaveTag(Sector s, int tag)
	{
		for (int i = 0; i < s.CountTags(); i++)
		{
			if (s.GetTag(i) == tag)
				return true;
		}
		return false;
	}
	
	override void Tick()
	{
		if (!BothPortalsArePlaced())
			return;
	
		for (int i = 0; i < players.Size(); i++)
		{
			if (players[i].PlayerState == PST_LIVE && players[i].Mo != null)
				TeleportActor(players[i].Mo);
		}
		
		ActorIterator iter = Level.CreateActorIterator(ACTORTAG_PORTAL_USER);
		Actor actor;
		while ((actor = iter.Next()) != null)
		{
			TeleportActor(actor);
		}
		
		ActorIterator watcherIter = Level.CreateActorIterator(ACTORTAG_ENEMY_WATCHER, "Hellportal_EnemyWatcher");
		Actor watcherActor;
		while ((watcherActor = watcherIter.Next()) != null)
		{
			int watcherEnemyTag = watcherActor.args[4];
			
			ActorIterator watchedIter = Level.CreateActorIterator(watcherEnemyTag);
			Actor watchedActor;
			while ((watchedActor = watchedIter.Next()) != null)
			{
				TeleportActor(watchedActor);
			}
		}
	}
	
	void TeleportActor(Actor actor)
	{
		double velAngle = Hellportal_VectorUtils.VectorToAngle(actor.Vel.Xy);
		double velPitch = Hellportal_VectorUtils.VectorToPitch(actor.Vel);
		double velLen = actor.Vel.Length();
	
		FLineTraceData data;
		actor.LineTrace(velAngle, velLen + actor.Radius + 5, velPitch, TRF_THRUACTORS, 0, 0, 0, data);
	
		Hellportal_Portal p = null;
		if (data.HitType == TRACE_HitWall)
		{
			p = PortalOfLine(data.HitLine);
		}
		else if (data.HitType == TRACE_HitFloor)
		{
			p = PortalOfFloor(data.HitSector);
		}
		else
		{
			actor.LineTrace(velAngle, velLen + actor.Height + 5, velPitch, TRF_THRUACTORS, 0, 0, 0, data);
			if (data.HitType == TRACE_HitCeiling)
				p = PortalOfCeiling(data.HitSector);
		}
		
		if (p != null)
		{
			TeleportActorThroughPortal(actor, p, data.HitLocation);
			return;
		}
		
		if (actor.args[4] > 0)
		{
			actor.args[4]--;
		}
		else
		{
			if (CheckFailsafeTeleport(actor, portals[COLOR_BLUE]) || CheckFailsafeTeleport(actor, portals[COLOR_ORANGE]))
				actor.args[4] = 30;
		}
	}
	
	static bool CheckFailsafeTeleport(Actor actor, Hellportal_Portal p)
	{
		Vector3 distance = p.center - actor.Pos;
		
		// TODO: Make these numbers come from Portal and calculate them based on sector/wall size.
		if (Abs(distance dot p.up) < 65 && Abs(distance dot p.sideways) < 65 && Abs(distance dot p.normal) < (actor.Radius + 5))
		{
			TeleportActorThroughPortal(actor, p, actor.Pos);
			return true;
		}
		
		return false;
	}
	
	static void TeleportActorThroughPortal(Actor actor, Hellportal_Portal p, Vector3 enterSpot)
	{
		if (actor.GetClass() == "Hellprotal_TurretProjectile")
			actor.Target = null;
	
		Vector3 offsetWorld = actor.Pos - p.center;
		Vector3 offsetPortalSpace = p.WorldToPortal(offsetWorld);
		Vector3 dirPortalSpace = p.WorldToPortal(Hellportal_VectorUtils.AngleAndPitchToVector(actor.Angle, actor.Pitch));
		Vector3 velPortalSpace = p.WorldToPortal(actor.Vel);
		
		dirPortalSpace.Y *= -1;
		dirPortalSpace.X *= -1;
		
		velPortalSpace.Y *= -1;
		velPortalSpace.X *= -1;
		
		offsetPortalSpace.X *= -1;
		
		
		double wallstuckMarginY = actor.Radius + 5;
		
		if (offsetPortalSpace.Y < wallstuckMarginY)
			offsetPortalSpace.Y = wallstuckMarginY;
		
		// TODO: portal size
		double wallStuckLimit = 64 - actor.Radius;
		
		if (offsetPortalSpace.X > wallStuckLimit)
			offsetPortalSpace.X = wallStuckLimit;
		if (offsetPortalSpace.Z > wallStuckLimit)
			offsetPortalSpace.Z = wallStuckLimit;
		
		if (offsetPortalSpace.X < -wallStuckLimit)
			offsetPortalSpace.X = -wallStuckLimit;
		if (p.other.pline == null && offsetPortalSpace.Z < -wallStuckLimit)
			offsetPortalSpace.Z = -wallStuckLimit;
		
		velPortalSpace.Y += 1;
		
		Vector3 offsetWorldOther = p.other.PortalToWorld(offsetPortalSpace);
		Vector3 dirWorldOther = p.other.PortalToWorld(dirPortalSpace);
		Vector3 velOther = p.other.PortalToWorld(velPortalSpace);
	
		actor.SetOrigin(p.other.center + offsetWorldOther, false);
		actor.Angle = Hellportal_VectorUtils.VectorToAngle(dirWorldOther.Xy);
		actor.Pitch = Hellportal_VectorUtils.VectorToPitch(dirWorldOther);
		actor.Vel = velOther;
		
		p.cam.A_StartSound("hellprotal/teleport");
		p.other.cam.A_StartSound("hellprotal/teleport");
	}
}

class Hellportal_TechActorBase : Actor abstract
{
	default
	{
		+NOBLOCKMAP
		+NOGRAVITY
		+DONTSPLASH
		RenderStyle "None";
		Radius 8;
	}
}

class Hellportal_PortalCamera : Hellportal_TechActorBase
{
	Hellportal_Portal myPortal;
	Color particleColor;

	default
	{
		CameraHeight 0;
	}
	
	override void Tick()
	{
		if (myPortal.IsPlaced())
		{
			for (int i = 0; i < 10; i++)
			{
				FSpawnParticleParams p;
				p.color1 = particleColor;
				p.vel = myPortal.PortalToWorld((FRandom(-0.1, 0.1), FRandom(0, 0.5), FRandom(-0.1, 0.1)));
				p.lifetime = 35;
				p.size = 16;
				p.startalpha = 1;
				p.flags = SPF_FULLBRIGHT;
				
				double bigOffset = FRandom(-60, 60);
				int side = Random(0, 3);
				
				if (side == 0)
					p.Pos = myPortal.center + myPortal.PortalToWorld((bigOffset, 4, -60));
				else if (side == 1)
					p.Pos = myPortal.center + myPortal.PortalToWorld((bigOffset, 4, 60));
				else if (side == 2)
					p.Pos = myPortal.center + myPortal.PortalToWorld((-60, 4, bigOffset));
				else if (side == 3)
					p.Pos = myPortal.center + myPortal.PortalToWorld((60, 4, bigOffset));
				
				Level.SpawnParticle(p);
			}
		}
		
		super.Tick();
	}
}

class Hellportal_FizzlerParticles : Hellportal_TechActorBase
{
	TextureID texture;
	
	override void BeginPlay()
	{
		texture = TexMan.CheckForTexture("PRTLFZLR");
		super.BeginPlay();
	}
	
	override void Tick()
	{
		if (CheckProximity("PlayerPawn", 1500, 1, CPXF_ANCESTOR))
		{
			for (int i = 0; i < Args[3]; i++)
			{
				FSpawnParticleParams p;
				p.texture = texture;
				p.color1 = "cornflower blue";
				p.size = 16;
				p.startalpha = 1;
				p.flags = SPF_FULLBRIGHT;
				p.style = STYLE_Add;
				
				double zOffset = FRandom(0, Args[4]);
				Vector2 offset = (Args[0], Args[1]);
				int side = Random(0, 1);
				
				p.lifetime = 2 * offset.Length();
				
				if (side == 0)
				{
					p.Pos = Pos + (offset, zOffset);
					p.Vel = (-1 * offset.Unit(), 0);
				}
				else if (side == 1)
				{
					p.Pos = Pos + (-1 * offset, zOffset);
					p.Vel = (1 * offset.Unit(), 0);
				}
				
				Level.SpawnParticle(p);
			}
		}
		
		super.Tick();
	}
}

class Hellportal_PortalShotgun : SuperShotgun
{
	bool forceReload;

	default
	{
		Weapon.AmmoUse 0;
		Weapon.SlotNumber 0;
		Inventory.PickupMessage "You got the Portal Shotgun!";
		+WEAPON.NOAUTOAIM
	}
	
	States
	{
		Ready:
			SHT2 A 1 {
				A_WeaponReady(WRF_ALLOWRELOAD);
				if (invoker.forceReload)
				{
					invoker.forceReload = false;
					return ResolveState("Reload");
				}
				else
				{
					return ResolveState(null);
				}
			}
			Loop;
		
		Reload:
			SHT2 A 1 Offset(0,32+0) { Hellportal_PortalManager.Instance().KillBoth(); }
			SHT2 A 5 Offset(0,32+8) A_StartSound("menu/cursor");
			SHT2 A 5 Offset(0,32+16);
			SHT2 A 5 Offset(0,32+8);
			SHT2 A 1 Offset(0,32+0);
			Goto Ready;
		
		Fire:
			SHT2 A 3;
			SHT2 A 4 A_FireProjectile("Hellportal_BluePortalProjectile");
			Goto FireCommon;
			
		AltFire:
			SHT2 A 3;
			SHT2 A 4 A_FireProjectile("Hellportal_OrangePortalProjectile");
			Goto FireCommon;
			
		FireCommon:
			SHT2 B 4;
			SHT2 C 4;
			SHT2 D 4 A_OpenShotgun2;
			SHT2 E 4;
			SHT2 F 4 A_LoadShotgun2;
			SHT2 G 3;
			SHT2 H 3 A_CloseShotgun2;
			SHT2 A 2 A_ReFire;
			Goto Ready;
	}
}

class Hellportal_BluePortalProjectile : Hellportal_PortalProjectile
{
	default
	{
		Hellportal_PortalProjectile.Color Hellportal_PortalManager.COLOR_BLUE;
	}
}

class Hellportal_OrangePortalProjectile : Hellportal_PortalProjectile
{
	default
	{
		Hellportal_PortalProjectile.Color Hellportal_PortalManager.COLOR_ORANGE;
	}
}

class Hellportal_PortalProjectile : Rocket abstract
{
	int color;
	property Color: color;
	
	bool crashedIntoSomething;
	
	default
	{
		Damage 0;
		Speed 100;
		Radius 5;
		Height 4;
		-RANDOMIZE;
		+THRUACTORS;
	}
	
	states
	{
		Death:
			MISL B 8 Bright;
			MISL C 6 Bright;
			MISL D 4 Bright;
			Stop;
	}
	
	override void Tick()
	{
		if (!crashedIntoSomething)
		{
			if ((BlockingLine != null && Hellportal_PortalManager.Instance().ShootPortalAtLine(BlockingLine, color)) ||
				(BlockingFloor != null && Hellportal_PortalManager.Instance().ShootPortalAtFloor(BlockingFloor, color)) ||
				(BlockingCeiling != null && Hellportal_PortalManager.Instance().ShootPortalAtCeiling(BlockingCeiling, color)))
			{
				crashedIntoSomething = true;
			}
		}
		
		super.Tick();
	}
}

class Hellportal_EnemyWatcher : Hellportal_TechActorBase
{
	bool enemiesAreDead;
	int sleep;

	override void Tick()
	{
		if (!enemiesAreDead)
		{
			if (sleep > 0)
			{
				sleep--;
			}
			else
			{
				sleep = 10;
				
				bool deadNow = true;
			
				ActorIterator iter = Level.CreateActorIterator(args[4]);
				Actor actor;
				while ((actor = iter.Next()) != null)
				{
					if (actor.Health > 0)
					{
						deadNow = false;
						break;
					}
				}
				
				if (deadNow)
				{
					enemiesAreDead = true;
					CallACS("Hellportal_Door_Open", args[0], args[1], args[2]);
					
					int indicatorLineId = args[3];
					if (indicatorLineId != 0)
					{
						int lineIndex = Level.CreateLineIdIterator(indicatorLineId).Next();
						if (lineIndex != -1)
							Level.Lines[lineIndex].sidedef[Line.front].SetTexture(Side.mid, TexMan.CheckForTexture("COMPLOK2"));
					}
				}
			}
		}
		
		super.Tick();
	}
}

class Hellprotal_TurretProjectile : FatShot
{
	default
	{
		Speed 5;
		Radius 12;
		Height 16;
		SeeSound "hellprotal/turretprojectile/attack";
		DeathSound "hellprotal/turretprojectile/shotx";
	}
}

class Hellportal_Turret : Hellportal_TechActorBase
{
	int sleep;

	override void Tick()
	{
		if (sleep > 0)
		{
			sleep--;
		}
		else
		{
			sleep = args[0];
			
			Actor fired = A_SpawnProjectile("Hellprotal_TurretProjectile", 0, 0, 0, CMF_AIMDIRECTION);
			if (fired != null)
				fired.ChangeTid(Hellportal_PortalManager.ACTORTAG_PORTAL_USER);
		}
	}
}

mixin class Hellportal_CardMixin
{
	Vector3 posBeforePickup;
	int tidBeforePickup;

	override void AttachToOwner(Actor other)
	{
		tidBeforePickup = TID;
		posBeforePickup = Pos;
		CallACS("Hellportal_Door_Open", args[0], args[1], args[2]);
		
		super.AttachToOwner(other);
	}
	
	override void DetachFromOwner()
	{
		CallACS("Hellportal_Door_Close", args[0], args[1], args[2]);
		
		Actor newCard = Spawn(GetClass(), posBeforePickup);
		newCard.args[0] = args[0];
		newCard.args[1] = args[1];
		newCard.args[2] = args[2];
		newCard.ChangeTid(tidBeforePickup);
		
		Spawn("TeleportFog", posBeforePickup);
		
		super.DetachFromOwner();
	}
	
	override bool HandlePickup(Inventory item)
	{
		if (item.GetClass() == GetClass())
			return true;
		else
			return false;
	}
}

class Hellportal_BlueCard : BlueCard
{
	default
	{
		Species "BlueCard";
	}

	mixin Hellportal_CardMixin;
}

class Hellportal_RedCard : RedCard
{
	default
	{
		Species "RedCard";
		Inventory.Pickupmessage "Picked up a massive Red Keycard.";
		Radius 64;
		Height 128;
		Scale 8;
	}

	mixin Hellportal_CardMixin;
}

class Hellportal_YellowCard : YellowCard
{
	default
	{
		Species "YellowCard";
	}

	mixin Hellportal_CardMixin;
}

class Hellportal_FinalBoss : Cyberdemon
{
	const maxMissiles = 25;
	int missleCount;

	default
	{
		Health 1;
		Height 220;
		Scale 2;
		MinMissileChance 3;
	}
	
	states
	{
		Missile:
			CYBR E 6 { A_FaceTarget(); invoker.missleCount = 0; }
			Goto MoreMissile;
		MoreMissile:
			CYBR F 1
			{
				Actor fired = A_SpawnProjectile("Hellprotal_TurretProjectile", 110, -40, FRandom(-10, 10) + (missleCount - maxMissiles/2) * 3.5, 0, 0);
				if (fired != null)
					fired.ChangeTid(Hellportal_PortalManager.ACTORTAG_PORTAL_USER);
			}
			CYBR E 2;
			CYBR E 1
			{
				A_FaceTarget();
				if (missleCount++ < maxMissiles)
					return ResolveState("MoreMissile");
				else
					return ResolveState(null);
			}
			Goto See;
	}
}