mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-29 23:42:38 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
1664 lines
43 KiB
C++
1664 lines
43 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999 - 2005, Id Software, Inc.
|
|
Copyright (C) 2000 - 2013, Raven Software, Inc.
|
|
Copyright (C) 2001 - 2013, Activision, Inc.
|
|
Copyright (C) 2013 - 2015, OpenJK contributors
|
|
|
|
This file is part of the OpenJK source code.
|
|
|
|
OpenJK is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License version 2 as
|
|
published by the Free Software Foundation.
|
|
|
|
This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "g_items.h"
|
|
#include "wp_saber.h"
|
|
#include "../cgame/cg_local.h"
|
|
#include "b_local.h"
|
|
|
|
extern qboolean missionInfo_Updated;
|
|
|
|
extern void CrystalAmmoSettings(gentity_t *ent);
|
|
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
|
|
extern qboolean PM_InKnockDown( playerState_t *ps );
|
|
extern qboolean PM_InGetUp( playerState_t *ps );
|
|
extern void WP_SetSaber( gentity_t *ent, int saberNum, const char *saberName );
|
|
extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
|
|
extern void WP_SaberFallSound( gentity_t *owner, gentity_t *saber );
|
|
extern saber_colors_t TranslateSaberColor( const char *name );
|
|
|
|
extern cvar_t *g_spskill;
|
|
extern cvar_t *g_sex;
|
|
extern cvar_t *g_saberPickuppableDroppedSabers;
|
|
|
|
#define MAX_BACTA_HEAL_AMOUNT 25
|
|
|
|
/*
|
|
|
|
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.
|
|
*/
|
|
|
|
// Item Spawn flags
|
|
#define ITMSF_SUSPEND 1
|
|
#define ITMSF_NOPLAYER 2
|
|
#define ITMSF_ALLOWNPC 4
|
|
#define ITMSF_NOTSOLID 8
|
|
#define ITMSF_VERTICAL 16
|
|
#define ITMSF_INVISIBLE 32
|
|
#define ITMSF_NOGLOW 64
|
|
#define ITMSF_USEPICKUP 128
|
|
#define ITMSF_STATIONARY 2048
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
===============
|
|
G_InventorySelectable
|
|
===============
|
|
*/
|
|
qboolean G_InventorySelectable( int index,gentity_t *other)
|
|
{
|
|
if (other->client->ps.inventory[index])
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
extern qboolean INV_GoodieKeyGive( gentity_t *target );
|
|
extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname );
|
|
int Pickup_Holdable( gentity_t *ent, gentity_t *other )
|
|
{
|
|
int i,original;
|
|
|
|
other->client->ps.stats[STAT_ITEMS] |= (1<<ent->item->giTag);
|
|
|
|
if ( ent->item->giTag == INV_SECURITY_KEY )
|
|
{//give the key
|
|
//FIXME: temp message
|
|
gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SECURITY_KEY" );
|
|
INV_SecurityKeyGive( other, ent->message );
|
|
}
|
|
else if ( ent->item->giTag == INV_GOODIE_KEY )
|
|
{//give the key
|
|
//FIXME: temp message
|
|
gi.SendServerCommand( 0, "cp @SP_INGAME_YOU_TOOK_SUPPLY_KEY" );
|
|
INV_GoodieKeyGive( other );
|
|
}
|
|
else
|
|
{// Picking up a normal item?
|
|
other->client->ps.inventory[ent->item->giTag]++;
|
|
}
|
|
// Got a security key
|
|
|
|
// Set the inventory select, just in case it hasn't
|
|
original = cg.inventorySelect;
|
|
for ( i = 0 ; i < INV_MAX ; i++ )
|
|
{
|
|
if ((cg.inventorySelect < INV_ELECTROBINOCULARS) || (cg.inventorySelect >= INV_MAX))
|
|
{
|
|
cg.inventorySelect = (INV_MAX - 1);
|
|
}
|
|
|
|
if ( G_InventorySelectable( cg.inventorySelect,other ) )
|
|
{
|
|
return 60;
|
|
}
|
|
cg.inventorySelect++;
|
|
}
|
|
|
|
cg.inventorySelect = original;
|
|
|
|
return 60;
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
int Add_Ammo2 (gentity_t *ent, int ammoType, int count)
|
|
{
|
|
|
|
if (ammoType != AMMO_FORCE)
|
|
{
|
|
ent->client->ps.ammo[ammoType] += count;
|
|
|
|
// since the ammo is the weapon in this case, picking up ammo should actually give you the weapon
|
|
switch( ammoType )
|
|
{
|
|
case AMMO_THERMAL:
|
|
ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_THERMAL );
|
|
break;
|
|
case AMMO_DETPACK:
|
|
ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_DET_PACK );
|
|
break;
|
|
case AMMO_TRIPMINE:
|
|
ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_TRIP_MINE );
|
|
break;
|
|
}
|
|
|
|
if ( ent->client->ps.ammo[ammoType] > ammoData[ammoType].max )
|
|
{
|
|
ent->client->ps.ammo[ammoType] = ammoData[ammoType].max;
|
|
return qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ent->client->ps.forcePower >= ammoData[ammoType].max )
|
|
{//if have full force, just get 25 extra per crystal
|
|
ent->client->ps.forcePower += 25;
|
|
}
|
|
else
|
|
{//else if don't have full charge, give full amount, up to max + 25
|
|
ent->client->ps.forcePower += count;
|
|
if ( ent->client->ps.forcePower >= ammoData[ammoType].max + 25 )
|
|
{//cap at max + 25
|
|
ent->client->ps.forcePower = ammoData[ammoType].max + 25;
|
|
}
|
|
}
|
|
|
|
if ( ent->client->ps.forcePower >= ammoData[ammoType].max*2 )
|
|
{//always cap at twice a full charge
|
|
ent->client->ps.forcePower = ammoData[ammoType].max*2;
|
|
return qfalse; // can't hold any more
|
|
}
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
void Add_Ammo (gentity_t *ent, int weapon, int count)
|
|
{
|
|
Add_Ammo2(ent,weaponData[weapon].ammoIndex,count);
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
int Pickup_Ammo (gentity_t *ent, gentity_t *other)
|
|
{
|
|
int quantity;
|
|
|
|
if ( ent->count ) {
|
|
quantity = ent->count;
|
|
} else {
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
Add_Ammo2 (other, ent->item->giTag, quantity);
|
|
|
|
return 30;
|
|
}
|
|
|
|
//======================================================================
|
|
void Add_Batteries( gentity_t *ent, int *count )
|
|
{
|
|
if ( ent->client && ent->client->ps.batteryCharge < MAX_BATTERIES && *count )
|
|
{
|
|
if ( *count + ent->client->ps.batteryCharge > MAX_BATTERIES )
|
|
{
|
|
// steal what we need, then leave the rest for later
|
|
*count -= ( MAX_BATTERIES - ent->client->ps.batteryCharge );
|
|
ent->client->ps.batteryCharge = MAX_BATTERIES;
|
|
}
|
|
else
|
|
{
|
|
// just drain all of the batteries
|
|
ent->client->ps.batteryCharge += *count;
|
|
*count = 0;
|
|
}
|
|
|
|
G_AddEvent( ent, EV_BATTERIES_CHARGED, 0 );
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
int Pickup_Battery( gentity_t *ent, gentity_t *other )
|
|
{
|
|
int quantity;
|
|
|
|
if ( ent->count )
|
|
{
|
|
quantity = ent->count;
|
|
}
|
|
else
|
|
{
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
// There may be some left over in quantity if the player is close to full, but with pickup items, this amount will just be lost
|
|
Add_Batteries( other, &quantity );
|
|
|
|
return 30;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
void G_CopySaberItemValues( gentity_t *pickUpSaber, gentity_t *oldSaber )
|
|
{
|
|
if ( oldSaber && pickUpSaber )
|
|
{
|
|
oldSaber->spawnflags = pickUpSaber->spawnflags;
|
|
oldSaber->random = pickUpSaber->random;
|
|
oldSaber->flags = pickUpSaber->flags;
|
|
}
|
|
}
|
|
|
|
gentity_t *G_DropSaberItem( const char *saberType, saber_colors_t saberColor, vec3_t saberPos, vec3_t saberVel, vec3_t saberAngles, gentity_t *copySaber )
|
|
{//turn it into a pick-uppable item!
|
|
gentity_t *newItem = NULL;
|
|
if ( saberType
|
|
&& saberType[0] )
|
|
{//have a valid string to use for saberType
|
|
newItem = G_Spawn();
|
|
if ( newItem )
|
|
{
|
|
newItem->classname = G_NewString( "weapon_saber" );
|
|
VectorCopy( saberPos, newItem->s.origin );
|
|
G_SetOrigin( newItem, newItem->s.origin );
|
|
VectorCopy( saberAngles, newItem->s.angles );
|
|
G_SetAngles( newItem, newItem->s.angles );
|
|
newItem->spawnflags = 128;/*ITMSF_USEPICKUP*/
|
|
newItem->spawnflags |= 64;/*ITMSF_NOGLOW*/
|
|
newItem->NPC_type = G_NewString( saberType );//saberType
|
|
//FIXME: transfer per-blade color somehow?
|
|
newItem->NPC_targetname = (char *)saberColorStringForColor[saberColor];
|
|
newItem->count = 1;
|
|
newItem->flags = FL_DROPPED_ITEM;
|
|
G_SpawnItem( newItem, FindItemForWeapon( WP_SABER ) );
|
|
newItem->s.pos.trType = TR_GRAVITY;
|
|
newItem->s.pos.trTime = level.time;
|
|
VectorCopy( saberVel, newItem->s.pos.trDelta );
|
|
//newItem->s.eFlags |= EF_BOUNCE_HALF;
|
|
//copy some values from another saber, if provided:
|
|
G_CopySaberItemValues( copySaber, newItem );
|
|
//don't *think* about calling FinishSpawningItem, just do it!
|
|
newItem->e_ThinkFunc = thinkF_NULL;
|
|
newItem->nextthink = -1;
|
|
FinishSpawningItem( newItem );
|
|
newItem->delay = level.time + 500;//so you can't pick it back up right away
|
|
}
|
|
}
|
|
return newItem;
|
|
}
|
|
|
|
extern void G_SetSabersFromCVars( gentity_t *ent );
|
|
qboolean Pickup_Saber( gentity_t *self, qboolean hadSaber, gentity_t *pickUpSaber )
|
|
{
|
|
//NOTE: loopAnim = saberSolo, alt_fire = saberLeftHand, NPC_type = saberType, NPC_targetname = saberColor
|
|
qboolean foundIt = qfalse;
|
|
|
|
if ( !pickUpSaber || !self || !self->client )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
//G_RemoveWeaponModels( ent );//???
|
|
if ( Q_stricmp( "player", pickUpSaber->NPC_type ) == 0 )
|
|
{//"player" means use cvar info
|
|
G_SetSabersFromCVars( self );
|
|
foundIt = qtrue;
|
|
}
|
|
else
|
|
{
|
|
saberInfo_t newSaber={0};
|
|
qboolean swapSabers = qfalse;
|
|
|
|
if ( self->client->ps.weapon == WP_SABER
|
|
&& self->client->ps.weaponTime > 0 )
|
|
{//can't pick up a new saber while the old one is busy (also helps to work as a debouncer so you don't swap out sabers rapidly when touching more than one at a time)
|
|
return qfalse;
|
|
}
|
|
|
|
if ( pickUpSaber->count == 1
|
|
&& g_saberPickuppableDroppedSabers->integer )
|
|
{
|
|
swapSabers = qtrue;
|
|
}
|
|
|
|
if ( WP_SaberParseParms( pickUpSaber->NPC_type, &newSaber ) )
|
|
{//successfully found a saber .sab entry to use
|
|
int saberNum = 0;
|
|
qboolean removeLeftSaber = qfalse;
|
|
if ( pickUpSaber->alt_fire )
|
|
{//always go in the left hand
|
|
if ( !hadSaber )
|
|
{//can't have a saber only in your left hand!
|
|
return qfalse;
|
|
}
|
|
saberNum = 1;
|
|
//just in case...
|
|
removeLeftSaber = qtrue;
|
|
}
|
|
else if ( !hadSaber )
|
|
{//don't have a saber at all yet, put it in our right hand
|
|
saberNum = 0;
|
|
//just in case...
|
|
removeLeftSaber = qtrue;
|
|
}
|
|
else if ( pickUpSaber->loopAnim//only supposed to use this one saber when grab this pickup
|
|
|| (newSaber.saberFlags&SFL_TWO_HANDED) //new saber is two-handed
|
|
|| (hadSaber && (self->client->ps.saber[0].saberFlags&SFL_TWO_HANDED)) )//old saber is two-handed
|
|
{//replace the old right-hand saber and remove the left hand one
|
|
saberNum = 0;
|
|
removeLeftSaber = qtrue;
|
|
}
|
|
else
|
|
{//have, at least, a saber in our right hand and the new one could go in either left or right hand
|
|
if ( self->client->ps.dualSabers )
|
|
{//I already have 2 sabers
|
|
vec3_t dir2Saber, rightDir;
|
|
//to determine which one to replace, see which side of me it's on
|
|
VectorSubtract( pickUpSaber->currentOrigin, self->currentOrigin, dir2Saber );
|
|
dir2Saber[2] = 0;
|
|
AngleVectors( self->currentAngles, NULL, rightDir, NULL );
|
|
rightDir[2] = 0;
|
|
if ( DotProduct( rightDir, dir2Saber ) > 0 )
|
|
{
|
|
saberNum = 0;
|
|
}
|
|
else
|
|
{
|
|
saberNum = 1;
|
|
//just in case...
|
|
removeLeftSaber = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{//just add it as a second saber
|
|
saberNum = 1;
|
|
//just in case...
|
|
removeLeftSaber = qtrue;
|
|
}
|
|
}
|
|
if ( saberNum == 0 )
|
|
{//want to reach out with right hand
|
|
if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD )
|
|
{//but only if already playing the pickup with left hand anim...
|
|
NPC_SetAnim( self, SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
}
|
|
if ( swapSabers )
|
|
{//drop first one where the one we're picking up is
|
|
G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
|
|
if ( removeLeftSaber )
|
|
{//drop other one at my origin
|
|
G_DropSaberItem( self->client->ps.saber[1].name, self->client->ps.saber[1].blade[0].color, self->currentOrigin, (float *)vec3_origin, self->currentAngles, pickUpSaber );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( swapSabers )
|
|
{
|
|
G_DropSaberItem( self->client->ps.saber[saberNum].name, self->client->ps.saber[saberNum].blade[0].color, pickUpSaber->currentOrigin, (float *)vec3_origin, pickUpSaber->currentAngles, pickUpSaber );
|
|
}
|
|
}
|
|
if ( removeLeftSaber )
|
|
{
|
|
WP_RemoveSaber( self, 1 );
|
|
}
|
|
WP_SetSaber( self, saberNum, pickUpSaber->NPC_type );
|
|
WP_SaberInitBladeData( self );
|
|
if ( self->client->ps.saber[saberNum].stylesLearned )
|
|
{
|
|
self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].stylesLearned;
|
|
}
|
|
if ( self->client->ps.saber[saberNum].singleBladeStyle )
|
|
{
|
|
self->client->ps.saberStylesKnown |= self->client->ps.saber[saberNum].singleBladeStyle;
|
|
}
|
|
if ( pickUpSaber->NPC_targetname != NULL )
|
|
{//NPC_targetname = saberColor
|
|
saber_colors_t saber_color = TranslateSaberColor( pickUpSaber->NPC_targetname );
|
|
for ( int bladeNum = 0; bladeNum < MAX_BLADES; bladeNum++ )
|
|
{
|
|
self->client->ps.saber[saberNum].blade[bladeNum].color = saber_color;
|
|
}
|
|
}
|
|
if ( self->client->ps.torsoAnim == BOTH_BUTTON_HOLD
|
|
|| self->client->ps.torsoAnim == BOTH_SABERPULL )
|
|
{//don't let them attack right away, force them to finish the anim
|
|
self->client->ps.weaponTime = self->client->ps.torsoAnimTimer;
|
|
}
|
|
foundIt = qtrue;
|
|
}
|
|
WP_SaberFreeStrings(newSaber);
|
|
}
|
|
return foundIt;
|
|
}
|
|
|
|
extern void CG_ChangeWeapon( int num );
|
|
int Pickup_Weapon (gentity_t *ent, gentity_t *other)
|
|
{
|
|
int quantity;
|
|
qboolean hadWeapon = qfalse;
|
|
|
|
/*
|
|
if ( ent->count || (ent->activator && !ent->activator->s.number) )
|
|
{
|
|
quantity = ent->count;
|
|
}
|
|
else
|
|
{
|
|
quantity = ent->item->quantity;
|
|
}
|
|
*/
|
|
|
|
// dropped items are always picked up
|
|
if ( ent->flags & FL_DROPPED_ITEM )
|
|
{
|
|
quantity = ent->count;
|
|
}
|
|
else
|
|
{//wasn't dropped
|
|
quantity = ent->item->quantity?ent->item->quantity:50;
|
|
}
|
|
|
|
// add the weapon
|
|
if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
|
|
{
|
|
hadWeapon = qtrue;
|
|
}
|
|
other->client->ps.stats[STAT_WEAPONS] |= ( 1 << ent->item->giTag );
|
|
|
|
if ( ent->item->giTag == WP_SABER && (!hadWeapon || ent->NPC_type != NULL) )
|
|
{//didn't have a saber or it is specifying a certain kind of saber to use
|
|
if ( !Pickup_Saber( other, hadWeapon, ent ) )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ( other->s.number )
|
|
{//NPC
|
|
if ( other->s.weapon == WP_NONE
|
|
|| ent->item->giTag == WP_SABER )
|
|
{//NPC with no weapon picked up a weapon, change to this weapon
|
|
//FIXME: clear/set the alt-fire flag based on the picked up weapon and my class?
|
|
other->client->ps.weapon = ent->item->giTag;
|
|
other->client->ps.weaponstate = WEAPON_RAISING;
|
|
ChangeWeapon( other, ent->item->giTag );
|
|
if ( ent->item->giTag == WP_SABER )
|
|
{
|
|
other->client->ps.SaberActivate();
|
|
WP_SaberAddG2SaberModels( other );
|
|
}
|
|
else
|
|
{
|
|
G_CreateG2AttachedWeaponModel( other, weaponData[ent->item->giTag].weaponMdl, other->handRBolt, 0 );
|
|
}
|
|
}
|
|
}
|
|
if ( ent->item->giTag == WP_SABER )
|
|
{//picked up a saber
|
|
if ( other->s.weapon != WP_SABER )
|
|
{//player picking up saber
|
|
other->client->ps.weapon = WP_SABER;
|
|
other->client->ps.weaponstate = WEAPON_RAISING;
|
|
if ( other->s.number < MAX_CLIENTS )
|
|
{//make sure the cgame-side knows this
|
|
CG_ChangeWeapon( WP_SABER );
|
|
}
|
|
else
|
|
{//make sure the cgame-side knows this
|
|
ChangeWeapon( other, WP_SABER );
|
|
}
|
|
}
|
|
if ( !other->client->ps.SaberActive() )
|
|
{//turn it/them on!
|
|
other->client->ps.SaberActivate();
|
|
}
|
|
}
|
|
|
|
if ( quantity )
|
|
{
|
|
// Give ammo
|
|
Add_Ammo( other, ent->item->giTag, quantity );
|
|
}
|
|
return 5;
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
int ITM_AddHealth (gentity_t *ent, int count)
|
|
{
|
|
|
|
ent->health += count;
|
|
|
|
if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) // Past max health
|
|
{
|
|
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
int Pickup_Health (gentity_t *ent, gentity_t *other) {
|
|
int max;
|
|
int quantity;
|
|
|
|
max = other->client->ps.stats[STAT_MAX_HEALTH];
|
|
|
|
if ( ent->count ) {
|
|
quantity = ent->count;
|
|
} else {
|
|
quantity = ent->item->quantity;
|
|
}
|
|
|
|
other->health += quantity;
|
|
|
|
if (other->health > max ) {
|
|
other->health = max;
|
|
}
|
|
|
|
if ( ent->item->giTag == 100 ) { // mega health respawns slow
|
|
return 120;
|
|
}
|
|
|
|
return 30;
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
int ITM_AddArmor (gentity_t *ent, int count)
|
|
{
|
|
|
|
ent->client->ps.stats[STAT_ARMOR] += count;
|
|
|
|
if (ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH];
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
int Pickup_Armor( gentity_t *ent, gentity_t *other ) {
|
|
|
|
// make sure that the shield effect is on
|
|
other->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;
|
|
|
|
other->client->ps.stats[STAT_ARMOR] += ent->item->quantity;
|
|
if ( other->client->ps.stats[STAT_ARMOR] > other->client->ps.stats[STAT_MAX_HEALTH] ) {
|
|
other->client->ps.stats[STAT_ARMOR] = other->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
|
|
return 30;
|
|
}
|
|
|
|
|
|
|
|
//======================================================================
|
|
|
|
int Pickup_Holocron( gentity_t *ent, gentity_t *other )
|
|
{
|
|
int forcePower = ent->item->giTag;
|
|
int forceLevel = ent->count;
|
|
// check if out of range
|
|
if( forceLevel < 0 || forceLevel >= NUM_FORCE_POWER_LEVELS )
|
|
{
|
|
gi.Printf(" Pickup_Holocron : count %d not in valid range\n", forceLevel );
|
|
return 1;
|
|
}
|
|
|
|
// don't pick up if already known AND your level is higher than pickup level
|
|
if ( ( other->client->ps.forcePowersKnown & ( 1 << forcePower )) )
|
|
{
|
|
//don't pickup if item is lower than current level
|
|
if( other->client->ps.forcePowerLevel[forcePower] >= forceLevel )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
other->client->ps.forcePowerLevel[forcePower] = forceLevel;
|
|
other->client->ps.forcePowersKnown |= ( 1 << forcePower );
|
|
|
|
missionInfo_Updated = qtrue; // Activate flashing text
|
|
gi.cvar_set("cg_updatedDataPadForcePower1", va("%d",forcePower+1)); // The +1 is offset in the print routine.
|
|
cg_updatedDataPadForcePower1.integer = forcePower+1;
|
|
gi.cvar_set("cg_updatedDataPadForcePower2", "0"); // The +1 is offset in the print routine.
|
|
cg_updatedDataPadForcePower2.integer = 0;
|
|
gi.cvar_set("cg_updatedDataPadForcePower3", "0"); // The +1 is offset in the print routine.
|
|
cg_updatedDataPadForcePower3.integer = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
===============
|
|
RespawnItem
|
|
===============
|
|
*/
|
|
void RespawnItem( gentity_t *ent ) {
|
|
}
|
|
|
|
|
|
qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper )
|
|
{
|
|
if ( !item->item ) {
|
|
return qfalse;
|
|
}
|
|
if ( item->item->giType == IT_HOLDABLE &&
|
|
item->item->giTag == INV_SECURITY_KEY ) {
|
|
return qfalse;
|
|
}
|
|
if ( (item->flags&FL_DROPPED_ITEM)
|
|
&& item->activator != &g_entities[0]
|
|
&& pickerupper->s.number
|
|
&& pickerupper->s.weapon == WP_NONE
|
|
&& pickerupper->enemy
|
|
&& pickerupper->painDebounceTime < level.time
|
|
&& pickerupper->NPC && pickerupper->NPC->surrenderTime < level.time //not surrendering
|
|
&& !(pickerupper->NPC->scriptFlags&SCF_FORCED_MARCH) ) // not being forced to march
|
|
{//non-player, in combat, picking up a dropped item that does NOT belong to the player and it *not* a security key
|
|
if ( level.time - item->s.time < 3000 )//was 5000
|
|
{
|
|
return qfalse;
|
|
}
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean G_CanPickUpWeapons( gentity_t *other )
|
|
{
|
|
if ( !other || !other->client )
|
|
{
|
|
return qfalse;
|
|
}
|
|
switch ( other->client->NPC_class )
|
|
{
|
|
case CLASS_ATST:
|
|
case CLASS_GONK:
|
|
case CLASS_MARK1:
|
|
case CLASS_MARK2:
|
|
case CLASS_MOUSE:
|
|
case CLASS_PROBE:
|
|
case CLASS_PROTOCOL:
|
|
case CLASS_R2D2:
|
|
case CLASS_R5D2:
|
|
case CLASS_SEEKER:
|
|
case CLASS_REMOTE:
|
|
case CLASS_RANCOR:
|
|
case CLASS_WAMPA:
|
|
case CLASS_JAWA: //FIXME: in some cases it's okay?
|
|
case CLASS_UGNAUGHT: //FIXME: in some cases it's okay?
|
|
case CLASS_SENTRY:
|
|
return qfalse;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return qtrue;
|
|
}
|
|
/*
|
|
===============
|
|
Touch_Item
|
|
===============
|
|
*/
|
|
extern cvar_t *g_timescale;
|
|
void Touch_Item (gentity_t *ent, gentity_t *other, trace_t *trace) {
|
|
int respawn = 0;
|
|
|
|
if (!other->client)
|
|
return;
|
|
if (other->health < 1)
|
|
return; // dead people can't pickup
|
|
|
|
if ( other->client->ps.pm_time > 0 )
|
|
{//cant pick up when out of control
|
|
return;
|
|
}
|
|
|
|
// NPCs can pick it up
|
|
if ((ent->spawnflags & ITMSF_ALLOWNPC) && (!other->s.number))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Players cannot pick it up
|
|
if ( (ent->spawnflags & ITMSF_NOPLAYER) && (other->s.number) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( ent->noDamageTeam != TEAM_FREE && other->client->playerTeam != ent->noDamageTeam )
|
|
{//only one team can pick it up
|
|
return;
|
|
}
|
|
|
|
if ( !G_CanPickUpWeapons( other ) )
|
|
{//FIXME: some flag would be better
|
|
//droids can't pick up items/weapons!
|
|
return;
|
|
}
|
|
|
|
//FIXME: need to make them run toward a dropped weapon when fleeing without one?
|
|
//FIXME: need to make them come out of flee mode when pick up their old weapon?
|
|
if ( CheckItemCanBePickedUpByNPC( ent, other ) )
|
|
{
|
|
if ( other->NPC && other->NPC->goalEntity && other->NPC->goalEntity == ent )
|
|
{//they were running to pick me up, they did, so clear goal
|
|
other->NPC->goalEntity = NULL;
|
|
other->NPC->squadState = SQUAD_STAND_AND_SHOOT;
|
|
NPCInfo->tempBehavior = BS_DEFAULT;
|
|
TIMER_Set(other, "flee", -1);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if ( !(ent->spawnflags & ITMSF_ALLOWNPC) )
|
|
{// NPCs cannot pick it up
|
|
if ( other->s.number != 0 )
|
|
{// Not the player?
|
|
return;
|
|
}
|
|
}
|
|
|
|
// the same pickup rules are used for client side and server side
|
|
if ( !BG_CanItemBeGrabbed( &ent->s, &other->client->ps ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( other->client )
|
|
{
|
|
if ( (other->client->ps.eFlags&EF_FORCE_GRIPPED) || (other->client->ps.eFlags&EF_FORCE_DRAINED) )
|
|
{//can't pick up anything while being gripped
|
|
return;
|
|
}
|
|
if ( PM_InKnockDown( &other->client->ps ) && !PM_InGetUp( &other->client->ps ) )
|
|
{//can't pick up while in a knockdown
|
|
return;
|
|
}
|
|
}
|
|
if (!ent->item) { //not an item!
|
|
gi.Printf( "Touch_Item: %s is not an item!\n", ent->classname);
|
|
return;
|
|
}
|
|
|
|
if ( ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER )
|
|
{//a saber
|
|
if ( ent->delay > level.time )
|
|
{//just picked it up, don't pick up again right away
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( other->s.number < MAX_CLIENTS
|
|
&& (ent->spawnflags&ITMSF_USEPICKUP) )
|
|
{//only if player is holing use button
|
|
if ( !(other->client->usercmd.buttons&BUTTON_USE) )
|
|
{//not holding use?
|
|
return;
|
|
}
|
|
}
|
|
|
|
qboolean bHadWeapon = qfalse;
|
|
// call the item-specific pickup function
|
|
switch( ent->item->giType )
|
|
{
|
|
case IT_WEAPON:
|
|
if ( other->NPC && other->s.weapon == WP_NONE )
|
|
{//Make them duck and sit here for a few seconds
|
|
int pickUpTime = Q_irand( 1000, 3000 );
|
|
TIMER_Set( other, "duck", pickUpTime );
|
|
TIMER_Set( other, "roamTime", pickUpTime );
|
|
TIMER_Set( other, "stick", pickUpTime );
|
|
TIMER_Set( other, "verifyCP", pickUpTime );
|
|
TIMER_Set( other, "attackDelay", 600 );
|
|
respawn = 0;
|
|
}
|
|
if ( other->client->ps.stats[STAT_WEAPONS] & ( 1 << ent->item->giTag ) )
|
|
{
|
|
bHadWeapon = qtrue;
|
|
}
|
|
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_HOLDABLE:
|
|
respawn = Pickup_Holdable(ent, other);
|
|
break;
|
|
case IT_BATTERY:
|
|
respawn = Pickup_Battery( ent, other );
|
|
break;
|
|
case IT_HOLOCRON:
|
|
respawn = Pickup_Holocron( ent, other );
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if ( !respawn )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// play the normal pickup sound
|
|
if ( !other->s.number && g_timescale->value < 1.0f )
|
|
{//SIGH... with timescale on, you lose events left and right
|
|
extern void CG_ItemPickup( int itemNum, qboolean bHadItem );
|
|
// but we're SP so we'll cheat
|
|
cgi_S_StartSound( NULL, other->s.number, CHAN_AUTO, cgi_S_RegisterSound( ent->item->pickup_sound ) );
|
|
// show icon and name on status bar
|
|
CG_ItemPickup( ent->s.modelindex, bHadWeapon );
|
|
}
|
|
else
|
|
{
|
|
if ( bHadWeapon )
|
|
{
|
|
G_AddEvent( other, EV_ITEM_PICKUP, -ent->s.modelindex );
|
|
}
|
|
else
|
|
{
|
|
G_AddEvent( other, EV_ITEM_PICKUP, ent->s.modelindex );
|
|
}
|
|
}
|
|
|
|
// fire item targets
|
|
G_UseTargets (ent, other);
|
|
|
|
if ( ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER )
|
|
{//a saber that was picked up
|
|
if ( ent->count < 0 )
|
|
{//infinite supply
|
|
ent->delay = level.time + 500;
|
|
return;
|
|
}
|
|
ent->count--;
|
|
if ( ent->count > 0 )
|
|
{//still have more to pick up
|
|
ent->delay = level.time + 500;
|
|
return;
|
|
}
|
|
}
|
|
// wait of -1 will not respawn
|
|
// if ( ent->wait == -1 )
|
|
{
|
|
//why not just remove me?
|
|
G_FreeEntity( ent );
|
|
/*
|
|
//NOTE: used to do this: (for respawning?)
|
|
ent->svFlags |= SVF_NOCLIENT;
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->contents = 0;
|
|
ent->unlinkAfterEvent = qtrue;
|
|
*/
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
================
|
|
LaunchItem
|
|
|
|
Spawns an item and tosses it forward
|
|
================
|
|
*/
|
|
gentity_t *LaunchItem( gitem_t *item, const vec3_t origin, const vec3_t velocity, char *target ) {
|
|
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 = G_NewString(item->classname); //copy it so it can be freed safely
|
|
dropped->item = item;
|
|
|
|
// try using the "correct" mins/maxs first
|
|
VectorSet( dropped->mins, item->mins[0], item->mins[1], item->mins[2] );
|
|
VectorSet( dropped->maxs, item->maxs[0], item->maxs[1], item->maxs[2] );
|
|
|
|
if ((!dropped->mins[0] && !dropped->mins[1] && !dropped->mins[2]) &&
|
|
(!dropped->maxs[0] && !dropped->maxs[1] && !dropped->maxs[2]))
|
|
{
|
|
VectorSet( dropped->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS );
|
|
VectorScale( dropped->maxs, -1, dropped->mins );
|
|
}
|
|
|
|
dropped->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_TRIGGER;//not CONTENTS_BODY for dropped items, don't need to ID them
|
|
|
|
if ( target && target[0] )
|
|
{
|
|
dropped->target = G_NewString( target );
|
|
}
|
|
else
|
|
{
|
|
// if not targeting something, auto-remove after 30 seconds
|
|
// only if it's NOT a security or goodie key
|
|
if (dropped->item->giTag != INV_SECURITY_KEY )
|
|
{
|
|
dropped->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
dropped->nextthink = level.time + 30000;
|
|
}
|
|
|
|
if ( dropped->item->giType == IT_AMMO && dropped->item->giTag == AMMO_FORCE )
|
|
{
|
|
dropped->nextthink = -1;
|
|
dropped->e_ThinkFunc = thinkF_NULL;
|
|
}
|
|
}
|
|
|
|
dropped->e_TouchFunc = touchF_Touch_Item;
|
|
|
|
if ( item->giType == IT_WEAPON )
|
|
{
|
|
// give weapon items zero pitch, a random yaw, and rolled onto their sides...but would be bad to do this for a bowcaster
|
|
if ( item->giTag != WP_BOWCASTER
|
|
&& item->giTag != WP_THERMAL
|
|
&& item->giTag != WP_TRIP_MINE
|
|
&& item->giTag != WP_DET_PACK )
|
|
{
|
|
VectorSet( dropped->s.angles, 0, Q_flrand(-1.0f, 1.0f) * 180, 90.0f );
|
|
G_SetAngles( dropped, dropped->s.angles );
|
|
}
|
|
}
|
|
|
|
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->flags = FL_DROPPED_ITEM;
|
|
|
|
gi.linkentity (dropped);
|
|
|
|
return dropped;
|
|
}
|
|
|
|
/*
|
|
================
|
|
Drop_Item
|
|
|
|
Spawns an item and tosses it forward
|
|
================
|
|
*/
|
|
gentity_t *Drop_Item( gentity_t *ent, gitem_t *item, float angle, qboolean copytarget ) {
|
|
gentity_t *dropped = NULL;
|
|
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, 150, velocity );
|
|
velocity[2] += 200 + Q_flrand(-1.0f, 1.0f) * 50;
|
|
|
|
if ( copytarget )
|
|
{
|
|
dropped = LaunchItem( item, ent->s.pos.trBase, velocity, ent->opentarget );
|
|
}
|
|
else
|
|
{
|
|
dropped = LaunchItem( item, ent->s.pos.trBase, velocity, NULL );
|
|
}
|
|
|
|
dropped->activator = ent;//so we know who we belonged to so they can pick it back up later
|
|
dropped->s.time = level.time;//mark this time so we aren't picked up instantly by the guy who dropped us
|
|
return dropped;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
Use_Item
|
|
|
|
Respawn the item
|
|
================
|
|
*/
|
|
void Use_Item( gentity_t *ent, gentity_t *other, gentity_t *activator )
|
|
{
|
|
if ( (ent->svFlags&SVF_PLAYER_USABLE) && other && !other->s.number )
|
|
{//used directly by the player, pick me up
|
|
if ( (ent->spawnflags&ITMSF_USEPICKUP) )
|
|
{//player has to be touching me and hit use to pick it up, so don't allow this
|
|
if ( !G_BoundsOverlap( ent->absmin, ent->absmax, other->absmin, other->absmax ) )
|
|
{//not touching
|
|
return;
|
|
}
|
|
}
|
|
GEntity_TouchFunc( ent, other, NULL );
|
|
}
|
|
else
|
|
{//use me
|
|
if ( ent->spawnflags & 32 ) // invisible
|
|
{
|
|
// If it was invisible, first use makes it visible....
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;
|
|
|
|
ent->spawnflags &= ~32;
|
|
return;
|
|
}
|
|
|
|
G_ActivateBehavior( ent, BSET_USE );
|
|
RespawnItem( ent );
|
|
}
|
|
}
|
|
|
|
//======================================================================
|
|
|
|
/*
|
|
================
|
|
FinishSpawningItem
|
|
|
|
Traces down to find where an item should rest, instead of letting them
|
|
free fall from their spawn points
|
|
================
|
|
*/
|
|
extern int delayedShutDown;
|
|
extern cvar_t *g_saber;
|
|
void FinishSpawningItem( gentity_t *ent ) {
|
|
trace_t tr;
|
|
vec3_t dest;
|
|
gitem_t *item;
|
|
int itemNum;
|
|
|
|
itemNum=1;
|
|
for ( item = bg_itemlist + 1 ; item->classname ; item++,itemNum++)
|
|
{
|
|
if (!strcmp(item->classname,ent->classname))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set bounding box for item
|
|
VectorSet( ent->mins, item->mins[0],item->mins[1] ,item->mins[2]);
|
|
VectorSet( ent->maxs, item->maxs[0],item->maxs[1] ,item->maxs[2]);
|
|
|
|
if ((!ent->mins[0] && !ent->mins[1] && !ent->mins[2]) &&
|
|
(!ent->maxs[0] && !ent->maxs[1] && !ent->maxs[2]))
|
|
{
|
|
VectorSet (ent->mins, -ITEM_RADIUS, -ITEM_RADIUS, -2);//to match the comments in the items.dat file!
|
|
VectorSet (ent->maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS);
|
|
}
|
|
|
|
if ((item->quantity) && (item->giType == IT_AMMO))
|
|
{
|
|
ent->count = item->quantity;
|
|
}
|
|
|
|
if ((item->quantity) && (item->giType == IT_BATTERY))
|
|
{
|
|
ent->count = item->quantity;
|
|
}
|
|
|
|
ent->s.radius = 20;
|
|
VectorSet( ent->s.modelScale, 1.0f, 1.0f, 1.0f );
|
|
|
|
if ( ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER
|
|
&& ent->NPC_type
|
|
&& ent->NPC_type[0] )
|
|
{
|
|
saberInfo_t itemSaber;
|
|
if ( Q_stricmp( "player", ent->NPC_type ) == 0
|
|
&& g_saber->string
|
|
&& g_saber->string[0]
|
|
&& Q_stricmp( "none", g_saber->string )
|
|
&& Q_stricmp( "NULL", g_saber->string ) )
|
|
{//player's saber
|
|
WP_SaberParseParms( g_saber->string, &itemSaber );
|
|
}
|
|
else
|
|
{//specific saber
|
|
WP_SaberParseParms( ent->NPC_type, &itemSaber );
|
|
}
|
|
//NOTE: should I keep this string around for any reason? Will I ever need it later?
|
|
//ent->??? = G_NewString( itemSaber.model );
|
|
gi.G2API_InitGhoul2Model( ent->ghoul2, itemSaber.model, G_ModelIndex( itemSaber.model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
|
|
WP_SaberFreeStrings(itemSaber);
|
|
}
|
|
else
|
|
{
|
|
gi.G2API_InitGhoul2Model( ent->ghoul2, ent->item->world_model, G_ModelIndex( ent->item->world_model ), NULL_HANDLE, NULL_HANDLE, 0, 0);
|
|
}
|
|
|
|
// Set crystal ammo amount based on skill level
|
|
/* if ((itemNum == ITM_AMMO_CRYSTAL_BORG) ||
|
|
(itemNum == ITM_AMMO_CRYSTAL_DN) ||
|
|
(itemNum == ITM_AMMO_CRYSTAL_FORGE) ||
|
|
(itemNum == ITM_AMMO_CRYSTAL_SCAVENGER) ||
|
|
(itemNum == ITM_AMMO_CRYSTAL_STASIS))
|
|
{
|
|
CrystalAmmoSettings(ent);
|
|
}
|
|
*/
|
|
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->contents = CONTENTS_TRIGGER|CONTENTS_ITEM;//CONTENTS_BODY;//CONTENTS_TRIGGER|
|
|
ent->e_TouchFunc = touchF_Touch_Item;
|
|
// useing an item causes it to respawn
|
|
ent->e_UseFunc = useF_Use_Item;
|
|
ent->svFlags |= SVF_PLAYER_USABLE;//so player can pick it up
|
|
|
|
// Hang in air?
|
|
ent->s.origin[2] += 1;//just to get it off the damn ground because coplanar = insolid
|
|
if ( (ent->spawnflags&ITMSF_SUSPEND)
|
|
|| (ent->flags&FL_DROPPED_ITEM) )
|
|
{
|
|
// suspended
|
|
G_SetOrigin( ent, ent->s.origin );
|
|
}
|
|
else
|
|
{
|
|
// drop to floor
|
|
VectorSet( dest, ent->s.origin[0], ent->s.origin[1], MIN_WORLD_COORD );
|
|
gi.trace( &tr, ent->s.origin, ent->mins, ent->maxs, dest, ent->s.number, MASK_SOLID|CONTENTS_PLAYERCLIP, (EG2_Collision)0, 0 );
|
|
if ( tr.startsolid )
|
|
{
|
|
if ( &g_entities[tr.entityNum] != NULL )
|
|
{
|
|
gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin), g_entities[tr.entityNum].classname );
|
|
}
|
|
else
|
|
{
|
|
gi.Printf (S_COLOR_RED"FinishSpawningItem: removing %s startsolid at %s (in a %s)\n", ent->classname, vtos(ent->s.origin) );
|
|
}
|
|
assert( 0 && "item starting in solid");
|
|
if (!g_entities[ENTITYNUM_WORLD].s.radius){ //not a region
|
|
delayedShutDown = level.time + 100;
|
|
}
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
// allow to ride movers
|
|
ent->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( ent, tr.endpos );
|
|
}
|
|
|
|
/* ? don't need this
|
|
// team slaves and targeted items aren't present at start
|
|
if ( ( ent->flags & FL_TEAMSLAVE ) || ent->targetname ) {
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->contents = 0;
|
|
return;
|
|
}
|
|
*/
|
|
if ( ent->spawnflags & ITMSF_INVISIBLE ) // invisible
|
|
{
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->contents = 0;
|
|
}
|
|
|
|
if ( ent->spawnflags & ITMSF_NOTSOLID ) // not solid
|
|
{
|
|
ent->contents = 0;
|
|
}
|
|
|
|
if ( (ent->spawnflags&ITMSF_STATIONARY) )
|
|
{//can't be pushed around
|
|
ent->flags |= FL_NO_KNOCKBACK;
|
|
}
|
|
|
|
if ( (ent->flags&FL_DROPPED_ITEM) )
|
|
{//go away after 30 seconds
|
|
ent->e_ThinkFunc = thinkF_G_FreeEntity;
|
|
ent->nextthink = level.time + 30000;
|
|
}
|
|
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
|
|
char itemRegistered[MAX_ITEMS+1];
|
|
|
|
|
|
/*
|
|
==============
|
|
ClearRegisteredItems
|
|
==============
|
|
*/
|
|
void ClearRegisteredItems( void ) {
|
|
for ( int i = 0; i < bg_numItems; i++ )
|
|
{
|
|
itemRegistered[i] = '0';
|
|
}
|
|
itemRegistered[ bg_numItems ] = 0;
|
|
|
|
//these are given in g_client, ClientSpawn(), but MUST be registered HERE, BEFORE cgame starts.
|
|
//RegisterItem( FindItemForWeapon( WP_NONE ) ); //has no item
|
|
RegisterItem( FindItemForInventory( INV_ELECTROBINOCULARS ));
|
|
//RegisterItem( FindItemForInventory( INV_BACTA_CANISTER ));
|
|
// saber or baton is cached in SP_info_player_deathmatch now.
|
|
|
|
extern void Player_CacheFromPrevLevel(void);//g_client.cpp
|
|
Player_CacheFromPrevLevel(); //reads from transition carry-over;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
RegisterItem
|
|
|
|
The item will be added to the precache list
|
|
===============
|
|
*/
|
|
void RegisterItem( gitem_t *item ) {
|
|
if ( !item ) {
|
|
G_Error( "RegisterItem: NULL" );
|
|
}
|
|
itemRegistered[ item - bg_itemlist ] = '1';
|
|
gi.SetConfigstring(CS_ITEMS, itemRegistered); //Write the needed items to a config string
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
SaveRegisteredItems
|
|
|
|
Write the needed items to a config string
|
|
so the client will know which ones to precache
|
|
===============
|
|
*/
|
|
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;
|
|
|
|
gi.Printf( "%i items registered\n", count );
|
|
gi.SetConfigstring(CS_ITEMS, string);
|
|
*/
|
|
gi.SetConfigstring(CS_ITEMS, itemRegistered);
|
|
}
|
|
|
|
/*
|
|
============
|
|
item_spawn_use
|
|
|
|
if an item is given a targetname, it will be spawned in when used
|
|
============
|
|
*/
|
|
void item_spawn_use( gentity_t *self, gentity_t *other, gentity_t *activator )
|
|
//-----------------------------------------------------------------------------
|
|
{
|
|
self->nextthink = level.time + 50;
|
|
self->e_ThinkFunc = thinkF_FinishSpawningItem;
|
|
// I could be fancy and add a count or something like that to be able to spawn the item numerous times...
|
|
self->e_UseFunc = useF_NULL;
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_SpawnItem
|
|
|
|
Sets the clipping size and plants the object on the floor.
|
|
|
|
Items can't be immediately dropped to floor, because they might
|
|
be on an entity that hasn't spawned yet.
|
|
============
|
|
*/
|
|
void G_SpawnItem (gentity_t *ent, gitem_t *item) {
|
|
G_SpawnFloat( "random", "0", &ent->random );
|
|
G_SpawnFloat( "wait", "0", &ent->wait );
|
|
|
|
RegisterItem( item );
|
|
ent->item = item;
|
|
|
|
// targetname indicates they want to spawn it later
|
|
if( ent->targetname )
|
|
{
|
|
ent->e_UseFunc = useF_item_spawn_use;
|
|
}
|
|
else
|
|
{ // some movers spawn on the second frame, so delay item
|
|
// spawns until the third frame so they can ride trains
|
|
ent->nextthink = level.time + START_TIME_MOVERS_SPAWNED + 50;
|
|
ent->e_ThinkFunc = thinkF_FinishSpawningItem;
|
|
}
|
|
|
|
ent->physicsBounce = 0.50; // items are bouncy
|
|
|
|
// Set a default infoString text color
|
|
// NOTE: if we want to do cool cross-hair colors for items, we can just modify this, but for now, don't do it
|
|
VectorSet( ent->startRGBA, 1.0f, 1.0f, 1.0f );
|
|
|
|
if ( ent->team && ent->team[0] )
|
|
{
|
|
ent->noDamageTeam = (team_t)GetIDForString( TeamTable, ent->team );
|
|
if ( ent->noDamageTeam == TEAM_FREE )
|
|
{
|
|
G_Error("team name %s not recognized\n", ent->team);
|
|
}
|
|
}
|
|
|
|
if ( ent->item
|
|
&& ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER )
|
|
{//weapon_saber item
|
|
if ( !ent->count )
|
|
{//can only pick up once
|
|
ent->count = 1;
|
|
}
|
|
}
|
|
ent->team = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_BounceItem
|
|
|
|
================
|
|
*/
|
|
void G_BounceItem( gentity_t *ent, trace_t *trace ) {
|
|
vec3_t velocity;
|
|
float dot;
|
|
int hitTime;
|
|
qboolean droppedSaber = qtrue;
|
|
|
|
if ( ent->item
|
|
&& ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER
|
|
&& (ent->flags&FL_DROPPED_ITEM) )
|
|
{
|
|
droppedSaber = qtrue;
|
|
}
|
|
|
|
// reflect the velocity on the trace plane
|
|
hitTime = level.previousTime + ( level.time - level.previousTime ) * trace->fraction;
|
|
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 );
|
|
|
|
if ( droppedSaber )
|
|
{//a dropped saber item
|
|
//FIXME: use NPC_type (as saberType) to get proper bounce sound?
|
|
WP_SaberFallSound( NULL, ent );
|
|
}
|
|
|
|
// check for stop
|
|
if ( trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40 )
|
|
{//stop
|
|
G_SetOrigin( ent, trace->endpos );
|
|
ent->s.groundEntityNum = trace->entityNum;
|
|
if ( droppedSaber )
|
|
{//a dropped saber item
|
|
//stop rotation
|
|
VectorClear( ent->s.apos.trDelta );
|
|
ent->currentAngles[PITCH] = SABER_PITCH_HACK;
|
|
ent->currentAngles[ROLL] = 0;
|
|
if ( ent->NPC_type
|
|
&& ent->NPC_type[0] )
|
|
{//we have a valid saber for this
|
|
saberInfo_t saber;
|
|
if ( WP_SaberParseParms( ent->NPC_type, &saber ) )
|
|
{
|
|
if ( (saber.saberFlags&SFL_BOLT_TO_WRIST) )
|
|
{
|
|
ent->currentAngles[PITCH] = 0;
|
|
}
|
|
}
|
|
}
|
|
pitch_roll_for_slope( ent, trace->plane.normal, ent->currentAngles, qtrue );
|
|
G_SetAngles( ent, ent->currentAngles );
|
|
}
|
|
return;
|
|
}
|
|
//bounce
|
|
if ( droppedSaber )
|
|
{//a dropped saber item
|
|
//change rotation
|
|
VectorCopy( ent->currentAngles, ent->s.apos.trBase );
|
|
ent->s.apos.trType = TR_LINEAR;
|
|
ent->s.apos.trTime = level.time;
|
|
VectorSet( ent->s.apos.trDelta, Q_irand( -300, 300 ), Q_irand( -300, 300 ), Q_irand( -300, 300 ) );
|
|
}
|
|
|
|
VectorAdd( ent->currentOrigin, trace->plane.normal, ent->currentOrigin);
|
|
VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
|
|
ent->s.pos.trTime = level.time;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
G_RunItem
|
|
|
|
================
|
|
*/
|
|
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 == ENTITYNUM_NONE )
|
|
{
|
|
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 );
|
|
if ( !g_gravity->value )
|
|
{
|
|
ent->s.pos.trType = TR_GRAVITY;
|
|
ent->s.pos.trTime = level.time;
|
|
ent->s.pos.trDelta[0] += Q_flrand(-1.0f, 1.0f) * 40.0f; // I dunno, just do this??
|
|
ent->s.pos.trDelta[1] += Q_flrand(-1.0f, 1.0f) * 40.0f;
|
|
ent->s.pos.trDelta[2] += Q_flrand(0.0f, 1.0f) * 20.0f;
|
|
}
|
|
else if ( (ent->flags&FL_DROPPED_ITEM)
|
|
&& ent->item
|
|
&& ent->item->giType == IT_WEAPON
|
|
&& ent->item->giTag == WP_SABER )
|
|
{//a dropped saber item, check below, just in case
|
|
int ignore = ENTITYNUM_NONE;
|
|
if ( ent->clipmask )
|
|
{
|
|
mask = ent->clipmask;
|
|
}
|
|
else
|
|
{
|
|
mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
|
|
}
|
|
if ( ent->owner )
|
|
{
|
|
ignore = ent->owner->s.number;
|
|
}
|
|
else if ( ent->activator )
|
|
{
|
|
ignore = ent->activator->s.number;
|
|
}
|
|
VectorSet( origin, ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2]-1 );
|
|
gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
|
|
if ( !tr.allsolid
|
|
&& !tr.startsolid
|
|
&& tr.fraction > 0.001f )
|
|
{//wha? fall....
|
|
ent->s.pos.trType = TR_GRAVITY;
|
|
ent->s.pos.trTime = level.time;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// get current position
|
|
EvaluateTrajectory( &ent->s.pos, level.time, origin );
|
|
if ( ent->s.apos.trType != TR_STATIONARY )
|
|
{
|
|
EvaluateTrajectory( &ent->s.apos, level.time, ent->currentAngles );
|
|
G_SetAngles( ent, ent->currentAngles );
|
|
}
|
|
|
|
// trace a line from the previous position to the current position
|
|
if ( ent->clipmask )
|
|
{
|
|
mask = ent->clipmask;
|
|
}
|
|
else
|
|
{
|
|
mask = MASK_SOLID|CONTENTS_PLAYERCLIP;//shouldn't be able to get anywhere player can't
|
|
}
|
|
|
|
int ignore = ENTITYNUM_NONE;
|
|
if ( ent->owner )
|
|
{
|
|
ignore = ent->owner->s.number;
|
|
}
|
|
else if ( ent->activator )
|
|
{
|
|
ignore = ent->activator->s.number;
|
|
}
|
|
gi.trace( &tr, ent->currentOrigin, ent->mins, ent->maxs, origin, ignore, mask, (EG2_Collision)0, 0 );
|
|
|
|
VectorCopy( tr.endpos, ent->currentOrigin );
|
|
|
|
if ( tr.startsolid )
|
|
{
|
|
tr.fraction = 0;
|
|
}
|
|
|
|
gi.linkentity( ent ); // FIXME: avoid this for stationary?
|
|
|
|
// check think function
|
|
G_RunThink( ent );
|
|
|
|
if ( tr.fraction == 1 )
|
|
{
|
|
if ( g_gravity->value <= 0 )
|
|
{
|
|
if ( ent->s.apos.trType != TR_LINEAR )
|
|
{
|
|
VectorCopy( ent->currentAngles, ent->s.apos.trBase );
|
|
ent->s.apos.trType = TR_LINEAR;
|
|
ent->s.apos.trDelta[1] = Q_flrand( -300, 300 );
|
|
ent->s.apos.trDelta[0] = Q_flrand( -10, 10 );
|
|
ent->s.apos.trDelta[2] = Q_flrand( -10, 10 );
|
|
ent->s.apos.trTime = level.time;
|
|
}
|
|
}
|
|
//friction in zero-G
|
|
if ( !g_gravity->value )
|
|
{
|
|
float friction = 0.975f;
|
|
/*friction -= ent->mass/1000.0f;
|
|
if ( friction < 0.1 )
|
|
{
|
|
friction = 0.1f;
|
|
}
|
|
*/
|
|
VectorScale( ent->s.pos.trDelta, friction, ent->s.pos.trDelta );
|
|
VectorCopy( ent->currentOrigin, ent->s.pos.trBase );
|
|
ent->s.pos.trTime = level.time;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if it is in a nodrop volume, remove it
|
|
contents = gi.pointcontents( ent->currentOrigin, -1 );
|
|
if ( contents & CONTENTS_NODROP )
|
|
{
|
|
G_FreeEntity( ent );
|
|
return;
|
|
}
|
|
|
|
if ( !tr.startsolid )
|
|
{
|
|
G_BounceItem( ent, &tr );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
ItemUse_Bacta
|
|
|
|
================
|
|
*/
|
|
void ItemUse_Bacta(gentity_t *ent)
|
|
{
|
|
if (!ent || !ent->client)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ent->health >= ent->client->ps.stats[STAT_MAX_HEALTH] || !ent->client->ps.inventory[INV_BACTA_CANISTER] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ent->health += MAX_BACTA_HEAL_AMOUNT;
|
|
|
|
if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
|
|
ent->client->ps.inventory[INV_BACTA_CANISTER]--;
|
|
|
|
G_SoundOnEnt( ent, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", Q_irand( 1, 4 ), g_sex->string[0] ) );
|
|
}
|
|
|