📄 g_items.c
字号:
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
//
#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 25
#define RESPAWN_HEALTH 35
#define RESPAWN_AMMO 40
#define RESPAWN_HOLDABLE 60
#define RESPAWN_MEGAHEALTH 35//120
#define RESPAWN_POWERUP 120
//======================================================================
int Pickup_Powerup( gentity_t *ent, gentity_t *other ) {
int quantity;
int i;
gclient_t *client;
if ( !other->client->ps.powerups[ent->item->giTag] ) {
// round timing to seconds to make multiple powerup timers
// count in sync
other->client->ps.powerups[ent->item->giTag] =
level.time - ( level.time % 1000 );
}
if ( ent->count ) {
quantity = ent->count;
} else {
quantity = ent->item->quantity;
}
other->client->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_PLAYEREVENTS] ^= PLAYEREVENT_DENIEDREWARD;
}
return RESPAWN_POWERUP;
}
//======================================================================
#ifdef MISSIONPACK
int Pickup_PersistantPowerup( gentity_t *ent, gentity_t *other ) {
int clientNum;
char userinfo[MAX_INFO_STRING];
float handicap;
int max;
other->client->ps.stats[STAT_PERSISTANT_POWERUP] = ent->item - bg_itemlist;
other->client->persistantPowerup = ent;
switch( ent->item->giTag ) {
case PW_GUARD:
clientNum = other->client->ps.clientNum;
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
if( handicap<=0.0f || handicap>100.0f) {
handicap = 100.0f;
}
max = (int)(2 * handicap);
other->health = max;
other->client->ps.stats[STAT_HEALTH] = max;
other->client->ps.stats[STAT_MAX_HEALTH] = max;
other->client->ps.stats[STAT_ARMOR] = max;
other->client->pers.maxHealth = max;
break;
case PW_SCOUT:
clientNum = other->client->ps.clientNum;
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
if( handicap<=0.0f || handicap>100.0f) {
handicap = 100.0f;
}
other->client->pers.maxHealth = handicap;
other->client->ps.stats[STAT_ARMOR] = 0;
break;
case PW_DOUBLER:
clientNum = other->client->ps.clientNum;
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
if( handicap<=0.0f || handicap>100.0f) {
handicap = 100.0f;
}
other->client->pers.maxHealth = handicap;
break;
case PW_AMMOREGEN:
clientNum = other->client->ps.clientNum;
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
if( handicap<=0.0f || handicap>100.0f) {
handicap = 100.0f;
}
other->client->pers.maxHealth = handicap;
memset(other->client->ammoTimes, 0, sizeof(other->client->ammoTimes));
break;
default:
clientNum = other->client->ps.clientNum;
trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
handicap = atof( Info_ValueForKey( userinfo, "handicap" ) );
if( handicap<=0.0f || handicap>100.0f) {
handicap = 100.0f;
}
other->client->pers.maxHealth = handicap;
break;
}
return -1;
}
//======================================================================
#endif
int Pickup_Holdable( gentity_t *ent, gentity_t *other ) {
other->client->ps.stats[STAT_HOLDABLE_ITEM] = ent->item - bg_itemlist;
if( ent->item->giTag == HI_KAMIKAZE ) {
other->client->ps.eFlags |= EF_KAMIKAZE;
}
return RESPAWN_HOLDABLE;
}
//======================================================================
void Add_Ammo (gentity_t *ent, int weapon, int count)
{
ent->client->ps.ammo[weapon] += count;
if ( ent->client->ps.ammo[weapon] > 200 ) {
ent->client->ps.ammo[weapon] = 200;
}
}
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 RESPAWN_AMMO;
}
//======================================================================
int Pickup_Weapon (gentity_t *ent, gentity_t *other) {
int quantity;
if ( ent->count < 0 ) {
quantity = 0; // None for you, sir!
} else {
if ( ent->count ) {
quantity = ent->count;
} else {
quantity = ent->item->quantity;
}
// dropped items and teamplay weapons always have full ammo
if ( ! (ent->flags & FL_DROPPED_ITEM) && g_gametype.integer != GT_TEAM ) {
// respawning rules
// drop the quantity if the already have over the minimum
if ( other->client->ps.ammo[ ent->item->giTag ] < quantity ) {
quantity = quantity - other->client->ps.ammo[ ent->item->giTag ];
} else {
quantity = 1; // only add a single shot
}
}
}
// add the weapon
other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
Add_Ammo( other, ent->item->giTag, quantity );
if (ent->item->giTag == WP_GRAPPLING_HOOK)
other->client->ps.ammo[ent->item->giTag] = -1; // unlimited ammo
// team deathmatch has slow weapon respawns
if ( g_gametype.integer == GT_TEAM ) {
return g_weaponTeamRespawn.integer;
}
return g_weaponRespawn.integer;
}
//======================================================================
int Pickup_Health (gentity_t *ent, gentity_t *other) {
int max;
int quantity;
// small and mega healths will go over the max
#ifdef MISSIONPACK
if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
max = other->client->ps.stats[STAT_MAX_HEALTH];
}
else
#endif
if ( ent->item->quantity != 5 && ent->item->quantity != 100 ) {
max = other->client->ps.stats[STAT_MAX_HEALTH];
} else {
max = other->client->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;
}
other->client->ps.stats[STAT_HEALTH] = other->health;
if ( ent->item->quantity == 100 ) { // mega health respawns slow
return RESPAWN_MEGAHEALTH;
}
return RESPAWN_HEALTH;
}
//======================================================================
int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
#ifdef MISSIONPACK
int upperBound;
other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
if( other->client && bg_itemlist[other->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
upperBound = other->client->ps.stats[STAT_MAX_HEALTH];
}
else {
upperBound = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
}
if ( other->client->ps.stats[STAT_ARMOR] > upperBound ) {
other->client->ps.stats[STAT_ARMOR] = upperBound;
}
#else
other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH] * 2;
}
#endif
return RESPAWN_ARMOR;
}
//======================================================================
/*
===============
RespawnItem
===============
*/
void RespawnItem( gentity_t *ent ) {
// 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->teamchain, count++)
;
}
ent->r.contents = CONTENTS_TRIGGER;
ent->s.eFlags &= ~EF_NODRAW;
ent->r.svFlags &= ~SVF_NOCLIENT;
trap_LinkEntity (ent);
if ( ent->item->giType == IT_POWERUP ) {
// play powerup spawn sound to all clients
gentity_t *te;
// if the powerup respawn sound should Not be global
if (ent->speed) {
te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
}
else {
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
}
te->s.eventParm = G_SoundIndex( "sound/items/poweruprespawn.wav" );
te->r.svFlags |= SVF_BROADCAST;
}
if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == HI_KAMIKAZE ) {
// play powerup spawn sound to all clients
gentity_t *te;
// if the powerup respawn sound should Not be global
if (ent->speed) {
te = G_TempEntity( ent->s.pos.trBase, EV_GENERAL_SOUND );
}
else {
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_SOUND );
}
te->s.eventParm = G_SoundIndex( "sound/items/kamikazerespawn.wav" );
te->r.svFlags |= SVF_BROADCAST;
}
// play the normal respawn sound only to nearby clients
G_AddEvent( ent, EV_ITEM_RESPAWN, 0 );
ent->nextthink = 0;
}
/*
===============
Touch_Item
===============
*/
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
int respawn;
qboolean predict;
if (!other->client)
return;
if (other->health < 1)
return; // dead people can't pickup
// the same pickup rules are used for client side and server side
if ( !BG_CanItemBeGrabbed( g_gametype.integer, &ent->s, &other->client->ps ) ) {
return;
}
G_LogPrintf( "Item: %i %s\n", other->s.number, ent->item->classname );
predict = other->client->pers.predictItemPickup;
// call the item-specific pickup function
switch( ent->item->giType ) {
case IT_WEAPON:
respawn = Pickup_Weapon(ent, other);
// predict = qfalse;
break;
case IT_AMMO:
respawn = Pickup_Ammo(ent, other);
// predict = qfalse;
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);
predict = qfalse;
break;
#ifdef MISSIONPACK
case IT_PERSISTANT_POWERUP:
respawn = Pickup_PersistantPowerup(ent, other);
break;
#endif
case IT_TEAM:
respawn = Pickup_Team(ent, other);
break;
case IT_HOLDABLE:
respawn = Pickup_Holdable(ent, other);
break;
default:
return;
}
if ( !respawn ) {
return;
}
// play the normal pickup sound
if (predict) {
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) {
// if we want the global sound to play
if (!ent->speed) {
gentity_t *te;
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
te->s.eventParm = ent->s.modelindex;
te->r.svFlags |= SVF_BROADCAST;
} else {
gentity_t *te;
te = G_TempEntity( ent->s.pos.trBase, EV_GLOBAL_ITEM_PICKUP );
te->s.eventParm = ent->s.modelindex;
// only send this temp entity to a single client
te->r.svFlags |= SVF_SINGLECLIENT;
te->r.singleClient = other->s.number;
}
}
// fire item targets
G_UseTargets (ent, other);
// wait of -1 will not respawn
if ( ent->wait == -1 ) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -