Class UInvisibility : UnrealInventory
{
	Default
	{
		Tag "$T_INVISIBILITY";
		+COUNTITEM;
		+INVENTORY.BIGPOWERUP;
		+INVENTORY.ALWAYSPICKUP;
		Inventory.Icon "I_Invis";
		Inventory.PickupMessage "$I_INVISIBILITY";
		Inventory.RespawnTics 3500;
		Inventory.MaxAmount 1;
		UnrealInventory.Charge 100;
	}
	override bool Use( bool pickup )
	{
		if ( pickup ) return false;
		bActive = !bActive;
		if ( bActive )
		{
			Owner.A_StartSound("uinvis/toggle",CHAN_ITEM);
			Owner.GiveInventory("PowerUInvisibility",1);
		}
		else Owner.TakeInventory("PowerUInvisibility",1);
		return false;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( !bActive ) return;
		if ( special1 == -1 )
		{
			Owner.TakeInventory("PowerUInvisibility",1);
			special1 = 3;
		}
		else if ( special1 > 1 ) special1--;
		else if ( special1 == 1 )
		{
			Owner.GiveInventory("PowerUInvisibility",1);
			special1 = 0;
		}
		if ( !(level.maptime%35) && DrainCharge(1) )
		{
			if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_INVISIBILITY"));
			Owner.TakeInventory("PowerUInvisibility",1);
			DepleteOrDestroy();
		}
	}
	override void DetachFromOwner()
	{
		Owner.TakeInventory("PowerUInvisibility",1);
		Super.DetachFromOwner();
	}
	override void OwnerDied()
	{
		Owner.TakeInventory("PowerUInvisibility",1);
		Super.OwnerDied();
	}
	override void Travelled()
	{
		if ( bActive ) Owner.TakeInventory("PowerUInvisibility",1);
		Super.Travelled();
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		tracer = Spawn("UInvisibilityX",pos);
		tracer.angle = angle;
		tracer.target = self;
	}
	States
	{
	Spawn:
		INVS A -1;
		Stop;
	}
}

Class UInvisibilityX : AsmdAmmoX
{
	States
	{
	Spawn:
		INVS A -1 Bright;
		Stop;
	}
}

Class PowerUInvisibility : PowerInvisibility
{
	Default
	{
		Powerup.Duration 0x7FFFFFFD;
		Powerup.Strength 90;
		Powerup.Mode "Additive";
	}
}

Class Amplifier : UnrealInventory
{
	Default
	{
		Tag "$T_AMPLIFIER";
		+COUNTITEM;
		+INVENTORY.BIGPOWERUP;
		+INVENTORY.ALWAYSPICKUP;
		Inventory.Icon "I_Amp";
		Inventory.PickupMessage "$I_AMPLIFIER";
		Inventory.RespawnTics 3150;
		Inventory.MaxAmount 1;
		UnrealInventory.Charge 1000;
	}
	static double GetMult( Actor Owner, int val )
	{
		if ( !Owner ) return 1.;
		let d = Amplifier(Owner.FindInventory("Amplifier"));
		if ( !d || !d.bActive ) return 1.;
		double Multiplier = max(1,4*(double(d.Charge-val)/d.DefaultCharge));
		if ( d.DrainCharge(val) )
		{
			d.Owner.A_StartSound("amplifier/unset",CHAN_ITEM);
			if ( d.Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_AMPLIFIER"));
			if ( d.tracer ) d.tracer.Destroy();
			if ( d.Amount <= 0 ) d.DepleteOrDestroy();
		}
		return Multiplier;
	}
	override bool Use( bool pickup )
	{
		if ( pickup ) return false;
		bActive = !bActive;
		Owner.A_StartSound(bActive?"amplifier/set":"amplifier/unset",CHAN_ITEM);
		return false;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( bActive && !tracer )
		{
			tracer = Spawn("AmpSound",Owner.pos);
			tracer.target = Owner;
			tracer.master = self;
		}
		else if ( !bActive && tracer ) tracer.Destroy();
		if ( !bActive ) return;
		if ( !(level.maptime%35) && DrainCharge(2) )
		{
			Owner.A_StartSound("amplifier/unset",CHAN_ITEM);
			if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_AMPLIFIER"));
			if ( tracer ) tracer.Destroy();
			if ( Amount <= 0 ) DepleteOrDestroy();
		}
	}
	States
	{
	Spawn:
		AMPP A -1;
		Stop;
	}
}

Class AmpSound : Actor
{
	Default
	{
		+NOBLOCKMAP;
		+NOGRAVITY;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target || !master )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
		if ( target.CheckLocalView() )
		{
			A_SoundVolume(CHAN_VOICE,0.0);
			A_SoundVolume(CHAN_7,1.0);
		}
		else
		{
			A_SoundVolume(CHAN_VOICE,0.25);
			A_SoundVolume(CHAN_7,0.0);
		}
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		A_StartSound("amplifier/act",CHAN_VOICE,CHANF_LOOPING,.25,1.5);
		A_StartSound("amplifier/act",CHAN_7,CHANF_LOOPING,1.,ATTN_NONE);
	}
	override void OnDestroy()
	{
		Super.OnDestroy();
		A_StopSound(CHAN_VOICE);
		A_StopSound(CHAN_7);
	}
}

Class UJumpBoots : UnrealInventory
{
	int draincnt;

	Default
	{
		Tag "$T_LBOOTS";
		+COUNTITEM;
		+INVENTORY.BIGPOWERUP;
		+INVENTORY.ALWAYSPICKUP;
		Inventory.Icon "I_Boots";
		Inventory.PickupMessage "$I_LBOOTS";
		Inventory.RespawnTics 1050;
		Inventory.MaxAmount 1;
		UnrealInventory.Charge 3;
	}
	override bool Use( bool pickup )
	{
		if ( pickup ) return false;
		bActive = !bActive;
		Owner.A_StartSound("boot/pickup",CHAN_ITEM);
		if ( bActive ) Owner.GiveInventory("PowerJumpBoots_HighJump",1);
		else Owner.TakeInventory("PowerJumpBoots_HighJump",1);
		return false;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( !Owner || !Owner.player ) return;
		draincnt++;
		if ( (draincnt >= 700) || (bActive && (owner.player.jumptics == -1)) )
		{
			draincnt = 0;
			charge--;
			Owner.A_StartSound("boot/jump",CHAN_POWERUP3,CHANF_OVERLAP);
		}
		else if ( (charge <= 0) && owner.player.onground )
		{
			if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_LBOOTS"));
			Amount--;
			Owner.TakeInventory("PowerJumpBoots_HighJump",1);
			charge = defaultcharge;
			bActive = false;
			if ( Amount <= 0 ) DepleteOrDestroy();
		}
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		if ( (level.maptime > 0) || !InStateSequence(CurState,FindState("Spawn")) || (gameinfo.gametype&GAME_RAVEN) ) return;
		// detect hurtfloors
		// can't detect terraindef-based damage
		// this is currently an engine limitation
		bool foundslime = false;
		bool foundlava = false;
		bool foundswim = false;
		for ( int i=0; i<level.Sectors.Size(); i++ )
		{
			Sector s = level.Sectors[i];
			if ( s.MoreFlags&(Sector.SECMF_UNDERWATER|Sector.SECMF_FORCEDUNDERWATER) ) foundswim = true;
			for ( int i=0; i<s.Get3DFloorCount(); i++ )
				if ( s.Get3DFloor(i).flags&F3DFloor.FF_SWIMMABLE ) foundswim = true;
			if ( !s.DamageInterval || !s.DamageAmount ) continue;
			if ( s.DamageType == 'Slime' ) foundslime = true;
			else if ( s.DamageType == 'Fire' ) foundlava = true;
		}
		// random chance to ignore if a replacement already was made
		let ti = ThinkerIterator.Create("Inventory");
		Inventory a;
		while ( a = Inventory(ti.Next()) )
		{
			// exclude owned items
			if ( a.Owner ) continue;
			if ( (a is "ToxinSuit") && Random[LBootRep](0,2) )
				foundslime = false;
			if ( (a is "AsbestosSuit") && Random[LBootRep](0,2) )
				foundlava = false;
			if ( (a is "SCUBAGear") && Random[LBootRep](0,2) )
				foundswim = false;
		}
		// replace self with asbestos/toxin suits or scuba
		// this is extremely naive and doesn't check proximity to the
		// hurtfloor/swimmable, but it's better than nothing
		Array<String> replacements;
		replacements.Clear();
		if ( foundslime ) replacements.Push("ToxinSuit");
		if ( foundlava ) replacements.Push("AsbestosSuit");
		if ( foundswim ) replacements.Push("SCUBAGear");
		if ( replacements.Size() > 0 )
		{
			int rslot = Random[LBootRep](0,replacements.Size()-1);
			let r = Spawn(replacements[rslot],pos,ALLOW_REPLACE);
			r.spawnangle = spawnangle;
			r.spawnpoint = spawnpoint;
			r.angle = angle;
			r.pitch = pitch;
			r.roll = roll;
			r.special = special;
			r.args[0] = args[0];
			r.args[1] = args[1];
			r.args[2] = args[2];
			r.args[3] = args[3];
			r.args[4] = args[4];
			r.ChangeTid(tid);
			r.SpawnFlags = SpawnFlags&~MTF_SECRET;
			r.HandleSpawnFlags();
			r.SpawnFlags = SpawnFlags;
			r.bCountSecret = SpawnFlags&MTF_SECRET;
			r.vel = vel;
			r.master = master;
			r.target = target;
			r.tracer = tracer;
			r.bDropped = bDropped;
			ClearCounters();	// DO NOT FORGET THIS
			Destroy();
		}
	}
	override void AttachToOwner( Actor Other )
	{
		Super.AttachToOwner(Other);
		Other.GiveInventory("PowerJumpBoots_IronFeet",1);
	}
	override void DetachFromOwner()
	{
		Super.DetachFromOwner();
		Owner.TakeInventory("PowerJumpBoots_HighJump",1);
		Owner.TakeInventory("PowerJumpBoots_IronFeet",1);
	}
	States
	{
	Spawn:
		JBUT A -1;
		Stop;
	}
}

// cheap replacement when proto content disabled
Class UAllMap : MapRevealer
{
	Default
	{
		Tag "$T_ALLMAP";
		+COUNTITEM;
		+INVENTORY.ALWAYSPICKUP;
		Inventory.MaxAmount 0;
		Inventory.Icon "I_Detect";
		Inventory.PickupSound "misc/p_pkup";
		Inventory.PickupMessage "$I_ALLMAP";
		Inventory.RespawnTics 1050;
	}
	States
	{
	Spawn:
		TRNS ABCDCB 6;
		Loop;
	}
}

Class MotionDetector : UnrealInventory
{
	ui TextureID DetHud, DetSpot[2];
	Array<Actor> nearscan;

	Default
	{
		Tag "$T_DETECTOR";
		+COUNTITEM;
		+INVENTORY.ALWAYSPICKUP;
		Inventory.MaxAmount 1;
		Inventory.Icon "I_Detect";
		Inventory.PickupMessage "$I_DETECTOR";
		Inventory.RespawnTics 1050;
		UnrealInventory.Charge 7000;
	}
	override bool TryPickup( in out Actor toucher )
	{
		if ( !sting_proto ) return false;	// not allowed
		bool valid = Super.TryPickup(toucher);
		if ( valid ) level.allmap = true;
		return valid;
	}
	override void Tick()
	{
		Super.Tick();
		if ( sting_proto ) return;
		if ( !Owner )
		{
			let r = Spawn("AllMap",pos,ALLOW_REPLACE);
			r.spawnangle = spawnangle;
			r.spawnpoint = spawnpoint;
			r.angle = angle;
			r.pitch = pitch;
			r.roll = roll;
			r.special = special;
			r.args[0] = args[0];
			r.args[1] = args[1];
			r.args[2] = args[2];
			r.args[3] = args[3];
			r.args[4] = args[4];
			r.ChangeTid(tid);
			r.SpawnFlags = SpawnFlags&~MTF_SECRET;
			r.HandleSpawnFlags();
			r.SpawnFlags = SpawnFlags;
			r.bCountSecret = SpawnFlags&MTF_SECRET;
			r.vel = vel;
			r.master = master;
			r.target = target;
			r.tracer = tracer;
			r.bDropped = bDropped;
			r.bNeverRespawn = bNeverRespawn;
		}
		else Owner.RemoveInventory(self);
		Destroy();
	}
	override void PostRender( double lbottom )
	{
		if ( !bActive ) return;
		double xpos = UnrealHUD(StatusBar).ClipX-96;
		double ypos = 0;
		// offset if inventory display present on top right
		if ( UnrealHUD(StatusBar).HudMode < 2 ) ypos += 32;
		if ( DetHud.IsNull() ) DetHud = TexMan.CheckForTexture("DetHud",TexMan.Type_Any);
		if ( DetSpot[0].IsNull() ) DetSpot[0] = TexMan.CheckForTexture("DetSpot0",TexMan.Type_Any);
		if ( DetSpot[1].IsNull() ) DetSpot[1] = TexMan.CheckForTexture("DetSpot1",TexMan.Type_Any);
		Screen.DrawTexture(DetHud,false,xpos,ypos,DTA_VirtualWidthF,UnrealHUD(StatusBar).ClipX,DTA_VirtualHeightF,UnrealHUD(StatusBar).ClipY,DTA_KeepRatio,true);
		double alph = max(0.,0.25+0.75*sin((gametic+UnrealHUD(StatusBar).fractic)*6.));
		Screen.Dim(Color("FF 40 00"),alph*.25,int((xpos+3)*UnrealHUD(StatusBar).scalev.x),int((ypos+3)*UnrealHUD(StatusBar).scalev.y),int(90*UnrealHUD(StatusBar).scalev.x),int(90*UnrealHUD(StatusBar).scalev.y));
		Screen.DrawTexture(DetSpot[0],false,xpos+48,ypos+48,DTA_VirtualWidthF,UnrealHUD(StatusBar).ClipX,DTA_VirtualHeightF,UnrealHUD(StatusBar).ClipY,DTA_KeepRatio,true);
		alph = max(0.,0.5+0.5*sin((gametic+UnrealHUD(StatusBar).fractic-10)*6.));
		for ( int i=0; i<nearscan.Size(); i++ )
		{
			Vector2 absofs = level.Vec2Diff(Owner.pos.xy,nearscan[i].pos.xy);
			absofs *= (96./1024.);
			absofs.y *= -1;
			double ang = Owner.angle-90;
			Vector2 relofs = RotateVector(absofs,ang);
			if ( max(abs(relofs.x),abs(relofs.y)) > 48. ) continue;
			// this is a long line, but it's not the longest I've ever seen
			// oh I have seen things... php code that would make you want to stab your eyes with forks...
			Screen.DrawTexture(DetSpot[1],false,xpos+48+relofs.x,ypos+48+relofs.y,DTA_VirtualWidthF,UnrealHUD(StatusBar).ClipX,DTA_VirtualHeightF,UnrealHUD(StatusBar).ClipY,DTA_KeepRatio,true,DTA_Alpha,alph,DTA_ClipLeft,int((xpos+3)*UnrealHUD(StatusBar).scalev.x),DTA_ClipTop,int((ypos+3)*UnrealHUD(StatusBar).scalev.y),DTA_ClipRight,int((xpos+93)*UnrealHUD(StatusBar).scalev.x),DTA_ClipBottom,int((ypos+93)*UnrealHUD(StatusBar).scalev.y));
		}
	}
	override bool Use( bool pickup )
	{
		if ( pickup ) return false;
		bActive = !bActive;
		if ( bActive ) Owner.A_StartSound("detector/start",CHAN_ITEM);
		else Owner.A_StartSound("detector/stop",CHAN_ITEM,volume:.1,attenuation:3.);
		return false;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( bActive && !tracer )
		{
			tracer = Spawn("DetectorSound",Owner.pos);
			tracer.target = Owner;
			tracer.master = self;
		}
		else if ( !bActive && tracer ) tracer.Destroy();
		if ( !bActive ) return;
		nearscan.Clear();
		let bi = BlockThingsIterator.Create(Owner,1024);
		while ( bi.Next() )
		{
			if ( !bi.Thing || (bi.Thing == Owner) || !bi.Thing.bISMONSTER || bi.Thing.Health <= 0 ) continue;
			nearscan.Push(bi.Thing);
		}
		if ( (Charge <= 0) || DrainCharge(1) )
		{
			Owner.A_StartSound("detector/stop",CHAN_ITEM,volume:.1,attenuation:3.);
			if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DETECTOR"));
			if ( tracer ) tracer.Destroy();
			DepleteOrDestroy();
		}
	}

	States
	{
	Spawn:
		MDET A -1;
		Stop;
	}
}

Class DetectorSound : Actor
{
	Default
	{
		+NOBLOCKMAP;
		+NOGRAVITY;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target || !master )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
		A_SoundVolume(CHAN_VOICE,target.CheckLocalView()?0.1:0.0);
		A_SoundVolume(CHAN_7,!target.CheckLocalView()?0.1:0.0);
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		A_StartSound("detector/active",CHAN_VOICE,CHANF_LOOPING,.1,ATTN_NONE);
		A_StartSound("detector/active",CHAN_7,CHANF_LOOPING,.1,3.);
	}
	override void OnDestroy()
	{
		Super.OnDestroy();
		A_StopSound(CHAN_VOICE);
		A_StopSound(CHAN_7);
	}
}

Class SCUBAGear : UnrealInventory
{
	int oldwaterlevel;
	Default
	{
		Tag "$T_SCUBA";
		Inventory.MaxAmount 1;
		Inventory.Icon "I_Scuba";
		Inventory.PickupMessage "$I_SCUBA";
		Inventory.RespawnTics 700;
		UnrealInventory.Charge 4200;
	}
	override bool Use( bool pickup )
	{
		if ( pickup ) return false;
		bActive = !bActive;
		if ( !bActive ) Owner.A_StartSound("scuba/stop",CHAN_ITEM);
		return false;
	}
	override void AttachToOwner( Actor other )
	{
		Super.AttachToOwner(other);
		oldwaterlevel = other.waterlevel;
	}
	override void AbsorbDamage( int damage, Name damageType, out int newdamage )
	{
		if ( bActive && (damageType == 'Drowning') )
			newdamage = 0;
	}
	override void DoEffect()
	{
		Super.DoEffect();
		if ( (oldwaterlevel < 2) && (owner.waterlevel > 2) )
		{
			if ( sting_autoscuba && !bActive ) Use(false);
			oldwaterlevel = owner.waterlevel;
		}
		else if ( (oldwaterlevel > 2) && (owner.waterlevel < 2) )
		{
			if ( sting_autoscuba && bActive ) Use(false);
			oldwaterlevel = owner.waterlevel;
		}
		if ( bActive && !tracer )
		{
			tracer = Spawn("SCUBASound",Owner.pos);
			tracer.target = Owner;
			tracer.master = self;
		}
		else if ( !bActive && tracer ) tracer.Destroy();
		if ( !bActive ) return;
		if ( PlayerPawn(Owner) ) PlayerPawn(Owner).ResetAirSupply();
		if ( (Charge <= 0) || DrainCharge(1) )
		{
			Owner.A_StartSound("scuba/stop",CHAN_ITEM);
			if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_SCUBA"));
			if ( tracer ) tracer.Destroy();
			DepleteOrDestroy();
		}
	}
	States
	{
	Spawn:
		SCUB A -1;
		Stop;
	}
}

Class SCUBASound : Actor
{
	Default
	{
		+NOBLOCKMAP;
		+NOGRAVITY;
	}
	override void Tick()
	{
		Super.Tick();
		if ( !target || !master )
		{
			Destroy();
			return;
		}
		SetOrigin(target.pos,true);
		if ( target.CheckLocalView() )
		{
			A_SoundVolume(CHAN_VOICE,0.0);
			A_SoundVolume(CHAN_5,0.0);
			A_SoundVolume(CHAN_6,(target.waterlevel<3)?1.0:0.0);
			A_SoundVolume(CHAN_7,(target.waterlevel>=3)?1.0:0.0);
		}
		else
		{
			A_SoundVolume(CHAN_VOICE,(target.waterlevel<3)?0.25:0.0);
			A_SoundVolume(CHAN_5,(target.waterlevel>=3)?0.25:0.0);
			A_SoundVolume(CHAN_6,0.0);
			A_SoundVolume(CHAN_7,0.0);
		}
	}
	override void PostBeginPlay()
	{
		Super.PostBeginPlay();
		A_StartSound("scuba/air",CHAN_VOICE,CHANF_LOOPING,.25,1.5);
		A_StartSound("scuba/water",CHAN_5,CHANF_LOOPING,.25,1.5);
		A_StartSound("scuba/air",CHAN_6,CHANF_LOOPING,1.,ATTN_NONE);
		A_StartSound("scuba/water",CHAN_7,CHANF_LOOPING,1.,ATTN_NONE);
	}
	override void OnDestroy()
	{
		Super.OnDestroy();
		A_StopSound(CHAN_VOICE);
		A_StopSound(CHAN_5);
		A_StopSound(CHAN_6);
		A_StopSound(CHAN_7);
	}
}
