Class UTPlayer : DoomPlayer
{
	Default
	{
		Player.StartItem "MiniAmmo", 30;
		Player.StartItem "Enforcer";
		Player.StartItem "Translocator";
		Player.StartItem "ImpactHammer";
	}

	// Have to modify the give cheat to handle UT armor
	override void CheatGive( String name, int amount )
	{
		if ( PlayerNumber() != consoleplayer )
			A_Log(String.Format("%s is a cheater: give %s\n",player.GetUserName(),name));
		if ( !player.mo || (player.health <= 0) ) return;
		int giveall = ALL_NO;
		if ( name ~== "all" ) giveall = ALL_YES;
		else if (name ~== "everything") giveall = ALL_YESYES;
		if ( name ~== "health" )
		{
			if ( amount > 0 )
			{
				health += amount;
				player.health = health;
			}
			else player.health = health = GetMaxHealth(true);
		}
		if ( giveall || (name ~== "backpack") )
		{
			// Select the correct type of backpack based on the game
			let type = (class<Inventory>)(gameinfo.backpacktype);
			if ( type ) GiveInventory(type,1,true);
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "ammo") )
		{
			// Find every unique type of ammo. Give it to the player if
			// he doesn't have it already, and set each to its maximum.
			for ( int i=0; i<AllActorClasses.Size(); i++ )
			{
				let type = (class<Ammo>)(AllActorClasses[i]);
				if ( !type || (type.GetParentClass() != "Ammo") )
					continue;
				// Only give if it's for a valid weapon, unless using "give everything"
				bool isvalid = false;
				for ( int j=0; j<AllActorClasses.Size(); j++ )
				{
					let type2 = (class<Weapon>)(AllActorClasses[j]);
					if ( !type2 ) continue;
					let rep = GetReplacement(type2);
					if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
					readonly<Weapon> weap = GetDefaultByType(type2);
					if ( !player.weapons.LocateWeapon(type2) || (weap.bCheatNotWeapon && (giveall != ALL_YESYES)) ) continue;
					if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) )
					{
						isvalid = true;
						break;
					}
				}
				if ( !isvalid ) continue;
				let ammoitem = FindInventory(type);
				if ( !ammoitem )
				{
					ammoitem = Inventory(Spawn(type));
					ammoitem.AttachToOwner(self);
					ammoitem.Amount = ammoitem.MaxAmount;
				}
				else if ( ammoitem.Amount < ammoitem.MaxAmount )
					ammoitem.Amount = ammoitem.MaxAmount;
			}
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "armor") )
		{
			// Doom Tournament just gives the player a shield belt and maximum bonuses
			let belt = Inventory(Spawn("UTShieldBelt"));
			if ( !belt.CallTryPickup(self) ) belt.Destroy();
			let bonus = Inventory(Spawn("UTArmorBonus"));
			bonus.Amount = bonus.MaxAmount;
			if ( !bonus.CallTryPickup(self) ) bonus.Destroy();
			level.total_items -= 2; // spawning them in raises item count
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "keys") )
		{
			for ( int i=0; i<AllActorClasses.Size(); i++ )
			{
				if ( !(AllActorClasses[i] is "Key") ) continue;
				let keyitem = GetDefaultByType(AllActorClasses[i]);
				if ( keyitem.special1 )
				{
					let item = Inventory(Spawn(AllActorClasses[i]));
					if ( !item.CallTryPickup(self) ) item.Destroy();
				}
			}
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "weapons") )
		{
			let savedpending = player.PendingWeapon;
			for ( int i=0; i<AllActorClasses.Size(); i++ )
			{
				let type = (class<Weapon>)(AllActorClasses[i]);
				if ( !type || (type == "Weapon") ) continue;
				// Don't give replaced weapons unless the replacement was done by Dehacked.
				let rep = GetReplacement(type);
				if ( (rep == type) || (rep is "DehackedPickup") )
				{
					// Give the weapon only if it is set in a weapon slot.
					if ( !player.weapons.LocateWeapon(type) ) continue;
					readonly<Weapon> def = GetDefaultByType(type);
					if ( (giveall == ALL_YESYES) || !def.bCheatNotWeapon )
						GiveInventory(type,1,true);
				}
			}
			player.PendingWeapon = savedpending;
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "artifacts") )
		{
			for ( int i=0; i<AllActorClasses.Size(); i++ )
			{
				let type = (class<Inventory>)(AllActorClasses[i]);
				if ( !type ) continue;
				let def = GetDefaultByType (type);
				if ( def.Icon.isValid() && (def.MaxAmount > 1) &&
					!(type is "PuzzleItem") && !(type is "Powerup") && !(type is "Ammo") &&	!(type is "Armor"))
				{
					// Do not give replaced items unless using "give everything"
					if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
						GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
				}
			}
			if ( !giveall ) return;
		}
		if ( giveall || (name ~== "puzzlepieces") )
		{
			for ( int i=0; i<AllActorClasses.Size(); i++ )
			{
				let type = (class<PuzzleItem>)(AllActorClasses[i]);
				if ( !type ) continue;
				let def = GetDefaultByType(type);
				if ( !def.Icon.isValid() ) continue;
				// Do not give replaced items unless using "give everything"
				if ( (giveall == ALL_YESYES) || (GetReplacement(type) == type) )
					GiveInventory(type,(amount<=0)?def.MaxAmount:amount,true);
			}
			if ( !giveall ) return;
		}
		if ( giveall ) return;
		let type = name;
		if ( !type )
		{
			if ( PlayerNumber() == consoleplayer )
				A_Log(String.Format("Unknown item \"%s\"\n",name));
		}
		else GiveInventory(type,amount,true);
	}
}

// Random Spawner that passes through dropped status to items
Class RandomSpawner2 : RandomSpawner
{
	override void PostSpawn( Actor spawned )
	{
		if ( !bDROPPED ) return;
		if ( spawned is 'Inventory' ) Inventory(spawned).bTOSSED = bDROPPED;
		if ( spawned is 'UTWeapon' )
		{
			spawned.SetState(spawned.ResolveState("Spawn")+1);
			Inventory(spawned).bALWAYSPICKUP = true;
		}
	}
}

Class UTWeapon : Weapon
{
	// Drawstuffs under HUD
	virtual ui void PreRender() {}
	// Drawstuffs over HUD
	virtual ui void PostRender() {}

	override Inventory CreateTossable( int amt )
	{
		if ( Ammo1 && (Ammo1.Amount <= 0) ) return null;
		Inventory d = Super.CreateTossable(amt);
		if ( d && (d.GetClass() == GetClass()) )
		{
			d.SetState(d.ResolveState("Spawn")+1);
			d.bALWAYSPICKUP = true;
		}
		return d;
	}

	override bool SpecialDropAction( Actor dropper )
	{
		SetState(ResolveState("Spawn")+1);
		bALWAYSPICKUP = true;
		return false;
	}

	override void Tick()
	{
		Super.Tick();
		if ( !Owner || !Owner.player || (Owner.player.ReadyWeapon != self) ) return;
		Owner.player.WeaponState |= WF_WEAPONBOBBING; // UT weapons always bob
	}

	void FireEffect()
	{
		let amp = DamageAmplifier(Owner.FindInventory("DamageAmplifier",true));
		if ( amp ) amp.FireEffect();
	}

	Default
	{
		Weapon.BobStyle "Smooth";
		Weapon.BobSpeed 1.5;
		Weapon.BobRangeX 0.2;
		Weapon.BobRangeY 0.4;
		+WEAPON.NOALERT;
	}
}

Class UTTeleportLight : DynamicLight
{
	Default
	{
		DynamicLight.Type "Point";
		Args 128,160,255,80;
	}
	override void Tick()
	{
		Super.Tick();
		if ( alpha <= 0 )
		{
			Destroy();
			return;
		}
		args[LIGHT_RED] = 128*alpha;
		args[LIGHT_GREEN] = 160*alpha;
		args[LIGHT_BLUE] = 255*alpha;
		args[LIGHT_INTENSITY] = Random[Tele](10,14)*8;
		alpha -= 1./35;
	}
}

Class UTItemLight : DynamicLight
{
	Default
	{
		DynamicLight.Type "Point";
		Args 255,224,160,48;
	}
	override void Tick()
	{
		Super.Tick();
		if ( alpha <= 0 )
		{
			Destroy();
			return;
		}
		args[LIGHT_RED] = 255*alpha;
		args[LIGHT_GREEN] = 224*alpha;
		args[LIGHT_BLUE] = 160*alpha;
		args[LIGHT_INTENSITY] = Random[Tele](6,8)*8;
		alpha -= 3./35;
	}
}

Class UTTeleportFog : Actor replaces TeleportFog
{
	Default
	{
		+NOBLOCKMAP;
		+NOTELEPORT;
		+NOGRAVITY;
		RenderStyle "Add";
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		Spawn("UTTeleportLight",pos+(0,0,16));
		A_PlaySound ("misc/teleport");
	}
	States
	{
	Spawn:
		TELE ABCDEFGHIJKLMNOPQRSTUVWXYZ 1 Bright A_FadeOut(1./35);
		TEL2 ABCDEFGHI 1 Bright A_FadeOut(1./35);
		Stop;
	}
}

Class UTItemFog : Actor replaces ItemFog
{
	Default
	{
		+NOBLOCKMAP;
		+NOTELEPORT;
		+NOGRAVITY;
		RenderStyle "Add";
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		Spawn("UTItemLight",pos+(0,0,16));
	}
	States
	{
	Spawn:
		TNT1 A 1;
		Stop;
	}
}

Class UTSpark : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 2;
		Height 0;
		+NOBLOCKMAP;
		+FORCEXYBILLBOARD;
		+MISSILE;
		+MOVEWITHSECTOR;
		+THRUACTORS;
		+NOTELEPORT;
		BounceType "Doom";
		BounceFactor 0.4;
		Gravity 0.5;
		Scale 0.05;
	}
	override void Tick()
	{
		Super.Tick();
		if ( waterlevel > 0 )
		{
			let b = Spawn("UTBubble",pos);
			b.vel = vel;
			b.scale *= 0.3;
			Destroy();
		}
	}
	States
	{
	Spawn:
		SPRK A 1 Bright A_FadeOut(0.01);
		Wait;
	Death:
		SPRK A 1 Bright A_FadeOut(0.05);
		Wait;
	}
}

Class UTChip : Actor
{
	int deadtimer;
	double rollvel, anglevel, pitchvel;

	Default
	{
		Radius 2;
		Height 0;
		+NOBLOCKMAP;
		+MISSILE;
		+MOVEWITHSECTOR;
		+THRUACTORS;
		+NOTELEPORT;
		BounceType "Doom";
		BounceFactor 0.3;
		Gravity 0.7;
		Scale 0.2;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		deadtimer = 0;
		anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
		pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
		rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
		frame = Random[Junk](0,3);
		scale *= Frandom[Junk](0.8,1.2);
	}
	override void Tick()
	{
		Super.Tick();
		if ( level.frozen || globalfreeze ) return;
		if ( InStateSequence(CurState,ResolveState("Death")) )
		{
			deadtimer++;
			if ( deadtimer > 300 ) A_FadeOut(0.05);
			return;
		}
	}
	States
	{
	Spawn:
		CHIP # 1
		{
			A_SetAngle(angle+anglevel,SPF_INTERPOLATE);
			A_SetPitch(pitch+pitchvel,SPF_INTERPOLATE);
			A_SetRoll(roll+rollvel,SPF_INTERPOLATE);
		}
		Loop;
	Bounce:
		CHIP # 0
		{
			anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
			pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
			rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
		}
		Goto Spawn;
	Death:
		CHIP # -1;
		Stop;
	Dummy:
		CHIP ABCD -1;
		Stop;
	}
}

Class UTBubble : Actor
{
	Default
	{
		RenderStyle "Add";
		Radius 2;
		Height 0;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+NOTELEPORT;
		Scale 0.05;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		double ang, pt;
		scale *= FRandom[Puff](0.5,1.5);
		ang = FRandom[Puff](0,360);
		pt = FRandom[Puff](-90,90);
		vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
		if ( waterlevel <= 0 ) Destroy();
		SetState(ResolveState("Spawn")+Random[Puff](0,2));
	}
	override void Tick()
	{
		Super.Tick();
		if ( level.frozen || globalfreeze ) return;
		vel *= 0.96;
		vel.z += 0.05;
		if ( (waterlevel <= 0) || !Random[Puff](0,100) ) Destroy();
	}
	States
	{
	Spawn:
		BUBL ABC -1;
		Stop;
	}
}

Class UTSmoke : Actor
{
	Default
	{
		RenderStyle "Shaded";
		StencilColor "FFFFFF";
		Radius 2;
		Height 0;
		+NOBLOCKMAP;
		+NOGRAVITY;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		+THRUACTORS;
		+NOTELEPORT;
		BounceType "Hexen";
		BounceFactor 1.0;
		WallBounceFactor 1.0;
		Scale 0.5;
	}

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		double ang, pt;
		scale *= FRandom[Puff](0.5,1.5);
		alpha *= FRandom[Puff](0.5,1.5);
		ang = FRandom[Puff](0,360);
		pt = FRandom[Puff](-90,90);
		vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
	}
	override void Tick()
	{
		Super.Tick();
		if ( level.frozen || globalfreeze ) return;
		vel *= 0.96;
		vel.z += 0.01;
		A_FadeOut(1/32.);
		if ( waterlevel > 0 )
		{
			let b = Spawn("UTBubble",pos);
			b.vel = vel;
			Destroy();
		}
	}

	States
	{
	Spawn:
		TNT1 A 0 NoDelay A_Jump(255,"US1","US2","US3","US4","US5","US6","US7","US8","US9","US10");
		Stop;
	US1:
		US1_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US2:
		US2_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US3:
		US3_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US4:
		US4_ ABCDEFGHIJKLMNO 2;
		Stop;
	US5:
		US5_ ABCDEFGHIJKLMNO 2;
		Stop;
	US6:
		US6_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US7:
		US7_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US8:
		US8_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US9:
		US9_ ABCDEFGHIJKLMNOP 2;
		Stop;
	US10:
		US10 ABCDEFGHIJKLMNOP 2;
		Stop;
	}
}

Class UTRedSkull : RedSkull replaces RedSkull
{
	Default
	{
		Tag "Red Skull";
		Species "RedSkull";
		Inventory.PickupMessage "You got the Red Skull.";
	}
	States
	{
	Spawn:
		USKL A -1;
		Stop;
	}
}

Class UTGoldSkull : YellowSkull replaces YellowSkull
{
	Default
	{
		Tag "Gold Skull";
		Species "YellowSkull";
		Inventory.PickupMessage "You got the Gold Skull.";
	}
	States
	{
	Spawn:
		USKL B -1;
		Stop;
	}
}

Class UTBlueSkull : BlueSkull replaces BlueSkull
{
	Default
	{
		Tag "Blue Skull";
		Species "BlueSkull";
		Inventory.PickupMessage "You got the Blue Skull.";
	}
	States
	{
	Spawn:
		USKL C -1;
		Stop;
	}
}

Class UTRedKey : RedCard replaces RedCard
{
	Default
	{
		Tag "Red Key";
		Species "RedCard";
		Inventory.PickupMessage "You got the Red Key.";
	}
	States
	{
	Spawn:
		UKEY A -1;
		Stop;
	}
}

Class UTGoldKey : YellowCard replaces YellowCard
{
	Default
	{
		Tag "Gold Key";
		Species "YellowCard";
		Inventory.PickupMessage "You got the Gold Key.";
	}
	States
	{
	Spawn:
		UKEY B -1;
		Stop;
	}
}

Class UTBlueKey : BlueCard replaces BlueCard
{
	Default
	{
		Tag "Blue Key";
		Species "BlueCard";
		Inventory.PickupMessage "You got the Blue Key.";
	}
	States
	{
	Spawn:
		UKEY C -1;
		Stop;
	}
}

Class GenericFlash : HUDMessageBase
{
	Color col;
	int duration;
	double alpha;
	Actor cam;
	GenericFlash Setup( Actor camera, Color c, int d )
	{
		alpha = 1.0;
		col = c;
		duration = d;
		cam = camera;
		return self;
	}
	override bool Tick()
	{
		alpha -= 1./duration;
		return (alpha<=0);
	}
	override void Draw( int bottom, int visibility )
	{
		if ( automapactive || (visibility != BaseStatusBar.HUDMSGLayer_UnderHUD) ) return;
		if ( cam && (players[consoleplayer].camera != cam) ) return;
		Screen.Dim(col,(col.a/255.)*alpha,0,0,Screen.GetWidth(),Screen.GetHeight());
	}
}

Class QueuedFlash
{
	Color c;
	int duration;
	int tic;
	Actor cam;
}

Class UTMainHandler : StaticEventHandler
{
	ui TextureID tex;
	Array<QueuedFlash> flashes;

	override void WorldLoaded( WorldEvent e )
	{
		if ( gamestate != GS_LEVEL || e.IsSaveGame ) return;
		if ( level.levelname ~== "Modder Test Map" )
		{
			TexMan.ReplaceTextures("-noflat-","-kinsie-",0);
			TextureID skytx = TexMan.CheckForTexture("BlueSky",TexMan.Type_Any);
			level.ChangeSky(skytx,skytx);
		}
	}

	override void WorldThingSpawned( WorldEvent e )
	{
		if ( e.Thing.bBOSS ) e.Thing.bNOTELEFRAG = true;
	}

	ui void StartMenu()
	{
		if ( gamestate != GS_TITLELEVEL ) return;
		if ( CVar.GetCVar('flak_protomenu',players[consoleplayer]).GetBool() )
		{
			S_ChangeMusic("xyzdMenu");
			tex = TexMan.CheckForTexture("protobg",TexMan.Type_Any);
		}
		else
		{
			S_ChangeMusic("utmenu23");
			tex = TexMan.CheckForTexture("finalbg",TexMan.Type_Any);
		}
	}

	override void ConsoleProcess( ConsoleEvent e )
	{
		if ( e.Name ~== "refreshmenu" ) StartMenu();
	}

	override void WorldTick()
	{
		for ( int i=0; i<flashes.size(); i++ )
		{
			if ( flashes[i].tic >= gametic ) continue;
			flashes.Delete(i);
			i--;
		}
	}

	override void PostUiTick()
	{
		for ( int i=0; i<flashes.size(); i++ )
		{
			if ( flashes[i].tic < gametic ) continue;
			GenericFlash gf = new("GenericFlash").Setup(flashes[i].cam,flashes[i].c,flashes[i].duration);
			StatusBar.AttachMessage(gf,0,BaseStatusBar.HUDMSGLayer_UnderHUD);
		}
		if ( gametic <= 0 ) StartMenu();
	}

	override void RenderOverlay( RenderEvent e )
	{
		if ( gamestate != GS_TITLELEVEL ) return;
		if ( tex.IsNull() || !tex.IsValid() ) return;
		Screen.Dim("Black",1.0,0,0,Screen.GetWidth(),Screen.GetHeight());
		Screen.DrawTexture(tex,true,0,0,DTA_VirtualWidth,1024,DTA_VirtualHeight,768);
	}

	static void DoFlash( Actor camera, Color c, int duration )
	{
		QueuedFlash qf = new("QueuedFlash");
		qf.duration = duration;
		qf.c = c;
		qf.tic = gametic;
		qf.cam = camera;
		let hnd = UTMainHandler(StaticEventHandler.Find("UTMainHandler"));
		hnd.flashes.push(qf);
	}
}
