// Copyright (C) 1999-2000 Id Software, Inc.
//
// g_combat.c

#include "g_local.h"


/*
============
ScorePlum
============
*/
void ScorePlum( gentity_t *ent, vec3_t origin, int score ) {
    gentity_t *plum;

    plum = G_TempEntity( origin, EV_SCOREPLUM );
    // only send this temp entity to a single client
    plum->r.svFlags |= SVF_SINGLECLIENT;
    plum->r.singleClient = ent->s.number;
    //
    plum->s.otherEntityNum = ent->s.number;
    plum->s.time = score;
}

/*
============
AddScore
 
Adds score to both the client and his team
============
*/
void AddScore( gentity_t *ent, vec3_t origin, int score ) {
    if ( !ent->client ) {
        return;
    }
    // no scoring during pre-match warmup
    if ( level.warmupTime ) {
        return;
    }
    if(score == 0) {
        return;
    }
    // show score plum
    ScorePlum(ent, origin, score);
    //
    ent->client->ps.persistant[PERS_SCORE] += score;
    if ( g_gametype.integer == GT_TEAM )
        level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score;
    CalculateRanks();
}

/*
=================
TossClientItems
 
Toss the weapon and powerups for the killed player
=================
*/
void TossClientItems( gentity_t *self ) {
    gitem_t		*item;
    int			weapon;
    float		angle;
    int			i;
    gentity_t	*drop;



    // drop the weapon if not a gauntlet or machinegun
    weapon = self->s.weapon;

    // make a special check to see if they are changing to a new
    // weapon that isn't the mg or gauntlet.  Without this, a client
    // can pick up a weapon, be killed, and not drop the weapon because
    // their weapon change hasn't completed yet and they are still holding the MG.
    // Alliance: grapple isn't a weapon. Might be dropped anyway?
    if ( weapon == WP_GAUNTLET || weapon == WP_MACHINEGUN || weapon == WP_GRAPPLING_HOOK ) {
        if ( self->client->ps.weaponstate == WEAPON_DROPPING ) {
            weapon = self->client->pers.cmd.weapon;
        }
        if ( !( self->client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) {
            weapon = WP_NONE;
        }
    }

    // Alliance: grapple isn't a weapon
    if ( weapon > WP_GAUNTLET && weapon < WP_GRAPPLING_HOOK &&
            self->client->ps.ammo[ weapon ] ||
            weapon > WP_MACHINEGUN && weapon != WP_GRAPPLING_HOOK &&
            self->client->ps.ammo[ weapon ]) {
        // find the item type for this weapon
        item = BG_FindItemForWeapon( weapon );

        // spawn the item
        Drop_Item( self, item, 0 );
    }

    // drop all the powerups if not in teamplay
    if ( g_gametype.integer != GT_TEAM ) {
        angle = 45;
        for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
            if ( self->client->ps.powerups[ i ] > level.time ) {
                item = BG_FindItemForPowerup( i );
                if ( !item ) {
                    continue;
                }
                drop = Drop_Item( self, item, angle );
                // decide how many seconds it has left
                drop->count = ( self->client->ps.powerups[ i ] - level.time ) / 1000;
                if ( drop->count < 1 ) {
                    drop->count = 1;
                }
                angle += 45;
            }
        }
    }
}

#ifdef MISSIONPACK

/*
=================
TossClientCubes
=================
*/
//extern gentity_t	*neutralObelisk;
void TossClientCubes( gentity_t *self ) {
    gitem_t		*item;
    gentity_t	*drop;
    vec3_t		velocity;
    vec3_t		angles;
    vec3_t		origin;

    self->client->ps.generic1 = 0;

    // this should never happen but we should never
    // get the server to crash due to skull being spawned in
    if (!G_EntitiesFree()) {
        return;
    }

    if( self->client->sess.sessionTeam == TEAM_RED ) {
        item = BG_FindItem( "Red Cube" );
    } else {
        item = BG_FindItem( "Blue Cube" );
    }

    angles[YAW] = (float)(level.time % 360);
    angles[PITCH] = 0;	// always forward
    angles[ROLL] = 0;

    AngleVectors( angles, velocity, NULL, NULL );
    VectorScale( velocity, 150, velocity );
    velocity[2] += 200 + crandom() * 50;

    if( teamgame.neutralObelisk ) {
        VectorCopy( teamgame.neutralObelisk->s.pos.trBase, origin );
        origin[2] += 44;
    } else {
        VectorClear( origin ) ;
    }

    drop = LaunchItem( item, origin, velocity );

    drop->nextthink = level.time + g_cubeTimeout.integer * 1000;
    drop->think = G_FreeEntity;
    drop->spawnflags = self->client->sess.sessionTeam;
}


/*
=================
TossClientPersistantPowerups
=================
*/
void TossClientPersistantPowerups( gentity_t *ent ) {
    gentity_t	*powerup;

    if( !ent->client ) {
        return;
    }

    if( !ent->client->persistantPowerup ) {
        return;
    }

    powerup = ent->client->persistantPowerup;

    powerup->r.svFlags &= ~SVF_NOCLIENT;
    powerup->s.eFlags &= ~EF_NODRAW;
    powerup->r.contents = CONTENTS_TRIGGER;
    trap_LinkEntity( powerup );

    ent->client->ps.stats[STAT_PERSISTANT_POWERUP] = 0;
    ent->client->persistantPowerup = NULL;
}
#endif


/*
==================
LookAtKiller
==================
*/
void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) {
    vec3_t		dir;
    vec3_t		angles;

    if ( attacker && attacker != self ) {
        VectorSubtract (attacker->s.pos.trBase, self->s.pos.trBase, dir);
    } else if ( inflictor && inflictor != self ) {
        VectorSubtract (inflictor->s.pos.trBase, self->s.pos.trBase, dir);
    } else {
        self->client->ps.stats[STAT_DEAD_YAW] = self->s.angles[YAW];
        return;
    }

    self->client->ps.stats[STAT_DEAD_YAW] = vectoyaw ( dir );

    angles[YAW] = vectoyaw ( dir );
    angles[PITCH] = 0;
    angles[ROLL] = 0;
}

/*
==================
GibEntity
==================
*/
void GibEntity( gentity_t *self, int killer ) {
    gentity_t *ent;
    int i;

    //if this entity still has kamikaze
    if (self->s.eFlags & EF_KAMIKAZE) {
        // check if there is a kamikaze timer around for this owner
        for (i = 0; i < MAX_GENTITIES; i++) {
            ent = &g_entities[i];
            if (!ent->inuse)
                continue;
            if (ent->activator != self)
                continue;
            if (Q_stricmp(ent->classname, "kamikaze timer"))
                continue;
            G_FreeEntity(ent);
            break;
        }
    }
    G_AddEvent( self, EV_GIB_PLAYER, killer );
    self->takedamage = qfalse;
    self->s.eType = ET_INVISIBLE;
    self->r.contents = 0;
}

/*
==================
body_die
==================
*/
void body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
    if ( self->health > GIB_HEALTH ) {
        return;
    }
    if ( !g_blood.integer ) {
        self->health = GIB_HEALTH+1;
        return;
    }

    GibEntity( self, 0 );
}


// these are just for logging, the client prints its own messages
char	*modNames[] = {
                       "MOD_UNKNOWN",
                       "MOD_SHOTGUN",
                       "MOD_GAUNTLET",
                       "MOD_MACHINEGUN",
                       "MOD_GRENADE",
                       "MOD_GRENADE_SPLASH",
                       "MOD_ROCKET",
                       "MOD_ROCKET_SPLASH",
                       "MOD_PLASMA",
                       "MOD_PLASMA_SPLASH",
                       "MOD_RAILGUN",
                       "MOD_LIGHTNING",
                       "MOD_BFG",
                       "MOD_BFG_SPLASH",
                       "MOD_WATER",
                       "MOD_SLIME",
                       "MOD_LAVA",
                       "MOD_CRUSH",
                       "MOD_TELEFRAG",
                       "MOD_FALLING",
                       "MOD_SUICIDE",
                       "MOD_TARGET_LASER",
                       "MOD_TRIGGER_HURT",
#ifdef MISSIONPACK
                       "MOD_NAIL",
                       "MOD_CHAINGUN",
                       "MOD_PROXIMITY_MINE",
                       "MOD_KAMIKAZE",
                       "MOD_JUICED",
#endif
                       "MOD_GRAPPLE",
                       "MOD_HANDGRENADE",
                       "MOD_HANDGRENADE_SPLASH",
                       "MOD_RAILGUN_SPLASH",
                       "MOD_TEAMCHANGE"
                   };

#ifdef MISSIONPACK
/*
==================
Kamikaze_DeathActivate
==================
*/
void Kamikaze_DeathActivate( gentity_t *ent ) {
    G_StartKamikaze(ent);
    G_FreeEntity(ent);
}

/*
==================
Kamikaze_DeathTimer
==================
*/
void Kamikaze_DeathTimer( gentity_t *self ) {
    gentity_t *ent;

    ent = G_Spawn();
    ent->classname = "kamikaze timer";
    VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
    ent->r.svFlags |= SVF_NOCLIENT;
    ent->think = Kamikaze_DeathActivate;
    ent->nextthink = level.time + 5 * 1000;

    ent->activator = self;
}

#endif

/*
==================
CheckAlmostCapture
==================
*/
void CheckAlmostCapture( gentity_t *self, gentity_t *attacker ) {
    gentity_t	*ent;
    vec3_t		dir;
    char		*classname;

    // if this player was carrying a flag
    if ( self->client->ps.powerups[PW_REDFLAG]
            || self->client->ps.powerups[PW_BLUEFLAG]
#ifdef MISSIONPACK
            || self->client->ps.powerups[PW_NEUTRALFLAG]
#endif
       ) {
        // get the goal flag this player should have been going for
        if ( g_gametype.integer == GT_CTF || g_gametype.integer == GT_RTF) {
            if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
                classname = "team_CTF_blueflag";
            } else {
                classname = "team_CTF_redflag";
            }
        } else {
            if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
                classname = "team_CTF_redflag";
            } else {
                classname = "team_CTF_blueflag";
            }
        }
        ent = NULL;
        do {
            ent = G_Find(ent, FOFS(classname), classname);
        } while (ent && (ent->flags & FL_DROPPED_ITEM));
        // if we found the destination flag and it's not picked up
        if (ent && !(ent->r.svFlags & SVF_NOCLIENT) ) {
            // if the player was *very* close
            VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
            if ( VectorLength(dir) < 200 ) {
                self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
                if ( attacker->client ) {
                    attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
                }
            }
        }
    }
}

/*
==================
CheckAlmostScored
==================
*/
void CheckAlmostScored( gentity_t *self, gentity_t *attacker ) {
    gentity_t	*ent;
    vec3_t		dir;
    char		*classname;

    // if the player was carrying cubes
    if ( self->client->ps.generic1 ) {
        if ( self->client->sess.sessionTeam == TEAM_BLUE ) {
            classname = "team_redobelisk";
        } else {
            classname = "team_blueobelisk";
        }
        ent = G_Find(NULL, FOFS(classname), classname);
        // if we found the destination obelisk
        if ( ent ) {
            // if the player was *very* close
            VectorSubtract( self->client->ps.origin, ent->s.origin, dir );
            if ( VectorLength(dir) < 200 ) {
                self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
                if ( attacker->client ) {
                    attacker->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_HOLYSHIT;
                }
            }
        }
    }
}

/*
==================
player_die
==================
*/
void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
    gentity_t	*ent;
    int			anim;
    int			contents;
    int			killer;
    int			i;
    char		*killerName, *obit;

    if ( self->client->ps.pm_type == PM_DEAD ) {
        return;
    }

    if ( level.intermissiontime ) {
        return;
    }


    //unlagged - backward reconciliation #2
    // make sure the body shows up in the client's current position
    G_UnTimeShiftClient( self );
    //unlagged - backward reconciliation #2
#ifndef MISSIONPACK
    // BB - Drop Runes
    DropRunes( self );
#endif
    // check for an almost capture
    CheckAlmostCapture( self, attacker );
    // check for a player that almost brought in cubes
    CheckAlmostScored( self, attacker );

    if (self->client && self->client->offhandhook)
        Weapon_Offhand_HookFree(self->client->offhandhook);

    if (self->client && self->client->hook) {
        Weapon_HookFree(self->client->hook);
    }
#ifdef MISSIONPACK

    if ((self->client->ps.eFlags & EF_TICKING) && self->activator) {
        self->client->ps.eFlags &= ~EF_TICKING;
        self->activator->think = G_FreeEntity;
        self->activator->nextthink = level.time;
    }
#endif
    self->client->ps.pm_type = PM_DEAD;

    if ( attacker ) {
        killer = attacker->s.number;
        if ( attacker->client ) {
            killerName = attacker->client->pers.netname;
        } else {
            killerName = "<non-client>";
        }
    } else {
        killer = ENTITYNUM_WORLD;
        killerName = "<world>";
    }

    if ( killer < 0 || killer >= MAX_CLIENTS ) {
        killer = ENTITYNUM_WORLD;
        killerName = "<world>";
    }

    if ( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) {
        obit = "<bad obituary>";
    } else {
        obit = modNames[ meansOfDeath ];
    }

    G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n",
                killer, self->s.number, meansOfDeath, killerName,
                self->client->pers.netname, obit );

    // broadcast the death event to everyone
    ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY );
    ent->s.eventParm = meansOfDeath;
    ent->s.otherEntityNum = self->s.number;
    ent->s.otherEntityNum2 = killer;

    // BB: send powerups bitvector for detecting carrier kills,
    // quad kills, rune kills etc.
    ent->s.powerups = self->s.powerups;

    // BB SPAWN: send aLocation for carriers
    if (self->client->ps.powerups[PW_REDFLAG] ||
            self->client->ps.powerups[PW_BLUEFLAG]) {
        ent->s.modelindex = self->client->pers.aLocation;
    }

    ent->r.svFlags = SVF_BROADCAST;	// send to everyone

    self->enemy = attacker;

    self->client->ps.persistant[PERS_KILLED]++;

    if (attacker && attacker->client) {
        attacker->client->lastkilled_client = self->s.number;
        //battle
        if ( attacker == self || OnSameTeam (self, attacker ) ) {

            //FIXME:Diordna
            if ((g_gametype.integer != GT_CTF
                    || g_gametype.integer != GT_RTF)) {

                AddScore( attacker, self->r.currentOrigin, -1 );
            }

        } else {
            //FIXME:Diordna
            if ((g_gametype.integer != GT_CTF
                    || g_gametype.integer != GT_RTF)) {

                AddScore( attacker, self->r.currentOrigin, 1 );
            }

            // Alliance Joe health regen
            // Combat : award health instantly upon kill in
            // Alternate Restore mode
            else if (healthRestore()) {
                alternateRestoreKill(attacker, self);
            }

            //attacker->client->ps.stats[STAT_FRAGS]++;
            //end battle

            if( meansOfDeath == MOD_GAUNTLET ) {
                // play humiliation on player
                attacker->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++;

                // add the sprite over the player's head
                attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
                attacker->client->ps.eFlags |= EF_AWARD_GAUNTLET;
                attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;

                // also play humiliation on target
                self->client->ps.persistant[PERS_PLAYEREVENTS] ^= PLAYEREVENT_GAUNTLETREWARD;
            }

            // check for two kills in a short amount of time
            // if this is close enough to the last kill, give a reward sound
            if ( level.time - attacker->client->lastKillTime < CARNAGE_REWARD_TIME ) {
                // play excellent on player
                attacker->client->ps.persistant[PERS_EXCELLENT_COUNT]++;

                // add the sprite over the player's head
                attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
                attacker->client->ps.eFlags |= EF_AWARD_EXCELLENT;
                attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
            }
            attacker->client->lastKillTime = level.time;

        }
    } else {
        // Alliance CTF: Myrkul: in CTF mode let CombatCTFScoring do all scoring
        if (g_gametype.integer != GT_CTF
                || g_gametype.integer != GT_RTF) {
            AddScore( self, self->r.currentOrigin, -1 );
        }
    }

    // Add team bonuses
    // BB SCORING battlectfscoring
    // Bonuses on kill
    // Team_FragBonuses(self, inflictor, attacker);
    if (g_gametype.integer >= GT_CTF) {

        BattleCTFScoring(self, inflictor, attacker, DAMAGE_KILL, meansOfDeath);
    }
    // BB END SCORING

    // if I committed suicide, the flag does not fall, it returns.
    //NT - made it so your own flag never gets teleported back if you commit suicide
    if (meansOfDeath == MOD_SUICIDE) {
#ifdef MISSIONPACK
        if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {		// only happens in One Flag CTF
            Team_ReturnFlag(NULL,  TEAM_FREE );
            self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
        }
#endif
		if ( self->client->ps.powerups[PW_REDFLAG] &&
			self->client->ps.powerups[PW_BLUEFLAG]	) {		// only happens in standard RTF
			Team_ReturnFlag(NULL,  TEAM_RED );
			self->client->ps.powerups[PW_REDFLAG] = 0;
			Team_ReturnFlag(NULL,  TEAM_BLUE );
			self->client->ps.powerups[PW_BLUEFLAG] = 0;
		}
		
		if ( self->client->ps.powerups[PW_REDFLAG] &&
			self->client->sess.sessionTeam == TEAM_BLUE ) {		// only happens in standard CTF
			Team_ReturnFlag(NULL,  TEAM_RED );
			self->client->ps.powerups[PW_REDFLAG] = 0;
		}
		if ( self->client->ps.powerups[PW_BLUEFLAG] &&
			self->client->sess.sessionTeam == TEAM_RED ) {	// only happens in standard CTF
			Team_ReturnFlag(NULL,  TEAM_BLUE );
			self->client->ps.powerups[PW_BLUEFLAG] = 0;
		}
		//possible problems losing flags when there your own teams flags
    }


    // if client is in a nodrop area, don't drop anything (but return CTF flags!)
    //INCIN --- wait times on some maps when a flag has fallen into pit of death
    //is insanely long.. this should keep the flags in play at least.

    contents = trap_PointContents( self->r.currentOrigin, -1 );
    //if(	g_gametype.integer == GT_RTF)
    //{
    if ( !( contents & CONTENTS_NODROP) ||
            !( contents & CONTENTS_LAVA)	||
            !( contents & CONTENTS_SLIME)) {
        TossClientItems( self );
    }
    else {
        //NT - changed else-if's to if's because you can have two flags now
#ifdef MISSIONPACK
        if ( self->client->ps.powerups[PW_NEUTRALFLAG] ) {		// only happens in One Flag CTF
            Team_ReturnFlag( NULL, TEAM_FREE );
            self->client->ps.powerups[PW_NEUTRALFLAG] = 0;
        }
#endif	
		//this should work for RTF
        if ( self->client->ps.powerups[PW_REDFLAG] ) {		// only happens in standard CTF
            Team_ReturnFlag( NULL, TEAM_RED );
            self->client->ps.powerups[PW_REDFLAG] = 0;
        }
        if ( self->client->ps.powerups[PW_BLUEFLAG] ) {	// only happens in standard CTF
            Team_ReturnFlag( NULL, TEAM_BLUE );
            self->client->ps.powerups[PW_BLUEFLAG] = 0;
        }
    }

#ifdef MISSIONPACK
    TossClientPersistantPowerups( self );
    if( g_gametype.integer == GT_HARVESTER ) {
        TossClientCubes( self );
    }
#endif

    Cmd_Score_f( self );		// show scores
    // send updated scores to any clients that are following this one,
    // or they would get stale scoreboards
    for ( i = 0 ; i < level.maxclients ; i++ ) {
        gclient_t	*client;

        client = &level.clients[i];
        if ( client->pers.connected != CON_CONNECTED ) {
            continue;
        }
        if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
            continue;
        }
        // BB SPECTATOR

        if ( client->sess.sessionTeam != TEAM_SPECTATOR_RED ) {
            continue;
        }
        if ( client->sess.sessionTeam != TEAM_SPECTATOR_BLUE ) {
            continue;
        }

        // BB END SPECTATOR
        if ( client->sess.spectatorClient == self->s.number ) {
            Cmd_Score_f( g_entities + i );
        }
    }

    self->takedamage = qtrue;	// can still be gibbed

    self->s.weapon = WP_NONE;
    self->s.powerups = 0;
    self->r.contents = CONTENTS_CORPSE;

    self->s.angles[0] = 0;
    self->s.angles[2] = 0;
    LookAtKiller (self, inflictor, attacker);

    VectorCopy( self->s.angles, self->client->ps.viewangles );

    self->s.loopSound = 0;

    self->r.maxs[2] = -8;

    // don't allow respawn until the death anim is done
    // g_forcerespawn may force spawning at some later time
    self->client->respawnTime = level.time + 1700;


    // BB SCORING
    //SCORING: discourage commiting suicide to obtain better spawn
    // position.  Note this also covers the team change case.
    /*
    if (meansOfDeath == MOD_SUICIDE) {
        PrintMsg (self, S_COLOR_RED "Suicide: delaying respawn by 5 seconds\n");
        self->client->respawnTime += 5000;
    }  else if (attacker == self) {
        PrintMsg (self, S_COLOR_YELLOW "Killed self: delaying respawn by 2 seconds\n");
        self->client->respawnTime += 2000;
    }*/

    // remove powerups
    memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );

    // never gib in a nodrop
    if ( (self->health <= GIB_HEALTH && !(contents & CONTENTS_NODROP) && g_blood.integer) || meansOfDeath == MOD_SUICIDE) {
        // gib death
        GibEntity( self, killer );
    } else {
        // normal death
        static int i;

        switch ( i ) {
        case 0:
            anim = BOTH_DEATH1;
            break;
        case 1:
            anim = BOTH_DEATH2;
            break;
        case 2:
        default:
            anim = BOTH_DEATH3;
            break;
        }

        // for the no-blood option, we need to prevent the health
        // from going to gib level
        if ( self->health <= GIB_HEALTH ) {
            self->health = GIB_HEALTH+1;
        }

        self->client->ps.legsAnim =
            ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
        self->client->ps.torsoAnim =
            ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;

        G_AddEvent( self, EV_DEATH1 + i, killer );

        // the body can still be gibbed
        self->die = body_die;

        // globally cycle through the different death animations
        i = ( i + 1 ) % 3;

#ifdef MISSIONPACK

        if (self->s.eFlags & EF_KAMIKAZE) {
            Kamikaze_DeathTimer( self );
        }
#endif

    }

    trap_LinkEntity (self);


}


/*
================
CheckArmor
================
*/
int CheckArmor (gentity_t *ent, int damage, int dflags, qboolean selfdamage) {
    gclient_t	*client;
    int			save;
    int			count;

    if (!damage)
        return 0;

    client = ent->client;

    if (!client)
        return 0;

    if (dflags & DAMAGE_NO_ARMOR)
        return 0;

    //NT - self damage game setting and flag checking
    if ( ! selfdamage ) {
        // armor
        count = client->ps.stats[STAT_ARMOR];
        save = ceil( damage * ARMOR_PROTECTION );
        if (save >= count)
            save = count;

        if (!save)
            return 0;

        //client->ps.stats[STAT_ARMOR] -= save;
        ent->armor -= save;

        return save;
    } else {
        //NT - sometimes you just want to damage the armor, not the player
        ent->armor -= damage;
        if ( ent->armor < 0 )
            ent->armor = 0;

        return damage;
    }
}

/*
================
RaySphereIntersections
================
*/
int RaySphereIntersections( vec3_t origin, float radius, vec3_t point, vec3_t dir, vec3_t intersections[2] ) {
    float b, c, d, t;

    //	| origin - (point + t * dir) | = radius
    //	a = dir[0]^2 + dir[1]^2 + dir[2]^2;
    //	b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
    //	c = (point[0] - origin[0])^2 + (point[1] - origin[1])^2 + (point[2] - origin[2])^2 - radius^2;

    // normalize dir so a = 1
    VectorNormalize(dir);
    b = 2 * (dir[0] * (point[0] - origin[0]) + dir[1] * (point[1] - origin[1]) + dir[2] * (point[2] - origin[2]));
    c = (point[0] - origin[0]) * (point[0] - origin[0]) +
        (point[1] - origin[1]) * (point[1] - origin[1]) +
        (point[2] - origin[2]) * (point[2] - origin[2]) -
        radius * radius;

    d = b * b - 4 * c;
    if (d > 0) {
        t = (- b + sqrt(d)) / 2;
        VectorMA(point, t, dir, intersections[0]);
        t = (- b - sqrt(d)) / 2;
        VectorMA(point, t, dir, intersections[1]);
        return 2;
    } else if (d == 0) {
        t = (- b ) / 2;
        VectorMA(point, t, dir, intersections[0]);
        return 1;
    }
    return 0;
}

#ifdef MISSIONPACK
/*
================
G_InvulnerabilityEffect
================
*/
int G_InvulnerabilityEffect( gentity_t *targ, vec3_t dir, vec3_t point, vec3_t impactpoint, vec3_t bouncedir ) {
    gentity_t	*impact;
    vec3_t		intersections[2], vec;
    int			n;

    if ( !targ->client ) {
        return qfalse;
    }
    VectorCopy(dir, vec);
    VectorInverse(vec);
    // sphere model radius = 42 units
    n = RaySphereIntersections( targ->client->ps.origin, 42, point, vec, intersections);
    if (n > 0) {
        impact = G_TempEntity( targ->client->ps.origin, EV_INVUL_IMPACT );
        VectorSubtract(intersections[0], targ->client->ps.origin, vec);
        vectoangles(vec, impact->s.angles);
        impact->s.angles[0] += 90;
        if (impact->s.angles[0] > 360)
            impact->s.angles[0] -= 360;
        if ( impactpoint ) {
            VectorCopy( intersections[0], impactpoint );
        }
        if ( bouncedir ) {
            VectorCopy( vec, bouncedir );
            VectorNormalize( bouncedir );
        }
        return qtrue;
    } else {
        return qfalse;
    }
}
#endif
/*
============
T_Damage
 
targ		entity that is being damaged
inflictor	entity that is causing the damage
attacker	entity that caused the inflictor to damage targ
	example: targ=monster, inflictor=rocket, attacker=player
 
dir			direction of the attack for knockback
point		point at which the damage is being inflicted, used for headshots
damage		amount of damage being inflicted
knockback	force to be applied against targ as a result of the damage
 
inflictor, attacker, dir, and point can be NULL for environmental effects
 
dflags		these flags are used to control how T_Damage works
	DAMAGE_RADIUS			damage was indirect (from a nearby explosion)
	DAMAGE_NO_ARMOR			armor does not protect from this damage
	DAMAGE_NO_KNOCKBACK		do not affect velocity, just view angles
	DAMAGE_NO_PROTECTION	kills godmode, armor, everything
============
*/
void damageRegenerate(gentity_t *attacker, gentity_t *targ, int damage);
void damageRegenerate(gentity_t *attacker, gentity_t *targ, int damage) {
	int healthrestore;
	int armorrestore;
	
	armorrestore = 1;

	if (!attacker->client || !targ->client  ||
		attacker->client == targ->client || 
		attacker->client->ps.pm_type == PM_DEAD ||
        OnSameTeam(attacker, targ))
	{
		return;
	}

	healthrestore = (int)(damage * .20);
	
	if(armorRestore())
		armorrestore = (int)(damage * .10);

	//Com_Printf("attacker health: %i, targ health: %i\n", attacker->health,targ->health);
	//Com_Printf("attacker client ps stats[STAT_HEALTH] = %i\n",attacker->client->ps.stats[STAT_HEALTH]);

	if(g_damageRegen.integer ){
		if(damage > 0)
			//attacker->client->ps.stats[STAT_HEALTH] +=  (damage * 2/3);
			if(targ->client->ps.pm_type == PM_DEAD){
				attacker->health += 30;
				if(armorRestore())
					attacker->armor += 15;
			}
			else if(targ->client->ps.pm_type != PM_DEAD)
			{
				attacker->health = attacker->health + healthrestore;
				if(armorRestore())
					attacker->armor = attacker->armor + armorrestore;
			}

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

			if(armorRestore())
				attacker->client->ps.stats[STAT_ARMOR] = attacker->armor;

	}
	//Com_Printf("attacker->client->ps.stats[STAT_HEALTH] = %i\n",attacker->client->ps.stats[STAT_HEALTH]);
}

//NT - returns qtrue if the target was damaged
qboolean G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,
                   vec3_t dir, vec3_t point, int damage, int dflags, int mod ) {
    gclient_t	*client;
    int			take;
    int			save;
    int			asave;
    int			knockback;
    int			max;
    qboolean	selfdamage = qfalse;	//NT
#ifdef MISSIONPACK

    vec3_t		bouncedir, impactpoint;
#endif

    //BB
    int damageToZero;
    damageToZero = 0;

    if (!targ->takedamage) {
        return qfalse; //NT
    }

    // the intermission has allready been qualified for, so don't
    // allow any extra scoring




    if ( level.intermissionQueued ) {
        return qfalse; //NT
    }
#ifndef MISSIONPACK
    // alliance - Hugo - Make the runes bounce when hit
    if ( (targ->item) && (targ->item->giType == IT_PERSISTANT_POWERUP ) ) {
        BounceRune( targ, dir );
        return qfalse;
    }
#endif
    // Hugo
    //if ( ( !attacker ) )
    //return;

#ifdef MISSIONPACK

    if ( targ->client && mod != MOD_JUICED) {
        if ( targ->client->invulnerabilityTime > level.time) {
            if ( dir && point ) {
                G_InvulnerabilityEffect( targ, dir, point, impactpoint, bouncedir );
            }
            return qfalse; //NT
        }
    }
#endif

    if ( !inflictor ) {
        inflictor = &g_entities[ENTITYNUM_WORLD];
    }
    if ( !attacker ) {
        attacker = &g_entities[ENTITYNUM_WORLD];
    }

    // shootable doors / buttons don't actually have any health
    if ( targ->s.eType == ET_MOVER ) {
        if ( targ->use && targ->moverState == MOVER_POS1 ) {
            targ->use( targ, inflictor, attacker );
        }
        return qfalse; //NT
    }
#ifdef MISSIONPACK

    if( g_gametype.integer == GT_OBELISK && CheckObeliskAttack( targ, attacker ) ) {
        return qfalse; //NT
    }
#endif

    // reduce damage by the attacker's handicap value
    // unless they are rocket jumping



    if ( attacker->client && attacker != targ ) {
        max = attacker->client->ps.stats[STAT_MAX_HEALTH];
#ifdef MISSIONPACK

        if( bg_itemlist[attacker->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
            max /= 2;
        }
#endif
        if ( !healthRestore() )
            damage = damage * max / 100;
    }

    client = targ->client;

    if ( client ) {
        if ( client->noclip ) {
            return qfalse; //NT
        }
    }

    if ( !dir ) {
        dflags |= DAMAGE_NO_KNOCKBACK;
    } else {
        VectorNormalize(dir);
    }

    //NT - don't even fire the gauntlet at a teammate - keeps players from
    // getting free gauntlet ammo by smacking their teammates around
    if ( targ != attacker
            && OnSameTeam (targ, attacker)
            && !g_friendlyFire.integer && mod == MOD_GAUNTLET ) {
        return qfalse; //NT
    }

    knockback = damage;
    if ( knockback > 100 ) {
        knockback = 100;
    }
    if ( targ->flags & FL_NO_KNOCKBACK ) {
        knockback = 0;
    }
    if ( dflags & DAMAGE_NO_KNOCKBACK ) {
        knockback = 0;
    }

    // figure momentum add, even if the damage won't be taken
    if ( knockback && targ->client ) {
        vec3_t	kvel;
        float	mass;

        mass = 200;

        VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
        VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);

        // set the timer so that the other client can't cancel
        // out the movement immediately
        if ( !targ->client->ps.pm_time ) {
            int		t;

            t = knockback * 2;
            if ( t < 50 ) {
                t = 50;
            }
            if ( t > 100 ) {
                t = 100;
            }
            targ->client->ps.pm_time = t;
            targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
        }
    }

    // check for completely getting out of the damage
    if ( !(dflags & DAMAGE_NO_PROTECTION) ) {

        // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target
        // if the attacker was on the same team
#ifdef MISSIONPACK
        if ( mod != MOD_JUICED && targ != attacker && !(dflags & DAMAGE_NO_TEAM_PROTECTION) && OnSameTeam (targ, attacker)  ) {
#else
        if ( targ != attacker && OnSameTeam (targ, attacker)  ) {
#endif
            // Alliance: damage could still happen in partial friendly
            // fire
            if ( !g_friendlyFire.integer && !partialFriendlyFireEnabled()) {
                return qfalse; //NT
            }
        }
#ifdef MISSIONPACK

        if (mod == MOD_PROXIMITY_MINE) {
            if (inflictor && inflictor->parent && OnSameTeam(targ, inflictor->parent)) {
                return qfalse; //NT
            }
            if (targ == attacker) {
                return qfalse; //NT
            }
        }
#endif

        // check for godmode



        if ( targ->flags & FL_GODMODE ) {
            return qfalse; //NT
        }
    }

    // battlesuit protects from all radius damage (but takes knockback)
    // and protects 50% against all damage

    if ( client && client->ps.powerups[PW_BATTLESUIT] ) {

        G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );

        if ( ( dflags & DAMAGE_RADIUS ) || ( mod == MOD_FALLING ) ) {
            return qfalse; //NT
        }
        damage *= 0.5;
    }
    // add to the attacker's hit counter (if the target isn't a general entity like a prox mine)
    if (client && attacker->client && targ != attacker && targ->health > 0
            && targ->s.eType != ET_MISSILE
            && targ->s.eType != ET_GENERAL) {
        if ( OnSameTeam( targ, attacker ) ) {
            attacker->client->ps.persistant[PERS_HITS]--;
        } else {
            attacker->client->ps.persistant[PERS_HITS]++;
        }
        attacker->client->ps.persistant[PERS_ATTACKEE_ARMOR] = (targ->health<<8)|(client->ps.stats[STAT_ARMOR]);
    }

    // always give half damage if hurting self
    // calculated after knockback, so rocket jumping works
    if (targ == attacker && (mod == MOD_ROCKET || mod == MOD_ROCKET_SPLASH)
		&& inflictor->bounceCount == 0){
		damage *= 0.125; 
		//i want rocketjumps to be less damaging		
	}
	else if(targ == attacker) {
        damage *= 0.5;
    }

    if ( damage < 1 ) {
        damage = 1;
    }

#ifndef MISSIONPACK
    // BB
    // Resist Rune takes 25% damage
    if ( client && client->ps.powerups[PW_RUNERESIST] ) {
        G_AddEvent( targ, EV_POWERUP_BATTLESUIT, 0 );
        damage *= 0.75;
    }
    // BB
#endif

    //INCIN -- lethality mode damage
    if(g_lethality.integer > 1)
        damage = damage * atof(g_lethality.string);

    take = damage;
    save = 0;

    // save some from armor
    //NT - send self damage flag
    if(mod != MOD_WEAPONKNOCKBACK)
        asave = CheckArmor (targ, take, dflags, selfdamage);
    else
        asave = 0;

    take -= asave;
    /*
        if ( g_debugDamage.integer ) {
            G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number,
                      targ->health, take, asave );
        }
    */

    if ( !dir ) {
        dflags |= DAMAGE_NO_KNOCKBACK;
    } else {
        VectorNormalize(dir);
    }

    // alliance Joe no knockback in combat
    if(battleWeapons()/*&& attacker != targ*/)
        dflags |= DAMAGE_NO_KNOCKBACK;
    // end alliance

    knockback = damage;
    if ( knockback > 100 ) {
        knockback = 100;
    }
    if ( targ->flags & FL_NO_KNOCKBACK ) {
        knockback = 0;
    }
    if ( dflags & DAMAGE_NO_KNOCKBACK ) {
        knockback = 0;
    }

    // figure momentum add, even if the damage won't be taken
    if ( knockback && targ->client ) {
        vec3_t	kvel;
        float	mass;

        mass = 200;

        VectorScale (dir, g_knockback.value * (float)knockback / mass, kvel);
        VectorAdd (targ->client->ps.velocity, kvel, targ->client->ps.velocity);

        // set the timer so that the other client can't cancel
        // out the movement immediately
        if ( !targ->client->ps.pm_time ) {
            int		t;

            t = knockback * 2;
            if ( t < 50 ) {
                t = 50;
            }
            if ( t > 100 ) {
                t = 100;
            }
            targ->client->ps.pm_time = t;
            targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
        }
    }

    // add to the damage inflicted on a player this frame
    // the total will be turned into screen blends and view angle kicks
    // at the end of the frame
    if(mod == MOD_WEAPONKNOCKBACK)
        return qfalse;	//if a weapon passed damage for firing dont apply damage to player

    if ( client ) {
        if ( attacker ) {
            client->ps.persistant[PERS_ATTACKER] = attacker->s.number;
        } else {
            client->ps.persistant[PERS_ATTACKER] = ENTITYNUM_WORLD;
        }
        client->damage_armor += asave;
        client->damage_blood += take;
        client->damage_knockback += knockback;
        if ( dir ) {
            VectorCopy ( dir, client->damage_from );
            client->damage_fromWorld = qfalse;
        } else {
            VectorCopy ( targ->r.currentOrigin, client->damage_from );
            client->damage_fromWorld = qtrue;
        }

        // begin Hugo - Grapple release
        // BB removed
        /*
              if ( ( client->hook ) && ( damage >= 100 ) ) {
                  Weapon_HookFree( client->hook );
              }
        */
        // end Hugo

    }

    // See if it's the player hurting the emeny flag carrier
#ifdef MISSIONPACK

    if( g_gametype.integer >= GT_CTF || g_gametype.integer == GT_1FCTF ) {
#else
    if( g_gametype.integer >= GT_CTF) {
#endif
        Team_CheckHurtCarrier(targ, attacker);
    }

    if (targ->client) {
        // set the last client who damaged the target
        targ->client->lasthurt_client = attacker->s.number;
        targ->client->lasthurt_mod = mod;
    }

    // Alliance: if the victim is on the same team and partial friendly fire
    // is enabled..
    if (targ != attacker
            && OnSameTeam(targ, attacker)
            && partialFriendlyFireEnabled())

        take *= partialFriendlyFireFraction();

	if(g_damageRegen.integer > 0 && take){
		if (attacker->client && targ->client && attacker->client != targ->client ) {
			//Com_Printf("Take = %i\n", take);
			damageToZero = targ->health < take ? targ->health : take;
			damageRegenerate(attacker , targ , damageToZero);
		}
	}

    // do the damage
    if (take ) {
		
        // BB SCORING  health regen rules
        // Note only consider damage down to zero health
        //INCIN -- attacker->client was coming up NULL sometimes
        // an example is attacker was a multi trigger like falling death
#ifndef MISSIONPACK
        if (attacker->client && attacker->client->ps.powerups[PW_RUNEVAMPIRE] ) {
            //PrintMsg (attacker, S_COLOR_RED "PW_RUNEVAMPIRE\n");
            G_AddEvent( attacker, EV_POWERUP_VAMPIRE, 0 );
            damageToZero = targ->health < take ? targ->health : take;
            damageRestore(attacker, targ, damageToZero);
        }
#endif
        // BB SCORING: call scoring on every hit to award partial bonuses
        if (g_gametype.integer >= GT_CTF && g_damageScoring.integer) {
            BattleCTFScoring(targ, inflictor, attacker, damageToZero, mod);
        }
        targ->health = targ->health - take;
        if ( targ->client ) {
            targ->client->ps.stats[STAT_HEALTH] = targ->health;
            targ->client->ps.stats[STAT_ARMOR] = targ->armor;
        }

        if ( targ->health <= 0 ) {
            if ( client )
                targ->flags |= FL_NO_KNOCKBACK;

            if (targ->health < -999)
                targ->health = -999;

            targ->enemy = attacker;
            targ->die (targ, inflictor, attacker, take, mod);
            return qtrue; //NT
        }
        else if ( targ->pain ) {
            targ->pain (targ, attacker, take);
        }
    }

    return qtrue; //NT
}


/*
============
CanDamage
 
Returns qtrue if the inflictor can directly damage the target.  Used for
explosions and melee attacks.
============
*/
qboolean CanDamage (gentity_t *targ, vec3_t origin) {
    vec3_t	dest;
    trace_t	tr;
    vec3_t	midpoint;

    // use the midpoint of the bounds instead of the origin, because
    // bmodels may have their origin is 0,0,0
    VectorAdd (targ->r.absmin, targ->r.absmax, midpoint);
    VectorScale (midpoint, 0.5, midpoint);

    VectorCopy (midpoint, dest);
    trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
    if (tr.fraction == 1.0 || tr.entityNum == targ->s.number)
        return qtrue;

    // this should probably check in the plane of projection,
    // rather than in world coordinate, and also include Z
    VectorCopy (midpoint, dest);
    dest[0] += 15.0;
    dest[1] += 15.0;
    trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
    if (tr.fraction == 1.0)
        return qtrue;

    VectorCopy (midpoint, dest);
    dest[0] += 15.0;
    dest[1] -= 15.0;
    trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
    if (tr.fraction == 1.0)
        return qtrue;

    VectorCopy (midpoint, dest);
    dest[0] -= 15.0;
    dest[1] += 15.0;
    trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
    if (tr.fraction == 1.0)
        return qtrue;

    VectorCopy (midpoint, dest);
    dest[0] -= 15.0;
    dest[1] -= 15.0;
    trap_Trace ( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID);
    if (tr.fraction == 1.0)
        return qtrue;


    return qfalse;
}


/*
============
G_RadiusDamage
============
*/
qboolean G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage,
                          float radius, gentity_t *ignore, int mod) {
    return G_RadiusDamageWithInflictor(origin, attacker, NULL,
                                       damage, radius, ignore, mod);
}

/*
============
G_RadiusDamageWithInflictor
============
*/
qboolean G_RadiusDamageWithInflictor (vec3_t origin, gentity_t *attacker,
                                      gentity_t *inflictor, float damage,
                                      float radius, gentity_t *ignore, int mod) {
    float		points, dist;
    gentity_t	*ent;
    int			entityList[MAX_GENTITIES];
    int			numListedEntities;
    vec3_t		mins, maxs;
    vec3_t		v;
    vec3_t		dir;
    int			i, e;
    qboolean	hitClient = qfalse;

    if ( radius < 1 ) {
        radius = 1;
    }

    for ( i = 0 ; i < 3 ; i++ ) {
        mins[i] = origin[i] - radius;
        maxs[i] = origin[i] + radius;
    }

    numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );

    for ( e = 0 ; e < numListedEntities ; e++ ) {
        ent = &g_entities[entityList[ e ]];

        if (ent == ignore)
            continue;
        if (!ent->takedamage)
            continue;

        // find the distance from the edge of the bounding box
        for ( i = 0 ; i < 3 ; i++ ) {
            if ( origin[i] < ent->r.absmin[i] ) {
                v[i] = ent->r.absmin[i] - origin[i];
            } else if ( origin[i] > ent->r.absmax[i] ) {
                v[i] = origin[i] - ent->r.absmax[i];
            } else {
                v[i] = 0;
            }
        }

        dist = VectorLength( v );
        if ( dist >= radius ) {
            continue;
        }

        // begin Joe
        // like q2 expert T_ScaledRadiusDamage()
        // lower the dropoff, higher the damage
        // inflictor->s.weapon field of hg same as gl
        if (mod == MOD_HANDGRENADE_SPLASH) {
            points = damage - BATTLE_HG_DROPOFF * dist;
        } else {
            if (BG_battleWeaponsEnabled()) {
                points = damage - BATTLE_DEFAULT_DROPOFF * dist;
                //                    G_Printf("scaled damage: %d  old damage %d dist %d weap "
                //                               "%d\n", (int)points, (int)(damage*(1.0 -
                //                              dist/radius)), (int)dist, inflictor->s.weapon);
            }
            else {
                points = damage * ( 1.0 - dist / radius );
            }
        }

        if( CanDamage (ent, origin) ) {
            if( LogAccuracyHit( ent, attacker ) ) {
                hitClient = qtrue;
            }
            VectorSubtract (ent->r.currentOrigin, origin, dir);
            // push the center of mass higher than the origin so players
            // get knocked into the air more
            dir[2] += 24;
            // Alliance: Myrkul: pass inflictor if available
            G_Damage (ent, inflictor, attacker, dir, origin, (int)points, DAMAGE_RADIUS, mod);
        }
    }

    return hitClient;
}
