Class ModuleHitbox : Actor
{
	Default
	{
		Radius 10;
		Height 4;
		+SHOOTABLE;
		+NOGRAVITY;
		+NOCLIP;
		+DONTSPLASH;
		+NOBLOOD;
	}
	override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
	{
		if ( target )
		{
			if ( inflictor ) target.vel += level.Vec3Diff(inflictor.pos,pos).unit()*damage*0.2;
			else if ( source ) target.vel += level.Vec3Diff(source.pos,pos).unit()*damage*0.2;
			target.vel.z = 5;
		}
		if ( !target || (target.target && ((target.target == source) || target.target.isTeammate(source))) ) return 0;
		target.bAMBUSH = true;
		return 0;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos-(0,0,height*0.5),true);
		if ( target.bMISSILE ) return;
		let bi = BlockThingsIterator.Create(self,32);
		while ( bi.Next() )
		{
			if ( !bi.Thing || (bi.Thing != target.target) ) continue;
			if ( (Distance2D(bi.Thing)-bi.Thing.radius <= radius) && ((bi.Thing.pos.z <= pos.z+height) && (bi.Thing.pos.z+bi.Thing.height >= pos.z-height)) )
			{
				A_PlaySound("misc/i_pkup");
				target.Destroy();
				return;
			}
		}
	}
	States
	{
	Spawn:
		TNT1 A 10 A_AlertMonsters(0,AMF_TARGETEMITTER);
		Wait;
	}
}

Class TranslocatorGlowLight : DynamicLight
{
	Default
	{
		DynamicLight.Type "Point";
		Args 255,255,255,40;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
	}
}

Class TranslocatorGlow : Actor
{
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		let l = Spawn("TranslocatorGlowLight",pos);
		l.target = self;
		l.args[0] = fillcolor.r;
		l.args[1] = fillcolor.g;
		l.args[2] = fillcolor.b;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target )
		{
			Destroy();
			return;
		}
		SetOrigin(target.Vec3Offset(0,0,10),true);
	}
	Default
	{
		Radius 0.1;
		Height 0;
		+NOCLIP;
		+NOGRAVITY;
		+DONTSPLASH;
		+FORCEXYBILLBOARD;
		RenderStyle "AddShaded";
		StencilColor "FFFFFF";
		Scale 0.5;
	}
	States
	{
	Spawn:
		TGLO A -1 Bright;
		Stop;
	}
}

Class TranslocatorModule : Actor
{
	Actor b;

	Default
	{
		Radius 2;
		Height 0;
		Speed 25;
		PROJECTILE;
		-NOGRAVITY;
		+USEBOUNCESTATE;
		+SKYEXPLODE;
		+HITTRACER;
		+MOVEWITHSECTOR;
		BounceType "Doom";
		BounceFactor 0.5;
		WallBounceFactor 0.5;
	}

	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		if ( !target )
		{
			Destroy();
			return;
		}
		b = Spawn("ModuleHitbox",pos);
		b.target = self;
		A_PlaySound("transloc/hum",CHAN_VOICE,0.5,true,2.0);
	}

	override bool CanCollideWith( Actor other, bool passive )
	{
		return (other != tracer);
	}

	action void A_LightUp()
	{
		let l = Spawn("TranslocatorGlow",Vec3Offset(0,0,10));
		l.target = self;
		if ( !target || !target.player ) return;
		Color gcol;
		if ( deathmatch && (target.player.GetTeam() < teams.size()) ) gcol = teams[target.player.GetTeam()].mName;
		else gcol = target.player.GetColor();
		// maximize brightness
		if ( (gcol.r+gcol.g+gcol.b) <= 0 ) gcol = "White";
		else
		{
			int maxcomp = max(gcol.r,max(gcol.g,gcol.b));
			int newr = int(gcol.r*(255./maxcomp));
			int newg = int(gcol.g*(255./maxcomp));
			int newb = int(gcol.b*(255./maxcomp));
			gcol = Color(newr,newg,newb);
		}
		l.SetShade(gcol);
	}

	override void Tick()
	{
		Super.Tick();
		if ( bAMBUSH && !Random[Transloc](0,40) )
		{
			A_PlaySound("transloc/spark");
			int numpt = Random[Transloc](20,40);
			for ( int i=0; i<numpt; i++ )
			{
				Vector3 pvel = (FRandom[Transloc](-1,1),FRandom[Transloc](-1,1),FRandom[Transloc](-1,1)).unit()*FRandom[Transloc](0.1,1.2);
				let s = Spawn("UTSmoke",pos);
				s.vel = pvel;
				s.SetShade(Color(1,1,1)*Random[Transloc](128,192));
			}
			numpt = Random[Transloc](4,12);
			for ( int i=0; i<numpt; i++ )
			{
				Vector3 pvel = (FRandom[Transloc](-1,1),FRandom[Transloc](-1,1),FRandom[Transloc](-1,1)).unit()*FRandom[Transloc](2,8);
				let s = Spawn("UTSpark",pos);
				s.vel = pvel;
			}
		}
	}

	States
	{
	Spawn:
		TMOD A 1;
		Wait;
	Bounce:
		TMOD A 0
		{
			A_SetPitch(0);
			A_PlaySound("transloc/bounce");
		}
		Goto Spawn;
	Death:
		TMOD A 0
		{
			A_SetPitch(0);
			if ( tracer )
			{
				SetOrigin(tracer.Vec2OffsetZ(0,0,pos.z),false);
				vel.xy *= 0;
			}
		}
		TMOD A 12;
		TMOD B 8;
		TMOD C -1 A_LightUp();
		Stop;
	}
}

Class TranslocatorAfterimage : Actor
{
	Vector3 spreaddir;
	Default
	{
		RenderStyle "AddStencil";
		StencilColor "FF0000";
		+NOCLIP;
		+NOGRAVITY;
		+DONTSPLASH;
		+NOTELEPORT;
		Radius 0.1;
		Height 0;
		Speed 1;
		Alpha 0.1;
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		if ( !target )
		{
			Destroy();
			return;
		}
		scale = target.scale;
		angle = target.angle;
		pitch = target.pitch;
		roll = target.roll;
		sprite = target.sprite;
		frame = target.frame;
		vel = (FRandom[Transloc](-.5,.5),FRandom[Transloc](-.5,.5),FRandom[Transloc](-.5,.5));
	}
	action void A_Spread()
	{
		vel += invoker.spreaddir*speed;
		A_FadeOut(0.003);
	}
	States
	{
	Spawn:
		#### # 10 A_SetTics(Random[Transloc](20,50));
		#### # 1 A_Spread();
		Wait;
	}
}

Class Translocator : UTWeapon
{
	Actor module;

	action void A_ThrowModule()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		A_PlaySound("transloc/throw",CHAN_WEAPON);
		invoker.FireEffect();
		A_AlertMonsters();
		Vector3 x, y, z;
		[x, y, z] = Matrix4.GetAxes(pitch,angle,roll);
		Vector3 origin = (pos.x,pos.y,player.viewz)+10.0*x-8.0*y-12.0*z;
		let p = Spawn("TranslocatorModule",origin);
		p.target = self;
		p.angle = angle;
		p.pitch = BulletSlope();
		Vector3 dir = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch));
		dir.z += 0.35*(1-abs(dir.z));
		p.vel = dir*p.speed;
		invoker.module = p;
	}

	action void A_ReturnModule()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		A_PlaySound("transloc/return",CHAN_WEAPON);
		invoker.FireEffect();
		A_AlertMonsters();
		if ( invoker.module && invoker.module.bAMBUSH )
		{
			UTMainHandler.DoFlash(self,Color(255,255,255,255),50);
			A_PlaySound("transloc/spark",CHAN_WEAPON);
			DamageMobj(invoker,self,int.max,'Telefrag',DMG_THRUSTLESS);
		}
		if ( invoker.module ) invoker.module.Destroy();
	}

	action void A_Translocate()
	{
		Weapon weap = Weapon(invoker);
		if ( !weap ) return;
		if ( !invoker.module )
		{
			invoker.FireEffect();
			A_AlertMonsters();
			A_PlaySound("transloc/return",CHAN_WEAPON);
			return;
		}
		// check if there's enough space
		Vector3 oldpos = pos, newpos = invoker.module.pos;
		bool bBroken = invoker.module.bAMBUSH;
		invoker.module.Destroy();
		invoker.FireEffect();
		A_AlertMonsters();
		if ( Warp(self,newpos.x,newpos.y,newpos.z,flags:WARPF_ABSOLUTEPOSITION|WARPF_TESTONLY) && TeleportMove(newpos,true) )
		{
			SpawnTeleportFog(oldpos,true,false);
			Vector3 diff = level.Vec3Diff(oldpos,newpos);
			for ( int i=0; i<40; i++ )
			{
				let a = Spawn("TranslocatorAfterimage",oldpos);
				a.target = self;
				TranslocatorAfterimage(a).spreaddir = diff.unit();
				a.speed = (diff.length()/400)**0.5;
			}
			SpawnTeleportFog(newpos,false,false);
			player.fov = min(175,player.desiredfov+60);
		}
		else A_PlaySound("transloc/return",CHAN_WEAPON);
		if ( bBroken )
		{
			UTMainHandler.DoFlash(self,Color(255,255,255,255),50);
			A_PlaySound("transloc/spark",CHAN_WEAPON);
			DamageMobj(invoker,self,int.max,'Telefrag',DMG_THRUSTLESS);
		}
	}

	Default
	{
		Tag "Translocator";
		Inventory.PickupMessage "You got the Translocator Source Module.";
		Weapon.SlotNumber 1;
		Weapon.SelectionOrder 10;
		+WEAPON.NO_AUTO_SWITCH;
	}
	States
	{
	Spawn:
		TLCP A -1;
		Stop;
		TLCP B -1;
		Stop;
	Select:
		TLCS A 1 A_Raise(int.max);
		Wait;
	Ready:
		TLCS A 0 A_JumpIf(invoker.module,"Ready2");
		TLCS ABCDEFGHIJKL 1;
		TLCS L 0 A_JumpIf(invoker.module,"Idle");
		Goto Idle2;
	Ready2:
		TLD2 GFEDCBA 2;
	Idle:
		TLCI A 0 A_Overlay(-9999,"Dummy");	// little hackeroo to make this more responsive
		TLCI AB 25 A_JumpIf(!invoker.module,"PickedUp");
		Goto Idle+1;
	Dummy:
		TNT1 A 2
		{
			A_WeaponReady();
			return A_JumpIf(!invoker.module,"Null");
		}
		Wait;
	Idle2:
		TLI2 ABCDEFGHIJKLMNOPQRS 2 A_WeaponReady(WRF_NOSECONDARY);
		Loop;
	PickedUp:
		TLCI A 5;
		TLI2 A 0;
		Goto Idle2;
	Fire:
		TLCF A 0 A_JumpIf(invoker.module,"Return");
		TLCF A 0 A_ThrowModule();
		TLCF ABCDEFGH 1;
		TLCF IJKLMNOPQRS 1 A_WeaponReady();
		Goto Idle;
	Return:
		TLCF A 0 A_ReturnModule();
		TLCF ABCDEFGH 1;
		Goto Idle2;
	AltFire:
		TLCT A 0 A_Translocate();
		TLCT ABCDEFGHIJKLM 1;
		Goto Idle2;
	Deselect:
		TLCD A 0 A_JumpIf(invoker.module,"Deselect2");
		TLCD ABCDEFG 2;
		TLCD G 1 A_Lower(int.max);
		Wait;
	Deselect2:
		TLD2 ABCDEFG 2;
		TLD2 G 1 A_Lower(int.max);
		Wait;
	}
}
