mirror of
https://github.com/UberGames/rpgxEF.git
synced 2024-11-15 01:11:25 +00:00
1113 lines
28 KiB
C
1113 lines
28 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
#include "g_local.h"
|
|
|
|
/*
|
|
|
|
Items are any object that a player can touch to gain some effect.
|
|
|
|
Pickup will return the number of seconds until they should respawn.
|
|
|
|
all items should pop when dropped in lava or slime
|
|
|
|
Respawnable items don't actually go away when picked up, they are
|
|
just made invisible and untouchable. This allows them to ride
|
|
movers and respawn apropriately.
|
|
*/
|
|
|
|
|
|
#define RESPAWN_ARMOR 20
|
|
#define RESPAWN_TEAM_WEAPON 30
|
|
#define RESPAWN_HEALTH 30
|
|
#define RESPAWN_AMMO 40
|
|
#define RESPAWN_HOLDABLE 60
|
|
#define RESPAWN_MEGAHEALTH 120
|
|
#define RESPAWN_POWERUP 120
|
|
|
|
//Replacement for Max_Ammo table
|
|
vmCvar_t *Max_Weapons[WP_NUM_WEAPONS] = {
|
|
0,
|
|
0,
|
|
&rpg_maxTricorders,
|
|
&rpg_maxPADDs,
|
|
&rpg_maxCups,
|
|
&rpg_maxPhasers,
|
|
&rpg_maxRifles,
|
|
&rpg_maxTR116s,
|
|
&rpg_maxAdminguns,
|
|
&rpg_maxPhotonbursts,
|
|
&rpg_maxDisruptors,
|
|
&rpg_maxMedkits,
|
|
&rpg_maxHyposprays,
|
|
&rpg_maxRegenerators,
|
|
&rpg_maxToolkits,
|
|
&rpg_maxHyperSpanners
|
|
};
|
|
|
|
vmCvar_t *Min_Weapons[WP_NUM_WEAPONS] = {
|
|
0,
|
|
0,
|
|
&rpg_minTricorders,
|
|
&rpg_minPADDs,
|
|
&rpg_minCups,
|
|
&rpg_minPhasers,
|
|
&rpg_minRifles,
|
|
&rpg_minTR116s,
|
|
&rpg_minAdminguns,
|
|
&rpg_minPhotonbursts,
|
|
&rpg_minDisruptors,
|
|
&rpg_minMedkits,
|
|
&rpg_minHyposprays,
|
|
&rpg_minRegenerators,
|
|
&rpg_minToolkits,
|
|
&rpg_minHyperSpanners
|
|
};
|
|
|
|
|
|
// RPG-X: Marcin: Definitions of the PADD messaging system data structures - 06/12/2008
|
|
paddData_t paddData[PADD_DATA_MAX];
|
|
int paddDataNum = 0;
|
|
int numTotalDropped = 0;
|
|
|
|
/*
|
|
===============
|
|
Max_Weapon
|
|
RPG-X | Marcin | 06/12/2008
|
|
===============
|
|
*/
|
|
/**
|
|
* Get the maximum ammount of weapons the player can carry for this weapon.
|
|
* \num weapn num (WP_..)
|
|
* \author Ubergames - Marcin
|
|
* \date 06/12/2008
|
|
*/
|
|
int Max_Weapon(int num)
|
|
{
|
|
if ( Max_Weapons[num] == NULL ) {
|
|
return 1;
|
|
} else {
|
|
return Max_Weapons[num]->integer;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
Min_Weapon
|
|
RPG-X | Marcin | 06/12/2008
|
|
===============
|
|
*/
|
|
/**
|
|
* Get the minimum ammount of weapons a player can carry for a weapon.
|
|
* \param num weapon number (WP_...)
|
|
* \author Ubergames - Marcin
|
|
* \date 06/12/2008
|
|
*/
|
|
int Min_Weapon(int num)
|
|
{
|
|
if ( Min_Weapons[num] == NULL ) {
|
|
return 1;
|
|
} else {
|
|
return Min_Weapons[num]->integer;
|
|
}
|
|
}
|
|
|
|
void Padd_Add( gentity_t *key, gentity_t *who, char *txt )
|
|
{
|
|
int i = 0;
|
|
char *txtp;
|
|
paddData_t *empty = 0;
|
|
|
|
DPRINTF(( S_COLOR_YELLOW "in Padd_Add: txt = %s and last = %s\n", txt, who->client->lastPaddMsg ));
|
|
|
|
while ( !empty ) {
|
|
if ( i >= PADD_DATA_MAX ) {
|
|
G_Printf( S_COLOR_RED "Padd_Add: Too much PADDs!\n" S_COLOR_WHITE );
|
|
return;
|
|
} else if ( paddData[i].key == 0 ) {
|
|
empty = &paddData[i];
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
DPRINTF(( S_COLOR_YELLOW "added: %i with %s on nr %i\n" S_COLOR_WHITE, key, txt, i - 1));
|
|
|
|
empty->key = key;
|
|
if ( txt && txt[0] ) {
|
|
txtp = txt;
|
|
Q_strncpyz( who->client->lastPaddMsg, txt, sizeof( who->client->lastPaddMsg ) );
|
|
} else if ( who->client->lastPaddMsg && who->client->lastPaddMsg[0] ) {
|
|
txtp = who->client->lastPaddMsg;
|
|
} else {
|
|
txtp = 0;
|
|
}
|
|
|
|
if (txtp) {
|
|
Q_strncpyz( empty->value, txtp, sizeof( empty->value ) );
|
|
}
|
|
|
|
Q_strncpyz( empty->owner, who->client->pers.netname, sizeof( empty->owner ) );
|
|
|
|
++paddDataNum;
|
|
}
|
|
|
|
char *Padd_Get( gentity_t *key, gentity_t *who )
|
|
{
|
|
int i, j;
|
|
for ( i = 0; i < PADD_DATA_MAX; ++i ) {
|
|
if ( paddData[i].key == key ) {
|
|
DPRINTF(("^3got: %i with %s on nr %i\n", key, paddData[i].value, i));
|
|
//Inform admins
|
|
for ( j = 0; j < level.maxclients; ++j ) {
|
|
gentity_t *player = &g_entities[j];
|
|
if ( !player->client->noAdminChat && IsAdmin( player ) && rpg_respectPrivacy.integer == 0 && player != who && paddData[i].value && paddData[i].value[0] ) {
|
|
trap_SendServerCommand( player-g_entities, va("print \"%s" S_COLOR_CYAN" (picked up by %s" S_COLOR_CYAN ") " S_COLOR_WHITE "%s\n\"", paddData[i].owner, who->client->pers.netname, paddData[i].value ) );
|
|
}
|
|
}
|
|
|
|
//Store string
|
|
Q_strncpyz( who->client->lastPaddMsg, paddData[i].value, sizeof( who->client->lastPaddMsg ) );
|
|
return paddData[i].value;
|
|
}
|
|
}
|
|
|
|
G_Printf( S_COLOR_RED "Padd_Get: Unable to find the text for this PADD!\n" S_COLOR_WHITE );
|
|
return 0;
|
|
}
|
|
|
|
void Padd_Remove( gentity_t *key )
|
|
{
|
|
int i = 0;
|
|
|
|
while ( qtrue ) {
|
|
if ( paddData[i].key == key ) {
|
|
paddData[i].key = 0;
|
|
paddData[i].value[0] = '\0';
|
|
paddData[i].owner[0] = '\0';
|
|
DPRINTF(( S_COLOR_YELLOW "deleting: %i on %i\n", key, i));
|
|
--paddDataNum;
|
|
return;
|
|
} else if ( i >= PADD_DATA_MAX ) {
|
|
G_Printf( S_COLOR_RED "Padd_Remove: Can not delete PADD!\n" S_COLOR_WHITE);
|
|
return;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// For more than four players, adjust the respawn times, up to 1/4.
|
|
int adjustRespawnTime(float respawnTime)
|
|
{
|
|
if (!g_adaptRespawn.integer)
|
|
{
|
|
return((int)respawnTime);
|
|
}
|
|
|
|
if (level.numPlayingClients > 4)
|
|
{ // Start scaling the respawn times.
|
|
if (level.numPlayingClients > 32)
|
|
{ // 1/4 time minimum.
|
|
respawnTime *= 0.25;
|
|
}
|
|
else if (level.numPlayingClients > 12)
|
|
{ // From 12-32, scale from 0.5 to 0.25;
|
|
respawnTime *= 20.0 / (float)(level.numPlayingClients + 8);
|
|
}
|
|
else
|
|
{ // From 4-12, scale from 1.0 to 0.5;
|
|
respawnTime *= 8.0 / (float)(level.numPlayingClients + 4);
|
|
}
|
|
}
|
|
|
|
if (respawnTime < 1.0)
|
|
{ // No matter what, don't go lower than 1 second, or the pickups become very noisy!
|
|
respawnTime = 1.0;
|
|
}
|
|
|
|
return ((int)respawnTime);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
|
|
int quantity;
|
|
int i;
|
|
gclient_t *client;
|
|
playerState_t *ps = &other->client->ps;
|
|
|
|
if ( !ps->powerups[ent->item->giTag] ) {
|
|
// round timing to seconds to make multiple powerup timers
|
|
// count in sync
|
|
ps->powerups[ent->item->giTag] =
|
|
level.time - ( level.time % 1000 );
|
|
|
|
// kef -- log the fact that we picked up this powerup
|
|
G_LogWeaponPowerup(other->s.number, ent->item->giTag);
|
|
}
|
|
|
|
if ( ent->count ) {
|
|
quantity = ent->count;
|
|
} else {
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
ps->powerups[ent->item->giTag] += quantity * 1000;
|
|
|
|
// give any nearby players a "denied" anti-reward
|
|
for ( i = 0 ; i < level.maxclients ; i++ ) {
|
|
vec3_t delta;
|
|
float len;
|
|
vec3_t forward;
|
|
trace_t tr;
|
|
|
|
client = &level.clients[i];
|
|
if ( client == other->client ) {
|
|
continue;
|
|
}
|
|
if ( client->pers.connected == CON_DISCONNECTED ) {
|
|
continue;
|
|
}
|
|
if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// if same team in team game, no sound
|
|
// cannot use OnSameTeam as it expects to g_entities, not clients
|
|
if ( g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam ) {
|
|
continue;
|
|
}
|
|
|
|
// if too far away, no sound
|
|
VectorSubtract( ent->s.pos.trBase, client->ps.origin, delta );
|
|
len = VectorNormalize( delta );
|
|
if ( len > 192 ) {
|
|
continue;
|
|
}
|
|
|
|
// if not facing, no sound
|
|
AngleVectors( client->ps.viewangles, forward, NULL, NULL );
|
|
if ( DotProduct( delta, forward ) < 0.4 ) {
|
|
continue;
|
|
}
|
|
|
|
// if not line of sight, no sound
|
|
trap_Trace( &tr, client->ps.origin, NULL, NULL, ent->s.pos.trBase, ENTITYNUM_NONE, CONTENTS_SOLID );
|
|
if ( tr.fraction != 1.0 ) {
|
|
continue;
|
|
}
|
|
|
|
// anti-reward
|
|
client->ps.persistant[PERS_REWARD_COUNT]++;
|
|
client->ps.persistant[PERS_REWARD] = REWARD_DENIED;
|
|
}
|
|
|
|
return RESPAWN_POWERUP;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Holdable( gentity_t *ent, gentity_t *other )
|
|
{
|
|
int nItem = ent->item - bg_itemlist;
|
|
|
|
other->client->ps.stats[STAT_HOLDABLE_ITEM] = nItem;
|
|
|
|
// if we just picked up the detpack, indicate that it has not been placed yet
|
|
if (HI_DETPACK == bg_itemlist[nItem].giTag)
|
|
{
|
|
other->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
}
|
|
// kef -- log the fact that we picked up this item
|
|
G_LogWeaponItem(other->s.number, bg_itemlist[nItem].giTag);
|
|
|
|
return adjustRespawnTime(RESPAWN_HOLDABLE);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
void Add_Ammo (gentity_t *ent, int weapon, int count)
|
|
{
|
|
playerState_t *ps = &ent->client->ps;
|
|
ps->ammo[weapon] += count;
|
|
if ( ps->ammo[weapon] > Max_Weapon(weapon) ) {
|
|
ps->ammo[weapon] = Max_Weapon(weapon);
|
|
}
|
|
}
|
|
|
|
int Pickup_Ammo (gentity_t *ent, gentity_t *other)
|
|
{
|
|
int quantity;
|
|
|
|
if ( ent->count ) {
|
|
quantity = ent->count;
|
|
} else {
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
Add_Ammo (other, ent->item->giTag, quantity);
|
|
|
|
return adjustRespawnTime(RESPAWN_AMMO);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
|
|
int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
|
|
char *msg;
|
|
|
|
// add the weapon
|
|
other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
|
|
Add_Ammo( other, ent->item->giTag, 1 );
|
|
|
|
|
|
|
|
// RPG-X: Marcin: print PADD message - 06/12/2008
|
|
|
|
if ( ent->item->giTag == WP_3 ) {
|
|
msg = Padd_Get( ent, other );
|
|
if (msg) {
|
|
trap_SendServerCommand( other-g_entities, va("print \"" S_COLOR_CYAN "(padd)" S_COLOR_WHITE " %s\n\"", msg) );
|
|
}
|
|
}
|
|
|
|
G_LogWeaponPickup(other->s.number, ent->item->giTag);
|
|
|
|
// team deathmatch has slow weapon respawns
|
|
if ( g_gametype.integer == GT_TEAM )
|
|
{
|
|
return adjustRespawnTime(RESPAWN_TEAM_WEAPON);
|
|
}
|
|
|
|
return adjustRespawnTime(g_weaponRespawn.integer);
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Health (gentity_t *ent, gentity_t *other) {
|
|
int max;
|
|
int quantity;
|
|
playerState_t *ps = &other->client->ps;
|
|
|
|
// small and mega healths will go over the max
|
|
if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
|
|
max = ps->stats[STAT_MAX_HEALTH];
|
|
} else {
|
|
max = ps->stats[STAT_MAX_HEALTH] * 2;
|
|
}
|
|
|
|
if ( ent->count ) {
|
|
quantity = ent->count;
|
|
} else {
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
other->health += quantity;
|
|
|
|
if (other->health > max ) {
|
|
other->health = max;
|
|
}
|
|
ps->stats[STAT_HEALTH] = other->health;
|
|
|
|
if ( ent->item->giTag == 100 ) { // mega health respawns slow
|
|
return RESPAWN_MEGAHEALTH; // It also does not adapt like other health pickups.
|
|
}
|
|
|
|
return adjustRespawnTime(RESPAWN_HEALTH);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
|
|
playerState_t *ps = &other->client->ps;
|
|
ps->stats[STAT_ARMOR] += ent->item->quantity;
|
|
if ( ps->stats[STAT_ARMOR] > ps->stats[STAT_MAX_HEALTH] * 2 ) {
|
|
ps->stats[STAT_ARMOR] = ps->stats[STAT_MAX_HEALTH] * 2;
|
|
}
|
|
|
|
return adjustRespawnTime(RESPAWN_ARMOR);
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
void RespawnItem( gentity_t *ent ) {
|
|
if(!ent) return;
|
|
|
|
// randomly select from teamed entities
|
|
if (ent->team) {
|
|
gentity_t *master;
|
|
int count;
|
|
int choice;
|
|
|
|
if ( !ent->teammaster ) {
|
|
G_Error( "RespawnItem: bad teammaster");
|
|
}
|
|
master = ent->teammaster;
|
|
|
|
for (count = 0, ent = master; ent; ent = ent->teamchain, count++)
|
|
;
|
|
|
|
choice = rand() % count;
|
|
|
|
for (count = 0, ent = master; count < choice && ent; ent = ent->teamchain, count++)
|
|
;
|
|
}
|
|
|
|
if(!ent) return;
|
|
|
|
ent->r.contents = CONTENTS_TRIGGER;
|
|
ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER);
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
trap_LinkEntity (ent);
|
|
|
|
if ( ent->item->giType == IT_POWERUP ) {
|
|
// play powerup spawn sound to all clients
|
|
gentity_t *te;
|
|
|
|
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
|
|
te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );//cgs.media.poweruprespawn
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
}
|
|
|
|
// play the normal respawn sound only to nearby clients
|
|
G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
|
|
|
|
ent->nextthink = 0;
|
|
}
|
|
|
|
|
|
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
|
|
int respawn;
|
|
|
|
if (!other->client)
|
|
return;
|
|
if (other->health < 1)
|
|
return; // dead people can't pickup
|
|
|
|
// RPG-X: Marcin: Press USE to pick up items. - 03/12/2008
|
|
if ( !(other->client->pers.cmd.buttons & BUTTON_USE) || other->client->pressedUse == qtrue) {
|
|
return;
|
|
} else {
|
|
other->client->pressedUse = qtrue;
|
|
}
|
|
|
|
// the same pickup rules are used for client side and server side
|
|
if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps, Max_Weapon(other->client->ps.weapon) )
|
|
&& IsAdmin( other ) == qfalse )
|
|
{
|
|
return;
|
|
}
|
|
|
|
numTotalDropped--;
|
|
|
|
G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
|
|
|
|
// call the item-specific pickup function
|
|
|
|
switch( ent->item->giType )
|
|
{
|
|
case IT_WEAPON:
|
|
respawn = Pickup_Weapon(ent, other);
|
|
break;
|
|
case IT_AMMO:
|
|
respawn = Pickup_Ammo(ent, other);
|
|
break;
|
|
case IT_ARMOR:
|
|
respawn = Pickup_Armor(ent, other);
|
|
break;
|
|
case IT_HEALTH:
|
|
respawn = Pickup_Health(ent, other);
|
|
break;
|
|
case IT_POWERUP:
|
|
respawn = Pickup_Powerup(ent, other);
|
|
break;
|
|
case IT_TEAM:
|
|
respawn = 0; // TODO remove?
|
|
break;
|
|
case IT_HOLDABLE:
|
|
respawn = Pickup_Holdable(ent, other);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if ( !respawn) {
|
|
return;
|
|
}
|
|
|
|
// play the normal pickup sound
|
|
if ( other->client->pers.predictItemPickup ) {
|
|
G_AddPredictableEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
|
|
} else {
|
|
G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
|
|
}
|
|
|
|
// powerup pickups are global broadcasts
|
|
if ( ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) {
|
|
gentity_t *te;
|
|
|
|
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
|
|
te->s.eventParm = ent->s.modelindex;
|
|
// tell us which client fired off this global sound
|
|
te->s.otherEntityNum = other->s.number;
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
}
|
|
|
|
// fire item targets
|
|
G_UseTargets (ent, other);
|
|
|
|
if ( rpg_weaponsStay.integer == 1 && IsAdmin( ent->parent ) == qtrue && IsAdmin( other ) == qfalse ) {
|
|
return;
|
|
}
|
|
|
|
if ( ent->item->giTag == WP_3 ) {
|
|
Padd_Remove( ent );
|
|
}
|
|
|
|
// wait of -1 will not respawn
|
|
if ( ent->wait == -1 ) {
|
|
ent->r.svFlags |= SVF_NOCLIENT;
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->r.contents = 0;
|
|
ent->unlinkAfterEvent = qtrue;
|
|
return;
|
|
}
|
|
|
|
// non zero wait overrides respawn time
|
|
if ( ent->wait ) {
|
|
respawn = ent->wait;
|
|
}
|
|
|
|
// random can be used to vary the respawn time
|
|
if ( ent->random ) {
|
|
respawn += crandom() * ent->random;
|
|
if ( respawn < 1 ) {
|
|
respawn = 1;
|
|
}
|
|
}
|
|
|
|
// dropped items will not respawn
|
|
if ( ent->flags & FL_DROPPED_ITEM ) {
|
|
ent->freeAfterEvent = qtrue;
|
|
}
|
|
|
|
// picked up items still stay around, they just don't
|
|
// draw anything. This allows respawnable items
|
|
// to be placed on movers.
|
|
|
|
if (ent->item->giType==IT_WEAPON || ent->item->giType==IT_POWERUP)
|
|
{
|
|
ent->s.eFlags |= EF_ITEMPLACEHOLDER;
|
|
}
|
|
else
|
|
{
|
|
// this line used to prevent items that were picked up from being drawn, but we now want to draw the techy grid thing instead
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->r.svFlags |= SVF_NOCLIENT;
|
|
}
|
|
|
|
ent->r.contents = 0;
|
|
|
|
// ***************
|
|
// ZOID
|
|
// A negative respawn times means to never respawn this item (but don't
|
|
// delete it). This is used by items that are respawned by third party
|
|
// events such as ctf flags
|
|
if ( respawn <= 0 ) {
|
|
ent->nextthink = 0;
|
|
ent->think = 0;
|
|
} else {
|
|
ent->nextthink = level.time + respawn * 1000;
|
|
ent->think = RespawnItem;
|
|
}
|
|
trap_LinkEntity( ent );
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
================
|
|
LaunchItem
|
|
|
|
Spawns an item and tosses it forward
|
|
================
|
|
*/
|
|
gentity_t *LaunchItem( gitem_t *item, gentity_t *who, vec3_t origin, vec3_t velocity, int flags, char *txt) // RPG-X: Marcin: for ThrowWeapon 03/12/2008
|
|
{ // and for PADD stuff too 06/12/2008, 08/12/2008
|
|
gentity_t *dropped;
|
|
|
|
dropped = G_Spawn();
|
|
|
|
dropped->s.eType = ET_ITEM;
|
|
dropped->s.modelindex = item - bg_itemlist; // store item number in modelindex
|
|
dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item
|
|
|
|
dropped->classname = item->classname;
|
|
dropped->item = item;
|
|
VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS);
|
|
VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
|
|
dropped->r.contents = CONTENTS_TRIGGER;
|
|
|
|
dropped->touch = Touch_Item;
|
|
|
|
G_SetOrigin( dropped, origin );
|
|
dropped->s.pos.trType = TR_GRAVITY;
|
|
dropped->s.pos.trTime = level.time;
|
|
VectorCopy( velocity, dropped->s.pos.trDelta );
|
|
|
|
dropped->s.eFlags |= EF_BOUNCE_HALF;
|
|
dropped->s.eFlags |= EF_DEAD; // Yes, this is totally lame, but we use it bg_misc to check
|
|
// if the item has been droped, and if so, make it pick-up-able
|
|
// cdr
|
|
|
|
if ( item->giTag == WP_3 ) {
|
|
Padd_Add(dropped, who, txt);
|
|
}
|
|
|
|
dropped->think = G_FreeEntity;
|
|
dropped->nextthink = level.time + 6000000; //30000; // RPG-X: Marcin: increased - 03/12/2008
|
|
|
|
dropped->flags = flags; // FL_DROPPED_ITEM; // RPG-X: Marcin: for ThrowWeapon - 03/12/2008
|
|
|
|
if( flags & FL_THROWN_ITEM) {
|
|
dropped->clipmask = MASK_SHOT;
|
|
dropped->s.pos.trTime = level.time - 50;
|
|
VectorScale( velocity, 300, dropped->s.pos.trDelta );
|
|
SnapVector( dropped->s.pos.trDelta );
|
|
dropped->physicsBounce = 0.2;
|
|
}
|
|
|
|
|
|
trap_LinkEntity (dropped);
|
|
|
|
return dropped;
|
|
}
|
|
|
|
gentity_t *DropWeapon( gentity_t *ent, gitem_t *item, float angle, int flags, char *txt ) {
|
|
vec3_t velocity;
|
|
vec3_t origin;
|
|
|
|
VectorCopy( ent->s.pos.trBase, origin );
|
|
|
|
// set aiming directions
|
|
AngleVectors (ent->client->ps.viewangles, velocity, NULL, NULL);
|
|
|
|
origin[2] += ent->client->ps.viewheight + 10;
|
|
VectorMA( origin, 14, velocity, origin );
|
|
// snap to integer coordinates for more efficient network bandwidth usage
|
|
SnapVector( origin );
|
|
|
|
// extra vertical velocity
|
|
velocity[2] += 0.2;
|
|
VectorNormalize( velocity );
|
|
return LaunchItem( item, ent, origin, velocity, flags, txt );
|
|
}
|
|
|
|
gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle ) {
|
|
vec3_t velocity;
|
|
vec3_t angles;
|
|
|
|
VectorCopy( ent->s.apos.trBase, angles );
|
|
angles[YAW] += angle;
|
|
angles[PITCH] = 0; // always forward
|
|
|
|
AngleVectors( angles, velocity, NULL, NULL );
|
|
VectorScale( velocity, 300, velocity );
|
|
velocity[2] += 75 + crandom() * 50;
|
|
|
|
return LaunchItem( item, ent, ent->s.pos.trBase, velocity, FL_DROPPED_ITEM, 0 ); // RPG-X: Marcin: for ThrowWeapon - 03/12/2008
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
Use_Item
|
|
|
|
Respawn the item
|
|
================
|
|
*/
|
|
void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator ) {
|
|
RespawnItem( ent );
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
void FinishSpawningItem( gentity_t *ent ) {
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
|
|
if ( !ent )
|
|
{
|
|
Com_Printf("print \"Ent Missing\"");
|
|
return;
|
|
}
|
|
|
|
if ( !ent->item )
|
|
{
|
|
Com_Printf("print \"Ent->item Missing\"");
|
|
return;
|
|
}
|
|
|
|
VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS );
|
|
VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
|
|
|
|
ent->s.eType = ET_ITEM;
|
|
ent->s.modelindex = ent->item - bg_itemlist; // store item number in modelindex
|
|
ent->s.modelindex2 = 0; // zero indicates this isn't a dropped item
|
|
|
|
ent->r.contents = CONTENTS_TRIGGER;
|
|
ent->touch = Touch_Item;
|
|
// useing an item causes it to respawn
|
|
ent->use = Use_Item;
|
|
|
|
|
|
Com_Printf("print \"giType %i!\n\"", ent->item->giType);
|
|
|
|
if ( ent->spawnflags & 1 ) {
|
|
// suspended
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
} else {
|
|
// 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 ) {
|
|
G_Printf ("FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
#ifndef FINAL_BUILD
|
|
G_Error("FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
#endif
|
|
G_Printf ("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// allow to ride movers
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
}
|
|
|
|
// team slaves and targeted items aren't present at start
|
|
if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->r.contents = 0;
|
|
return;
|
|
}
|
|
|
|
trap_LinkEntity (ent);
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
FinishSpawningDetpack
|
|
|
|
Traces down to find where an item should rest, instead of letting them
|
|
free fall from their spawn points
|
|
================
|
|
*/
|
|
extern void detpack_shot( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath );
|
|
|
|
qboolean FinishSpawningDetpack( gentity_t *ent, int itemIndex )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
|
|
VectorSet( ent->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, 0 );
|
|
VectorSet( ent->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
|
|
|
|
ent->s.eType = ET_USEABLE;
|
|
ent->s.modelindex = bg_itemlist[itemIndex].giTag; // this'll be used in CG_Useable()
|
|
ent->s.modelindex2 = itemIndex; // store item number in modelindex
|
|
|
|
ent->classname = bg_itemlist[itemIndex].classname;
|
|
ent->r.contents = CONTENTS_CORPSE;//CONTENTS_TRIGGER;
|
|
ent->takedamage = qtrue;
|
|
ent->health = 5;
|
|
ent->touch = 0;
|
|
ent->die = detpack_shot;
|
|
|
|
// useing an item causes it to respawn
|
|
ent->use = Use_Item;
|
|
|
|
// 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 )
|
|
{
|
|
G_FreeEntity( ent );
|
|
return qfalse;
|
|
}
|
|
|
|
// allow to ride movers
|
|
ent->physicsObject = qtrue;
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
|
|
trap_LinkEntity (ent);
|
|
|
|
ent->noise_index = G_SoundIndex("sound/weapons/detpacklatch.wav");
|
|
G_AddEvent( ent, EV_GENERAL_SOUND, ent->noise_index );
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
FinishSpawningDecoy
|
|
|
|
Traces down to find where an item should rest, instead of letting them
|
|
free fall from their spawn points
|
|
================
|
|
*/
|
|
qboolean FinishSpawningDecoy( gentity_t *ent, int itemIndex )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
|
|
ent->classname = bg_itemlist[itemIndex].classname;
|
|
ent->touch = 0; // null touch function pointer
|
|
// useing an item causes it to respawn
|
|
ent->use = Use_Item;
|
|
|
|
// drop to floor
|
|
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 1 ); //4096
|
|
trap_Trace( &tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID );
|
|
|
|
if ( tr.startsolid )
|
|
{ // If stuck in a solid, give up and go home
|
|
G_FreeEntity( ent );
|
|
return qfalse;
|
|
}
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
|
|
// allow to ride movers
|
|
if ( tr.contents & CONTENTS_SOLID ) { //TiM - only if u spawn them ON the elevator. Otherwise, leave them in the air
|
|
ent->physicsObject = qtrue;
|
|
ent->s.pos.trType = TR_GRAVITY;//have to do this because it thinks it's an ET_PLAYER
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
}
|
|
|
|
// Turn off the NODRAW and NOCLIENT flags
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->r.svFlags &= ~SVF_NOCLIENT;
|
|
|
|
trap_LinkEntity (ent);
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
|
|
|
|
qboolean itemRegistered[MAX_ITEMS];
|
|
|
|
void ClearRegisteredItems( void ) {
|
|
memset( itemRegistered, 0, sizeof( itemRegistered ) );
|
|
// players always start with the base weapon
|
|
RegisterItem( BG_FindItemForWeapon( WP_5 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_6 ) ); //this is for the podium at the end, make sure we have the model
|
|
|
|
RegisterItem( BG_FindItemForWeapon( WP_1 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_10 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_13 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_12 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_14 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_11 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_2 ) );
|
|
|
|
RegisterItem( BG_FindItemForWeapon( WP_3 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_15 ) );
|
|
RegisterItem( BG_FindItemForWeapon( WP_7 ) );
|
|
}
|
|
|
|
void RegisterItem( gitem_t *item ) {
|
|
if ( !item ) {
|
|
G_Error( "RegisterItem: NULL" );
|
|
//RPG-X
|
|
Com_Printf( "Missing Item In RegisterItem\n" );
|
|
}
|
|
itemRegistered[ item - bg_itemlist ] = qtrue;
|
|
}
|
|
|
|
void SaveRegisteredItems( void ) {
|
|
char string[MAX_ITEMS+1];
|
|
int i;
|
|
int count;
|
|
|
|
count = 0;
|
|
for ( i = 0 ; i < bg_numItems ; i++ ) {
|
|
if ( itemRegistered[i] ) {
|
|
count++;
|
|
string[i] = '1';
|
|
} else {
|
|
string[i] = '0';
|
|
}
|
|
}
|
|
string[ bg_numItems ] = 0;
|
|
|
|
G_Printf( "%i items registered\n", count );
|
|
trap_SetConfigstring(CS_ITEMS, string);
|
|
}
|
|
|
|
|
|
qboolean G_ItemSuppressed( int itemType, int itemTag )
|
|
{
|
|
if ( rpg_rpg.integer != 0 )
|
|
{
|
|
switch( itemType )
|
|
{
|
|
case IT_HEALTH:
|
|
case IT_ARMOR:
|
|
case IT_HOLDABLE:
|
|
case IT_POWERUP:
|
|
case IT_AMMO:
|
|
case IT_WEAPON:
|
|
return qtrue;
|
|
break;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean G_ItemClassnameSuppressed( char *itemname )
|
|
{
|
|
gitem_t *item = NULL;
|
|
int itemType = 0;
|
|
int itemTag = 0;
|
|
|
|
item = BG_FindItemWithClassname( itemname );
|
|
|
|
if ( !item )
|
|
{
|
|
return qfalse;
|
|
}
|
|
itemType = item->giType;
|
|
itemTag = item->giTag;
|
|
|
|
return G_ItemSuppressed( itemType, itemTag );
|
|
}
|
|
|
|
void G_SpawnItem (gentity_t *ent, gitem_t *item) {
|
|
if ( G_ItemSuppressed( item->giType, item->giTag ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
G_SpawnFloat( "random", "0", &ent->random );
|
|
G_SpawnFloat( "wait", "0", &ent->wait );
|
|
|
|
RegisterItem( item );
|
|
ent->item = item;
|
|
// some movers spawn on the second frame, so delay item
|
|
// spawns until the third frame so they can ride trains
|
|
ent->nextthink = level.time + FRAMETIME * 2;
|
|
ent->think = FinishSpawningItem;
|
|
|
|
ent->physicsBounce = 0.50; // items are bouncy
|
|
|
|
if ( item->giType == IT_POWERUP ) {
|
|
G_SoundIndex( "sound/items/poweruprespawn.wav" );//cgs.media.poweruprespawn
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_BounceItem
|
|
|
|
================
|
|
*/
|
|
void G_BounceItem( gentity_t *ent, trace_t *trace ) {
|
|
vec3_t velocity;
|
|
float dot;
|
|
int hitTime;
|
|
|
|
// reflect the velocity on the trace plane
|
|
hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
|
|
BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
|
|
dot = DotProduct( velocity, trace->plane.normal );
|
|
VectorMA( velocity, -2*dot, trace->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 ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 ) {
|
|
trace->endpos[2] += 1.0; // make sure it is off ground
|
|
SnapVector( trace->endpos );
|
|
G_SetOrigin( ent, trace->endpos );
|
|
ent->s.groundEntityNum = trace->entityNum;
|
|
return;
|
|
}
|
|
|
|
VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
|
|
VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
|
|
ent->s.pos.trTime = level.time;
|
|
}
|
|
|
|
void G_RunItem( gentity_t *ent ) {
|
|
vec3_t origin;
|
|
trace_t tr;
|
|
int contents;
|
|
int mask;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
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 );
|
|
|
|
// trace a line from the previous position to the current position
|
|
if ( ent->clipmask ) {
|
|
mask = ent->clipmask;
|
|
} else {
|
|
mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
|
|
}
|
|
trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
|
|
ent->r.ownerNum, mask );
|
|
|
|
VectorCopy( tr.endpos, ent->r.currentOrigin );
|
|
|
|
if ( tr.startsolid ) {
|
|
tr.fraction = 0;
|
|
}
|
|
|
|
trap_LinkEntity( ent ); // FIXME: avoid this for stationary?
|
|
|
|
// 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 ) {
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
G_BounceItem( ent, &tr );
|
|
}
|
|
|