rpgxef/code/game/g_items.cpp

1230 lines
30 KiB
C++
Raw Normal View History

// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_items.h"
#include "g_local.h"
#include "g_client.h"
#include "g_spawn.h"
#include "g_syscalls.h"
2014-10-24 23:00:31 +00:00
#include "g_logger.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.
2014-11-10 20:03:19 +00:00
*/
2014-11-10 20:03:19 +00:00
enum g_itemsRespawn_e {
RESPAWN_ARMOR = 20,
RESPAWN_TEAM_WEAPON = 30,
RESPAWN_HEALTH = 30,
RESPAWN_AMMO = 40,
RESPAWN_HOLDABLE = 60,
RESPAWN_MEGAHEALTH = 120,
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];
int32_t paddDataNum = 0;
int32_t 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
*/
int32_t Max_Weapon(int32_t num)
{
2014-11-10 20:03:19 +00:00
if (Max_Weapons[num] == NULL) {
return 1;
2014-11-10 20:03:19 +00:00
}
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
*/
int32_t Min_Weapon(int32_t num)
{
2014-11-10 20:03:19 +00:00
if (Min_Weapons[num] == NULL) {
return 1;
2014-11-10 20:03:19 +00:00
}
else {
return Min_Weapons[num]->integer;
}
}
/**
* Add a new padd.
*
* \param key entity
* \param who owner of the padd
* \param txt text of the padd
* \author Ubergames - Marcin
* \date 06/12/2008
*/
2014-11-10 20:03:19 +00:00
static void Padd_Add(gentity_t *key, gentity_t *who, char *txt)
{
2014-11-10 20:03:19 +00:00
int32_t i = 0;
char* txtp = NULL;
paddData_t* empty = NULL;
G_LocLogger(LL_DEBUG, S_COLOR_YELLOW "in Padd_Add: txt = %s and last = %s\n", txt, who->client->lastPaddMsg);
while (empty == NULL) {
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;
}
G_LocLogger(LL_DEBUG, S_COLOR_YELLOW "added: %i with %s on nr %i\n" S_COLOR_WHITE, (int32_t)key, txt, i - 1);
empty->key = key;
if ((txt != NULL) && (txt[0] != 0)) {
txtp = txt;
Q_strncpyz(who->client->lastPaddMsg, txt, sizeof(who->client->lastPaddMsg));
}
else if (who->client->lastPaddMsg[0] != 0) {
txtp = who->client->lastPaddMsg;
}
else {
txtp = 0;
}
if (txtp != NULL) {
Q_strncpyz(empty->value, txtp, sizeof(empty->value));
}
Q_strncpyz(empty->owner, who->client->pers.netname, sizeof(empty->owner));
++paddDataNum;
}
/**
* Pickup padd.
*
* \param key entity
* \param who Who picked up the padd.
* \return Text of the padd.
* \author Ubergames - Marcin
* \date 06/12/2008
*/
2014-11-10 20:03:19 +00:00
static char* Padd_Get(gentity_t* key, gentity_t* who)
{
2014-11-10 20:03:19 +00:00
int32_t i = 0;
int32_t j = 0;
2014-11-10 20:03:19 +00:00
if ((key != NULL) && (who != NULL)) {
for (; i < PADD_DATA_MAX; ++i) {
if (paddData[i].key == key) {
2014-10-24 23:00:31 +00:00
G_LocLogger(LL_DEBUG, "^3got: %i with %s on nr %i\n", (int32_t)key, paddData[i].value, i);
//Inform admins
2014-11-10 20:03:19 +00:00
for (j = 0; j < level.maxclients; ++j) {
gentity_t *player = &g_entities[j];
2014-11-10 20:03:19 +00:00
if ((player != NULL) && (!player->client->noAdminChat) && G_Client_IsAdmin(player) && (rpg_respectPrivacy.integer == 0) && (player != who) && (paddData[i].value[0] != 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
2014-11-10 20:03:19 +00:00
Q_strncpyz(who->client->lastPaddMsg, paddData[i].value, sizeof(who->client->lastPaddMsg));
return paddData[i].value;
}
}
}
2014-11-10 20:03:19 +00:00
G_Printf(S_COLOR_RED "Padd_Get: Unable to find the text for this PADD!\n" S_COLOR_WHITE);
return NULL;
}
/**
* Remove a padd.
*
* \param key entity
* \author Ubergames - Marcin
* \date 06/12/2008
*/
2014-11-10 20:03:19 +00:00
static void Padd_Remove(gentity_t* key)
{
2014-11-10 20:03:19 +00:00
int32_t i = 0;
while (qtrue) {
if (paddData[i].key == key) {
paddData[i].key = 0;
paddData[i].value[0] = '\0';
paddData[i].owner[0] = '\0';
G_LocLogger(LL_DEBUG, S_COLOR_YELLOW "deleting: %i on %i\n", (int32_t)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.
static int32_t adjustRespawnTime(double respawnTime) {
if (g_adaptRespawn.integer == 0) {
return ((int32_t)respawnTime);
}
if (level.numPlayingClients > 4) {
// Start scaling the respawn times.
2014-11-10 20:03:19 +00:00
if (level.numPlayingClients > 32) {
// 1/4 time minimum.
respawnTime *= 0.25;
2014-11-10 20:03:19 +00:00
}
else if (level.numPlayingClients > 12) {
// From 12-32, scale from 0.5 to 0.25;
respawnTime *= 20.0 / (double)(level.numPlayingClients + 8);
2014-11-10 20:03:19 +00:00
}
else {
// From 4-12, scale from 1.0 to 0.5;
respawnTime *= 8.0 / (double)(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 ((int32_t)respawnTime);
}
//======================================================================
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Powerup(gentity_t* ent, gentity_t* other) {
int32_t quantity = 0;
int32_t i = 0;
gclient_t* client = NULL;
playerState_t* ps = &other->client->ps;
2014-11-10 20:03:19 +00:00
if (other != NULL && other->client != NULL && ent != NULL) {
if (ps->powerups[ent->item->giTag] == 0) {
// round timing to seconds to make multiple powerup timers
// count in sync
2014-11-10 20:03:19 +00:00
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);
}
2014-11-10 20:03:19 +00:00
if (ent->count != 0) {
quantity = ent->count;
2014-11-10 20:03:19 +00:00
}
else {
quantity = ent->item->quantity;
}
ps->powerups[ent->item->giTag] += quantity * 1000;
// give any nearby players a "denied" anti-reward
2014-11-10 20:03:19 +00:00
for (i = 0; i < level.maxclients; i++) {
vec3_t delta;
double len = 0.0;
vec3_t forward;
trace_t tr;
client = &level.clients[i];
2014-11-10 20:03:19 +00:00
if (client == other->client) {
continue;
}
2014-11-10 20:03:19 +00:00
if (client->pers.connected == CON_DISCONNECTED) {
continue;
}
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if (g_gametype.integer >= GT_TEAM && other->client->sess.sessionTeam == client->sess.sessionTeam) {
continue;
}
// if too far away, no sound
2014-11-10 20:03:19 +00:00
VectorSubtract(ent->s.pos.trBase, client->ps.origin, delta);
len = VectorNormalize(delta);
if (len > 192) {
continue;
}
// if not facing, no sound
2014-11-10 20:03:19 +00:00
AngleVectors(client->ps.viewangles, forward, NULL, NULL);
if (DotProduct(delta, forward) < 0.4) {
continue;
}
// if not line of sight, no sound
memset(&tr, 0, sizeof(trace_t));
2014-11-10 20:03:19 +00:00
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;
}
//======================================================================
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Holdable(gentity_t* ent, gentity_t* other)
{
int32_t nItem;
2014-11-10 20:03:19 +00:00
if ((ent != NULL) && (other != NULL) && (other->client != NULL)) {
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);
}
//======================================================================
/**
* Add ammo for a weapon to a player.
*
* \param ent The player.
* \param weapon For which weapon.
* \param count Ammount of ammo.
*/
2014-11-10 20:03:19 +00:00
static void Add_Ammo(gentity_t* ent, int32_t weapon, int32_t count) {
playerState_t* ps = &ent->client->ps;
2014-11-10 20:03:19 +00:00
if (ent != NULL) {
ps->ammo[weapon] += count;
2014-11-10 20:03:19 +00:00
if (ps->ammo[weapon] > Max_Weapon(weapon)) {
ps->ammo[weapon] = Max_Weapon(weapon);
}
}
}
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Ammo(gentity_t* ent, gentity_t* other)
{
int32_t quantity = 0;
2014-11-10 20:03:19 +00:00
if (ent != NULL && other != NULL) {
if (ent->count != 0) {
quantity = ent->count;
2014-11-10 20:03:19 +00:00
}
else {
quantity = ent->item->quantity;
}
2014-11-10 20:03:19 +00:00
Add_Ammo(other, ent->item->giTag, quantity);
}
return adjustRespawnTime(RESPAWN_AMMO);
}
//======================================================================
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Weapon(gentity_t* ent, gentity_t* other) {
char *msg = NULL;
2014-11-10 20:03:19 +00:00
if (ent != NULL && other != NULL) {
// add the weapon
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if (ent->item->giTag == WP_3) {
msg = Padd_Get(ent, other);
if (msg != NULL) {
2014-11-10 20:03:19 +00:00
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);
2014-11-10 20:03:19 +00:00
// team deathmatch has slow weapon respawns
2014-11-10 20:03:19 +00:00
if (g_gametype.integer == GT_TEAM) {
return adjustRespawnTime(RESPAWN_TEAM_WEAPON);
}
}
return adjustRespawnTime(g_weaponRespawn.integer);
}
//======================================================================
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Health(gentity_t* ent, gentity_t* other) {
int32_t max = 0;
int32_t quantity = 0;
playerState_t* ps = NULL;
2014-11-10 20:03:19 +00:00
if (ent != NULL && other != NULL && other->client != NULL) {
ps = &other->client->ps;
// small and mega healths will go over the max
2014-11-10 20:03:19 +00:00
if (ent->item->quantity != 5 && ent->item->quantity != 100) {
max = ps->stats[STAT_MAX_HEALTH];
2014-11-10 20:03:19 +00:00
}
else {
max = ps->stats[STAT_MAX_HEALTH] * 2;
}
2014-11-10 20:03:19 +00:00
if (ent->count != 0) {
quantity = ent->count;
2014-11-10 20:03:19 +00:00
}
else {
quantity = ent->item->quantity;
}
other->health += quantity;
2014-11-10 20:03:19 +00:00
if (other->health > max) {
other->health = max;
}
ps->stats[STAT_HEALTH] = other->health;
2014-11-10 20:03:19 +00:00
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);
}
//======================================================================
2014-11-10 20:03:19 +00:00
static int32_t Pickup_Armor(gentity_t* ent, gentity_t* other) {
if (ent != NULL && other != NULL && other->client != NULL) {
playerState_t *ps = &other->client->ps;
ps->stats[STAT_ARMOR] += ent->item->quantity;
2014-11-10 20:03:19 +00:00
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);
}
//======================================================================
/**
* Repsawn an item.
*
* \param ent The item.
*/
2014-11-10 20:03:19 +00:00
static void RespawnItem(gentity_t* ent) {
if (ent != NULL) {
// randomly select from teamed entities
if (ent->team != NULL) {
gentity_t* master = NULL;
int32_t count = 0;
int32_t choice = 0;
2014-11-10 20:03:19 +00:00
if (ent->teammaster == NULL) {
G_Error("RespawnItem: bad teammaster");
}
master = ent->teammaster;
for (count = 0, ent = master; ent != NULL; ent = ent->teamchain, count++)
;
choice = rand() % count;
for (count = 0, ent = master; count < choice && ent; ent = ent->teamchain, count++)
;
}
2014-11-10 20:03:19 +00:00
if (ent == NULL) {
return;
}
ent->r.contents = CONTENTS_TRIGGER;
ent->s.eFlags &= ~(EF_NODRAW | EF_ITEMPLACEHOLDER);
ent->r.svFlags &= ~SVF_NOCLIENT;
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent);
2014-11-10 20:03:19 +00:00
if (ent->item->giType == IT_POWERUP) {
// play powerup spawn sound to all clients
gentity_t *te;
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
G_AddEvent(ent, EV_ITEM_RESPAWN, 0);
ent->nextthink = 0;
}
}
/**
* Touch function for items.
*
* \param ent The entity for the item.
* \param other The touching entity.
* \param trace A trace.
*/
2014-11-10 20:03:19 +00:00
static void Touch_Item(gentity_t* ent, gentity_t* other, trace_t* trace) {
int32_t respawn = 0;
if ((other == NULL) || (other->client == NULL) || (ent == NULL)) {
return;
}
if (other->health < 1) {
return; // dead people can't pickup
}
2014-11-10 20:03:19 +00:00
// RPG-X: Marcin: Press USE to pick up items. - 03/12/2008
if (((other->client->pers.cmd.buttons & BUTTON_USE) == 0) || (other->client->pressedUse == qtrue)) {
return;
}
else {
other->client->pressedUse = qtrue;
}
// the same pickup rules are used for client side and server side
2014-11-10 20:03:19 +00:00
if (!BG_CanItemBeGrabbed(&ent->s, &other->client->ps, Max_Weapon(other->client->ps.weapon))
&& G_Client_IsAdmin(other) == qfalse)
{
return;
}
numTotalDropped--;
2014-11-10 20:03:19 +00:00
G_LogPrintf("Item: %i %s\n", other->s.number, ent->item->classname);
// call the item-specific pickup function
2014-11-10 20:03:19 +00:00
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 == 0) {
return;
}
// play the normal pickup sound
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if (ent->item->giType == IT_POWERUP || ent->item->giType == IT_TEAM) {
gentity_t* te = NULL;
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
G_UseTargets(ent, other);
if (rpg_weaponsStay.integer == 1 && G_Client_IsAdmin(ent->parent) == qtrue && G_Client_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
2014-11-10 20:03:19 +00:00
if (ent->wait > 0.0f) {
respawn = ent->wait;
}
// random can be used to vary the respawn time
2014-11-10 20:03:19 +00:00
if (ent->random > 0.0f) {
respawn += crandom() * ent->random;
2014-11-10 20:03:19 +00:00
if (respawn < 1.0f) {
respawn = 1;
}
}
// dropped items will not respawn
2014-11-10 20:03:19 +00:00
if ((ent->flags & FL_DROPPED_ITEM) != 0) {
ent->freeAfterEvent = qtrue;
}
// picked up items still stay around, they just don't
// draw anything. This allows respawnable items
// to be placed on movers.
2014-11-10 20:03:19 +00:00
if (ent->item->giType == IT_WEAPON || ent->item->giType == IT_POWERUP)
{
ent->s.eFlags |= EF_ITEMPLACEHOLDER;
}
else
{
2014-11-10 20:03:19 +00:00
// 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;
2014-11-10 20:03:19 +00:00
// ***************
// 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
2014-11-10 20:03:19 +00:00
if (respawn <= 0) {
ent->nextthink = 0;
ent->think = 0;
2014-11-10 20:03:19 +00:00
}
else {
ent->nextthink = level.time + respawn * 1000;
ent->think = RespawnItem;
}
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent);
}
//======================================================================
/*
================
LaunchItem
Spawns an item and tosses it forward
================
*/
2014-11-10 20:03:19 +00:00
static gentity_t* LaunchItem(gitem_t* item, gentity_t* who, vec3_t origin, vec3_t velocity, int32_t 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 = NULL;
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;
2014-11-10 20:03:19 +00:00
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;
2014-11-10 20:03:19 +00:00
G_SetOrigin(dropped, origin);
dropped->s.pos.trType = TR_GRAVITY;
dropped->s.pos.trTime = level.time;
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
// if the item has been droped, and if so, make it pick-up-able
// cdr
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if ((flags & FL_THROWN_ITEM) != 0) {
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;
}
2014-11-10 20:03:19 +00:00
trap_LinkEntity(dropped);
return dropped;
}
2014-11-10 20:03:19 +00:00
gentity_t *DropWeapon(gentity_t *ent, gitem_t *item, double angle, int32_t flags, char *txt) {
vec3_t velocity;
vec3_t origin;
2014-11-10 20:03:19 +00:00
if (ent == NULL) {
return NULL;
}
2014-11-10 20:03:19 +00:00
VectorCopy(ent->s.pos.trBase, origin);
// set aiming directions
2014-11-10 20:03:19 +00:00
AngleVectors(ent->client->ps.viewangles, velocity, NULL, NULL);
origin[2] += ent->client->ps.viewheight + 10;
2014-11-10 20:03:19 +00:00
VectorMA(origin, 14, velocity, origin);
// snap to integer coordinates for more efficient network bandwidth usage
2014-11-10 20:03:19 +00:00
SnapVector(origin);
// extra vertical velocity
velocity[2] += 0.2;
2014-11-10 20:03:19 +00:00
VectorNormalize(velocity);
2014-11-10 20:03:19 +00:00
return LaunchItem(item, ent, origin, velocity, flags, txt);
}
2014-11-10 20:03:19 +00:00
gentity_t* Drop_Item(gentity_t* ent, gitem_t* item, double angle) {
vec3_t velocity;
vec3_t angles;
2014-11-10 20:03:19 +00:00
if (ent == NULL) {
return NULL;
}
2014-11-10 20:03:19 +00:00
VectorCopy(ent->s.apos.trBase, angles);
angles[YAW] += angle;
angles[PITCH] = 0; // always forward
2014-11-10 20:03:19 +00:00
AngleVectors(angles, velocity, NULL, NULL);
VectorScale(velocity, 300, velocity);
velocity[2] += 75 + crandom() * 50;
2014-11-10 20:03:19 +00:00
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
================
*/
2014-11-10 20:03:19 +00:00
static void Use_Item(gentity_t* ent, gentity_t* other, gentity_t* activator) {
if (ent != NULL) {
RespawnItem(ent);
}
}
//======================================================================
/**
* Traces down to find where an item should rest, instead of letting them
* free fall from their spawn points
*
* \param ent Entity for the item.
*/
2014-11-10 20:03:19 +00:00
static void FinishSpawningItem(gentity_t* ent) {
trace_t tr;
vec3_t dest;
2014-11-10 20:03:19 +00:00
if (ent == NULL)
{
DEVELOPER(G_Printf("print \"Ent Missing\"");)
2014-11-10 20:03:19 +00:00
return;
}
2014-11-10 20:03:19 +00:00
if (ent->item == NULL)
{
DEVELOPER(G_Printf("print \"Ent->item Missing\"");)
2014-11-10 20:03:19 +00:00
return;
}
2014-11-10 20:03:19 +00:00
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;
2014-11-10 20:03:19 +00:00
Com_Printf("print \"giType %i!\n\"", ent->item->giType);
2014-11-10 20:03:19 +00:00
if ((ent->spawnflags & 1) != 0) {
// suspended
2014-11-10 20:03:19 +00:00
G_SetOrigin(ent, ent->s.origin);
}
else {
// drop to floor
2014-11-10 20:03:19 +00:00
VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096);
memset(&tr, 0, sizeof(trace_t));
2014-11-10 20:03:19 +00:00
trap_Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID);
2014-11-10 20:03:19 +00:00
if (tr.startsolid) {
DEVELOPER(G_Printf("FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));)
#ifndef FINAL_BUILD
2014-11-10 20:03:19 +00:00
G_Error("FinishSpawningItem: removing %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
#endif
2014-11-10 20:03:19 +00:00
DEVELOPER(G_Printf("FinishSpawningItem: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));)
2014-11-10 20:03:19 +00:00
G_FreeEntity(ent);
return;
}
// allow to ride movers
ent->s.groundEntityNum = tr.entityNum;
2014-11-10 20:03:19 +00:00
G_SetOrigin(ent, tr.endpos);
}
// team slaves and targeted items aren't present at start
2014-11-10 20:03:19 +00:00
if (((ent->flags & FL_TEAMSLAVE) != 0) || (ent->targetname != NULL)) {
ent->s.eFlags |= EF_NODRAW;
ent->r.contents = 0;
return;
}
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent);
}
/*
================
FinishSpawningDetpack
Traces down to find where an item should rest, instead of letting them
free fall from their spawn points
================
*/
2014-11-10 20:03:19 +00:00
qboolean FinishSpawningDetpack(gentity_t *ent, int itemIndex)
{
trace_t tr;
vec3_t dest;
2014-11-10 20:03:19 +00:00
if (ent == NULL) {
return qfalse;
}
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
VectorSet(dest, ent->s.origin[0], ent->s.origin[1], ent->s.origin[2] - 4096);
memset(&tr, 0, sizeof(trace_t));
trap_Trace(&tr, ent->s.origin, ent->r.mins, ent->r.maxs, dest, ent->s.number, MASK_SOLID);
if (tr.startsolid)
{
2014-11-10 20:03:19 +00:00
G_FreeEntity(ent);
return qfalse;
}
// allow to ride movers
ent->physicsObject = qtrue;
ent->s.groundEntityNum = tr.entityNum;
2014-11-10 20:03:19 +00:00
G_SetOrigin(ent, tr.endpos);
ent->s.eFlags &= ~EF_NODRAW;
ent->r.svFlags &= ~SVF_NOCLIENT;
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent);
ent->noise_index = G_SoundIndex("sound/weapons/detpacklatch.wav");
2014-11-10 20:03:19 +00:00
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
================
*/
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
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);
2014-11-10 20:03:19 +00:00
if (tr.startsolid)
{ // If stuck in a solid, give up and go home
2014-11-10 20:03:19 +00:00
G_FreeEntity(ent);
return qfalse;
}
2014-11-10 20:03:19 +00:00
G_SetOrigin(ent, tr.endpos);
// allow to ride movers
2014-11-10 20:03:19 +00:00
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;
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent);
return qtrue;
}
qboolean itemRegistered[MAX_ITEMS];
2014-11-10 20:03:19 +00:00
void ClearRegisteredItems(void) {
memset(itemRegistered, 0, sizeof(itemRegistered));
// players always start with the base weapon
2014-11-10 20:03:19 +00:00
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));
}
2014-11-10 20:03:19 +00:00
void RegisterItem(gitem_t *item) {
if (!item) {
G_Error("RegisterItem: NULL");
//RPG-X
2014-11-10 20:03:19 +00:00
Com_Printf("Missing Item In RegisterItem\n");
}
2014-11-10 20:03:19 +00:00
itemRegistered[item - bg_itemlist] = qtrue;
}
2014-11-10 20:03:19 +00:00
void SaveRegisteredItems(void) {
char string[MAX_ITEMS + 1];
int i;
int count;
count = 0;
2014-11-10 20:03:19 +00:00
for (i = 0; i < bg_numItems; i++) {
if (itemRegistered[i]) {
count++;
string[i] = '1';
2014-11-10 20:03:19 +00:00
}
else {
string[i] = '0';
}
}
2014-11-10 20:03:19 +00:00
string[bg_numItems] = 0;
2014-11-10 20:03:19 +00:00
G_Printf("%i items registered\n", count);
trap_SetConfigstring(CS_ITEMS, string);
}
2014-11-10 20:03:19 +00:00
qboolean G_ItemSuppressed(int itemType, int itemTag)
{
2014-11-10 20:03:19 +00:00
if (rpg_rpg.integer != 0)
{
2014-11-10 20:03:19 +00:00
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;
}
2014-11-10 20:03:19 +00:00
qboolean G_ItemClassnameSuppressed(char *itemname)
{
gitem_t *item = NULL;
int itemType = 0;
int itemTag = 0;
2014-11-10 20:03:19 +00:00
item = BG_FindItemWithClassname(itemname);
2014-11-10 20:03:19 +00:00
if (!item)
{
return qfalse;
}
itemType = item->giType;
itemTag = item->giTag;
2014-11-10 20:03:19 +00:00
return G_ItemSuppressed(itemType, itemTag);
}
2014-11-10 20:03:19 +00:00
void G_SpawnItem(gentity_t *ent, gitem_t *item) {
if (G_ItemSuppressed(item->giType, item->giTag))
{
return;
}
2014-11-10 20:03:19 +00:00
G_SpawnFloat("random", "0", &ent->random);
G_SpawnFloat("wait", "0", &ent->wait);
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if (item->giType == IT_POWERUP) {
G_SoundIndex("sound/items/poweruprespawn.wav");//cgs.media.poweruprespawn
}
}
/*
================
G_BounceItem
================
*/
2014-11-10 20:03:19 +00:00
void G_BounceItem(gentity_t *ent, trace_t *trace) {
vec3_t velocity;
float dot;
int hitTime;
// reflect the velocity on the trace plane
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
VectorScale(ent->s.pos.trDelta, ent->physicsBounce, ent->s.pos.trDelta);
// check for stop
2014-11-10 20:03:19 +00:00
if (trace->plane.normal[2] > 0 && ent->s.pos.trDelta[2] < 40) {
trace->endpos[2] += 1.0; // make sure it is off ground
2014-11-10 20:03:19 +00:00
SnapVector(trace->endpos);
G_SetOrigin(ent, trace->endpos);
ent->s.groundEntityNum = trace->entityNum;
return;
}
2014-11-10 20:03:19 +00:00
VectorAdd(ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
VectorCopy(ent->r.currentOrigin, ent->s.pos.trBase);
ent->s.pos.trTime = level.time;
}
2014-11-10 20:03:19 +00:00
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
2014-11-10 20:03:19 +00:00
if (ent->s.groundEntityNum == -1) {
if (ent->s.pos.trType != TR_GRAVITY) {
ent->s.pos.trType = TR_GRAVITY;
ent->s.pos.trTime = level.time;
}
}
2014-11-10 20:03:19 +00:00
if (ent->s.pos.trType == TR_STATIONARY) {
// check think function
2014-11-10 20:03:19 +00:00
G_RunThink(ent);
return;
}
// get current position
2014-11-10 20:03:19 +00:00
BG_EvaluateTrajectory(&ent->s.pos, level.time, origin);
// trace a line from the previous position to the current position
2014-11-10 20:03:19 +00:00
if (ent->clipmask) {
mask = ent->clipmask;
2014-11-10 20:03:19 +00:00
}
else {
mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID;
}
2014-11-10 20:03:19 +00:00
trap_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin,
ent->r.ownerNum, mask);
2014-11-10 20:03:19 +00:00
VectorCopy(tr.endpos, ent->r.currentOrigin);
2014-11-10 20:03:19 +00:00
if (tr.startsolid) {
tr.fraction = 0;
}
2014-11-10 20:03:19 +00:00
trap_LinkEntity(ent); // FIXME: avoid this for stationary?
// check think function
2014-11-10 20:03:19 +00:00
G_RunThink(ent);
2014-11-10 20:03:19 +00:00
if (tr.fraction == 1) {
return;
}
// if it is in a nodrop volume, remove it
2014-11-10 20:03:19 +00:00
contents = trap_PointContents(ent->r.currentOrigin, -1);
if (contents & CONTENTS_NODROP) {
G_FreeEntity(ent);
return;
}
2014-11-10 20:03:19 +00:00
G_BounceItem(ent, &tr);
}