//-----------------------------------------------------------------------------
//
//  $Logfile:: /game/powerups.cpp                                             $
// $Revision:: 2                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 12/27/00 7:29 PM                                               $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Miscellaneous powerups

#include "powerups.h"
#include "specialfx.h"
#include "player.h"

#define POWERUP_TIME 30

CLASS_DECLARATION( InventoryItem, ScubaGear, "inventory_scubagear" )

ResponseDef ScubaGear::Responses[] =
{
	{ NULL, NULL }
};

ScubaGear::ScubaGear()
{
	setModel( "scubagear.def" );
}

CLASS_DECLARATION( InventoryItem, Adrenaline, "powerup_adrenaline" );

Event EV_Adrenaline_Powerdown( "adrenaline_powerdown" );

ResponseDef Adrenaline::Responses[] =
{
	{ &EV_Adrenaline_Powerdown, ( Response )& Adrenaline::Powerdown },
	{ NULL, NULL }
};

Adrenaline::Adrenaline()
{
	setModel( "adren.def" );
	Set( 1 );
	setRespawnTime( 180 + G_Random( 60 ) );
}

void Adrenaline::Powerdown( Event* ev )
{
	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	owner->flags &= ~FL_ADRENALINE;
	owner->edict->s.renderfx &= ~RF_DLIGHT;
	owner->edict->s.color_r = 0;
	owner->edict->s.color_g = 0;
	owner->edict->s.color_b = 0;
	owner->edict->s.radius  = 0;

	CancelPendingEvents();
	PostEvent( EV_Remove, 0 );
}

void Adrenaline::Use( Event* ev )
{
	str realname;
	Event*       event;

	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	if( owner->PowerupActive() )
	{
		return;
	}

	// Make sure there is one available
	assert( amount );
	amount--;
	if( amount <= 0 )
	{
		owner->RemoveItem( this );
	}

	PostEvent( EV_Adrenaline_Powerdown, POWERUP_TIME );

	owner->flags     |= FL_ADRENALINE;

	event = new Event( "poweruptimer" );
	event->AddInteger( POWERUP_TIME );
	event->AddInteger( P_ADRENALINE );
	owner->edict->s.renderfx |= RF_DLIGHT;
	owner->edict->s.color_r = 1;
	owner->edict->s.color_g = 1;
	owner->edict->s.color_b = 0;
	owner->edict->s.radius  = 120;
	owner->ProcessEvent( event );

	realname = GetRandomAlias( "snd_activate" );

	if( realname.length() )
		owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
}

CLASS_DECLARATION( InventoryItem, Cloak, "powerups_cloak" );

Event EV_Cloak_Powerdown( "Cloak_powerdown" );

ResponseDef Cloak::Responses[] =
{
	{ &EV_Cloak_Powerdown, ( Response )& Cloak::Powerdown },
	{ NULL, NULL }
};


Cloak::Cloak()
{
	setModel( "cloak.def" );
	Set( 1 );
	setRespawnTime( 180 + G_Random( 60 ) );
}

void Cloak::Powerdown( Event* ev )
{
	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	if( owner->flags & FL_CLOAK )
	{
		str realname;

		owner->flags &= ~FL_CLOAK;
		realname = GetRandomAlias( "snd_deactivate" );
		if( realname.length() )
			owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
		owner->edict->s.renderfx &= ~RF_DLIGHT;
		owner->edict->s.color_r = 0;
		owner->edict->s.color_g = 0;
		owner->edict->s.color_b = 0;
		owner->edict->s.radius  = 0;
	}
	CancelPendingEvents();
	PostEvent( EV_Remove, 0 );
}

void Cloak::Use( Event* ev )
{
	str realname;
	Event* event;

	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	if( owner->PowerupActive() )
	{
		return;
	}

	// Make sure there is one available
	assert( amount );
	amount--;
	if( amount <= 0 )
	{
		owner->RemoveItem( this );
	}

	PostEvent( EV_Cloak_Powerdown, POWERUP_TIME );

	owner->flags |= FL_CLOAK;
	owner->edict->s.renderfx |= RF_DLIGHT;
	owner->edict->s.color_r = 1;
	owner->edict->s.color_g = 1;
	owner->edict->s.color_b = 1;
	owner->edict->s.radius  = -120;

	event = new Event( "poweruptimer" );
	event->AddInteger( POWERUP_TIME );
	event->AddInteger( P_CLOAK );
	owner->ProcessEvent( event );

	realname = GetRandomAlias( "snd_activate" );

	if( realname.length() )
		owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
}

CLASS_DECLARATION( InventoryItem, Mutagen, "powerups_mutagen" );

Event EV_Mutagen_Powerdown( "mutagen_powerdown" );

ResponseDef Mutagen::Responses[] =
{
	{ &EV_Mutagen_Powerdown, ( Response )& Mutagen::Powerdown },
	{ NULL, NULL }
};

Mutagen::Mutagen()
{
	//### removed U4 from MFD to prevent major complications
	if( deathmatch->value == DEATHMATCH_MFD )
	{
		PostEvent( EV_Remove, 0 );
		return;
	}
	//###

	modelIndex( "manumit_pl.def" );
	modelIndex( "view_mutanthands.def" );
	setModel( "u4vial.def" );
	Set( 1 );
	setRespawnTime( 180 + G_Random( 60 ) );
}

void Mutagen::Powerdown( Event* ev )
{
	str realname;

	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	owner->flags &= ~FL_MUTANT;
	realname = GetRandomAlias( "snd_deactivate" );

	if( realname.length() )
		owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );

	if( owner->isClient() )
	{
		int playernum = owner->edict - g_edicts - 1;

		owner->setModel( owner->savemodel.c_str() );
		owner->RandomAnimate( "idle", NULL );

		strcpy( owner->client->pers.model, owner->savemodel.c_str() );
		strcpy( owner->client->pers.skin,  owner->saveskin.c_str() );

		// combine name, skin and model into a configstring
		gi.configstring( CS_PLAYERSKINS + playernum, va( "%s\\%s\\%s",
						 owner->client->pers.netname,
						 owner->client->pers.model,
						 owner->client->pers.skin ) );
	}

	owner->takeWeapon( "MutantHands" );
	CancelPendingEvents();
	PostEvent( EV_Remove, 0 );
}

void Mutagen::Use( Event* ev )
{
	str realname;
	Event*       event;
	Weapon*      mutanthands;

	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	if( owner->PowerupActive() )
	{
		return;
	}

	//### can't use U4 on a bike
	if( owner->IsOnBike() )
	{
		return;
	}
	//###

	// Make sure there is one available
	assert( amount );
	amount--;

	if( amount <= 0 )
	{
		owner->RemoveItem( this );
	}

	PostEvent( EV_Mutagen_Powerdown, POWERUP_TIME );
	owner->flags |= FL_MUTANT;

	//Set the timer
	event = new Event( "poweruptimer" );
	event->AddInteger( POWERUP_TIME );
	event->AddInteger( P_MUTAGEN );
	owner->ProcessEvent( event );

	TesselateModel( owner,
					1,
					1000,
					Vector( 0, 0, 1 ),
					50,
					0.1f,
					owner->tess_thickness,
					vec3_origin
				  );

	owner->savemodel = COM_SkipPath( owner->model.c_str() );
	owner->saveskin  = COM_SkipPath( owner->client->pers.skin );

	owner->setModel( "manumit_pl.def" );
	owner->RandomAnimate( "idle", NULL );

	if( owner->isClient() )
	{
		int playernum = owner->edict - g_edicts - 1;

		strcpy( owner->client->pers.model, "manumit_pl.def" );
		strcpy( owner->client->pers.skin,  "manu_base.tga" );
		// combine name, skin and model into a configstring
		gi.configstring( CS_PLAYERSKINS + playernum,
						 va( "%s\\%s\\%s",
							 owner->client->pers.netname,
							 owner->client->pers.model,
							 owner->client->pers.skin )
					   );

		FixDeadBodiesForPlayer( owner->edict );
	}

	// Give the mutanthands weapon. - Force it.
	mutanthands = owner->giveWeapon( "MutantHands" );
	owner->ForceChangeWeapon( mutanthands );

	realname = GetRandomAlias( "snd_activate" );

	if( realname.length() )
		owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
}

//### added to prevent people on hoverbikes from picking up the mutagen
qboolean Mutagen::Pickupable( Entity* other )
{
	if( !other->isSubclassOf( Sentient ) )
	{
		return false;
	}
	else
	{
		Sentient* sent;
		Item* item;

		sent = ( Sentient* )other;

		// check if on a hoverbike
		if( sent->IsOnBike() )
		{
			return false;
		}

		item = sent->FindItem( getClassname() );

		if( item && ( item->Amount() >= item->MaxAmount() ) )
		{
			return false;
		}

		// Mutants can't pick up anything but health
		if( other->flags & ( FL_MUTANT | FL_SP_MUTANT ) )
		{
			return false;
		}

		// If deathmatch and already in a powerup, don't pickup anymore when DF_INSTANT_ITEMS is on
		if( DM_FLAG( DF_INSTANT_ITEMS ) &&
		    this->isSubclassOf( InventoryItem ) &&
		    sent->PowerupActive() )
		{
			return false;
		}
	}
	return true;
}
//###

/*****************************************************************************/
/*SINED misc_medkit (1.0 0.2 0.2)
   Heals whoever uses it to their max health.
 *****************************************************************************/
CLASS_DECLARATION( Entity, Medkit, "misc_medkit" );

ResponseDef Medkit::Responses[] =
{
	{ &EV_Use, ( Response )& Medkit::Use },
	{ NULL, NULL }
};

Medkit::Medkit()
{
	setSolidType( SOLID_BSP );
	setMoveType( MOVETYPE_NONE );
	showModel();
}

void Medkit::Use( Event* ev )
{
	Entity* other;
	str realname;

	other = ev->GetEntity( 1 );
	if( other && other->health < other->max_health )
	{
		other->health = other->max_health;
		realname = GetRandomAlias( "snd_activate" );

		if( realname.length() )
			other->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
	}
}

CLASS_DECLARATION( InventoryItem, Oxygen, "powerups_oxygen" );

Event EV_Oxygen_Powerdown( "oxygen_powerdown" );

ResponseDef Oxygen::Responses[] =
{
	{ &EV_Oxygen_Powerdown, ( Response )& Oxygen::Powerdown },
	{ &EV_Item_Pickup, ( Response )& Oxygen::Pickup },
	{ NULL, NULL }
};

Oxygen::Oxygen()
{
	setModel( "oxygen.def" );
	Set( 1 );
	setRespawnTime( 180 + G_Random( 60 ) );
}

void Oxygen::Powerdown( Event* ev )
{
	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	if( owner->flags & FL_OXYGEN )
	{
		str realname;

		owner->flags &= ~FL_OXYGEN;
		realname = GetRandomAlias( "snd_deactivate" );
		if( realname.length() )
			owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
	}
	CancelPendingEvents();
	PostEvent( EV_Remove, 0 );
}

void Oxygen::Pickup( Event* ev )
{
	Entity*   other;
	Sentient* sen;
	Item*     item;

	other  = ev->GetEntity( 1 );

	if( !other )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	sen = ( Sentient* )other;

	// Single player must have the scuba gear to use oxygen
	if( !deathmatch->value && !sen->FindItem( "ScubaGear" ) )
	{
		Item* item;

		item = ( Item* )new ScubaGear;
		item->CancelEventsOfType( EV_Item_DropToFloor );
		item->CancelEventsOfType( EV_Remove );
		item->ProcessPendingEvents();

		gi.centerprintf( other->edict, "jcx yv 20 string \"You need this item to use Oxygen:\" jcx yv -20 icon %d", item->GetIconIndex() );
		delete item;

		return;
	}

	if( !ItemPickup( sen ) )
		return;

	if( sen->isSubclassOf( Player ) )
	{
		Player* player = ( Player* )sen;

		player->GiveOxygen( 60 );
	}

	item = sen->FindItem( getClassname() );
	if( item )
	{
		CancelPendingEvents();
		item->PostEvent( EV_Remove, 0 );
	}
}

void Oxygen::Use( Event* ev )
{
	str realname;
	Event*       event;

	if( !owner )
	{
		CancelPendingEvents();
		PostEvent( EV_Remove, 0 );
		return;
	}

	// Single player must have the scuba gear to use oxygen
	if( !deathmatch->value && !owner->FindItem( "ScubaGear" ) )
	{
		Item* item;

		item = ( Item* )new ScubaGear;
		item->CancelEventsOfType( EV_Item_DropToFloor );
		item->CancelEventsOfType( EV_Remove );
		item->ProcessPendingEvents();

		gi.centerprintf( owner->edict, "jcx yv 20 string \"You need this item to use Oxygen:\" jcx yv -20 icon %d", item->GetIconIndex() );
		delete item;

		return;
	}

	if( owner->PowerupActive() )
	{
		return;
	}

	// Make sure there is one available
	assert( amount );
	amount--;
	if( amount <= 0 )
	{
		owner->RemoveItem( this );
	}

	PostEvent( EV_Oxygen_Powerdown, 60 );

	owner->flags |= FL_OXYGEN;

	event = new Event( "poweruptimer" );
	event->AddInteger( 60 );
	event->AddInteger( P_OXYGEN );
	owner->ProcessEvent( event );

	realname = GetRandomAlias( "snd_activate" );

	if( realname.length() )
		owner->sound( realname, 1, CHAN_ITEM, ATTN_NORM );
}

//### =============================================================
// 2015 added items

CLASS_DECLARATION( InventoryItem, Goggles, "powerup_goggles" );

Event EV_Goggles_CheckOff( "goggles_checkoff" );

ResponseDef Goggles::Responses[] =
{
	{&EV_Goggles_CheckOff, ( Response )& Goggles::CheckTurnoff},
	{&EV_Item_Pickup, ( Response )& Goggles::Pickup},
	{NULL, NULL}
};

Goggles::Goggles()
{
	setModel( "goggles.def" );
	Set( 1 );
	setRespawnTime( 60 + G_Random( 30 ) );
	goggleson = false;
	flags |= FL_POSTTHINK;
}

Goggles::~Goggles()
{
	if( glowent )
	{
		glowent->ProcessEvent( EV_Remove );
	}
}

void Goggles::CheckTurnoff( Event* ev )
{
	// if player is dead or isn't for some reason
	// set as having goggles on, make sure that
	// the goggles are off
	if( owner->isClient() )
	{
		if( !( ( Player* )owner.Ptr() )->nightvision || owner->deadflag )
		{
			// turn the goggles off
			if( goggleson )
			{
				Event* ev2;
				ev2 = new Event( EV_InventoryItem_Use );
				ProcessEvent( ev2 );
			}
		}
	}
	else // non-client shouldn't have this
	{
		amount = 0;
		owner->RemoveItem( this );
	}
}

void Goggles::Use( Event* ev )
{
	str realname;

	assert( owner );

	// Make sure there is one available
	assert( amount );

	// only useable by clients
	if( !owner->isClient() )
		return;

	// don't allow turning them off in a gogles always on DM game
	if( goggleson && !( DM_FLAG( DF_GOGGLESON ) ) )
	{
		( ( Player* )owner.Ptr() )->nightvision = false;
		goggleson = false;

		realname = GetRandomAlias( "snd_deactivate" );
	}
	else
	{
		( ( Player* )owner.Ptr() )->nightvision = true;
		goggleson = true;

		realname = GetRandomAlias( "snd_activate" );
	}

	if( realname.length() > 1 )
		owner->sound( realname, 0.5, CHAN_ITEM, ATTN_NORM );
}

void Goggles::Postthink( void )
{
	if( goggleson && !deathmatch->value && owner )
	{
		Vector dir, start, pos;
		trace_t trace;
		int mask;

		if( !glowent )
			glowent = new Entity;

		mask = ( MASK_PLAYERSOLID | MASK_WATER ) & ~( CONTENTS_WINDOW | CONTENTS_FENCE );
		dir = owner->orientation[0];
		start = owner->origin + Vector( 0, 0, owner->viewheight );
		pos = start + dir * 50;
		trace = G_Trace( start, vec_zero, vec_zero, pos, owner, mask, "Goggles::PostThink" );

		glowent->setModel( "sprites/null.spr" );
		glowent->setMoveType( MOVETYPE_NONE );
		glowent->setSolidType( SOLID_NOT );
		glowent->edict->s.renderfx |= RF_DLIGHT;
		glowent->edict->s.radius = 300;
		glowent->edict->s.color_b = 0.06;
		glowent->edict->s.color_r = 0.06;
		glowent->edict->s.color_g = 0.09;
		glowent->setOrigin( trace.endpos );
	}
	else if( glowent )
	{
		glowent->ProcessEvent( EV_Remove );
		glowent = NULL;
	}
}

void Goggles::Pickup( Event* ev )
{
	ItemPickup( ev->GetEntity( 1 ) );
}

qboolean Goggles::Pickupable( Entity* other )
{
	if( !other->isSubclassOf( Sentient ) )
	{
		return false;
	}

	Sentient* sent;
	Item* item;

	sent = ( Sentient* )other;
	item = sent->FindItem( getClassname() );

	if( item && ( item->Amount() >= item->MaxAmount() ) )
	{
		return false;
	}

	// Mutants can't pick up anything but health
	if( other->flags & ( FL_MUTANT | FL_SP_MUTANT ) )
	{
		return false;
	}

	return true;
}

Item* Goggles::ItemPickup( Entity* other )
{
	Sentient* sent;
	Item* item;
	str realname;

	if( !Pickupable( other ) )
	{
		return NULL;
	}

	sent = ( Sentient* )other;

	item = sent->giveItem( getClassname(), Amount(), icon_index );

	if( !item )
		return NULL;

	realname = GetRandomAlias( "snd_pickup" );
	if( realname.length() > 1 )
		sent->sound( realname, 1, CHAN_ITEM, ATTN_NORM );

	if( !Removable() )
	{
		// leave the item for others to pickup
		return item;
	}

	CancelEventsOfType( EV_Item_DropToFloor );
	CancelEventsOfType( EV_Item_Respawn );
	CancelEventsOfType( EV_FadeOut );

	setSolidType( SOLID_NOT );
	hideModel();

	if( Respawnable() )
	{
		PostEvent( EV_Item_Respawn, RespawnTime() );
	}
	else
	{
		PostEvent( EV_Remove, 0.1 );
	}

	return item;
}

CLASS_DECLARATION( InventoryItem, EasterCandy, "powerup_eastercandy" );

ResponseDef EasterCandy::Responses[] =
{
	{NULL, NULL}
};

EasterCandy::EasterCandy()
{
	setModel( "eastercandy.def" );
	Set( 1 );
	setRespawnTime( 30 + G_Random( 30 ) );
}

void EasterCandy::Use( Event* ev )
{
	ScriptVariable* var;

	assert( owner );

	// Make sure there is one available
	assert( amount );

	// the player gets 100 health when he uses this
	owner->health += 100;
	if( owner->health > 200 )
	{
		owner->health = 200;
	}

	owner->RemoveItem( this );

	var = gameVars.GetVariable( "playeritem_EasterCandy" );
	if( var )
	{
		var->setIntValue( 0 );
	}
}

CLASS_DECLARATION( InventoryItem, EasterCoke, "powerup_eastercoke" );

ResponseDef EasterCoke::Responses[] =
{
	{NULL, NULL}
};

EasterCoke::EasterCoke()
{
	setModel( "eastercoke.def" );
	Set( 1 );
	setRespawnTime( 30 + G_Random( 30 ) );
}

void EasterCoke::Use( Event* ev )
{
	ScriptVariable* var;

	assert( owner );

	// Make sure there is one available
	assert( amount );

	// the player gets 100 health when he uses this
	owner->health += 100;
	if( owner->health > 200 )
	{
		owner->health = 200;
	}

	owner->RemoveItem( this );

	var = gameVars.GetVariable( "playeritem_EasterCoke" );
	if( var )
	{
		var->setIntValue( 0 );
	}
}

