/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2009 Darklegion Development

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"


void InitTrigger( gentity_t *self )
{
  if( !VectorCompare( self->s.angles, vec3_origin ) )
    G_SetMovedir( self->s.angles, self->movedir );

  trap_SetBrushModel( self, self->model );
  self->r.contents = CONTENTS_TRIGGER;    // replaces the -1 from trap_SetBrushModel
  self->r.svFlags = SVF_NOCLIENT;
}


// the wait time has passed, so set back up for another activation
void multi_wait( gentity_t *ent )
{
  ent->nextthink = 0;
}


// the trigger was just activated
// ent->activator should be set to the activator so it can be held through a delay
// so wait for the delay time before firing
void multi_trigger( gentity_t *ent, gentity_t *activator )
{
  ent->activator = activator;
  if( ent->nextthink )
    return;   // can't retrigger until the wait is over

  if( activator && activator->client )
  {
    if( ( ent->spawnflags & 1 ) &&
        activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )
      return;

    if( ( ent->spawnflags & 2 ) &&
        activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )
      return;
  }

  G_UseTargets( ent, ent->activator );

  if( ent->wait > 0 )
  {
    ent->think = multi_wait;
    ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000;
  }
  else
  {
    // we can't just remove (self) here, because this is a touch function
    // called while looping through area links...
    ent->touch = 0;
    ent->nextthink = level.time + FRAMETIME;
    ent->think = G_FreeEntity;
  }
}

void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  multi_trigger( ent, activator );
}

void Touch_Multi( gentity_t *self, gentity_t *other, trace_t *trace )
{
  if( !other->client && other->s.eType != ET_BUILDABLE )
    return;

  multi_trigger( self, other );
}

/*QUAKED trigger_multiple (.5 .5 .5) ?
"wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
"random"  wait variance, default is 0
Variable sized repeatable trigger.  Must be targeted at one or more entities.
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
*/
void SP_trigger_multiple( gentity_t *ent )
{
  G_SpawnFloat( "wait", "0.5", &ent->wait );
  G_SpawnFloat( "random", "0", &ent->random );

  if( ent->random >= ent->wait && ent->wait >= 0 )
  {
    ent->random = ent->wait - FRAMETIME;
    G_Printf( "trigger_multiple has random >= wait\n" );
  }

  ent->touch = Touch_Multi;
  ent->use = Use_Multi;

  InitTrigger( ent );
  trap_LinkEntity( ent );
}



/*
==============================================================================

trigger_always

==============================================================================
*/

void trigger_always_think( gentity_t *ent )
{
  G_UseTargets( ent, ent );
  G_FreeEntity( ent );
}

/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8)
This trigger will always fire.  It is activated by the world.
*/
void SP_trigger_always( gentity_t *ent )
{
  // we must have some delay to make sure our use targets are present
  ent->nextthink = level.time + 300;
  ent->think = trigger_always_think;
}


/*
==============================================================================

trigger_push

==============================================================================
*/

void trigger_push_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
  if( !other->client )
    return;
}


/*
=================
AimAtTarget

Calculate origin2 so the target apogee will be hit
=================
*/
void AimAtTarget( gentity_t *self )
{
  gentity_t *ent;
  vec3_t    origin;
  float     height, gravity, time, forward;
  float     dist;

  VectorAdd( self->r.absmin, self->r.absmax, origin );
  VectorScale( origin, 0.5, origin );

  ent = G_PickTarget( self->target );

  if( !ent )
  {
    G_FreeEntity( self );
    return;
  }

  height = ent->s.origin[ 2 ] - origin[ 2 ];
  gravity = g_gravity.value;
  time = sqrt( height / ( 0.5 * gravity ) );

  if( !time )
  {
    G_FreeEntity( self );
    return;
  }

  // set s.origin2 to the push velocity
  VectorSubtract( ent->s.origin, origin, self->s.origin2 );
  self->s.origin2[ 2 ] = 0;
  dist = VectorNormalize( self->s.origin2 );

  forward = dist / time;
  VectorScale( self->s.origin2, forward, self->s.origin2 );

  self->s.origin2[ 2 ] = time * gravity;
}


/*QUAKED trigger_push (.5 .5 .5) ?
Must point at a target_position, which will be the apex of the leap.
This will be client side predicted, unlike target_push
*/
void SP_trigger_push( gentity_t *self )
{
  InitTrigger( self );

  // unlike other triggers, we need to send this one to the client
  self->r.svFlags &= ~SVF_NOCLIENT;

  self->s.eType = ET_PUSH_TRIGGER;
  self->touch = trigger_push_touch;
  self->think = AimAtTarget;
  self->nextthink = level.time + FRAMETIME;
  trap_LinkEntity( self );
}


void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  if( !activator || !activator->client )
    return;

  if( activator->client->ps.pm_type != PM_NORMAL )
    return;

  VectorCopy( self->s.origin2, activator->client->ps.velocity );

}

/*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8)
Pushes the activator in the direction.of angle, or towards a target apex.
"speed"   defaults to 1000
*/
void SP_target_push( gentity_t *self )
{
  if( !self->speed )
    self->speed = 1000;

  G_SetMovedir( self->s.angles, self->s.origin2 );
  VectorScale( self->s.origin2, self->speed, self->s.origin2 );

  if( self->target )
  {
    VectorCopy( self->s.origin, self->r.absmin );
    VectorCopy( self->s.origin, self->r.absmax );
    self->think = AimAtTarget;
    self->nextthink = level.time + FRAMETIME;
  }

  self->use = Use_target_push;
}

/*
==============================================================================

trigger_teleport

==============================================================================
*/

void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
  gentity_t *dest;

  if( self->s.eFlags & EF_NODRAW )
    return;

  if( !other->client )
    return;

  if( other->client->ps.pm_type == PM_DEAD )
    return;

  // Spectators only?
  if( ( self->spawnflags & 1 ) &&
      other->client->sess.spectatorState == SPECTATOR_NOT )
    return;


  dest = G_PickTarget( self->target );

  if( !dest )
  {
    G_Printf( "Couldn't find teleporter destination\n" );
    return;
  }

  TeleportPlayer( other, dest->s.origin, dest->s.angles );
}

/*
===============
trigger_teleport_use
===============
*/
void trigger_teleporter_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  ent->s.eFlags ^= EF_NODRAW;
}


/*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR SPAWN_DISABLED
Allows client side prediction of teleportation events.
Must point at a target_position, which will be the teleport destination.

If spectator is set, only spectators can use this teleport
Spectator teleporters are not normally placed in the editor, but are created
automatically near doors to allow spectators to move through them
*/
void SP_trigger_teleport( gentity_t *self )
{
  InitTrigger( self );

  // unlike other triggers, we need to send this one to the client
  // unless is a spectator trigger
  if( self->spawnflags & 1 )
    self->r.svFlags |= SVF_NOCLIENT;
  else
    self->r.svFlags &= ~SVF_NOCLIENT;

  // SPAWN_DISABLED
  if( self->spawnflags & 2 )
    self->s.eFlags |= EF_NODRAW;

  self->s.eType = ET_TELEPORT_TRIGGER;
  self->touch = trigger_teleporter_touch;
  self->use = trigger_teleporter_use;

  trap_LinkEntity( self );
}


/*
==============================================================================

trigger_hurt

==============================================================================
*/

/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF - SILENT NO_PROTECTION SLOW
Any entity that touches this will be hurt.
It does dmg points of damage each server frame
Targeting the trigger will toggle its on / off state.

SILENT      supresses playing the sound
SLOW      changes the damage rate to once per second
NO_PROTECTION *nothing* stops the damage

"dmg"     default 5 (whole numbers only)

*/
void hurt_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  if( self->r.linked )
    trap_UnlinkEntity( self );
  else
    trap_LinkEntity( self );
}

void hurt_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
  int   dflags;

  if( !other->takedamage )
    return;

  if( self->timestamp > level.time )
    return;

  if( self->spawnflags & 16 )
    self->timestamp = level.time + 1000;
  else
    self->timestamp = level.time + FRAMETIME;

  // play sound
  if( !( self->spawnflags & 4 ) )
    G_Sound( other, CHAN_AUTO, self->noise_index );

  if( self->spawnflags & 8 )
    dflags = DAMAGE_NO_PROTECTION;
  else
    dflags = 0;

  G_Damage( other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT );
}

void SP_trigger_hurt( gentity_t *self )
{
  InitTrigger( self );

  self->noise_index = G_SoundIndex( "sound/misc/electro.wav" );
  self->touch = hurt_touch;

  if( self->damage <= 0 )
    self->damage = 5;

  self->r.contents = CONTENTS_TRIGGER;

  if( self->spawnflags & 2 )
    self->use = hurt_use;

  // link in to the world if starting active
  if( !( self->spawnflags & 1 ) )
    trap_LinkEntity( self );
}


/*
==============================================================================

timer

==============================================================================
*/


/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON
This should be renamed trigger_timer...
Repeatedly fires its targets.
Can be turned on or off by using.

"wait"      base time between triggering all targets, default is 1
"random"    wait variance, default is 0
so, the basic time between firing is a random time between
(wait - random) and (wait + random)

*/
void func_timer_think( gentity_t *self )
{
  G_UseTargets( self, self->activator );
  // set time before next firing
  self->nextthink = level.time + 1000 * ( self->wait + crandom( ) * self->random );
}

void func_timer_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  self->activator = activator;

  // if on, turn it off
  if( self->nextthink )
  {
    self->nextthink = 0;
    return;
  }

  // turn it on
  func_timer_think( self );
}

void SP_func_timer( gentity_t *self )
{
  G_SpawnFloat( "random", "1", &self->random );
  G_SpawnFloat( "wait", "1", &self->wait );

  self->use = func_timer_use;
  self->think = func_timer_think;

  if( self->random >= self->wait )
  {
    self->random = self->wait - FRAMETIME;
    G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) );
  }

  if( self->spawnflags & 1 )
  {
    self->nextthink = level.time + FRAMETIME;
    self->activator = self;
  }

  self->r.svFlags = SVF_NOCLIENT;
}


/*
===============
G_Checktrigger_stages

Called when stages change
===============
*/
void G_Checktrigger_stages( team_t team, stage_t stage )
{
  int i;
  gentity_t *ent;

  for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )
  {
    if( !ent->inuse )
      continue;

    if( !Q_stricmp( ent->classname, "trigger_stage" ) )
    {
      if( team == ent->stageTeam && stage == ent->stageStage )
        ent->use( ent, ent, ent );
    }
  }
}


/*
===============
trigger_stage_use
===============
*/
void trigger_stage_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  G_UseTargets( self, self );
}

void SP_trigger_stage( gentity_t *self )
{
  G_SpawnInt( "team", "0", (int *)&self->stageTeam );
  G_SpawnInt( "stage", "0", (int *)&self->stageStage );

  self->use = trigger_stage_use;

  self->r.svFlags = SVF_NOCLIENT;
}


/*
===============
trigger_win
===============
*/
void trigger_win( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  G_UseTargets( self, self );
}

void SP_trigger_win( gentity_t *self )
{
  G_SpawnInt( "team", "0", (int *)&self->stageTeam );

  self->use = trigger_win;

  self->r.svFlags = SVF_NOCLIENT;
}


/*
===============
trigger_buildable_match
===============
*/
qboolean trigger_buildable_match( gentity_t *self, gentity_t *activator )
{
  int i = 0;

  if( !activator )
    return qfalse;

  //if there is no buildable list every buildable triggers
  if( self->bTriggers[ i ] == BA_NONE )
    return qtrue;
  else
  {
    //otherwise check against the list
    for( i = 0; self->bTriggers[ i ] != BA_NONE; i++ )
    {
      if( activator->s.modelindex == self->bTriggers[ i ] )
        return qtrue;
    }
  }

  return qfalse;
}

/*
===============
trigger_buildable_trigger
===============
*/
void trigger_buildable_trigger( gentity_t *self, gentity_t *activator )
{
  self->activator = activator;

  if( self->s.eFlags & EF_NODRAW )
    return;

  if( self->nextthink )
    return;   // can't retrigger until the wait is over

  if( self->s.eFlags & EF_DEAD )
  {
    if( !trigger_buildable_match( self, activator ) )
      G_UseTargets( self, activator );
  }
  else
  {
    if( trigger_buildable_match( self, activator ) )
      G_UseTargets( self, activator );
  }

  if( self->wait > 0 )
  {
    self->think = multi_wait;
    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
  }
  else
  {
    // we can't just remove (self) here, because this is a touch function
    // called while looping through area links...
    self->touch = 0;
    self->nextthink = level.time + FRAMETIME;
    self->think = G_FreeEntity;
  }
}

/*
===============
trigger_buildable_touch
===============
*/
void trigger_buildable_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
{
  //only triggered by buildables
  if( !other || other->s.eType != ET_BUILDABLE )
    return;

  trigger_buildable_trigger( ent, other );
}

/*
===============
trigger_buildable_use
===============
*/
void trigger_buildable_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  ent->s.eFlags ^= EF_NODRAW;
}

/*
===============
SP_trigger_buildable
===============
*/
void SP_trigger_buildable( gentity_t *self )
{
  char *buffer;

  G_SpawnFloat( "wait", "0.5", &self->wait );
  G_SpawnFloat( "random", "0", &self->random );

  if( self->random >= self->wait && self->wait >= 0 )
  {
    self->random = self->wait - FRAMETIME;
    G_Printf( S_COLOR_YELLOW "WARNING: trigger_buildable has random >= wait\n" );
  }

  G_SpawnString( "buildables", "", &buffer );

  BG_ParseCSVBuildableList( buffer, self->bTriggers, BA_NUM_BUILDABLES );

  self->touch = trigger_buildable_touch;
  self->use = trigger_buildable_use;

  // SPAWN_DISABLED
  if( self->spawnflags & 1 )
    self->s.eFlags |= EF_NODRAW;

  // NEGATE
  if( self->spawnflags & 2 )
    self->s.eFlags |= EF_DEAD;

  InitTrigger( self );
  trap_LinkEntity( self );
}


/*
===============
trigger_class_match
===============
*/
qboolean trigger_class_match( gentity_t *self, gentity_t *activator )
{
  int i = 0;

  if( !activator )
    return qfalse;

  //if there is no class list every class triggers (stupid case)
  if( self->cTriggers[ i ] == PCL_NONE )
    return qtrue;
  else
  {
    //otherwise check against the list
    for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ )
    {
      if( activator->client->ps.stats[ STAT_CLASS ] == self->cTriggers[ i ] )
        return qtrue;
    }
  }

  return qfalse;
}

/*
===============
trigger_class_trigger
===============
*/
void trigger_class_trigger( gentity_t *self, gentity_t *activator )
{
  //sanity check
  if( !activator || !activator->client )
    return;

  if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )
    return;

  if( self->s.eFlags & EF_NODRAW )
    return;

  self->activator = activator;
  if( self->nextthink )
    return;   // can't retrigger until the wait is over

  if( self->s.eFlags & EF_DEAD )
  {
    if( !trigger_class_match( self, activator ) )
      G_UseTargets( self, activator );
  }
  else
  {
    if( trigger_class_match( self, activator ) )
      G_UseTargets( self, activator );
  }

  if( self->wait > 0 )
  {
    self->think = multi_wait;
    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
  }
  else
  {
    // we can't just remove (self) here, because this is a touch function
    // called while looping through area links...
    self->touch = 0;
    self->nextthink = level.time + FRAMETIME;
    self->think = G_FreeEntity;
  }
}

/*
===============
trigger_class_touch
===============
*/
void trigger_class_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
{
  //only triggered by clients
  if( !other->client )
    return;

  trigger_class_trigger( ent, other );
}

/*
===============
trigger_class_use
===============
*/
void trigger_class_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  ent->s.eFlags ^= EF_NODRAW;
}

/*
===============
SP_trigger_class
===============
*/
void SP_trigger_class( gentity_t *self )
{
  char *buffer;

  G_SpawnFloat( "wait", "0.5", &self->wait );
  G_SpawnFloat( "random", "0", &self->random );

  if( self->random >= self->wait && self->wait >= 0 )
  {
    self->random = self->wait - FRAMETIME;
    G_Printf( S_COLOR_YELLOW "WARNING: trigger_class has random >= wait\n" );
  }

  G_SpawnString( "classes", "", &buffer );

  BG_ParseCSVClassList( buffer, self->cTriggers, PCL_NUM_CLASSES );

  self->touch = trigger_class_touch;
  self->use = trigger_class_use;

  // SPAWN_DISABLED
  if( self->spawnflags & 1 )
    self->s.eFlags |= EF_NODRAW;

  // NEGATE
  if( self->spawnflags & 2 )
    self->s.eFlags |= EF_DEAD;

  InitTrigger( self );
  trap_LinkEntity( self );
}


/*
===============
trigger_equipment_match
===============
*/
qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator )
{
  int i = 0;

  if( !activator )
    return qfalse;

  //if there is no equipment list all equipment triggers (stupid case)
  if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE )
    return qtrue;
  else
  {
    //otherwise check against the lists
    for( i = 0; self->wTriggers[ i ] != WP_NONE; i++ )
    {
      if( BG_InventoryContainsWeapon( self->wTriggers[ i ], activator->client->ps.stats ) )
        return qtrue;
    }

    for( i = 0; self->uTriggers[ i ] != UP_NONE; i++ )
    {
      if( BG_InventoryContainsUpgrade( self->uTriggers[ i ], activator->client->ps.stats ) )
        return qtrue;
    }
  }

  return qfalse;
}

/*
===============
trigger_equipment_trigger
===============
*/
void trigger_equipment_trigger( gentity_t *self, gentity_t *activator )
{
  //sanity check
  if( !activator || !activator->client )
    return;

  if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )
    return;

  if( self->s.eFlags & EF_NODRAW )
    return;

  self->activator = activator;
  if( self->nextthink )
    return;   // can't retrigger until the wait is over

  if( self->s.eFlags & EF_DEAD )
  {
    if( !trigger_equipment_match( self, activator ) )
      G_UseTargets( self, activator );
  }
  else
  {
    if( trigger_equipment_match( self, activator ) )
      G_UseTargets( self, activator );
  }

  if( self->wait > 0 )
  {
    self->think = multi_wait;
    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000;
  }
  else
  {
    // we can't just remove (self) here, because this is a touch function
    // called while looping through area links...
    self->touch = 0;
    self->nextthink = level.time + FRAMETIME;
    self->think = G_FreeEntity;
  }
}

/*
===============
trigger_equipment_touch
===============
*/
void trigger_equipment_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
{
  //only triggered by clients
  if( !other->client )
    return;

  trigger_equipment_trigger( ent, other );
}

/*
===============
trigger_equipment_use
===============
*/
void trigger_equipment_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  ent->s.eFlags ^= EF_NODRAW;
}

/*
===============
SP_trigger_equipment
===============
*/
void SP_trigger_equipment( gentity_t *self )
{
  char *buffer;

  G_SpawnFloat( "wait", "0.5", &self->wait );
  G_SpawnFloat( "random", "0", &self->random );

  if( self->random >= self->wait && self->wait >= 0 )
  {
    self->random = self->wait - FRAMETIME;
    G_Printf( S_COLOR_YELLOW "WARNING: trigger_equipment has random >= wait\n" );
  }

  G_SpawnString( "equipment", "", &buffer );

  BG_ParseCSVEquipmentList( buffer, self->wTriggers, WP_NUM_WEAPONS,
      self->uTriggers, UP_NUM_UPGRADES );

  self->touch = trigger_equipment_touch;
  self->use = trigger_equipment_use;

  // SPAWN_DISABLED
  if( self->spawnflags & 1 )
    self->s.eFlags |= EF_NODRAW;

  // NEGATE
  if( self->spawnflags & 2 )
    self->s.eFlags |= EF_DEAD;

  InitTrigger( self );
  trap_LinkEntity( self );
}


/*
===============
trigger_gravity_touch
===============
*/
void trigger_gravity_touch( gentity_t *ent, gentity_t *other, trace_t *trace )
{
  //only triggered by clients
  if( !other->client )
    return;

  other->client->ps.gravity = ent->triggerGravity;
}

/*
===============
trigger_gravity_use
===============
*/
void trigger_gravity_use( gentity_t *ent, gentity_t *other, gentity_t *activator )
{
  if( ent->r.linked )
    trap_UnlinkEntity( ent );
  else
    trap_LinkEntity( ent );
}


/*
===============
SP_trigger_gravity
===============
*/
void SP_trigger_gravity( gentity_t *self )
{
  G_SpawnInt( "gravity", "800", &self->triggerGravity );

  self->touch = trigger_gravity_touch;
  self->use = trigger_gravity_use;

  InitTrigger( self );
  trap_LinkEntity( self );
}


/*
===============
trigger_heal_use
===============
*/
void trigger_heal_use( gentity_t *self, gentity_t *other, gentity_t *activator )
{
  if( self->r.linked )
    trap_UnlinkEntity( self );
  else
    trap_LinkEntity( self );
}

/*
===============
trigger_heal_touch
===============
*/
void trigger_heal_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
  int max;

  if( !other->client )
    return;

  if( self->timestamp > level.time )
    return;

  if( self->spawnflags & 2 )
    self->timestamp = level.time + 1000;
  else
    self->timestamp = level.time + FRAMETIME;

  max = other->client->ps.stats[ STAT_MAX_HEALTH ];

  other->health += self->damage;

  if( other->health > max )
    other->health = max;

  other->client->ps.stats[ STAT_HEALTH ] = other->health;
}

/*
===============
SP_trigger_heal
===============
*/
void SP_trigger_heal( gentity_t *self )
{
  G_SpawnInt( "heal", "5", &self->damage );

  if( self->damage <= 0 )
  {
    self->damage = 1;
    G_Printf( S_COLOR_YELLOW "WARNING: trigger_heal with negative damage key\n" );
  }

  self->touch = trigger_heal_touch;
  self->use = trigger_heal_use;

  InitTrigger( self );

  // link in to the world if starting active
  if( !( self->spawnflags & 1 ) )
    trap_LinkEntity( self );
}


/*
===============
trigger_ammo_touch
===============
*/
void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )
{
  int maxClips, maxAmmo;

  if( !other->client )
    return;

  if( other->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )
    return;

  if( self->timestamp > level.time )
    return;

  if( other->client->ps.weaponstate != WEAPON_READY )
    return;

  if( BG_Weapon( other->client->ps.weapon )->usesEnergy && self->spawnflags & 2 )
    return;

  if( !BG_Weapon( other->client->ps.weapon )->usesEnergy && self->spawnflags & 4 )
    return;

  if( self->spawnflags & 1 )
    self->timestamp = level.time + 1000;
  else
    self->timestamp = level.time + FRAMETIME;

  maxAmmo = BG_Weapon( other->client->ps.weapon )->maxAmmo;
  maxClips = BG_Weapon( other->client->ps.weapon )->maxClips;

  if( ( other->client->ps.ammo + self->damage ) > maxAmmo )
  {
    if( other->client->ps.clips < maxClips )
    {
      other->client->ps.clips++;
      other->client->ps.ammo = 1;
    }
    else
      other->client->ps.ammo = maxAmmo;
  }
  else
    other->client->ps.ammo += self->damage;
}

/*
===============
SP_trigger_ammo
===============
*/
void SP_trigger_ammo( gentity_t *self )
{
  G_SpawnInt( "ammo", "1", &self->damage );

  if( self->damage <= 0 )
  {
    self->damage = 1;
    G_Printf( S_COLOR_YELLOW "WARNING: trigger_ammo with negative ammo key\n" );
  }

  self->touch = trigger_ammo_touch;

  InitTrigger( self );
  trap_LinkEntity( self );
}
