/* Alliance code that's general to the whole game. */

#include "g_local.h"

// there's a min macro in msvc but not qvm or linux
#ifdef Q3_VM
#define min(a, b)	(a) < (b) ? a : b
#endif

#ifdef __linux__
#define min(a, b)       (a) < (b) ? a : b
#endif

// Hugo - maplist
//#define MAPLIST_PATH	"maplist.txt"	//INCIN not being used --removed 12-20-2002
#define MAX_MAPS	60

char *maplist=NULL;
//char **maps;
char maps[MAX_MAPS][MAX_TOKEN_CHARS];

int maxmaps;

// BB map remap variables
#define ENTMAP_PREFIX	"maps/"
#define ENTMAP_SUFFIX	".remap"

char *entmap=NULL;

#ifdef MISSIONPACK
int weaponAmmoRestore[] =
    {
        0,
        0,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_MACHINEGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_SHOTGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_GRENADE_LAUNCHER,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_ROCKET_LAUNCHER,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_LIGHTNING,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_RAILGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_PLASMAGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_BFG,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_HANDGRENADE,//INCIN swapped with 0
		1,											//WP_NAILGUN,
		1,											//WP_PROX_LAUNCHER,
		10,											//WP_CHAINGUN,
		-1,											//this is the grapple
        -1											//new grapple infinite usage
    };

int ammoMax[] =
    {
        0,
        0,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_MACHINEGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_SHOTGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_GRENADE_LAUNCHER_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_ROCKET_LAUNCHER_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_LIGHTNING_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_RAILGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_PLASMAGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_BFG_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_HANDGRENADE_MAX,//INCIN swapped with 0 below this
		15,
		8,
		130,
        -1,												//this is the grapple
        -1												//infinite usage
    };

#else

int weaponAmmoRestore[] =
    {
        0,
        0,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_MACHINEGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_SHOTGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_GRENADE_LAUNCHER,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_ROCKET_LAUNCHER,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_LIGHTNING,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_RAILGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_PLASMAGUN,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_BFG,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_HANDGRENADE,//INCIN swapped with 0
 //       0,											//this is the grapple
        -1											//new grapple infinite usage
    };

int ammoMax[] =
    {
        0,
        0,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_MACHINEGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_SHOTGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_GRENADE_LAUNCHER_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_ROCKET_LAUNCHER_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_LIGHTNING_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_RAILGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_PLASMAGUN_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_BFG_MAX,
        BATTLEBOTS_DEFAULT_AMMO_RESTORE_HANDGRENADE_MAX,//INCIN swapped with 0 below this
//        0,												//this is the grapple
        -1												//infinite usage
    };
#endif
//  called from game/g_client.c:ClientSpawn() everytime a player dies

void giveFreeGear(gentity_t *ent) {
    gclient_t *client = ent->client;

    if (freeGear() == qfalse)
        return;

    // the ordering of these weapon gives determines the default weapon
    if (freeShotgun())
        // Joe
    {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SHOTGUN);
        client->ps.ammo[WP_SHOTGUN] = ammoMax[WP_SHOTGUN];
        client->ps.weapon = (client->ps.weapon >= WP_SHOTGUN) ?
                            client->ps.weapon : WP_SHOTGUN;
    }
    if (freeGrenadeLauncher()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRENADE_LAUNCHER);
        client->ps.ammo[WP_GRENADE_LAUNCHER] = 	ammoMax[WP_GRENADE_LAUNCHER];
        client->ps.weapon = (client->ps.weapon >= WP_GRENADE_LAUNCHER)
                            ? client->ps.weapon : WP_GRENADE_LAUNCHER;
    }

    // begin handgrenade Joe
    if (freeHandgrenade()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_HANDGRENADE);
        client->ps.ammo[WP_HANDGRENADE] = ammoMax[WP_HANDGRENADE];
        // this seems to be for both hg and gl
        client->ps.weapon = (client->ps.weapon >= WP_HANDGRENADE) ?
                            client->ps.weapon : WP_HANDGRENADE;
    }
    // end handgrenade Joe


    if (freeRailgun()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_RAILGUN);
        client->ps.ammo[WP_RAILGUN] = ammoMax[WP_RAILGUN];
        client->ps.weapon = (client->ps.weapon >= WP_RAILGUN) ?
                            client->ps.weapon : WP_RAILGUN;
    }



    if (freeLightningGun()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_LIGHTNING);
        client->ps.ammo[WP_LIGHTNING] = ammoMax[WP_LIGHTNING];
        client->ps.weapon = (client->ps.weapon >= WP_LIGHTNING) ?
                            client->ps.weapon : WP_LIGHTNING;
    }
    if (freePlasmaGun()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_PLASMAGUN);
        client->ps.ammo[WP_PLASMAGUN] = ammoMax[WP_PLASMAGUN];
        client->ps.weapon = (client->ps.weapon >= WP_PLASMAGUN) ?
                            client->ps.weapon : WP_PLASMAGUN;
    }
    if (freeRocketLauncher()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ROCKET_LAUNCHER);
        client->ps.ammo[WP_ROCKET_LAUNCHER] = ammoMax[WP_ROCKET_LAUNCHER];
        client->ps.weapon = (client->ps.weapon >= WP_ROCKET_LAUNCHER)
                            ? client->ps.weapon : WP_ROCKET_LAUNCHER;
    }

    if (freeBFG()) {
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BFG);
        client->ps.ammo[WP_BFG] = ammoMax[WP_BFG];
        client->ps.weapon = (client->ps.weapon >= WP_BFG) ?
                            client->ps.weapon : WP_BFG;
    }

    // Joe likes rocket, without this keep ending up with plasmagun
    if (client->ps.stats[STAT_WEAPONS] & ( 1 << WP_ROCKET_LAUNCHER))
        client->ps.weapon = WP_ROCKET_LAUNCHER;

    /* dio - moved to
       if(freeOnhandGrapple()) {
    	client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK);
    	client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
    	client->ps.weapon = (client->ps.weapon >= WP_GRAPPLING_HOOK) ?
                               client->ps.weapon : WP_GRAPPLING_HOOK;
    }
    */
#ifdef MISSIONPACK
	if(freeCHAINGUN()){
		client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_CHAINGUN);
        client->ps.ammo[WP_CHAINGUN] = ammoMax[WP_CHAINGUN];
        client->ps.weapon = (client->ps.weapon >= WP_CHAINGUN) ?
                            client->ps.weapon : WP_CHAINGUN;
	}
	
	if(freeNAILGUN()){
		client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_NAILGUN);
        client->ps.ammo[WP_NAILGUN] = ammoMax[WP_NAILGUN];
        client->ps.weapon = (client->ps.weapon >= WP_NAILGUN) ?
                            client->ps.weapon : WP_NAILGUN;
	}

	if(freePROX_LAUNCHER()){
		client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_PROX_LAUNCHER);
        client->ps.ammo[WP_PROX_LAUNCHER] = ammoMax[WP_PROX_LAUNCHER];
        client->ps.weapon = (client->ps.weapon >= WP_PROX_LAUNCHER) ?
                            client->ps.weapon : WP_PROX_LAUNCHER;
	}

#endif
} // end giveFreeGear()

gitem_t *BG_FindItemByClass( const char *classname ) {
    gitem_t *it;

    for ( it = bg_itemlist + 1 ; it->classname ; it++ ) {
        if ( !Q_stricmp( it->classname, classname ) )
            return it;
    }

    return NULL;
}



// called from game/g_items.c:ClearRegisteredItems() once per map
void precacheFreeGear(void) {

    // precaching ammo in a desparate attempt to reduce lag -- not necessary
    // ammo_shells, ammo_bullets, ammo_grenades, cells, lightning, rockets, slugs, bfg,
    if (freeGear() == qfalse)
        return;

    if (freeMachinegun()) {
        RegisterItem( BG_FindItemForWeapon( WP_MACHINEGUN ) );
        //              RegisterItem (BG_FindItemByClass("ammo_grenades"));
    }

    if (freeShotgun()) {
        RegisterItem( BG_FindItemForWeapon( WP_SHOTGUN ) );
        //              RegisterItem (BG_FindItemByClass("ammo_shells"));
    }
    if (freeGrenadeLauncher()) {
        RegisterItem( BG_FindItemForWeapon( WP_GRENADE_LAUNCHER ) );
        //              RegisterItem (BG_FindItemByClass("ammo_grenades"));
    }

    // begin handgrenade Joe




    if (freeHandgrenade()) {
        RegisterItem( BG_FindItemForWeapon( WP_HANDGRENADE ) );
    }
    // end handgrenade Joe



    if (freeRailgun()) {
        RegisterItem( BG_FindItemForWeapon( WP_RAILGUN ) );
        //              RegisterItem (BG_FindItemByClass("ammo_slugs"));
    }
    if (freePlasmaGun()) {
        RegisterItem( BG_FindItemForWeapon( WP_PLASMAGUN ) );
        //              RegisterItem (BG_FindItemByClass("ammo_cells"));
    }
    if (freeLightningGun()) {
        RegisterItem( BG_FindItemForWeapon( WP_LIGHTNING ) );
        //              RegisterItem (BG_FindItemByClass("ammo_lightning"));
    }
    if (freeRocketLauncher()) {
        RegisterItem( BG_FindItemForWeapon( WP_ROCKET_LAUNCHER ) );
        //              RegisterItem (BG_FindItemByClass("ammo_rockets"));
    }

    if (freeBFG()) {
        RegisterItem( BG_FindItemForWeapon( WP_BFG ) );
        //              RegisterItem (BG_FindItemByClass("ammo_bfg"));
    }
#ifdef MISSIONPACK
	if(freeCHAINGUN()){
		RegisterItem( BG_FindItemForWeapon(WP_CHAINGUN));
	}
	
	if(freeNAILGUN()){
		RegisterItem( BG_FindItemForWeapon(WP_NAILGUN));
	}

	if(freePROX_LAUNCHER()){
		RegisterItem( BG_FindItemForWeapon(WP_PROX_LAUNCHER));
	}
#endif


}

// begin health regen rules
// See a_defs.h for other constants
#define BATTLE_OVER_MAX_DROP                                    60.
#define BATTLE_OVER_MAX_MIN_DROP                                10.
#define HIT_REGEN_RATIO                                         0.4
#define KILL_INSTANT_HEALTH                                     30
#define BATTLE_HEALTH_FROM_DROPPED_WEAPON                       15

// 1.  when hit enemy, get .2 * damage to target in health
// 2.  get 10 * (1 - health/max_health) every 2 seconds, higher health = slower rate
// 3.  get 20 when picking up victim's weapon
// 4.  when kill victim, get 25
// 5.  can get over max health when gaining health on hits and kills, but health
//     drops back down to max very fast.  Note this is needed for the case where
//     two players near max health hit each other almost simultaneously: if you
//     can't go over max, the player who scores the first hit throws away his
//     health bonus whereas the player who scores the second hit does get the
//     bonus, since he is now injured.



// Joe - health regen rule 1
// called from game/g_combat.c::G_Damage()




void damageRestore(gentity_t *attacker, gentity_t *targ, int damage) {
    // both are players, they aren't the same players, both are alive, and they
    // are not on the same team if teamplay is enabled
	if (!attacker->client || !targ->client || attacker == targ ||
            attacker->client->ps.pm_type == PM_DEAD ||
            targ->client->ps.pm_type == PM_DEAD ||
            OnSameTeam(attacker, targ)
#ifdef MISSIONPACK
			//|| attacker->client->ps.powerups[PW_REGEN]
#else
			|| attacker->client->ps.powerups[PW_RUNEREGEN]
#endif
            || attacker->client->ps.powerups[PW_REGEN]
			)
        return;
	else
	{
	    if (!g_healthRestore.integer) {
			attacker->health += ceil(HIT_REGEN_RATIO * damage);
		}
	}

    // Note it's possible to fire, die, respawn, have your shot hit and get some
    // bonus health.  This is fine; in fact it would be good to keep track of
    // health earned while dead and give it out on spawn, but it's such a rare
    // case we haven't bothered.

    // In Alternate Restore mode give health back on every hit
}

// health regen rule 2
// called from game/g_active.c:ClientTimerActions() once a second
// q2 had 10 * (1 - health/maxhealth) every 2 sec
void healthRegen(gentity_t *ent) {
    float rate;
    int healthAmount;
    int healthDiff;


    if(
#ifndef MISSIONPACK
		ent->client->ps.powerups[PW_RUNEREGEN] && 
#endif
		ent->health > BATTLE_MAX_HEALTH && !g_damageRegen.integer) {
        if(ent->health > BATTLE_MAX_HEALTH) {
            ent->health =- 5;
            return;
        }
    }
    //else if(ent->health > BATTLE_MAX_HEALTH){

    //}

    // rapidly drop toward max health if over
	if(!g_damageRegen.integer){ 
		if (ent->health > BATTLE_MAX_HEALTH) {
			// The higher over max health you are, the faster you drop
			rate = (float)(ent->health)/BATTLE_MAX_HEALTH - 1.0;
			// Use a minimum health drop to avoid asymptotically approaching max_health
			healthDiff = rate * BATTLE_OVER_MAX_DROP;
			healthAmount = BATTLE_OVER_MAX_MIN_DROP > healthDiff ?
						   BATTLE_OVER_MAX_MIN_DROP :
						   healthDiff;
			ent->health -= healthAmount;
			if (ent->health < BATTLE_MAX_HEALTH) {
				ent->health = BATTLE_MAX_HEALTH;
			}

			// since we're over max, obviously no regen needed
			return;
		}
	}
	else
	{
		if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
			ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] * 2;
			return;
		}
	}



    if (level.time / 1000 % g_healthRestoreInterval.integer != 0)
        return;

    // let getting 100 health sphere or regen powerup work
    // 100 health is a powerup according to the manual
    // healthRegen is only called if player doesn't have regen powerup

    healthDiff = g_healthRestoreMax.integer - ent->health;
    if (healthDiff > 0) {
        // higher the health slower the rate
        //Crock - added the (float) beside combat_max_health
        rate = 1.0 - (float)(ent->health)/(float)BATTLE_MAX_HEALTH;
        if (rate <= 0)
            return;
        healthAmount = ceil(rate * g_healthRestoreStep.integer);
        ent->health += min(healthAmount, healthDiff);
        //ent->health +=  rate;
        // Joe - this is how the powerup regen plays the sound
        //G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
    }
}

// health regen rule 2
// called from game/g_active.c:ClientTimerActions() once a second
// q2 had 10 * (1 - health/maxhealth) every 2 sec




void armorRegen(gentity_t *ent) {
    float rate;
    int armorAmount;
    int armorDiff;



    //INCIN -- if they have this rune draw less armor drop
    if(
#ifndef MISSIONPACK
		ent->client->ps.powerups[PW_RUNEREGEN] && 
#endif
		ent->armor > BATTLE_MAX_ARMOR && !g_damageRegen.integer) {
        if(ent->armor > BATTLE_MAX_ARMOR) {
            ent->armor =- 5;
            return;
        }
    }

    // rapidly drop toward max health if over
	//theres a steady drain on health and arrmor anyways so skip over this
	
	if(!g_damageRegen.integer){ 
			
		if (ent->armor > BATTLE_MAX_ARMOR) {
			// The higher over max health you are, the faster you drop
			rate = (float)(ent->armor)/BATTLE_MAX_ARMOR - 1.0;
			// Use a minimum health drop to avoid asymptotically approaching max_health
			armorDiff = rate * BATTLE_OVER_MAX_DROP;
			armorAmount = BATTLE_OVER_MAX_MIN_DROP > armorDiff ?
						  BATTLE_OVER_MAX_MIN_DROP :
						  armorDiff;
			ent->armor -= armorAmount;
			if (ent->armor < BATTLE_MAX_ARMOR) {
				ent->armor = BATTLE_MAX_ARMOR;
			}

			// since we're over max, obviously no regen needed
			return;
		}
	}
	else
	{
		if (ent->armor > ent->client->ps.stats[STAT_MAX_ARMOR] * 1.75) {
			ent->armor = ent->client->ps.stats[STAT_MAX_ARMOR] * 1.75;
			return;
		}
	}//already over max so dont restore


	if(g_armorRestore.integer &&  g_armorRestoreInterval.integer > 0)
	{
		if ((level.time / 1000) % g_armorRestoreInterval.integer != 0 )
        return;
	}
//#define BATTLEBOTS_DEFAULT_ARMOR_RESTORE_INTERVAL		"1"
//#define BATTLEBOTS_DEFAULT_ARMOR_RESTORE_STEP			"5"
//#define BATTLEBOTS_DEFAULT_ARMOR_RESTORE_MAX			"120"

	if(!g_armorRestoreMax.integer)
		armorDiff = (int)BATTLEBOTS_DEFAULT_ARMOR_RESTORE_MAX - ent->armor;
	// % 0 = 0 == ow
	/*
    if (level.time / 1000 % g_armorRestoreInterval.integer != 0) //didnt like this one time
        return;
	*/
    // let getting 100 health sphere or regen powerup work
    // 100 health is a powerup according to the manual
    // healthRegen is only called if player doesn't have regen powerup
	else
		armorDiff = g_armorRestoreMax.integer - ent->armor;

    if (armorDiff > 0) {
        // higher the health slower the rate
        //Crock - added the (float) beside combat_max_health
        rate = 1.0 - (float)(ent->armor)/(float)BATTLE_MAX_ARMOR;
        if (rate <= 0)
            return;
        armorAmount = ceil(rate * g_armorRestoreStep.integer);
        ent->armor += min(armorAmount, armorDiff);

        // Joe - this is how the powerup regen plays the sound
        //G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
    }
}


// called from game/g_items.c::Touch_Item()
// Joe health regen rule 3




void CombatPickupDroppedWeapon(gentity_t *ent, gentity_t *other) {
    int healthDiff;

    if (other->client->ps.powerups[PW_REGEN])
        return;

    // In alternate restore, give health on weapon pickup
    // Note this health bonus doesn't go over max
    healthDiff = BATTLE_MAX_HEALTH - other->health;
    if (healthDiff > 0) {
        if (g_healthRestore.integer) {
            other->health += min(BATTLE_HEALTH_FROM_DROPPED_WEAPON, healthDiff);
        }
    }
}

// Joe health regen rule 4
// called from game/g_combat.c::player_die()
void alternateRestoreKill(gentity_t *attacker, gentity_t *target) {
    if (attacker->client->ps.pm_type == PM_DEAD || attacker->client->ps.powerups[PW_REGEN])
        return;

    // give health
    attacker->health += KILL_INSTANT_HEALTH;
}
// end health regen rules



// called from game/g_active.c:ClientTimerActions() once a second
void ammoRegen(gclient_t *client) {
    int weapon;
    //    int ammoDiff;

    if (level.time / 1000 % g_ammoRestoreInterval.integer != 0)
        return;

#ifdef MISSIONPACK
	for (weapon = WP_MACHINEGUN/*WP_NAILGUN*/; weapon <= WP_CHAINGUN/*banshee was WP_MACHINEGUN to WP_BFG*/; weapon++) { 
#else
	// from 2 to 9
    for (weapon = WP_MACHINEGUN; weapon <= WP_BFG/*banshee was WP_MACHINEGUN to WP_BFG*/; weapon++) {
#endif
	/*
                ammoDiff = ammoMax[weapon] - client->ps.ammo[weapon];
                // get exactly to the max but not over
                // but let gamer get > max ammo from corpse's weapon
                if (ammoDiff > 0)
                  client->ps.ammo[weapon] +=
                  min(weaponAmmoRestore[weapon], ammoDiff); 
        */
        // Joe - screw it do it the old way
        // keep picking up weapons from going over max
        client->ps.ammo[weapon] += weaponAmmoRestore[weapon];
        if (client->ps.ammo[weapon] > ammoMax[weapon])
            client->ps.ammo[weapon] = ammoMax[weapon];
    }

    // handgrenade is weapon 11 //incin --- NOPE its 10 now
    // disabling weapon 11 would help in running server side only
    if (freeHandgrenade()) // should do this check for all of them
    {
        weapon = WP_HANDGRENADE;
        /*
                ammoDiff = ammoMax[weapon] - client->ps.ammo[weapon];
                if (ammoDiff > 0)
                    client->ps.ammo[weapon] += min(weaponAmmoRestore[weapon],
                                                   ammoDiff);
        */
        client->ps.ammo[weapon] += weaponAmmoRestore[weapon];
        if (client->ps.ammo[weapon] > ammoMax[weapon])
            client->ps.ammo[weapon] = ammoMax[weapon];

    }

} // end ammoRegen()


/****************************
 * game config predicates
 ****************************/

// return qtrue if free gear is enabled
qboolean freeGear(void) {
    switch (g_freeGear.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free shotguns are enabled
// note that you still have to check for free gear.
qboolean freeShotgun(void) {
    switch (g_freeShotgun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free Machinegun are enabled
// note that you still have to check for free gear.
qboolean freeMachinegun(void) {
    switch (g_freeMachinegun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free Grenade Launchers are enabled
// note that you still have to check for free gear.
qboolean freeGrenadeLauncher(void) {
    switch (g_freeGrenadeLauncher.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// begin handgrenade Joe
qboolean freeHandgrenade(void) {
    switch (g_freeHandgrenade.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}
// end handgrenade Joe

// returns qtrue if free Lightning Guns are enabled
// note that you still have to check for free gear.
qboolean freeLightningGun(void) {
    switch (g_freeLightningGun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free RLs are enabled
// note that you still have to check for free gear.
qboolean freeRocketLauncher(void) {
    switch (g_freeRocketLauncher.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free Plasma Guns are enabled
// note that you still have to check for free gear.
qboolean freePlasmaGun(void) {
    switch (g_freePlasmaGun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free Railguns are enabled
// note that you still have to check for free gear.
qboolean freeRailgun(void) {
    switch (g_freeRailgun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if free BFGs are enabled
// note that you still have to check for free gear.
qboolean freeBFG(void) {
    switch (g_freeBFG.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

#ifdef MISSIONPACK
qboolean freeCHAINGUN(void){
    switch (g_freeChaingun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

qboolean freeNAILGUN(void){
    switch (g_freeNailgun.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

qboolean freePROX_LAUNCHER(void) {
    switch (g_freeProxLauncher.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }

}

#endif

qboolean freeOnhandGrapple(void) {

    if (g_grappleOnHand.integer)
        return qtrue;
    else
        return qfalse;
}


// returns qtrue if alternate restore is enabled.
qboolean healthRestore(void) {

    if (g_healthRestore.integer)
        return qtrue;
    else
        return qfalse;

}

// returns qtrue if alternate restore is enabled.
qboolean armorRestore(void) {
    if (g_armorRestore.integer)

        return qtrue;
    else
        return qfalse;

}

// returns qtrue if ammo restore is enabled.
qboolean ammoRestore(void) {
    switch (g_ammoRestore.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if combat weapons are enabled.
qboolean battleWeapons(void) {
    switch (g_battleWeapons.integer) {
    case TRI_TRUE:
        return qtrue;
    default:
        return qfalse;
    }
}

// returns qtrue if the grapple is give to all players
// (via cvars as opposed to target_give)
qboolean globalGrapple(void) {
    if (g_grappleGlobal.integer)
        return qtrue;
    else
        return qfalse;
}

// Returns the string necessary to announce a team imbalance.
char *stringTeamImbalance(int blueMinusRed) {
    char *players="players";
    int num=blueMinusRed;

    if (blueMinusRed < 0)
        num*=-1;
    if (blueMinusRed == 1)
        players="player";

    if (blueMinusRed > 0)
        return va("%s has " S_COLOR_YELLOW "%d"
                  S_COLOR_WHITE " more than %s\n", TeamName(TEAM_BLUE),
                  num, TeamName(TEAM_RED));
    else
        return va("%s has " S_COLOR_YELLOW "%d" S_COLOR_WHITE
                  " more than %s\n", TeamName(TEAM_RED), num,
                  TeamName(TEAM_BLUE));
}

// Announces a large difference in player counts between the two teams.
// argument is the difference between the number of players in blue and
// red teams.
void announceTeamImbalance(int blueMinusRed) {
    trap_SendServerCommand( -1, va("cp \"%s\"",
                                   stringTeamImbalance(blueMinusRed)));
}

// returns qtrue if teams are imbalanced. Returns an integer as
// an output argument that indicates the difference in size between blue and red teams.
qboolean teamsImbalanced(int *blueMinusRed) {
    // count up all the teams
    int blueSize, redSize;

    blueSize = TeamCount(-1, TEAM_BLUE);
    redSize = TeamCount(-1, TEAM_RED);

    *blueMinusRed = blueSize - redSize;

    if (*blueMinusRed > 1 || *blueMinusRed < -1) {
        return qtrue;
    }

    return qfalse;
}

// checks the number of players on each team.
// if one team has at least 2 more players than the other team,
// a message is flashed.
void checkTeamSizes(void) {
    int blueMinusRed;

    if (teamsImbalanced(&blueMinusRed))
        announceTeamImbalance(blueMinusRed);
}

// 0 - 7
// typedef enum {
//      CHAN_AUTO,
//      CHAN_LOCAL,             // menu sounds, etc
//      CHAN_WEAPON,
//      CHAN_VOICE,
//      CHAN_ITEM,
//      CHAN_BODY,
//      CHAN_LOCAL_SOUND,       // chat messages, etc
//      CHAN_ANNOUNCER          // announcer voices, etc
// } soundChannel_t;

// this and va() are frought with pointer pitfalls
// strtok isn't even in the friggin qvm
//void lastToken(char *s, char *delim, char *output, int outputsize)
//{
//    char *token;
//    char *result;
//    char buf[MAX_TOKEN_CHARS];
//
//    Com_sprintf(buf, sizeof(buf), "%s", s);
//
//    if((result = strtok(buf, delim)))
//      while((token = strtok(NULL, delim)))
//          result = token;
//    Com_sprintf(output, outputsize, "%s", result);
//}

void lastToken(char *input, char *delim, char *output, int outputsize) {
    int i;
    int delimLastLoc = -1;

    output[0] = 0;

    //  input[i] != NULL gives error in qvm compile
    //  really screws me up if I try to compile it with wine and wcmd, since
    //  if errorlevel isn't supported in game.bat
    //  makes a .asm file even with the error
    for (i = 0; input[i] != 0; i++)
        if (input[i] == delim[0])
            delimLastLoc = i;

    if ( delimLastLoc != -1 )
        Com_sprintf(output, outputsize, "%s", &(input[delimLastLoc+1]));

}

void playVoiceSound(gentity_t *ent, char *soundfile) {
    char soundfilepath[MAX_TOKEN_CHARS];
    char ending[MAX_TOKEN_CHARS];

    // soundfile is like "voice/male/cover me"
    Com_sprintf(soundfilepath, sizeof(soundfilepath), "sound/%s.wav",
                soundfile);

    lastToken(soundfile, "/", ending, sizeof(ending));

    // should only show text to local people
    trap_SendServerCommand(-1, va("print \"%s: %c%c%s\n\"",
                                  ent->client->pers.netname,
                                  Q_COLOR_ESCAPE, COLOR_GREEN, ending));
    G_Sound(ent, CHAN_LOCAL_SOUND, G_SoundIndex(soundfilepath));

    // oops didn't log it
    G_LogPrintf( "say: %s: %s\n",
                 ent->client->pers.netname, ending );

    // end Joe
}

char    *ConcatArgs( int start );

// allow these during intermission in g_cmds.c
void Cmd_Play_Voice_f(gentity_t *ent) {
    char *soundfile;
    int clientNum = ent - g_entities;

    if ( trap_Argc () < 2) {

        trap_SendServerCommand(clientNum,
                               va("print \"usage:  play_voice voice/soundtype/soundfile\n\""));

        trap_SendServerCommand(clientNum, "print \"soundtypes are:  male, female, mech, demon, monster\n\"");

        trap_SendServerCommand(clientNum, "print \"soundfiles are:  cover me\n\"");

        trap_SendServerCommand(clientNum,"print \"follow me\n\"");
        trap_SendServerCommand(clientNum, "print \"good shot\n\"");
        trap_SendServerCommand(clientNum, "print \"I will hold this position\n\"");
        trap_SendServerCommand(clientNum, "print \"move\n\"");
        trap_SendServerCommand(clientNum, "print \"no\n\"");
        trap_SendServerCommand(clientNum, "print \"ok\n\"");
        trap_SendServerCommand(clientNum, "print \"take over this position\n\"");
        trap_SendServerCommand(clientNum, "print \"hahaha\n\"");
        trap_SendServerCommand(clientNum, "print \"escort me\n\"");

        return;
    }

    soundfile = ConcatArgs( 1 );

    playVoiceSound(ent, soundfile);

} // end Cmd_Play_Voice_f


qboolean allianceInhibitSpawn(gitem_t *item) {
    // XXX publius - ok, individual weapon checks aren't that necessary
    // (but may be in the future)

    /*
    if (g_battlePowerups.integer)
    	return qfalse;
    */

    if ( freeGear() && (item->giType == IT_WEAPON ))
        return qtrue;

    if (item->giType == IT_WEAPON) {

        if (!freeMachinegun() && item->giTag == WP_MACHINEGUN)
            return qtrue;

        if (!freeShotgun() && item->giTag == WP_SHOTGUN)
            return qtrue;

        if (!freeGrenadeLauncher() && item->giTag == WP_GRENADE_LAUNCHER)
            return qtrue;

        //if (freeHandgrenade() && item->giTag == WP_SHOTGUN) return qtrue;

        if (!freeRailgun() && item->giTag == WP_RAILGUN)
            return qtrue;

        if (!freePlasmaGun() && item->giTag == WP_PLASMAGUN)
            return qtrue;

        if (!freeLightningGun() && item->giTag == WP_LIGHTNING)
            return qtrue;

        if (!freeRocketLauncher() && item->giTag == WP_ROCKET_LAUNCHER)
            return qtrue;

        if (!freeBFG() && item->giTag == WP_BFG)
            return qtrue;

#ifdef MISSIONPACK
		if(!freeCHAINGUN() && item->giTag == WP_CHAINGUN)
			return qtrue;

		if(!freeNAILGUN() && item->giTag == WP_NAILGUN)
			return qtrue;

		if( !freePROX_LAUNCHER() && item->giTag == WP_PROX_LAUNCHER)
			return qtrue;

#endif
        //INCIN -- Inhibit a grapple being given
	//weapon grapple
        if(!freeOnhandGrapple()
                && !globalGrapple()
                && item->giTag == WP_GRAPPLING_HOOK)
            return qtrue;
    }
	//offhand hook
	else if (!globalGrapple() 
		&& freeOnhandGrapple()
		&& item->giTag == WP_GRAPPLING_HOOK){
		//(item->giType == IT_GRAPPLE)) {
        return qtrue;
    }


    if (healthRestore()) {

        if (item->giType == IT_HEALTH
                || item->giType == IT_ARMOR) {

            return qtrue;

        }

        if (item->giType == IT_POWERUP &&
                (item->giTag == PW_REGEN)) {

            return qtrue;
        }
        if (
#ifdef MISSIONPACK
			item->giTag == PW_AMMOREGEN &&
#else
			item->giType == PW_RUNEREGEN &&
#endif
			item->giTag == IT_PERSISTANT_POWERUP) {

            return qtrue;
        }
    }

    if ( ammoRestore() ) {

        if (item->giType == IT_AMMO) {

            return qtrue;
        }
        if (
#ifdef MISSIONPACK
			item->giTag == PW_AMMOREGEN &&
#else
			item->giType == PW_RUNEAMMOREGEN &&
#endif
			item->giTag == IT_PERSISTANT_POWERUP) {

            return qtrue;
        }
    }

    if ( g_battlePowerups.integer
            && item->giType == IT_POWERUP) {

        if (item->giTag == PW_QUAD
                || item->giTag == PW_HASTE
                || item->giTag == PW_BATTLESUIT
                || item->giTag == PW_INVIS
                || item->giTag == PW_REGEN
                || item->giTag == PW_FLIGHT
           ) {
            return qtrue;
        }
    }

    // BB
    // no powerups in combat, period
	//INCINERATOR
	//IN TA COMPILE  WE DON'T WANT THIS TO ALLOW ALL POWERUPS TO RUN.. GUARD, DOUBLER , ETC.
#ifndef MISSIONPACK
    if(BG_gamemode() == GM_BATTLE) {
        if(item->giType == IT_POWERUP
                || item->giType == IT_PERSISTANT_POWERUP) {

            return qtrue;
        }
    }
#endif

    return qfalse;
}

// returns qtrue if teammates receive partial friendly fire
qboolean partialFriendlyFireEnabled(void) {
    return (g_partialFriendlyFire.integer ?
            qtrue : qfalse);
}

// returns the fraction of damage given to teammates
// doesn't check if [partial] friendly fire is enabled.
float partialFriendlyFireFraction(void) {
    return g_partialFriendlyFireFraction.value;
}

// returns qtrue if weapons stay is enabled
qboolean weaponsStayEnabled(void) {
    return g_weaponsStay.integer ? qtrue : qfalse;
}



// returns qtrue if the game is teamplay.
// FIXME eventually the game source should be refactored to use this instead
// of looking at g_gametype directly. I'm holding off on this until I know
// just what depends on the teamplay inequality.
qboolean teamplayGame(void) {
    if (g_gametype.integer >= GT_TEAM)
        return qtrue;
    return qfalse;
}

#ifndef MISSIONPACK
/*
===============
Pickup_Rune
===============
*/
void Pickup_Rune (gentity_t *ent, gentity_t *other, trace_t *trace) {

    // can only hold one rune at a time
    //FIXED ElSEWHERE
    if ( ( other->client->ps.powerups[PW_RUNEDAMAGE] ) ||
            ( other->client->ps.powerups[PW_RUNERESIST] ) ||
            ( other->client->ps.powerups[PW_RUNEHASTE]  ) ||
            ( other->client->ps.powerups[PW_RUNEAMMOREGEN]  ) ||
            ( other->client->ps.powerups[PW_RUNEVAMPIRE]  ) ||
            ( other->client->ps.powerups[PW_RUNEREGEN]  ) ) {
        return;
    }

    // play global pickup sound
    //	if ( other->client->pers.predictItemPickup ) {
    //		G_AddPredictableEvent( other, EV_GLOBAL_ITEM_PICKUP, ent->s.modelindex );
    //	} else {
    G_AddEvent( other, EV_RUNE_PICKUP, ent->s.modelindex );
    //	}
    //Banshee add logging for rune pickup
    if (ent->item->giTag == PW_RUNEDAMAGE) {
        //G_Printf("PW_RUNEDAMAGE\n");
        G_LogPrintf("PICKUP_RUNEDAMAGE: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }
    if (ent->item->giTag == PW_RUNEHASTE) {
        //G_Printf("PW_RUNEHASTE\n");
        G_LogPrintf("PICKUP_RUNEHASTE: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }
    if (ent->item->giTag == PW_RUNERESIST) {
        //G_Printf("PW_RUNERESIST\n");
        G_LogPrintf("PICKUP_RUNERESIST: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }
    if (ent->item->giTag == PW_RUNEREGEN) {
        //G_Printf("PW_RUNEREGEN\n");
        G_LogPrintf("PICKUP_RUNEREGEN: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }
    if (ent->item->giTag == PW_RUNEAMMOREGEN) {
        //G_Printf("PW_RUNEAMMOREGEN\n");
        G_LogPrintf("PICKUP_RUNEAMMOREGEN: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }
    if (ent->item->giTag == PW_RUNEVAMPIRE) {
        //G_Printf("PW_RUNEVAMPIRE\n");
        G_LogPrintf("PICKUP_RUNEVAMPIRE: %i %s\n", other->client->ps.clientNum, other->client->pers.netname);
    }

    other->client->ps.powerups[ent->item->giTag] = INT_MAX;


    //other->client->ps.stats[STAT_PERSISTANT_POWERUP] = BG_FindItemForRune( ent->item->giTag ) - bg_itemlist;
    other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist;
    other->client->persistantPowerup = ent;

    //G_Printf("POWERUP -> %i\n", ent->item->giTag);
    G_FreeEntity( ent );

}

/*
=================
BounceRune
=================
*/
void BounceRune (gentity_t *self, vec3_t dir) {

    VectorNormalize (dir);
    self->health = INT_MAX;
    self->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    self->s.eFlags = EF_BOUNCE_HALF;
    self->s.pos.trType = TR_GRAVITY;
    self->s.pos.trTime = level.time;

    VectorScale( dir, (((rand() + 800) % 800) + 200), self->s.pos.trDelta );
    SnapVector( self->s.pos.trDelta );			// save net bandwidth

}


/*
===============
RespawnRunes
===============
*/
void RespawnRunes( gentity_t *ent ) {
    gentity_t	*spot;
    trace_t		tr;
    vec3_t		dest, dir;
    int x;

    trap_UnlinkEntity (ent);

    spot = SelectRuneSpawnPoint();

    if ( ! ( VectorCompare( spot->s.origin, ent->s.origin ) ) ) {
        VectorCopy( spot->s.origin, ent->s.origin );
    }

    ent->r.contents = CONTENTS_TRIGGER;
    ent->s.eFlags &= ~EF_NODRAW;

    // drop to floor
    VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
    trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );

    G_SetOrigin( ent, tr.endpos );

    trap_LinkEntity (ent);

    // bounce em away from the spawn point
    //VectorCopy( spot->s.origin, dir );

    x = ((rand() + 18 ) % 4);	//will always be 0 to 3
    switch(x) {
    case 0:
        dir[0] = 1;
        dir[1] = 1;
        //dir[2] = (random() * 10)
        break;
    case 1:
        dir[0] = 1;
        dir[1] = -1;
        //dir[2] = (random() * 10)
        break;
    case 2:
        dir[0] = -1;	//(dir[0]) - (random() * dir[0]);
        dir[1] = 1;		//(dir[1]) + (random() * dir[1]);
        //dir[2] = (random() * 10) + 5;	//(dir[2]) - (random() * dir[2]);
        break;
    case 3:
        dir[0] = -1; //(dir[0]) - (random() * dir[0]);
        dir[1] = -1; //(dir[1]) - (random() * dir[1]);
        //(dir[2]) - (random() * dir[2]);
        break;
    default:
        dir[0] = 1;		//(dir[0]) + (random() * dir[0]);
        dir[1] = 1;		//(dir[1]) + (random() * dir[1]);
        break;
    }

    x = ((rand() + 18 ) % 4);	//will always be 0 to 3
    switch(x) {
    case 0:
        dir[2] = 30;
        break;
    case 1:
        dir[2] = 20;
        break;
    case 2:
        dir[2] = 10;
        break;
    case 3:
        dir[2] = 5;
        break;
    default:
        dir[2] = 10;
    }

    //dir[0] -= crandom() * 10;
    //dir[1] -= crandom() * 10;
    //dir[2] -= 10;

    BounceRune(ent, dir);

    // Runes are moving every 30 seconds
    ent->think = RespawnRunes;
    ent->nextthink = level.time + 30000;
}


void SpawnRunes( powerup_t pw ) {
//void SpawnRunes(runepowerup_t pw) {
    gentity_t	*rune, *spot;
    gitem_t		*item;
    float	respawn;

    item = BG_FindItemForRune( pw );
    rune = G_Spawn();
    spot = SelectRuneSpawnPoint();

    RegisterItem( item );
    rune->item = item;
    rune->classname = item->classname;
    rune->takedamage = qtrue;
    rune->health = INT_MAX;
    rune->physicsBounce = 0.50;		// runes are bouncy

    VectorSet( rune->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
    VectorSet( rune->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );

    rune->s.eType = ET_ITEM;
    rune->s.modelindex = rune->item - bg_itemlist;		// store item number in modelindex
    rune->s.modelindex2 = 0; // zero indicates this isn't a dropped item

    rune->s.eFlags = EF_BOUNCE_HALF;
    rune->s.pos.trType = TR_GRAVITY;
    rune->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    rune->touch = Pickup_Rune;

    G_SetOrigin( rune, spot->s.origin );

    respawn = 30 + crandom() * 15;
    rune->s.eFlags |= EF_NODRAW;
    rune->r.contents = 0;
    rune->think = RespawnRunes;
    rune->nextthink = level.time + respawn * 1000;

}
#endif
gentity_t *BB_SpawnFlagbase( vec3_t origin, int team) {
    trace_t		tr;
    vec3_t		dest;
    gentity_t	*ent;

    ent = G_Spawn();

    VectorCopy( origin, ent->s.origin );
    VectorCopy( origin, ent->s.pos.trBase );
    VectorCopy( origin, ent->r.currentOrigin );

    VectorSet( ent->r.mins, -4, -4, 0 );
    VectorSet( ent->r.maxs, 4, 4, 1 );

    ent->s.eType = ET_GENERAL;
    ent->flags = FL_NO_KNOCKBACK;
    ent->r.contents = CONTENTS_SOLID;

    // mappers like to put them exactly on the floor, but being coplanar
    // will sometimes show up as starting in solid, so lif it up one pixel
    ent->s.origin[2] += 1;

    // drop to floor
    VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096 );
    trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
    if ( tr.startsolid ) {
        ent->s.origin[2] -= 1;
        G_Printf( "SpawnFlagbase: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin) );

        ent->s.groundEntityNum = ENTITYNUM_NONE;
        G_SetOrigin( ent, ent->s.origin );
    } else {
        // allow to ride movers
        ent->s.groundEntityNum = tr.entityNum;
        G_SetOrigin( ent, tr.endpos );
    }

    ent->spawnflags = team;

    trap_LinkEntity( ent );

    return ent;
}

void BB_SpawnFlagbases( int team ) {
    gentity_t	*base;
    gitem_t		*item;

    if (team == TEAM_RED) {
        item = BG_FindItemByClass( "team_CTF_redbase" );

        base = BB_SpawnFlagbase( teamgame.baseFlag[team]->s.origin, team);

        RegisterItem( item );

        base->item = item;
        base->classname = item->classname;

        base->s.eType = ET_TEAM;
        base->s.modelindex = base->item - bg_itemlist;		// store item number in modelindex
        base->s.generic1 = team;
        base->s.modelindex2 = 0;

        trap_LinkEntity(base);
    }
    else if (team == TEAM_BLUE) {
        item = BG_FindItemByClass( "team_CTF_bluebase" );

        base = BB_SpawnFlagbase( teamgame.baseFlag[team]->s.origin, team);

        RegisterItem( item );

        base->item = item;
        base->classname = item->classname;

        base->s.eType = ET_TEAM;
        base->s.modelindex = base->item - bg_itemlist;		// store item number in modelindex
        base->s.generic1 = team;
        base->s.modelindex2 = 0;

        trap_LinkEntity(base);
    }

/*
#ifdef MISSIONPACK
	else if (team == TEAM_FREE) {
        item = BG_FindItemByClass( "team_CTF_neutralbase" );

        base = BB_SpawnFlagbase( teamgame.baseFlag[team]->s.origin, team);

        RegisterItem( item );

        base->item = item;
        base->classname = item->classname;

        base->s.eType = ET_TEAM;
        base->s.modelindex = base->item - bg_itemlist;		// store item number in modelindex
        base->s.generic1 = team;
        base->s.modelindex2 = 0;

        trap_LinkEntity(base);
    }

#endif
*/


}
#ifndef MISSIONPACK
/*
=================
DropRunes
=================
*/
void DropRunes( gentity_t *ent ) {
    int		i;
    gitem_t	*item;

    item = NULL;
	for ( i = 1 ; i < PW_NUM_POWERUPS ; i++ ) {
        if ( ent->client->ps.powerups[ i ] ) {
            item = BG_FindItemForRune( i );
            if ( !item )  {
                continue;
            }
        }
    }
    if ( item ) {

        Drop_Item( ent, item, 0 );


        ent->client->ps.powerups[ item->giTag ] = 0;
        ent->client->ps.stats[STAT_PERSISTANT_POWERUP] = 0;
        ent->client->persistantPowerup = NULL;
    }
}


/*
================
G_RunRunes
 
================
*/
void G_RunRunes( gentity_t *ent ) {
    vec3_t		origin;
    trace_t		tr;
    int			contents;
    int			mask;
    vec3_t		velocity;
    float		dot;
    int			hitTime;

    // if groundentity has been set to -1, it may have been pushed off an edge
    if ( ent->s.groundEntityNum == -1 ) {
        if ( ent->s.pos.trType != TR_GRAVITY ) {
            ent->s.pos.trType = TR_GRAVITY;
            ent->s.pos.trTime = level.time;
            //			G_Printf( " -1 " );
        }
    }

    if ( ent->s.pos.trType == TR_STATIONARY ) {
        // check think function
        G_RunThink( ent );
        return;
    }

    // get current position
    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

    mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
    trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
                ENTITYNUM_NONE, mask );

    VectorCopy( tr.endpos, ent->r.currentOrigin );

    if ( tr.startsolid ) {
        tr.fraction = 0;
    }

    trap_LinkEntity( ent );

    // check think function
    G_RunThink( ent );

    if ( tr.fraction == 1 ) {
        return;
    }

    // if it is in a nodrop volume, remove it
    contents = trap_PointContents( ent->r.currentOrigin, -1 );
    if ( contents & CONTENTS_NODROP ) {
        // alliance - Hugo - Keep the runes alive :)
        //		SpawnRunes( ent->item->giTag );
        //		G_FreeEntity( ent );
        RespawnRunes( ent );
        return;
    }

    //	G_BounceItem( ent, &tr );
    // reflect the velocity on the trace plane
    hitTime = level.previousTime + ( level.time - level.previousTime ) * tr.fraction;
    BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
    dot = DotProduct( velocity, tr.plane.normal );
    VectorMA( velocity, -2*dot, tr.plane.normal, ent->s.pos.trDelta );

    // cut the velocity to keep from bouncing forever
    VectorScale( ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta );

    // check for stop
    if ( tr.plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
        tr.endpos[2] += 1.0;	// make sure it is off ground
        SnapVector( tr.endpos );
        G_SetOrigin( ent, tr.endpos );
        ent->s.groundEntityNum = tr.entityNum;
        return;
    }

    VectorAdd( ent->r.currentOrigin, tr.plane.normal, ent->r.currentOrigin);
    VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
    ent->s.pos.trTime = level.time;

}
#endif
// hugo - Runes
/*
================
SelectRuneSpawnPoint
 
go to a random team spawn point that doesn't telefrag
================
*/
//#define	MAX_SPAWN_POINTS	128
gentity_t *SelectRuneSpawnPoint( void ) {
    gentity_t	*spot;
    int			count;
    int			selection;
    gentity_t	*spots[MAX_SPAWN_POINTS];

    count = 0;
    spot = NULL;

    while ((spot = G_Find (spot, FOFS(classname), "team_CTF_redspawn")) != NULL) {
        spots[ count ] = spot;
        count++;
    }

    while ((spot = G_Find (spot, FOFS(classname), "team_CTF_bluespawn")) != NULL) {
        spots[ count ] = spot;
        count++;
    }

    while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
        spots[ count ] = spot;
        count++;
    }

    selection = rand() % count;
    return spots[ selection ];
}

void soundtypeForModel(char *soundtype, int soundtypeSize, char *model) {
    // 23 models

    if (Q_stricmpn(model, "anarki", sizeof("anarki") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "biker", sizeof("biker") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "bitterman", sizeof("bitterman") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "bones", sizeof("bones") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "demon");
    else if (Q_stricmpn(model, "crash", sizeof("crash") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "mech");
    else if (Q_stricmpn(model, "doom", sizeof("doom") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "grunt", sizeof("grunt") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "hunter", sizeof("hunter") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "female");
    else if (Q_stricmpn(model, "keel", sizeof("keel") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "klesk", sizeof("klesk") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "monster");
    else if (Q_stricmpn(model, "lucy", sizeof("lucy") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "female");
    else if (Q_stricmpn(model, "major", sizeof("major") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "female");
    else if (Q_stricmpn(model, "mynx", sizeof("mynx") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "female");
    else if (Q_stricmpn(model, "orbb", sizeof("orbb") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "mech");
    else if (Q_stricmpn(model, "ranger", sizeof("ranger") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "razor", sizeof("razor") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "sarge", sizeof("sarge") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "slash", sizeof("slash") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "female");
    else if (Q_stricmpn(model, "sorlag", sizeof("sorlag") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "monster");
    else if (Q_stricmpn(model, "tankjr", sizeof("tankjr") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "monster");
    else if (Q_stricmpn(model, "uriel", sizeof("uriel") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "demon");
    else if (Q_stricmpn(model, "visor", sizeof("visor") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");
    else if (Q_stricmpn(model, "xaero", sizeof("xaero") - 1) == 0)
        Com_sprintf(soundtype, soundtypeSize, "%s", "demon");
    else
        Com_sprintf(soundtype, soundtypeSize, "%s", "male");

} // end soundtypeForModel()

void playTeamSound(char *soundfile, int team) {
    gentity_t *te;
    vec3_t location = {0, 0, 0};

    // sound is everywhere, location doesn't matter
    te = G_TempEntity(location, EV_TEAM_SOUND );
    te->s.eventParm = G_SoundIndex(soundfile);
    te->s.modelindex = team;
    te->r.svFlags |= SVF_BROADCAST;
}


void play_team(gentity_t *ent, char *args) {
    int clientNum = ent - g_entities;
    char userinfo[MAX_INFO_STRING];
    char *model;//[MAX_INFO_STRING];
    char soundtype[MAX_INFO_STRING];

    char soundfile[MAX_TOKEN_CHARS];

    Com_sprintf(soundtype, sizeof(soundtype), "%s",
                ent->client->pers.soundtype);

    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );

    if ( g_gametype.integer >= GT_TEAM )
        model = Info_ValueForKey(userinfo, "team_headmodel");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "cg_teamheadModel"));
    else
        model = Info_ValueForKey(userinfo, "model");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "model"));


    if (! soundtype[0])
        soundtypeForModel(soundtype, sizeof(soundtype), model);

    Com_sprintf(soundfile, sizeof(soundfile), "sound/radio/%s/%s.wav",
                soundtype, args);

    if(fileExists(soundfile)) {
        playTeamSound(soundfile, ent->client->sess.sessionTeam);
    }

}

void play_voice(gentity_t *ent, char *args) {

    int clientNum = ent - g_entities;
    char userinfo[MAX_INFO_STRING];
    char soundtype[MAX_INFO_STRING];
    char soundfile[MAX_INFO_STRING];
    char *model;//[MAX_INFO_STRING];

    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );

    Com_sprintf(soundtype, sizeof(soundtype), "%s",
                ent->client->pers.soundtype);

    if ( g_gametype.integer >= GT_TEAM )
        model = Info_ValueForKey(userinfo, "team_headmodel");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "team_headmodel"));
    else
        model = Info_ValueForKey(userinfo, "model");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "model"));


    if (! soundtype[0])
        soundtypeForModel(soundtype, sizeof(soundtype), model);


    Com_sprintf(soundfile, sizeof(soundfile), "sound/voice/%s/%s.wav",
                soundtype, args);
    if (!fileExists(soundfile)) {
        Com_sprintf(soundfile, sizeof(soundfile), "sound/voice/%s.wav", args);
    }

    if (fileExists(soundfile)) {
        gentity_t *te = G_TempEntity( ent->r.currentOrigin, EV_VOICE_SOUND );
        te->s.eventParm = G_SoundIndex(soundfile);
        te->s.modelindex2 = clientNum;

        // annoying?
        if (level.intermissiontime) {
            te->r.svFlags |= SVF_BROADCAST;
            te->s.modelindex = 1;
        }
    }
}

// hijack say_team and add sound if there's a match
qboolean addSoundtoTeamMessage(gentity_t *ent) {
    int clientNum = ent - g_entities;
    char userinfo[MAX_INFO_STRING];
    char *model;//[MAX_INFO_STRING];
    char soundtype[MAX_INFO_STRING];
    char *args;

    char soundfile[MAX_TOKEN_CHARS];

    Com_sprintf(soundtype, sizeof(soundtype), "%s",
                ent->client->pers.soundtype);

    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );

    if ( g_gametype.integer >= GT_TEAM )
        model = Info_ValueForKey(userinfo, "team_headmodel");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "team_headmodel"));
    else
        model = Info_ValueForKey(userinfo, "model");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "model"));


    if (! soundtype[0])
        soundtypeForModel(soundtype, sizeof(soundtype), model);

    args = ConcatArgs( 1 );

    Com_sprintf(soundfile, sizeof(soundfile), "sound/radio/%s/%s.wav",
                soundtype, args);

    //G_Printf("Team -> %s\n", soundfile);

    if(fileExists(soundfile)) {
        playTeamSound(soundfile, ent->client->sess.sessionTeam);
        return qtrue;
    } else
        return qfalse;

} // end addSoundtoTeamMessage()


// hijack say command and add audio if we have it
qboolean addSoundtoMessage(gentity_t *ent) {

    int clientNum = ent - g_entities;
    char userinfo[MAX_INFO_STRING];
    char soundtype[MAX_INFO_STRING];
    char soundfile[MAX_INFO_STRING];
    char *model;//[MAX_INFO_STRING];
    char *args;

    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );

    Com_sprintf(soundtype, sizeof(soundtype), "%s",
                ent->client->pers.soundtype);

    if ( g_gametype.integer >= GT_TEAM )
        model = Info_ValueForKey(userinfo, "team_headmodel");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "team_headmodel"));
    else
        model = Info_ValueForKey(userinfo, "model");
    //Com_sprintf(model, sizeof(model), "%s", Info_ValueForKey(userinfo, "model"));

    if (! soundtype[0])
        soundtypeForModel(soundtype, sizeof(soundtype), model);

    args = ConcatArgs( 1 );

    Com_sprintf(soundfile, sizeof(soundfile), "sound/voice/%s/%s.wav",
                soundtype, args);

    if(!fileExists(soundfile)) {
        Com_sprintf(soundfile, sizeof(soundfile), "sound/voice/%s.wav", args);
    }

    //G_Printf("Voice -> %s\n", soundfile);


    if (fileExists(soundfile)) {
        gentity_t *te = G_TempEntity( ent->r.currentOrigin, EV_VOICE_SOUND );
        te->s.eventParm = G_SoundIndex(soundfile);
        te->s.modelindex2 = clientNum;

        // annoying?
        if (level.intermissiontime) {
            te->r.svFlags |= SVF_BROADCAST;
            te->s.modelindex = 1;
        }

        return qtrue;

    } else
        return qfalse;

} // end addSoundtoMessage()



// seems you have to use setu twice in a game on a new variable
void Cmd_Userinfo_f (int clientNum) {
    char userinfo[MAX_INFO_STRING];
    char userinfoOutput[MAX_INFO_STRING];

    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
    Com_sprintf(userinfoOutput, sizeof(userinfoOutput),
                "print \"userinfo: %s\n\"", userinfo );
    //	G_Printf("userinfo is %s\n", userinfo);
    //	G_Printf("userinfoOutput is %s\n", userinfoOutput);
    trap_SendServerCommand(clientNum, userinfoOutput);
}


/*
============
G_TouchWeapon
Hugo
Returns true if the player stands on an item spawn.
============
*/
qboolean G_TouchWeapon( gentity_t *ent ) {
    int			i, num;
    int			touch[MAX_GENTITIES];
    gentity_t	*hit;
    vec3_t		mins, maxs;
    static vec3_t	range = { 300, 300, 52 };

    VectorSubtract( ent->client->ps.origin, range, mins );
    VectorAdd( ent->client->ps.origin, range, maxs );

    num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );

    // can't use ent->absmin, because that has a one unit pad
    VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
    VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );

    for ( i=0 ; i<num ; i++ ) {
        hit = &g_entities[touch[i]];

        if ( !hit->touch && !ent->touch ) {
            continue;
        }
        if ( hit->item->giType != IT_WEAPON ) {
            continue;
        }

        return qtrue;
    }
    return qfalse;
}



// alliance Joe combatctfscoring
void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... );

// Myrkul: accumulate points and do side effects of awarding bonuses
// Think of it as a "Bonuses" object (try squinting).

int bonusVector;
static float totalPoints;
static qboolean isKill;
static qboolean isTeamKill;

static gentity_t *att;

void InitBonus(gentity_t *attacker, qboolean kill, qboolean teamKill) {
    att = attacker;
    isKill = kill;
    isTeamKill = teamKill;
    bonusVector = totalPoints = 0;
}


// certain bonus types have an extra param to be sent to the client
void AddBonusWithParam(float bonusAmount, scoringBonus_t bonusType,
                       int param) {
    vec3_t attOrigin;

    // add points
    totalPoints += bonusAmount;

    // if a kill, send an event notification to all clients
    if (isKill) {
        gentity_t *te;

        // BB: make this work for non-clients
        if (att->client) {
            VectorCopy( att->r.currentOrigin, attOrigin );
        } else {
            VectorCopy( att->s.origin, attOrigin );
        }

        te = G_TempEntity(attOrigin, EV_CTF_BONUS);
        te->r.svFlags |= SVF_BROADCAST;
        te->s.eventParm = bonusType;
        // note this rounds to nearest integer so client sees only whole number
        // bonuses.  Also note modelindex's are transmitted as 8 bits since
        // MAX_MODELS is 256, so bonuses over 256 wouldn't work
        te->s.modelindex = bonusAmount;
        // mark team kills as penalties (special messages on client)
        if (isTeamKill) {
            te->s.eventParm |= SCORE_FLAG_PENALTY;
        }

        // client number of receiver of bonus
        te->s.otherEntityNum = att->s.number;

        //test
        //te->s.otherEntityNum = att->r.s.clientNum;

        // for displaying the carry time in the carrier kill case
        if (bonusType == SCORE_CARRIER_KILL) {
            te->s.time2 = param;
        }
    }

    // remember what bonuses have been awarded
    bonusVector |= (1 << bonusType);

    switch (bonusType) {
        // offensive
    case SCORE_CARRIER_KILL:
        //Alliance - SST - Banshee
        //Hugo
        //att->client->pers.statEnemyCarrierKill++;
        //att->client->ps.persistant[PERS_FC_KILLS]++;
        att->client->pers.mvpDefense += MVP_DEFENSE_ECK_BONUS;
        break;
        //End SST
    case SCORE_DEFENDER_KILL:
        //Alliance - MVP - Banshee
        att->client->pers.mvpOffense += MVP_OFFENSE_DK_BONUS;
        //End - MVP
        //Alliance - SST - Banshee
        att->client->pers.statDefenderKill++;
        //Alliance - End SST
        break;
        // defensive
    case SCORE_CARRIER_DEFENSE:
        //Alliance - SST - Banshee
        att->client->pers.statCarrierDefense++;
        att->client->pers.mvpDefense += MVP_DEFENSE_CD_BONUS;
        break;
        //Alliance - End SST
    case SCORE_CARRIER_SAVE:
        //Alliance - SST - Banshee
        att->client->pers.statCarrierSave++;
        att->client->pers.mvpDefense += MVP_DEFENSE_CS_BONUS;
        break;
        //Alliance - End SST
    case SCORE_BASE_DEFENSE:
        //Alliance - MVP - Banshee
        att->client->pers.mvpDefense += MVP_DEFENSE_BD_BONUS;
        //End - MVP
        //Alliance - SST - Banshee
        att->client->pers.statBaseDefense++;
        //Alliance - End SST
        break;
        // don't separately MVP award flag holding and flag defense when they are
        // awarded with base defense
    case SCORE_FLAG_HOLDING:
        //Alliance - SST - Banshee
        att->client->pers.statFlagHolding++;
        att->client->pers.mvpOffense += MVP_OFFENSE_FH_BONUS;
        break;
        //Alliance - End SST
    case SCORE_FLAG_DEFENSE:
        if (!bonusVector & SCORE_BASE_DEFENSE) {
            //Alliance - MVP - Banshee
            att->client->pers.mvpDefense += MVP_DEFENSE_FD_BONUS;
            //End - MVP
            att->client->pers.statFlagDefense++;
        }
        break;
    }
}

void AddBonus(float bonusAmount, scoringBonus_t bonusType) {
    AddBonusWithParam(bonusAmount, bonusType, 0);
}

int GetTotalPoints(void) {
    return totalPoints;
}

int GetBonusVector(void) {
    return bonusVector;
}

// how much of the bonus is awarded for doing 100% of max health damage on a
// player
#define DAMAGE_FRACTION                1.5
// how much is awarded for the kill itself (cumulative with any damaege)
#define KILL_FRACTION                  1.0

// damage scored near death is worth this many times more than damage scored
// at max haelth
#define DAMAGE_NEAR_DEATH_BONUS          2

/*
Damage near death is worth more than damage on an uninjured opponent.
Specifically, the last point of damage is worth DAMAGE_NEAR_DEATH_BONUS times
the amount that the first point of damage on an uninjured opponent is worth.
 
The total amount of points awarded for going from max_health down to zero health
is DAMAGE_FRACTION * CTF_FRAG_POINTS (abbrev DFF).
 
We want a formula to compute the point value of a range of damage from eg 40
down to 10.
 
points
 
 |
 k_
 | \_
 |   \_
k/nd___\
 |
-------mx---- health
0
 
The entire health range from 0-max_health should be worth DAMAGE_FRACTION *
CTF_FRAG_POINTS.
 
float pointsForDamage(int damage, int maxHealth) {
    return (DAMAGE_FRACTION * CTF_FRAG_POINTS) * 
        ( (DAMAGE_NEAR_DEATH_BONUS * 2 * damage)/maxHealth +
          (damage * damage) )
}
 
float pointsForDamageRange(int start, int end) {
 
    
 
}
*/

/**
 * Determines whether "player1" or "player2" could be considered to be
 * protecting "target", which is determined by whether they are within a given
 * radius of each other.  If countPVS is true, being within the PVS also counts.  
 */
qboolean inProtectField(gentity_t *player1, gentity_t *player2,
                        gentity_t *target, int radius) {

    vec3_t v1, v2;
    vec3_t p1Origin, p2Origin, tOrigin;

    // BB: make this work for non-clients
    if (player1->client) {
        VectorCopy( player1->r.currentOrigin, p1Origin );
    } else {
        VectorCopy( player1->s.origin, p1Origin );
    }
    // BB: make this work for non-clients
    if (player2->client) {
        VectorCopy( player2->r.currentOrigin, p2Origin );
    } else {
        VectorCopy( player2->s.origin, p2Origin );
    }
    // BB: make this work for non-clients
    if (target->client) {
        VectorCopy( target->r.currentOrigin, tOrigin );
    } else {
        VectorCopy( target->s.origin, tOrigin );
    }

    // check for being within radius
    VectorSubtract(p1Origin, tOrigin, v1);
    VectorSubtract(p2Origin, tOrigin, v2);

    if (VectorLength(v1) < radius ||
            VectorLength(v2) < radius) {
        return qtrue;
    }

    // cvar controlling whether line of sight is counted as a sufficient
    // condition for being considered to have protected something.
    if (!g_countPVS.integer) {
        return qfalse;
    }

    // not in radius, consider PVS
    if (trap_InPVS(p1Origin, tOrigin) ||
            trap_InPVS(p2Origin, tOrigin)) {
        return qtrue;
    }

    return qfalse;
}

/**
 * In CTF mode, this function is responsible for all scoring.
 *
 * Gives out bonuses for advancing the team goal of capturing the flag.
 * Called from game/g_combat.c::G_Damage() (damage scoring)
 * Called from game/g_combat.c::player_die() (kill scoring)
 */
void BattleCTFScoring(gentity_t *targ, gentity_t *inflictor,
                      gentity_t *attacker, int damage, int typeofdeath) {

    int lastHurtCarrier;
    int attackerTeam, targTeam;
    float points = 0;
    gentity_t *attackerFlag, *targFlag, *carrier = NULL;
    qboolean gotCarrierSaveBonus = qfalse;
    qboolean teamKill = qfalse;
    int distance, carryTime;
    float distanceBonus, timeBonus;
    // Hugo
    int	mvp;
    vec3_t scoreOriginTarg, scoreOriginAtt;



    if (typeofdeath == MOD_TEAMCHANGE
            || typeofdeath == MOD_WATER
            || typeofdeath == MOD_SLIME
            || typeofdeath == MOD_LAVA
            || typeofdeath == MOD_CRUSH
            || typeofdeath == MOD_TELEFRAG
            || typeofdeath == MOD_FALLING
            || typeofdeath == MOD_SUICIDE
            || typeofdeath == MOD_TARGET_LASER
            || typeofdeath == MOD_TRIGGER_HURT
            || typeofdeath == MOD_UNKNOWN
       )
        return;

    // BB: make this work for non-clients
    if (targ->client) {
        VectorCopy( targ->r.currentOrigin, scoreOriginTarg );
    } else {
        VectorCopy( targ->s.origin, scoreOriginTarg );
    }

    // BB: make this work for non-clients
    if (attacker->client) {
        VectorCopy( attacker->r.currentOrigin, scoreOriginAtt );
    } else {
        VectorCopy( attacker->s.origin, scoreOriginAtt );
    }

    distance = 0;
    mvp = 0;

    if (!targ->client
            ||
            (damage != DAMAGE_KILL && targ->client->ps.pm_type == PM_DEAD)) {
        // only handle player targets
        // don't give points for damage to dead bodies
        return;
    }


    // SUICIDE
    if (!attacker || attacker == targ) {
        //if (!attacker || !attacker->client || attacker == targ) {
        // damaged by self or world
        // Because of certain instances of intentionally taking damage (like
        // rocket jumps), damaging yourself is not a consistent skill
        // indicator, so there is no penalty.
        if (damage == DAMAGE_KILL) {
            // You still get penalized for dying.
            AddScore(attacker, scoreOriginAtt, -CTF_FRAG_POINTS);
        }
        // There are also no special CTF bonuses or penalties for self-kill or
        // self-damage, since all the bonus situations are hectic situations
        // where a self-kill can happen to a competent player (eg spinaround
        // point blank rocket hit on the fourth player attacking you that you
        // didn't know was there).
        return;
    }


    // Now client to client damage and kills only.
    attackerTeam = attacker->client->sess.sessionTeam;
    targTeam = targ->client->sess.sessionTeam;

    // Award the basic points for normal damage and kills, which are received
    // even when not in any particular crucial CTF situation.
    if (damage == DAMAGE_KILL) {
        // scored a kill
        points = KILL_FRACTION * CTF_FRAG_POINTS;

    } else {
        // scored damage
        points = CTF_FRAG_POINTS *
                 (DAMAGE_FRACTION * damage)
                 / targ->client->ps.stats[STAT_MAX_HEALTH];
    }


    // PENALTIES for same team kill: go through the scoring system as though
    // attacker were actually on the enemy team, then penalize attacker in
    // proportion to how many points he would have earned if he were on the
    // other team.
    // To understand results, visualize the situation and reverse attacker's
    // team: team carrier becomes enemy carrier, enemy base becomes team base..
    if (targTeam == attackerTeam) {
        attackerTeam = OtherTeam(targTeam);
        teamKill = qtrue;
    }


    // a little subsystem to accrue bonuses and track side-effects of receiving
    // bonuses
    InitBonus(attacker, damage == DAMAGE_KILL, teamKill);


    // CARRIER KILL: targ is a enemy carrier
    if (
        (targ == teamgame.carrier[attackerTeam]
         ||
         targ == teamgame.carrier[targTeam])
    ) {

        if (damage == DAMAGE_KILL) {
            // remember last carrier death for assists
            attacker->client->pers.teamState.lastfraggedcarrier =
                level.time;
            // and in order to ignore irrelevant lastHurtCarrier values
            teamgame.lastCarrierDeath[targTeam] = level.time;
        }

        if (targ->client->pers.aLocation != LOC_INVALID) {
            // determine how far carrier has gotten from enemy base
            distance = relativeLocation(targTeam, targ->client->pers.aLocation);

            // bonus is proportional to distance, with a minimum of flag defense if
            // the carrier is killed in enemy base
            distanceBonus = CTF_BATTLE_FLAG_DEFENSE_BONUS +
                            (distance * CTF_CARRIER_KILL_BONUS_PER_DISTANCE);
        } else {
            // no location info in this map/for this player.
            // fall back to time-based scoring
            distanceBonus = 0;
        }

        // bonus according to how long carrier has stayed alive
        carryTime = (level.time - targ->client->pers.teamState.flagsince)/1000;
        timeBonus = CTF_BATTLE_FLAG_DEFENSE_BONUS +
                    (carryTime * CTF_CARRIER_KILL_BONUS_RATE);
        if (timeBonus > CTF_CARRIER_KILL_BONUS_MAX)
            timeBonus = CTF_CARRIER_KILL_BONUS_MAX;

        // give the player whichever bonus is larger, in order to award for
        // carriers that have reached home base, yet still award for carriers
        // who have been alive so long that they've probably reached home base
        // and come back to midfield to hide.
        if (timeBonus > distanceBonus) {
            AddBonusWithParam(timeBonus, SCORE_CARRIER_KILL, carryTime);
        } else {
            // pass flag indicating this was a distance based bonus, for special
            // message on the client side
            AddBonusWithParam(distanceBonus, SCORE_CARRIER_KILL,
                              distance | FL_DISTANCE);
        }

        // Hugo
        mvp = 3;
    }


    // CARRIER SAVE: Attacker killed a player who recently hurt attacker's flag
    // carrier.  Attacker can't be his team's carrier.
    // Targ must have hurt the carrier recently in general, and specifically
    // more recently than the last carrier death or capture for attacker's team
    // (if targ hurt a carrier longer ago than that, he must have hurt the
    // previous carrier)
    // also PENALTY ENEMY CARRIER SAVE: killed a teammate that recently hit an
    // enemy carrier
    // last time targ hit attacker's carrier
    targFlag = teamgame.carrier[targTeam];

    if (targFlag
            && inProtectField(targ, attacker, targFlag, CTF_TARGET_PROTECT_RADIUS)
            && targFlag->client->sess.sessionTeam == attackerTeam) {

        lastHurtCarrier = targ->client->pers.teamState.lasthurtcarrier;

        if (lastHurtCarrier
                && attacker != teamgame.carrier[attackerTeam]
                && attacker != teamgame.carrier[targTeam]
                && level.time - lastHurtCarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT
                && lastHurtCarrier > teamgame.lastCarrierDeath[attackerTeam]
                && lastHurtCarrier > teamgame.last_capture_team) {

            AddBonus(CTF_BATTLE_CARRIER_DANGER_PROTECT_BONUS, SCORE_CARRIER_SAVE);

            // Hugo
            if ( mvp == 0 )
                mvp = 2;

            // targ's been killed for his transgression, he's not worth extra points
            // until he attacks the carrier again.
            if (damage == DAMAGE_KILL) {
                targ->client->pers.teamState.lasthurtcarrier = 0;
            }

            // CARRIER SAVE is mutually exclusive with CARRIER DEFENSE, and is worth
            // more points
            gotCarrierSaveBonus = qtrue;
        }

    }

    // BASE DEFENSE, FLAG HOLDING, and FLAG DEFENSE
    // Radius-based flag and carrier defense bonuses: check if either
    // the target or attacker is in radius of some object of importance
    // in ctf: flags, bases, carriers.  If either the target or attacker
    // has line-of-sight to the object being defended, there is always a
    // bonus at any distance.

    // NOTE: should always be valid unless there are no flags in the map.
    attackerFlag = teamgame.baseFlag[attackerTeam];

    if (attackerFlag
            && inProtectField(targ, attacker, attackerFlag,
                              CTF_TARGET_PROTECT_RADIUS)
            && gotCarrierSaveBonus == qfalse) {
        // Hugo
        if ( mvp == 0 )
            mvp = 2;


        //G_Printf("^4GOT HERE!\n");
        if (
            attacker == teamgame.carrier[attackerTeam]
            ||
            attacker == teamgame.carrier[targTeam]
        ) {
            // FLAG HOLDING: the carrier killed someone while waiting at his
            // base for the flag to be returned.

            // NOTE: no penalty case.  Attacker can never be the carrier for the
            // enemy team.

            // NOTE: only possible when flag is not at base.  When the flag is at
            // base, the carrier should have no incentive to wait before
            // capturing.
            AddBonusWithParam(CTF_FLAG_HOLDING_BONUS, SCORE_FLAG_HOLDING, SCORE_FLAG_HOLDING);
            // Hugo
            if ( mvp < 3 )
                mvp = 1;
        } else
            if (
                (teamgame.redStatus == FLAG_ATBASE
                 && attacker->client->sess.sessionTeam == TEAM_RED)
                ||
                (teamgame.blueStatus == FLAG_ATBASE
                 && attacker->client->sess.sessionTeam == TEAM_BLUE)
            ) {
                // FLAG DEFENSE: the attacker made a kill while either he or the
                // player he killed was in his base by his flag.  This bonus is
                // cumulative with base defense (it's extremely important to defend
                // a flag at base, and only somewhat important to defend a base with
                // no flag).

                // also PENALTY: ENEMY FLAG DEFENSE: killed a teammate that was
                // attacking enemy base with flag present

                // NOTE: Flag defense is checked here as an optimization for the
                // case where the flag is at base.  It's also possible to get flag
                // defense near a dropped flag.  See below.
                AddBonusWithParam(CTF_BATTLE_FLAG_DEFENSE_BONUS, SCORE_FLAG_DEFENSE, SCORE_FLAG_DEFENSE);
            } else {
                // BASE DEFENSE: a defender made a kill while either he or the player
                // he killed were near the home base position of his flag.  This kind
                // of defensive bonus can be received even if the defender's flag is
                // not at base.

                // also PENALTY: ENEMY BASE DEFENSE: killed a teammate that was
                // attacking enemy base

                // NOTE: carriers are eligible for base defense: since they could score
                // base defense just as easily immediately before a capture as afer
                AddBonusWithParam(CTF_BASE_DEFENSE_BONUS, SCORE_BASE_DEFENSE, SCORE_BASE_DEFENSE );
            }
    }

    if (
        (teamgame.redStatus == FLAG_DROPPED
         && attacker->client->sess.sessionTeam == TEAM_RED)
        ||
        (teamgame.blueStatus == FLAG_DROPPED
         && attacker->client->sess.sessionTeam == TEAM_BLUE)
    ) {
        // attacker's flag is dropped somewhere

        // NOTE: should always be valid unless there are no flags in the map.
        attackerFlag = teamgame.touchableFlag[attackerTeam];

        if (attackerFlag
                && inProtectField(targ, attacker, attackerFlag,
                                  CTF_TARGET_PROTECT_RADIUS)) {
            // FLAG DEFENSE: the attacker made a kill while either he or the
            // player he killed by his flag, which is dropped in the open.
            // NOTE: this is mutually exclusive with the other flag defense case
            // above, which happens only when the flag is at base

            // also PENALTY: ENEMY FLAG DEFENSE: killed a teammate that was
            // near a dropped enemy flag
            AddBonusWithParam(CTF_BATTLE_FLAG_DEFENSE_BONUS, SCORE_FLAG_DEFENSE, SCORE_FLAG_DEFENSE);
            // Hugo
            if ( mvp == 0 )
                mvp = 2;
        }
    }

    // the target's (dying player's) currently grabbable flag.
    targFlag = teamgame.baseFlag[targTeam];
    //attackerFlag = teamgame.touchableFlag[attackerTeam];

    // DEFENDER KILL: the reverse of a FLAG DEFENSE.  The attacker has
    // killed the target near the target's flag.  Awarded during base
    // onslaughts as the attackers whittle away at base defenders, or during
    // mad rushes for a dropped flag, as attackers prevent the flag from
    // being returned.

    // also PENALTY: HOME DEFENDER KILL: killed a teammate who was defending
    // your flag
    if (targFlag
            &&
            inProtectField(targ, attacker, targFlag,
                           CTF_TARGET_PROTECT_RADIUS)) {

        AddBonusWithParam(CTF_DEFENDER_KILL_BONUS, SCORE_DEFENDER_KILL, SCORE_DEFENDER_KILL);
        // Hugo
        if ( mvp < 3 )
            mvp = 1;

    }

    // attacker's team's flag carrier
    carrier = teamgame.carrier[targTeam];

    // CARRIER DEFENSE, radius based.  Given for attacker or target being near
    // the flag carrier.
    // also PENALTY: ENEMY CARRIER DEFENSE: killed a teammate who was attacking
    // the enemy carrier
    if (!gotCarrierSaveBonus
            && carrier && carrier != attacker
            && inProtectField(targ, attacker, carrier,
                              CTF_ATTACKER_PROTECT_RADIUS)) {

        AddBonusWithParam(CTF_BATTLE_CARRIER_PROTECT_BONUS, SCORE_CARRIER_DEFENSE, SCORE_CARRIER_DEFENSE);
        // Hugo
        if ( mvp == 0 )
            mvp = 2;
    }

    // Figure out final point amount and award it
    // Note points still has the number of frags awarded for the normal damage
    // or kill outside of a crucial CTF situation
    if (damage == DAMAGE_KILL) {
        points += GetTotalPoints() * KILL_FRACTION;
    } else {
        // scale to proportion of maximum health
        points += GetTotalPoints() *
                  (DAMAGE_FRACTION * damage)/targ->client->ps.stats[STAT_MAX_HEALTH];
    }



    // we were pretending attacker was on the other team in order to calculate a
    // penalty.  Really, attacker hit/damaged a teammate.
    if (teamKill) {
        float ratio;
        int targScore;

        // make it negative
        points = -points;

#define MIN_PENALTY_RATIO           0.3f
#define MAX_PENALTY_RATIO           1.0f

        // not all team damage is the shooter's fault.  If the attacker has a
        // much higher score, assume targ was partially at fault for the
        // friendly fire and don't penalize attacker as badly.
        targScore = targ->client->ps.persistant[PERS_SCORE];
        if (targScore < 0)
            targScore = 1;
        ratio = attacker->client->ps.persistant[PERS_SCORE] /
                (float)targScore;
        if (ratio > MAX_PENALTY_RATIO) {
            ratio = MAX_PENALTY_RATIO;
        }
        if (ratio < MIN_PENALTY_RATIO) {
            ratio = MIN_PENALTY_RATIO;
        }

        points *= ratio;
    }

    AddScore(attacker, scoreOriginAtt, points);

    /*
       if (damage != DAMAGE_KILL
               && g_scoringDebug.integer) {

           G_Printf("DmgScoring: %s" S_COLOR_WHITE " -> %s"
                    S_COLOR_WHITE "[%s]: %f points for %d/%d\n",
                    attacker->client->pers.netname,
                    targ->client->pers.netname,
                    teamKill ? S_COLOR_RED "T" S_COLOR_WHITE : "n",
                    points,
                    damage,
                    targ->client->ps.stats[STAT_MAX_HEALTH]);
       }
    */
    /*
       // Hugo
       if ( ! ( attacker->client->ps.eFlags & (EF_AWARD_EXCELLENT|EF_AWARD_GAUNTLET|EF_AWARD_IMPRESSIVE|
                                               EF_AWARD_HEADSHOT|EF_MOVER_STOP) ) ) {
           if ( mvp == 1 ) {
               // Hugo - medal
               attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
               attacker->client->ps.persistant[PERS_REWARD] = REWARD_OMVP;
               // add the sprite over the player's head
               attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_HEADSHOT
                                                | EF_AWARD_CAPTURE | EF_AWARD_DEFENSE | EF_AWARD_ASSIST
                                                | EF_AWARD_GAUNTLET | EF_AWARD_OMVP | EF_AWARD_DMVP | EF_AWARD_RETURN);
               attacker->client->ps.eFlags |= EF_AWARD_OMVP;
               attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
           } else if ( mvp == 2 ) {
               // Hugo - medal
               attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
               attacker->client->ps.persistant[PERS_REWARD] = REWARD_DMVP;
               // add the sprite over the player's head
               attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_HEADSHOT
                                                | EF_AWARD_CAPTURE | EF_AWARD_DEFENSE | EF_AWARD_ASSIST
                                                | EF_AWARD_GAUNTLET | EF_AWARD_OMVP | EF_AWARD_DMVP | EF_AWARD_RETURN);
               attacker->client->ps.eFlags |= EF_AWARD_DMVP;
               attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
           } else if ( mvp == 3 ) {
               // Hugo - medal
               attacker->client->ps.persistant[PERS_REWARD_COUNT]++;
               attacker->client->ps.persistant[PERS_REWARD] = REWARD_DEFENSE;
               // add the sprite over the player's head
               attacker->client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_HEADSHOT
                                                | EF_AWARD_CAPTURE | EF_AWARD_DEFENSE | EF_AWARD_ASSIST
                                                | EF_AWARD_GAUNTLET | EF_AWARD_OMVP | EF_AWARD_DMVP | EF_AWARD_RETURN);
               attacker->client->ps.eFlags |= EF_AWARD_DEFENSE;
               attacker->client->rewardTime = level.time + REWARD_SPRITE_TIME;
           }
       }
    */
}

// Myrkul: In damage scoring mode, you can get bonuses every time you do damage,
// so the non-kill bonuses (like caps) are scaled up to compensate
float damageScoringScale(void) {
    if (g_damageScoring.integer) {
        return DAMAGE_FRACTION + KILL_FRACTION;
    } else {
        return 1;
    }
}


int entRelativeLocation(int team, gentity_t *ent) {
    gentity_t *loc = Team_GetLocation(ent);
    if (!loc)
        return -1;
    return relativeLocation(team, spawnflagsToLocation(loc->spawnflags));
}


// Joe combat command
void Svcmd_BattleMode_f(void) {
    char *cmdlist [] =
        {

            "set g_gameMode 1\n",

            "set g_freeGear 1\n",
            "set g_healthRestore 1\n",
            "set g_ammoRestore 1\n",

            "set g_battleWeapons 1\n",
            "set g_skyHook 1\n",

            "set dmflags 8\n",

            //"set g_airMove 30\n",
            //"set g_strafeJump 0\n",
            //"set g_speed 360\n",
            //"set g_airSpeed 360\n",

        };

    int i;

    for (i = 0; i < sizeof(cmdlist)/sizeof(char *); i++) {
        // G_Printf("%s", cmdlist[i]);
        trap_SendConsoleCommand( EXEC_INSERT, cmdlist[i]);

    }

    G_Printf("Setting battle mode options.  Change map after.\n");

    //    trap_SendConsoleCommand( EXEC_INSERT, "exec combat_ctf.cfg\n");

}

void Svcmd_DefaultMode_f(void) {
    char *cmdlist [] =
        {
            "set g_gameMode 0\n",

            "set g_freeGear 0\n",
            "set g_healthRestore 0\n",
            "set g_ammoRestore 0\n",

            "set g_battleWeapons 0\n",
            "set g_skyHook 0\n",

            "set dmflags 0\n",

            //"set g_airMove 10\n",
            //"set g_strafeJump 1\n",
            //"set g_speed 320\n",
            //"set g_airSpeed 320\n",
            //"set g_airSpeed 320\n",

        };

    int i;

    for (i = 0; i < sizeof(cmdlist)/sizeof(char *); i++) {
        // G_Printf("%s", cmdlist[i]);
        trap_SendConsoleCommand( EXEC_INSERT, cmdlist[i]);

    }

    G_Printf("Setting default mode options.  Change map after.\n");

    //    trap_SendConsoleCommand( EXEC_INSERT, "exec ctf.cfg\n");

}
// end Joe


// Joe




void Svcmd_Stuff_f(void) {
    char clientNumString[MAX_TOKEN_CHARS];
    char command[MAX_TOKEN_CHARS];
    int clientNum;
    gentity_t *ent;

    if ( trap_Argc() < 3 ) {
        G_Printf("Usage:  stuff <clientNum> \"command text\"\n");
        return;
    }

    trap_Argv( 1, clientNumString, sizeof( clientNumString ) );
    clientNum = atoi(clientNumString);

    trap_Argv( 2, command, sizeof( command ) );

    ent = &g_entities[clientNum];

    //if (ent->r.svFlags & SVF_BOT)
    //    trap_EA_Command(clientNum, va("%s\n", command));
    //else
    trap_SendServerCommand(clientNum, va("stuff \"%s\"", command));
}

// Joe
// call this when a player dies like in classic rocket arena
// bug: bot stays a spectator
void RemoveTournamentLoser( void );
void tourneyExitLevel(void) {
    if ( !level.restarted ) {
        RemoveTournamentLoser();
        trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
        level.restarted = qtrue;
        level.changemap = NULL;
        level.intermissiontime = 0;
    }
}
#ifdef Q3_VM
// from glibc
char *strpbrk (const char *s, const char *accept) {
    while (*s != '\0') {
        const char *a = accept;
        while (*a != '\0')
            if (*a++ == *s)
                return (char *) s;
        ++s;
    }

    return NULL;
}

// from glibc
size_t strspn (const char *s, const char *accept) {
    const char *p;
    const char *a;
    size_t count = 0;

    for (p = s; *p != '\0'; ++p) {
        for (a = accept; *a != '\0'; ++a)
            if (*p == *a)
                break;
        if (*a == '\0')
            return count;
        else
            ++count;
    }

    return count;
}

// glibc strtok(), with static char *olds passed in, olds should be set to NULL
// the first time looking for tokens
char *Q_strtok(char *s, const char *delim, char *olds) {
    char *token;

    if (s == NULL)
        s = olds;

    s += strspn (s, delim);
    if (*s == '\0')
        return NULL;

    token = s;
    s = strpbrk (token, delim);
    if (s == NULL)
        olds = strchr (token, '\0');
    else {
        *s = '\0';
        olds = s + 1;
    }
    return token;
}
#endif

qboolean fileExists(const char *filename) {
    int len;
    fileHandle_t f;

    len = trap_FS_FOpenFile(filename, &f, FS_READ);

    if(len < 0) {
        return qfalse;
    } else {
        trap_FS_FCloseFile(f);
        return qtrue;
    }
}

char *motd = NULL;

// stolen from Publius' locateEntmap() in g_spawn.c
void A_initMOTD(void) {
    int len;
    char *filename = "motd.txt";
    fileHandle_t handle;

    motd = NULL;

    // Try to open the file
    if ((len = trap_FS_FOpenFile(filename, &handle, FS_READ)) <= 0) {
        G_Printf("motd %s not found.\n", filename);
        return;
    }

    if ((motd = G_Alloc(len+1)) == NULL) {
        G_Printf("Unable to allocate memory for motd %s.\n", filename);
        trap_FS_FCloseFile(handle);
        return;
    }

    trap_FS_Read(motd, len, handle);
    motd[len] = 0;
    trap_FS_FCloseFile(handle);

    G_Printf("Loaded motd %s, size %d.\n", filename, len);

} // end initMOTD()

void A_displayMOTD(int clientNum) {
    // doesn't matter if motd in NULL
    // ^4 = blue, ^7 is white
    trap_SendServerCommand(clientNum,
                           va("cp \"^2NewExpert CTF MOD v%s^7\n"
                              "^7www.expertctf.net/^7\n\n"
                              "%s\"", BATTLEBOTS_VERSION, motd));
}

void A_printMOTD(int clientNum) {
    trap_SendServerCommand(clientNum,
                           va("print \"^4NewExpert v%s^7\n"
                              "www.expertctf.net/^7\n\n"
                              "%s\"", BATTLEBOTS_VERSION, motd));
}

void locateEntmap(void) {/*
            	int len;
            	char filename[MAX_OSPATH] = {0};
            	int nameRemaining=MAX_OSPATH-1;
            	fileHandle_t entmapHandle;
             
            	entmap = NULL;
             
            	// build the filename buffer-safe
            	Q_strcat(filename, nameRemaining, ENTMAP_PREFIX);
            	nameRemaining -= strlen(ENTMAP_PREFIX);
            	Q_strcat(filename, nameRemaining, mapTitle());
            	nameRemaining -= strlen(mapTitle());
            	Q_strcat(filename, nameRemaining, ENTMAP_SUFFIX);
            //	G_Printf("formed entmap name %s.\n", filename);
             
            	// Try to open the file
            	if ((len = trap_FS_FOpenFile(filename, &entmapHandle, FS_READ)) <= 0) {
            		G_Printf("Entmap %s not found.\n", filename);
            		return;
            	}
             
            	if ((entmap = G_Alloc(len+1)) == NULL) {
            		G_Printf("Unable to allocate memory for entmap %s.\n",
            			filename);
            		trap_FS_FCloseFile(entmapHandle);
            		entmap = NULL;
            		return;
            	}
             
            	trap_FS_Read(entmap, len, entmapHandle);
            	entmap[len] = 0;
            	trap_FS_FCloseFile(entmapHandle);
             
            	G_Printf("Loaded entmap %s, size %d.\n", filename, len);
            	*/
}
