//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/item.cpp                         $
// $Revision:: 51                                                             $
//   $Author:: Markd                                                          $
//     $Date:: 11/12/98 9:20p                                                 $
//
// Copyright (C) 1997 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.
//
// $Log:: /Quake 2 Engine/Sin/code/game/item.cpp                              $
// 
// 51    11/12/98 9:20p Markd
// fixed null sounds for snd_pickup
// 
// 50    10/24/98 2:07p Aldie
// Mutants can only pickup health
// 
// 49    10/22/98 10:22p Markd
// Put in support for game and level item script variables
// 
// 48    10/19/98 12:05a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// got rid of warning in Set
// 
// 47    10/14/98 1:18a Jimdose
// Got cross-level persistant info working
// Added amount_override
// 
// 46    10/11/98 8:57p Aldie
// Don't print out error message unecessarily
// 
// 45    10/10/98 1:28a Jimdose
// items no longer drop to the floor when loading savegames
// 
// 44    10/09/98 2:06a Aldie
// Updated DMFLAGS
// 
// 43    10/07/98 5:51p Markd
// Fixed small sized enemies getting resized and causing fall out of level
// errors
// 
// 42    10/04/98 6:15p Markd
// fixed pickup sounds
// 
// 41    9/29/98 5:59p Markd
// Added dialog_needed stuff
// 
// 40    9/29/98 11:05a Markd
// Added respawnsound support
// 
// 39    9/15/98 6:37p Markd
// Added RotatedBounds flag support
// 
// 38    9/08/98 9:29p Markd
// fixed picking things up by using them
// 
// 37    9/02/98 11:53a Markd
// Put in pickup by pressing the use key
// 
// 36    9/01/98 7:45p Aldie
// Added itemname
// 
// 35    8/29/98 7:23p Aldie
// Added itemname to get around targetname problem.
// 
// 34    8/18/98 11:08p Markd
// Added new Alias System
// 
// 33    8/15/98 1:48p Aldie
// Don't turn off glow when objects are picked up
// 
// 32    8/12/98 4:19p Aldie
// Fixed icons not showing up correctly
// 
// 31    7/26/98 1:17a Markd
// Put in respawn sounds for items
// 
// 30    7/14/98 6:57p Aldie
// Made dropped weapons fade out
// 
// 29    7/14/98 3:53p Markd
// made pickup sounds work properly
// 
// 28    7/12/98 4:34p Markd
// Fixed bounding boxes on items
// 
// 27    7/11/98 8:19p Jimdose
// Drop now returns true or false depending upon whether the item can be
// dropped or not
// 
// 26    7/10/98 2:48p Aldie
// Cleared targetname for all items to circumvent an error when removing an
// actor carrying a weapon of the same targetname as him.
// 
// 25    6/26/98 11:30a Markd
// Changed ItemPickup
// 
// 24    6/25/98 8:47p Markd
// Added keyed items for Triggers, Rewrote Item class, rewrote every pickup
// method
// 
// 23    6/24/98 1:36p Aldie
// Implementation of inventory system and picking stuff up
// 
// 22    6/17/98 1:20a Jimdose
// Moved setOwner to Item.  
// Added EV_Item_Pickup
// 
// 21    6/16/98 9:37p Markd
// made items capable of being orientated
// 
// 20    6/16/98 4:08p Jimdose
// Gave dropping weapons velocity
// 
// 19    6/04/98 7:36p Jimdose
// PlaceItem now posts the EV_Remove instead of processing it
// 
// 18    6/03/98 4:37p Markd
// When picking an item up, stop glowing, when dropping it start glowing
// 
// 17    5/13/98 4:45p Jimdose
// droptofloor event is now posted with 0 delay
// 
// 16    5/03/98 4:50p Jimdose
// Fixed bug where items fell through ground
// 
// 15    5/03/98 4:44p Jimdose
// changed Vector class
// 
// 14    5/02/98 12:39a Jimdose
// Changed PlaceItem so that items only test if they can drop to the floor.  If
// they can, they're left where they were spawned so that they fall, otherwise
// they're removed
// 
// 13    4/05/98 9:41p Markd
// Put in RF_GLOW
// 
// 12    4/04/98 6:05p Jimdose
// ItemPickup no longer activates targets, since the item should alread have
// triggered its targets when it was touched
// 
// 11    4/02/98 4:49p Jimdose
// changed droptofloor
// 
// 10    3/30/98 2:32p Jimdose
// now shows model on placement
// 
// 9     3/28/98 8:57p Jimdose
// changed bounding box
// 
// 8     3/23/98 1:31p Jimdose
// Revamped event and command system
// 
// 7     3/02/98 8:49p Jimdose
// Changed the classid parameter of CLASS_DECLARATION to a quoted string so
// that you could have a NULL classid.
// 
// 6     2/19/98 2:35p Jimdose
// Updated to work with Q2 based progs
// 
// 4     11/07/97 5:59p Markd
// Removed QUAKE specific sound effects
// 
// 3     10/27/97 3:29p Jimdose
// Removed dependency on quakedef.h
// 
// 2     9/26/97 5:30p Jimdose
// Added standard Ritual headers
//
// DESCRIPTION:
// Base class for respawnable, carryable objects.
// 

#include "g_local.h"
#include "entity.h"
#include "trigger.h"
#include "item.h"
#include "inventoryitem.h"
#include "scriptmaster.h"
#include "health.h"

Event EV_Item_Pickup( "item_pickup" );
Event EV_Item_DropToFloor( "droptofloor" );
Event EV_Item_Respawn( "respawn" );
Event EV_Item_SetAmount( "amount" );
Event EV_Item_SetMaxAmount( "maxamount" );
Event EV_Item_SetIconName( "iconname" );
Event EV_Item_SetItemName( "itemname" );
Event EV_Item_RespawnSound( "respawnsound" );
Event EV_Item_DialogNeeded( "dialogneeded" );

CLASS_DECLARATION( Trigger, Item, NULL );

ResponseDef Item::Responses[] =
	{
		{ &EV_Trigger_Effect,	   ( Response )&Item::ItemTouch },
	   { &EV_Item_DropToFloor,	   ( Response )&Item::DropToFloor },
	   { &EV_Item_Respawn,		   ( Response )&Item::Respawn },
	   { &EV_Item_SetAmount,      ( Response )&Item::SetAmount },
	   { &EV_Item_SetMaxAmount,   ( Response )&Item::SetMaxAmount },
	   { &EV_Item_SetIconName,    ( Response )&Item::SetIconName },
      { &EV_Item_SetItemName,    ( Response )&Item::SetItemName },
	   { &EV_Item_Pickup,			( Response )&Item::Pickup },
      { &EV_Use,		            ( Response )&Item::TriggerStuff },
      { &EV_Item_RespawnSound,   ( Response )&Item::RespawnSound },
      { &EV_Item_DialogNeeded,   ( Response )&Item::DialogNeeded },
		{ NULL, NULL }
	};

Item::Item()
	{
   str fullname;
   Vector defangles;

	setRespawnTime( 20 );
	setRespawn( false );

	setSolidType( SOLID_NOT );

	// Set default respawn behavior
	// Derived classes should use setRespawn
	// if they want to override the default behavior
	if ( deathmatch->value )
		{
		setRespawn( true );
		}
	else
		{
		setRespawn( false );
		}

   edict->s.renderfx |= RF_GLOW;

   // angles
   defangles = Vector( 0, G_GetFloatArg( "angle", 0 ), 0 );
   if (defangles.y == -1)
      {
      defangles = Vector( -90, 0, 0 );
      }
   else if (defangles.y == -2)
      {
      defangles = Vector( 90, 0, 0 );
      }
   angles = G_GetVectorArg( "angles", defangles );
	setAngles( angles );

   //
   // we want the bounds of this model auto-rotated
   //
   flags |= FL_ROTATEDBOUNDS;

   //
   // rotate the mins and maxs for the model
   //
   if ( size.length() < 10 )
	   setSize( "-10 -10 0", "10 10 20" );

   //
   // reset the mins and maxs to pickup the FL_ROTATEDBOUNDS flag
   //
   setSize( mins, maxs );

   if ( !LoadingSavegame )
      {
	   // Items can't be immediately dropped to floor, because they might
	   // be on an entity that hasn't spawned yet.
	   PostEvent( EV_Item_DropToFloor, 0 );
      }

	respondto = TRIGGER_PLAYERS;

   icon_name = str("");
   icon_index = 0;
   item_index = 0;
   maximum_amount = 1;
   playrespawn = false;

   amount_override = false;
   amount = 1;

   // FIXME
   // If the targetname is set by the spawn args, then this item
   // will have a targetname.  If we try to remove the owner of this
   // item, then we will remove the owner, then try to remove the item
   // which will already have been removed by the previous event.
   // This doesn't allow any items to have a targetname.
   SetTargetName( "" );

   // Using itemname as a temporary fix to this problem
   itemname = G_GetSpawnArg( "itemname", "");

   if ( G_GetSpawnArg( "amount" ) )
      {
      amount = G_GetIntArg( "amount" );
      if ( amount >= MaxAmount() )
         {
         SetMax( amount );
         }
      amount_override = true;
      }
	}

Item::~Item()
	{
	if ( owner )
		{
		owner->RemoveItem( this );
		owner = NULL;
		}
	}

void Item::CreateSpawnArgs
   (
   void
   )

   {
   G_SetIntArg( "amount", amount );
   G_SetSpawnArg( "model", model.c_str() );
   }

/*
============
PlaceItem

Puts an item back in the world
============
*/
void Item::PlaceItem
	(
	void
	)

	{
	setSolidType( SOLID_TRIGGER );
	setMoveType( MOVETYPE_TOSS );
	showModel();

   edict->s.renderfx |= RF_GLOW;
	groundentity = NULL;
	}

/*
============
DropToFloor

plants the object on the floor
============
*/
void Item::DropToFloor
	(
	Event *ev
	)

	{
   str fullname;
	Vector save;

	PlaceItem();

	setOrigin( origin + "0 0 1" );
	save = origin;
	if ( !droptofloor( 8192 ) )
		{
		gi.dprintf( "%s fell out of level at '%5.1f %5.1f %5.1f'\n", 
			getClassID(), origin.x, origin.y, origin.z );
		PostEvent( EV_Remove, 0 );
		return;
		}
   //
   // if the our global variable doesn't exist, lets zero it out
   //
   fullname = str( "playeritem_" ) + getClassname();
   if ( !gameVars.VariableExists( fullname.c_str() ) )
      {
      gameVars.SetVariable( fullname.c_str(), 0 );
      }

   if ( !levelVars.VariableExists( fullname.c_str() ) )
      {
      levelVars.SetVariable( fullname.c_str(), 0 );
      }

	setOrigin( save );
	groundentity = NULL;
	}

qboolean Item::Drop
	(
	void
	)

	{
	if ( !owner )
		{
		return false;
		}

	setOrigin( owner->worldorigin + "0 0 40" );

	// drop the item
	PlaceItem();
	velocity = owner->velocity * 0.5 + Vector( G_CRandom( 50 ), G_CRandom( 50 ), 100 );
	setAngles( owner->angles );
	avelocity = Vector( 0, G_CRandom( 360 ), 0 );

   trigger_time = level.time + 1;

	if ( owner->isClient() )
		{
		spawnflags |= DROPPED_PLAYER_ITEM;
		}
	else
		{
		spawnflags |= DROPPED_ITEM;
		}

   // Remove this from the owner's item list
   owner->RemoveItem( this );
	owner = NULL;

	return true;
	}


void Item::ItemTouch
	(
	Event *ev
	)

	{
	Entity	*other;
	Event		*e;

	if ( owner )
		{
		// Don't respond to trigger events after item is picked up.
		gi.dprintf( "%s with targetname of %s was triggered unexpectedly.\n", getClassID(), TargetName() );
		return;
		}

	other = ev->GetEntity( 1 );

	e = new Event( EV_Item_Pickup );
	e->AddEntity( other );
	ProcessEvent( e );
	}

void Item::SetOwner
	(
	Sentient *ent
	)

	{
	assert( ent );
	if ( !ent )
		{
		// return to avoid any buggy behaviour
		return;
		}

   owner = ent;
	setRespawn( false );

   edict->s.renderfx &= ~RF_GLOW;
	setSolidType( SOLID_NOT );
	hideModel();
	CancelEventsOfType( EV_Item_DropToFloor );
	CancelEventsOfType( EV_Remove );
//	ItemPickup( ent );
	}

Item * Item::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 );
		}

   if ( DM_FLAG( DF_INSTANT_ITEMS ) )
		{
      Event *ev;

      ev = new Event( EV_InventoryItem_Use );
      ev->AddEntity( other );

      item->ProcessEvent( ev );
		}

   return item;
	}

void Item::Respawn
	(
	Event *ev
	)

	{
	showModel();

	// allow it to be touched again
	setSolidType( SOLID_TRIGGER );

	// play respawn sound
   if ( playrespawn )
      {
   	RandomGlobalSound( "snd_itemspawn" );
      }

	setOrigin( origin );
	};

void Item::setRespawn
	( 
	qboolean flag 
	)

	{
	respawnable = flag;
	}

qboolean Item::Respawnable
	( 
	void 
	)

	{
	return respawnable;
	}

void Item::setRespawnTime
	( 
	float time 
	)

	{
	respawntime = time;
	}

float Item::RespawnTime
	( 
	void 
	)

	{
	return respawntime;
	}

int Item::Amount
   (
   void
   )

   {
   return amount;
   }

int Item::MaxAmount
   (
   void
   )

   {
   return maximum_amount;
   }

qboolean Item::Pickupable
   (
   Entity *other
   )

   {
	if ( !other->isSubclassOf( Sentient ) )
		{
		return false;
		}
   else
      {
      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) && !( this->isSubclassOf( Health ) ) )
         {
         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;
   }

void Item::Pickup
	(
   Event * ev
	)

	{
	ItemPickup( ev->GetEntity( 1 ) );
   }

void Item::setIcon
   (
   const char *i
   )

   {
   icon_name = i;
   icon_index = gi.imageindex( i );
   }

void Item::setName
   (
   const char *i
   )

   {
   item_name = i;
   item_index = gi.itemindex( i );
   }

int Item::Icon
   (
   void
   )

   {
   if ( icon_name.length() )
      return icon_index;
   else
      return -1;
   }

void Item::Set
	(
	int startamount
	)

	{
   if ( !amount_override )
      {
      amount = startamount;
      if ( amount >= MaxAmount() )
         SetMax( amount );
      }
   }

void Item::SetMax
	(
	int maxamount
	)

	{
   maximum_amount = maxamount;
   }

void Item::SetAmount
	(
	Event *ev
	)

	{
	Set( ev->GetInteger( 1 ) );
   }

void Item::SetMaxAmount
	(
	Event *ev
	)

	{
	SetMax( ev->GetInteger( 1 ) );
   }

void Item::SetIconName
	(
	Event *ev
	)

	{
   setIcon( ev->GetString( 1 ) );
	}

void Item::SetItemName
	(
	Event *ev
	)

	{
   setName( ev->GetString( 1 ) );
	}

void Item::Add
	(
	int num
	)

	{
	amount += num;
   if ( amount >= MaxAmount() )
      amount = MaxAmount();
	}

void Item::Remove
   (
   int num
   )
   {
   amount -= num;
   if (amount < 0)
      amount = 0;
   }


qboolean Item::Use
	(
	int num
	)

	{
	if ( num > amount )
		{
		return false;
		}

	amount -= num;
	return true;
	}

qboolean Item::Removable
	( 
	void 
	)

	{
   return true;
	}

void Item::RespawnSound
	(
	Event *ev
	)

	{
   playrespawn = true;
   }

void Item::DialogNeeded
	(
	Event *ev
	)

	{
   //
   // if this item is needed for a trigger, play this dialog
   //
   dialog_needed = ev->GetString( 1 );
   }

str Item::GetDialogNeeded
	(
   void
	)

	{
   return dialog_needed;
   }
