1301 lines
No EOL
27 KiB
C++
1301 lines
No EOL
27 KiB
C++
// *****************************************************************************
|
|
// dm.cpp
|
|
// *****************************************************************************
|
|
|
|
#include "g_local.h"
|
|
#include "dm_private.h"
|
|
#include "w_weapons.h"
|
|
#include "../strings/dm_generic.h"
|
|
#include "ai_private.h" // so we know what a bodyhuman_c is
|
|
#include "..\qcommon\configstring.h"
|
|
|
|
gamemode_ic *dm = NULL;
|
|
|
|
dmnone_c nodm;
|
|
dmstandard_c standard;
|
|
dmassassin_c assassin;
|
|
dmarsenal_c arsenal;
|
|
dmctf_c ctf;
|
|
dmreal_c real;
|
|
|
|
gamerules_c *gamerules[] =
|
|
{
|
|
&nodm,
|
|
&standard,
|
|
&assassin,
|
|
&arsenal,
|
|
&ctf,
|
|
&real
|
|
};
|
|
|
|
extern void W_InitInv(edict_t &ent);
|
|
extern void sendRestartPrediction(edict_t *ent);
|
|
|
|
/*
|
|
=================
|
|
countPlayers
|
|
|
|
Returns the number of players in the current game.
|
|
=================
|
|
*/
|
|
|
|
int countPlayers(void)
|
|
{
|
|
int i;
|
|
edict_t *e2;
|
|
int num = 0;
|
|
|
|
for(i = 1; i <= game.maxclients; i++)
|
|
{
|
|
e2 = &g_edicts[i];
|
|
|
|
if (!e2->inuse)
|
|
continue;
|
|
|
|
if (!e2->client)
|
|
continue;
|
|
|
|
if (e2->client->pers.spectator)
|
|
continue;
|
|
|
|
num++;
|
|
}
|
|
|
|
return(num);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
IsFemale
|
|
=================
|
|
*/
|
|
|
|
qboolean IsFemale(edict_t *ent)
|
|
{
|
|
if(!ent->client || !ent->client->body)
|
|
return false;
|
|
|
|
if (ent->client->body->GetClassCode()==BODY_FEMALE)
|
|
{
|
|
return (true);
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
/*
|
|
==============
|
|
setPlayernameColor
|
|
==============
|
|
*/
|
|
|
|
void setPlayernameColor(edict_t &ent,edict_t &other,byte color)
|
|
{
|
|
if(!ent.client)
|
|
return;
|
|
|
|
if(other.inuse)
|
|
ent.client->playernameColors[other.s.number-1]=color;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
InitDeathmatchSystem
|
|
=================
|
|
*/
|
|
|
|
void InitDeathmatchSystem(void)
|
|
{
|
|
if(!dm)
|
|
{
|
|
dm = new gamemode_c;
|
|
}
|
|
|
|
#ifndef _DEMO_
|
|
dm->setDMMode(((deathmatch->value>=DM_NONE)&&(deathmatch->value<=DM_REAL))?deathmatch->value:0);
|
|
#else
|
|
dm->setDMMode(((deathmatch->value>=DM_NONE)&&(deathmatch->value<=DM_STANDARD))?deathmatch->value:0);
|
|
#endif
|
|
|
|
gi.SP_Register("dm_generic");
|
|
|
|
// Generic DM specific precaching and stuff.
|
|
|
|
if(dm->isDM())
|
|
{
|
|
// Precache generic DM effects.
|
|
|
|
// gi.effectindex("environ/invuln");
|
|
gi.effectindex("environ/irespawn");
|
|
gi.effectindex("environ/prespawn");
|
|
|
|
// Precache generic DM sounds.
|
|
|
|
gi.soundindex("dm/armorpu.wav");
|
|
gi.soundindex("dm/irespawn.wav");
|
|
gi.soundindex("dm/frag.wav");
|
|
gi.soundindex("dm/selffrag.wav");
|
|
gi.soundindex("dm/healthpu.wav");
|
|
gi.soundindex("dm/prespawn.wav");
|
|
}
|
|
|
|
// Clear the CTF team color config strings, used to tell clients what blue
|
|
// and red team names are.
|
|
|
|
gi.configstring(CS_CTF_BLUE_TEAM, "");
|
|
gi.configstring(CS_CTF_RED_TEAM, "");
|
|
|
|
dm->preLevelInit();
|
|
|
|
}
|
|
|
|
/*
|
|
=================
|
|
DeleteDeathmatchSystem
|
|
=================
|
|
*/
|
|
|
|
void DeleteDeathmatchSystem(void)
|
|
{
|
|
if(dm)
|
|
{
|
|
delete dm;
|
|
|
|
dm=0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
respawnUntouchedItem
|
|
==================
|
|
|
|
This item was dropped at some point and hasn't been touched in a while. Move it
|
|
to its designer-specified spawn point.
|
|
*/
|
|
|
|
void respawnUntouchedItem(edict_t* ent)
|
|
{
|
|
dm->respawnUntouchedItem(ent);
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
ClearEffects
|
|
===============
|
|
*/
|
|
|
|
|
|
void ClearEffects (edict_t *ent)
|
|
{
|
|
// This also makes the item solid.
|
|
|
|
fxRunner.clearContinualEffects(ent);
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
DoRespawn
|
|
===============
|
|
*/
|
|
|
|
void DoRespawn (edict_t *ent)
|
|
{
|
|
ent->flags &= ~FL_RESPAWN;
|
|
ent->svflags &= ~SVF_NOCLIENT;
|
|
ent->solid = SOLID_NOT;
|
|
ent->touch_debounce_time = 0.0;
|
|
|
|
gi.linkentity(ent);
|
|
|
|
// Send a respawning effect.
|
|
fxRunner.exec("environ/irespawn", ent->s.origin);
|
|
|
|
// 1/4/00 kef -- play a "fade in" anim. at the end of that anim, switch back to non-fade anim.
|
|
PickupInst *pickupInst = thePickupList.GetPickupInstFromEdict(ent);
|
|
|
|
if (pickupInst)
|
|
{
|
|
pickupInst->FadeIn();
|
|
}
|
|
|
|
// Respawn sound. FIXME: Make part of FX? Not sure about PHS issues here tho'?
|
|
gi.sound(ent, CHAN_ITEM, gi.soundindex("dm/irespawn.wav"), .6, ATTN_NORM, 0);
|
|
|
|
ent->think = ClearEffects;
|
|
ent->nextthink = level.time + ITEM_RESPAWN_FADEIN;
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
itemDropToFloor
|
|
|
|
Adjust the Z component of an ent's origin of so that it lies on the floor.
|
|
================
|
|
*/
|
|
|
|
void itemDropToFloor (edict_t *ent)
|
|
{
|
|
trace_t tr;
|
|
vec3_t vec,dest;
|
|
|
|
VectorSet(vec,0.0,0.0,-128.0);
|
|
|
|
VectorAdd (ent->s.origin, vec, dest);
|
|
|
|
gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID, &tr);
|
|
|
|
if (tr.startsolid)
|
|
{
|
|
gi.dprintf ("itemDropToFloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
|
|
|
|
G_FreeEdict (ent);
|
|
|
|
return;
|
|
}
|
|
|
|
VectorCopy (tr.endpos, ent->s.origin);
|
|
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
ent->think = DoRespawn;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
gamerules_c::setRespawn
|
|
===============
|
|
*/
|
|
|
|
void gamerules_c::setRespawn (edict_t *ent, float delay)
|
|
{
|
|
float adjusted_delay;
|
|
int clients;
|
|
ent->flags |= FL_RESPAWN;
|
|
ent->svflags |= SVF_NOCLIENT;
|
|
ent->solid = SOLID_NOT;
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
clients=(float)level.num_clients;
|
|
if (clients<2.0)
|
|
clients=2.0;
|
|
adjusted_delay = delay * sqrt(2.0/clients); // Spawn more frequently when more players.
|
|
// Lemme see here: sqrt(2/2) = sqrt(1) = 1
|
|
// sqrt(2/8) = sqrt(1/4) = 1/2
|
|
// sqrt(2/32) = sqrt(1/16) = 1/4
|
|
}
|
|
else
|
|
{
|
|
adjusted_delay = delay;
|
|
}
|
|
|
|
ent->nextthink = level.time + adjusted_delay;
|
|
ent->think = DoRespawn;
|
|
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::openLogFile
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::openLogFile(void)
|
|
{
|
|
if(log_file_name->string[0])
|
|
{
|
|
char name[MAX_QPATH];
|
|
|
|
// Try to open the log-file.
|
|
// do we need a counter value?
|
|
Com_sprintf (name, sizeof(name), "%s", log_file_name->string);
|
|
if (log_file_counter->value)
|
|
{
|
|
char exten[10];
|
|
|
|
// copy the extension somewhere
|
|
strcpy(exten, COM_FileExtension(name));
|
|
// remove the extension from the file name
|
|
COM_StripExtension(name, name);
|
|
// add in number
|
|
Com_sprintf (name, sizeof(name), "%s%d.", name, (int)log_file_counter->value);
|
|
gi.cvar_setvalue("log_file_counter", log_file_counter->value +1.0);
|
|
// re-add extension
|
|
strcat(name, exten);
|
|
|
|
}
|
|
Com_Printf ("Dumping end game log to %s\n", name);
|
|
|
|
gi.FS_CreatePath (name);
|
|
// decide what we need to do, append or overwrite
|
|
if (!stricmp(log_file_mode->string, "o"))
|
|
{
|
|
filePtr = fopen (name, "w");
|
|
}
|
|
else
|
|
{
|
|
filePtr = fopen (name, "a");
|
|
}
|
|
|
|
// If we managed to open the log-file, write out the game details, else
|
|
// print an error message.
|
|
|
|
if(filePtr)
|
|
{
|
|
int hours,secs,mins;
|
|
cvar_t *host_name;
|
|
|
|
secs = level.time;
|
|
hours = secs / (60*60);
|
|
secs -= hours * (60*60);
|
|
mins = secs / 60;
|
|
secs -= mins * 60;
|
|
host_name = gi.cvar("hostname", "", 0);
|
|
|
|
fprintf (filePtr, "%s\n", log_file_header->string);
|
|
fprintf (filePtr, "%sMap Name : %s\n", log_file_line_header->string, level.mapname);
|
|
fprintf (filePtr, "%sHost Name : %s\n", log_file_line_header->string, host_name->string);
|
|
fprintf (filePtr, "%sGame type : %s\n", log_file_line_header->string, getGameName());
|
|
fprintf (filePtr, "%sGame Duration : %02i:%02i:%02i\n%s\n", log_file_line_header->string, hours, mins, secs, log_file_line_header->string);
|
|
}
|
|
else
|
|
{
|
|
Com_Printf("ERROR: couldn't open game log-file.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::closeLogFile
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::closeLogFile(void)
|
|
{
|
|
if(filePtr)
|
|
{
|
|
fprintf(filePtr,"%s\n",log_file_footer->string);
|
|
fclose(filePtr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientConnect
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientConnect(edict_t &ent)
|
|
{
|
|
|
|
// I see my own playername in TC_BLACK.
|
|
|
|
setPlayernameColor(ent,ent,0);
|
|
|
|
// I see all other players' playernames in TC_BLACK.
|
|
|
|
edict_t *other;
|
|
int i;
|
|
|
|
for(i=1;i<=game.maxclients;i++)
|
|
{
|
|
other=&g_edicts[i];
|
|
|
|
if(!other->inuse)
|
|
continue;
|
|
|
|
if(!other->client)
|
|
continue;
|
|
|
|
if(other!=&ent)
|
|
setPlayernameColor(ent,(edict_t &)*other,0);
|
|
}
|
|
|
|
// All other players see my playername in TC_BLACK.
|
|
|
|
for(i=1;i<=game.maxclients;i++)
|
|
{
|
|
other=&g_edicts[i];
|
|
|
|
if(!other->inuse)
|
|
continue;
|
|
|
|
if(!other->client)
|
|
continue;
|
|
|
|
if(other!=&ent)
|
|
setPlayernameColor((edict_t &)*other,ent,0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientSetDroppedItemThink
|
|
==================
|
|
*/
|
|
|
|
static void droppedItemThink(edict_t *ent)
|
|
{
|
|
// fxRunner.exec("environ/teleport7", ent->s.origin);
|
|
G_FreeEdict(ent);
|
|
return;
|
|
}
|
|
|
|
void gamerules_c::clientSetDroppedItemThink(edict_t *ent)
|
|
{
|
|
ent->think=droppedItemThink;
|
|
ent->nextthink=level.time+20.0F;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientDropItem
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientDropItem(edict_t *ent,int type,int ammoCount)
|
|
{
|
|
if (!ent->ghoulInst)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Pickup *pickup = NULL;
|
|
|
|
if (pickup = thePickupList.GetPickupFromType(PU_INV, type))
|
|
{
|
|
edict_t *dropped;
|
|
Matrix4 ZoneMatrix;
|
|
Vect3 zonePos;
|
|
vec3_t handPos,
|
|
dir;
|
|
|
|
// Whatever weapon player is holding, throw one off.
|
|
|
|
dropped=G_Spawn();
|
|
dropped->spawnflags|=DROPPED_ITEM;
|
|
I_Spawn(dropped,pickup);
|
|
|
|
dropped->enemy=ent;
|
|
dropped->touch_debounce_time=level.time+2.0;
|
|
|
|
ent->ghoulInst->
|
|
GetBoltMatrix(level.time,
|
|
ZoneMatrix,
|
|
ent->ghoulInst->GetGhoulObject()->FindPart("wbolt_hand_l"),
|
|
IGhoulInst::MatrixType::Entity);
|
|
|
|
ZoneMatrix.GetRow(3,zonePos);
|
|
|
|
handPos[0]=zonePos[0];
|
|
handPos[1]=zonePos[1];
|
|
handPos[2]=zonePos[2];
|
|
|
|
VectorAdd(ent->s.origin,handPos,dropped->s.origin);
|
|
AngleVectors(ent->client->ps.viewangles,dir,NULL, NULL);
|
|
dir[2] = 0;
|
|
VectorNormalize(dir);
|
|
VectorMA(dropped->s.origin, 20.0, dir, dropped->s.origin);
|
|
VectorScale(dir,250.0,dropped->velocity);
|
|
dropped->velocity[2]+=150.0;
|
|
dropped->health = ammoCount;
|
|
|
|
clientSetDroppedItemThink(dropped);
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientSetDroppedWeaponThink
|
|
==================
|
|
*/
|
|
|
|
static void droppedWeaponThink(edict_t *ent)
|
|
{
|
|
// fxRunner.exec("environ/teleport7", ent->s.origin);
|
|
G_FreeEdict(ent);
|
|
return;
|
|
}
|
|
|
|
void gamerules_c::clientSetDroppedWeaponThink(edict_t *ent)
|
|
{
|
|
ent->think=droppedWeaponThink;
|
|
ent->nextthink=level.time+20.0F;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientDieWeaponHandler
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientDieWeaponHandler(edict_t *ent)
|
|
{
|
|
sharedEdict_t sh;
|
|
|
|
sh.inv=(inven_c *)ent->client->inv;
|
|
sh.edict=ent;
|
|
sh.inv->setOwner(&sh);
|
|
sh.inv->rulesSetDropWeapons(-1);
|
|
sh.inv->handlePlayerDeath();
|
|
|
|
if(IGhoulInst *gun=ent->client->ps.gun)
|
|
gun->SetAllPartsOnOff(false);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientDropWeapon
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientDropWeapon(edict_t *ent,int type, int clipSize)
|
|
{
|
|
if (!ent->ghoulInst)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Pickup *pickup = NULL;
|
|
|
|
if (pickup = thePickupList.GetPickupFromType(PU_WEAPON, type))
|
|
{
|
|
edict_t *dropped;
|
|
Matrix4 ZoneMatrix;
|
|
Vect3 zonePos;
|
|
vec3_t handPos,
|
|
dir;
|
|
|
|
// Whatever weapon player is holding, throw one off.
|
|
|
|
dropped=G_Spawn();
|
|
dropped->spawnflags|=DROPPED_ITEM;
|
|
I_Spawn(dropped,pickup);
|
|
|
|
dropped->enemy=ent;
|
|
dropped->touch_debounce_time=level.time+2.0;
|
|
|
|
ent->ghoulInst->
|
|
GetBoltMatrix(level.time,
|
|
ZoneMatrix,
|
|
ent->ghoulInst->GetGhoulObject()->FindPart("wbolt_hand_r"),
|
|
IGhoulInst::MatrixType::Entity);
|
|
|
|
ZoneMatrix.GetRow(3,zonePos);
|
|
|
|
handPos[0]=zonePos[0];
|
|
handPos[1]=zonePos[1];
|
|
handPos[2]=zonePos[2];
|
|
|
|
VectorAdd(ent->s.origin,handPos,dropped->s.origin);
|
|
AngleVectors(ent->client->ps.viewangles,dir,NULL, NULL);
|
|
dir[2] = 0;
|
|
VectorNormalize(dir);
|
|
|
|
|
|
// 2/3/00 dk -- We need to see if we can spawn the weapon at a point in front of the player.
|
|
// If we can't, just spawn it as close as we can. Without this test, a player with his face against the wall may end
|
|
// up dropping it on the other side of that wall.
|
|
vec3_t spawnPoint;
|
|
trace_t trace;
|
|
|
|
|
|
VectorMA(dropped->s.origin, 20.0, dir, spawnPoint);
|
|
dropped->owner = ent; // don't want player to interfere with trace
|
|
gi.trace(dropped->s.origin, dropped->mins, dropped->maxs, spawnPoint, dropped, MASK_SOLID, &trace);
|
|
if (trace.fraction < 1)
|
|
{
|
|
if (trace.startsolid) // ugh, we're probably in a corner trying to drop a weapon that won't fit.
|
|
// well, try behind us a bit
|
|
{
|
|
vec3_t backPoint;
|
|
|
|
VectorMA(dropped->s.origin, -10, dir, backPoint);
|
|
gi.trace(backPoint, dropped->mins, dropped->maxs, dropped->s.origin, dropped, MASK_SOLID, &trace);
|
|
VectorMA(backPoint, 10.0 * trace.fraction, dir, dropped->s.origin);
|
|
}
|
|
else
|
|
{
|
|
VectorMA(dropped->s.origin, 20.0 * trace.fraction, dir, dropped->s.origin);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(spawnPoint, dropped->s.origin);
|
|
}
|
|
dropped->owner = NULL;
|
|
|
|
|
|
VectorScale(dir,250.0,dropped->velocity);
|
|
dropped->velocity[2]+=150.0;
|
|
dropped->health = clipSize;
|
|
|
|
clientSetDroppedWeaponThink(dropped);
|
|
|
|
if(ent->health>0)
|
|
{
|
|
const char *weapon = gi.SP_GetStringText(pickup->GetDroppedStringIndex());
|
|
gi.SP_Print(ent,DM_GENERIC_TEXT_DROPPED_WEAPON,weapon);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientHandlePredn
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientHandlePredn(edict_t &ent)
|
|
{
|
|
// Start with a zero init'd inventory and issue a (reliable) instruction to
|
|
// restart the player's client-side inventory system.
|
|
|
|
W_InitInv(ent);
|
|
sendRestartPrediction(&ent);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientObituary
|
|
==================
|
|
*/
|
|
|
|
void gamerules_c::clientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker)
|
|
{
|
|
int mod,
|
|
message;
|
|
|
|
mod = meansOfDeath & ~MOD_FRIENDLY_FIRE;
|
|
message = -1;
|
|
|
|
switch (mod)
|
|
{
|
|
case MOD_WATER:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_WATER;
|
|
break;
|
|
|
|
case MOD_SLIME:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_SLIME;
|
|
break;
|
|
|
|
case MOD_CRUSH:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_CRUSH;
|
|
break;
|
|
|
|
case MOD_FALLING:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_FALLING;
|
|
break;
|
|
|
|
case MOD_SUICIDE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_SUICIDE;
|
|
break;
|
|
|
|
case MOD_EXPLOSIVE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_EXPLOSIVE;
|
|
break;
|
|
|
|
case MOD_FIRE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_FIRE;
|
|
break;
|
|
|
|
case MOD_LAVA:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_LAVA;
|
|
break;
|
|
|
|
case MOD_EXIT:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_EXIT;
|
|
break;
|
|
|
|
case MOD_BARBWIRE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_BARBWIRE;
|
|
break;
|
|
|
|
case MOD_DOGBITE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_DOGBITE;
|
|
break;
|
|
}
|
|
|
|
if(attacker == self)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_PHOS_GRENADE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_PHOS_GRENADE;
|
|
break;
|
|
|
|
case MOD_ROCKET_SPLASH:
|
|
if (IsFemale(self))
|
|
message = DM_GENERIC_TEXT_OBT_MOD_ROCKET_SPLASH_F;
|
|
else
|
|
message = DM_GENERIC_TEXT_OBT_MOD_ROCKET_SPLASH_M;
|
|
break;
|
|
|
|
case MOD_C4:
|
|
if (IsFemale(self))
|
|
message = DM_GENERIC_TEXT_OBT_MOD_C4_F;
|
|
else
|
|
message = DM_GENERIC_TEXT_OBT_MOD_C4_M;
|
|
break;
|
|
|
|
case MOD_CLAYMORE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_CLAYMORE;
|
|
break;
|
|
|
|
case MOD_NEURAL_GRENADE:
|
|
if (IsFemale(self))
|
|
message = DM_GENERIC_TEXT_OBT_MOD_NEURAL_GRENADE_F;
|
|
else
|
|
message = DM_GENERIC_TEXT_OBT_MOD_NEURAL_GRENADE_M;
|
|
break;
|
|
|
|
case MOD_GRENADE:
|
|
if (IsFemale(self))
|
|
message = DM_GENERIC_TEXT_OBT_MOD_GRENADE_F;
|
|
else
|
|
message = DM_GENERIC_TEXT_OBT_MOD_GRENADE_M;
|
|
break;
|
|
|
|
default:
|
|
if (IsFemale(self))
|
|
message = DM_GENERIC_TEXT_OBT_F;
|
|
else
|
|
message = DM_GENERIC_TEXT_OBT_M;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(message>-1)
|
|
{
|
|
gi.SP_Print_Obit(0,message,self->s.number);
|
|
|
|
if (dedicated->value)
|
|
{
|
|
char consoleMsg[MAX_OSPATH];
|
|
|
|
gi.SP_SPrint(consoleMsg, MAX_OSPATH, message, self->s.number);
|
|
gi.Sys_ConsoleOutput (consoleMsg);
|
|
}
|
|
|
|
self->enemy = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
self->enemy = attacker;
|
|
|
|
if(attacker && attacker->client)
|
|
{
|
|
switch (mod)
|
|
{
|
|
case MOD_KNIFE_SLASH:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_KNIFE_SLASH;
|
|
break;
|
|
|
|
case MOD_KNIFE_THROWN:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_KNIFE_THROWN;
|
|
break;
|
|
|
|
case MOD_PISTOL1:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_PISTOL1;
|
|
break;
|
|
|
|
case MOD_PISTOL2:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_PISTOL2;
|
|
break;
|
|
|
|
case MOD_MPISTOL:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_MPISTOL;
|
|
break;
|
|
|
|
case MOD_ASSAULTRIFLE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_ASSAULTRIFLE;
|
|
break;
|
|
|
|
case MOD_SNIPERRIFLE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_SNIPERRIFLE;
|
|
break;
|
|
|
|
case MOD_AUTOSHOTGUN:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_AUTOSHOTGUN;
|
|
break;
|
|
|
|
case MOD_SHOTGUN:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_SHOTGUN;
|
|
break;
|
|
|
|
case MOD_MACHINEGUN:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_MACHINEGUN;
|
|
break;
|
|
|
|
case MOD_PHOS_GRENADE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_PHOS_GRENADE_2;
|
|
break;
|
|
|
|
case MOD_ROCKET:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_ROCKET;
|
|
break;
|
|
|
|
case MOD_ROCKET_SPLASH:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_ROCKET_SPLASH;
|
|
break;
|
|
|
|
case MOD_MPG:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_MPG;
|
|
break;
|
|
|
|
case MOD_FLAMEGUN:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_FLAMEGUN;
|
|
break;
|
|
|
|
case MOD_FLAMEGUN_NAPALM:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_FLAMEGUN_NAPALM;
|
|
break;
|
|
|
|
case MOD_C4:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_C4;
|
|
break;
|
|
|
|
case MOD_CLAYMORE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_CLAYMORE_2;
|
|
break;
|
|
|
|
case MOD_NEURAL_GRENADE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_NEURAL_GRENADE;
|
|
break;
|
|
|
|
case MOD_TELEFRAG:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_TELEFRAG;
|
|
break;
|
|
|
|
case MOD_GRENADE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_GRENADE_2;
|
|
break;
|
|
|
|
case MOD_CONC_GRENADE:
|
|
message = DM_GENERIC_TEXT_OBT_MOD_CONC_GRENADE;
|
|
break;
|
|
}
|
|
|
|
if(message>-1)
|
|
{
|
|
gi.SP_Print_Obit(0,message,self->s.number,attacker->s.number);
|
|
if (dedicated->value)
|
|
{
|
|
char consoleMsg[MAX_OSPATH];
|
|
gi.SP_SPrint(consoleMsg, MAX_OSPATH, message, self->s.number, attacker->s.number);
|
|
gi.Sys_ConsoleOutput (consoleMsg);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
gi.SP_Print_Obit(0,DM_GENERIC_TEXT_OBT_DIED,self->s.number);
|
|
|
|
if (dedicated->value)
|
|
{
|
|
char consoleMsg[MAX_OSPATH];
|
|
|
|
if (message>-1)
|
|
{
|
|
gi.SP_SPrint(consoleMsg, MAX_OSPATH, message, self->s.number);
|
|
gi.Sys_ConsoleOutput (consoleMsg);
|
|
}
|
|
else
|
|
{
|
|
gi.SP_SPrint(consoleMsg, MAX_OSPATH, DM_GENERIC_TEXT_OBT_DIED, self->s.number);
|
|
gi.Sys_ConsoleOutput (consoleMsg);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientScoreboardMessage
|
|
==================
|
|
*/
|
|
|
|
int TeamCompare(void const *a, void const *b)
|
|
{
|
|
struct TeamInfo_s *A, *B;
|
|
|
|
A = (struct TeamInfo_s *)a;
|
|
B = (struct TeamInfo_s *)b;
|
|
|
|
return (A->score < B->score);
|
|
}
|
|
|
|
void gamerules_c::clientScoreboardMessage (edict_t *ent, edict_t *killer, qboolean log_file)
|
|
{
|
|
int i,j,k;
|
|
int sorted[MAX_CLIENTS];
|
|
int sortedscores[MAX_CLIENTS];
|
|
struct TeamInfo_s teams[MAX_CLIENTS];
|
|
int score,total,total_teams, real_total;
|
|
int x,y,top;
|
|
gclient_t *cl;
|
|
edict_t *cl_ent;
|
|
|
|
total=total_teams = 0;
|
|
|
|
// Is logging to a log-file enabled?
|
|
|
|
if(log_file)
|
|
openLogFile();
|
|
|
|
// Sort all the clients by score from highest to lowest.
|
|
|
|
for(i=0;i<game.maxclients;i++)
|
|
{
|
|
cl_ent=g_edicts+1+i;
|
|
|
|
if(!cl_ent->inuse)
|
|
continue;
|
|
|
|
// Spectators get added separately if there's any room left.
|
|
|
|
if(cl_ent->client->resp.spectator)
|
|
continue;
|
|
|
|
score=game.clients[i].resp.score;
|
|
|
|
for(j=0;j<total;j++)
|
|
{
|
|
if(score>sortedscores[j])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(k=total;k>j;k--)
|
|
{
|
|
sorted[k]=sorted[k-1];
|
|
sortedscores[k]=sortedscores[k-1];
|
|
}
|
|
|
|
sorted[j]=i;
|
|
sortedscores[j]=score;
|
|
total++;
|
|
|
|
if(!dm->dmRule_TEAMPLAY())
|
|
continue;
|
|
|
|
if(!strcmp(Info_ValueForKey(cl_ent->client->pers.userinfo, "skin"),Info_ValueForKey (cl_ent->client->pers.userinfo, "teamname")))
|
|
{
|
|
// Ensure those clients with no team affiliation (i.e. skin == teamname)
|
|
// don't produce a team aggregate score.
|
|
|
|
continue;
|
|
}
|
|
|
|
for(j=0;j<total_teams;j++)
|
|
{
|
|
if (OnSameTeam(g_edicts + 1 + teams[j].rep, cl_ent))
|
|
{
|
|
teams[j].score += score;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == total_teams)
|
|
{
|
|
teams[total_teams].rep = i;
|
|
teams[total_teams].score = score;
|
|
teams[total_teams].teamname = Info_ValueForKey (cl_ent->client->pers.userinfo, "teamname");
|
|
total_teams++;
|
|
}
|
|
}
|
|
|
|
qsort((void *)teams, total_teams, sizeof(TeamInfo_s), TeamCompare);
|
|
|
|
real_total=total;
|
|
if(total>12)
|
|
{
|
|
total=12;
|
|
}
|
|
if (total_teams > 6)
|
|
{
|
|
total_teams = 6;
|
|
}
|
|
|
|
// Clear the client's layout.
|
|
|
|
top=32;
|
|
gi.SP_Print(ent,DM_GENERIC_LAYOUT_RESET);
|
|
|
|
// Handle team aggregate scores if teamplay is on.
|
|
|
|
if (dm->dmRule_TEAMPLAY())
|
|
{
|
|
top = -64;
|
|
y = top;
|
|
|
|
for(i=0;i<total_teams;i++)
|
|
{
|
|
x=(i>=3)?160:0;
|
|
|
|
if(i==3)
|
|
{
|
|
y = top;
|
|
}
|
|
|
|
gi.SP_Print(ent,DM_GENERIC_LAYOUT_SCOREBOARD_TEAM,
|
|
(short)x,(short)y,
|
|
teams[i].rep,
|
|
(short)teams[i].score);
|
|
|
|
// Write the scores to the open log-file if we have one.
|
|
|
|
if(filePtr)
|
|
{
|
|
fprintf(filePtr,"%sTeam %s\n",log_file_line_header->string,teams[i].teamname);
|
|
fprintf(filePtr,"%sScore %i\n",log_file_line_header->string,teams[i].score);
|
|
}
|
|
|
|
y+=32;
|
|
}
|
|
|
|
if(filePtr)
|
|
{
|
|
fprintf(filePtr,"\n");
|
|
}
|
|
|
|
if (total_teams >= 3)
|
|
{
|
|
top=64;
|
|
}
|
|
else
|
|
{
|
|
top = 32;
|
|
}
|
|
}
|
|
|
|
|
|
y = top;
|
|
|
|
// Send the scores for all active players to the client's layout.
|
|
|
|
for(i=0;i<total;i++)
|
|
{
|
|
cl=&game.clients[sorted[i]];
|
|
cl_ent=g_edicts+1+sorted[i];
|
|
|
|
x=(i>=6)?160:0;
|
|
|
|
if(i==6)
|
|
y=top;
|
|
|
|
gi.SP_Print(ent,DM_GENERIC_LAYOUT_SCOREBAORD_CLIENT,
|
|
(short)x,(short)y,
|
|
sorted[i],
|
|
(short)cl->resp.score,
|
|
(unsigned short)cl->ping,
|
|
(unsigned short)((level.framenum-cl->resp.enterframe)/600));
|
|
|
|
y+=32;
|
|
}
|
|
|
|
// Send the scores for all spectators to the client's layout - if there's
|
|
// any room left on the scoreboard.
|
|
|
|
j=0;
|
|
|
|
while((j<game.maxclients)&&(i<12))
|
|
{
|
|
cl=&game.clients[j];
|
|
cl_ent=g_edicts+1+j;
|
|
|
|
if((!cl_ent->inuse)||(!cl_ent->client->resp.spectator))
|
|
{
|
|
j++;
|
|
continue;
|
|
}
|
|
|
|
x=(i>=6)?160:0;
|
|
|
|
if(i==6)
|
|
y=top;
|
|
|
|
gi.SP_Print(ent,DM_GENERIC_LAYOUT_SCOREBAORD_SPECTATOR,
|
|
(short)x,(short)y,
|
|
j,
|
|
(unsigned short)cl->ping,
|
|
(unsigned short)((level.framenum-cl->resp.enterframe)/600));
|
|
|
|
y+=32;
|
|
i++;
|
|
j++;
|
|
}
|
|
|
|
// Write the scores to the open log-file if we have one.
|
|
|
|
if(filePtr)
|
|
{
|
|
// Active players.
|
|
|
|
for(i=0;i<real_total;i++)
|
|
{
|
|
cl=&game.clients[sorted[i]];
|
|
cl_ent=g_edicts+1+sorted[i];
|
|
|
|
fprintf(filePtr,"%sClient %s\n",log_file_line_header->string,cl_ent->client->pers.netname);
|
|
fprintf(filePtr,"%sScore %i\n",log_file_line_header->string,cl->resp.score);
|
|
fprintf(filePtr,"%sPing %i\n",log_file_line_header->string,cl->ping);
|
|
fprintf(filePtr,"%sTime %i\n%s\n",log_file_line_header->string,(level.framenum-cl->resp.enterframe)/600,log_file_line_header->string);
|
|
}
|
|
|
|
// Spectators.
|
|
|
|
for(i=0;i<game.maxclients;i++)
|
|
{
|
|
cl=&game.clients[i];
|
|
cl_ent=g_edicts+1+i;
|
|
|
|
if((!cl_ent->inuse)||(!cl_ent->client->resp.spectator))
|
|
continue;
|
|
|
|
fprintf(filePtr,"%sClient %s\n",log_file_line_header->string,cl_ent->client->pers.netname);
|
|
fprintf(filePtr,"%s %s\n",log_file_line_header->string,"spectator");
|
|
fprintf(filePtr,"%sPing %i\n",log_file_line_header->string,cl->ping);
|
|
fprintf(filePtr,"%sTime %i\n%s\n",log_file_line_header->string,(level.framenum-cl->resp.enterframe)/600,log_file_line_header->string);
|
|
}
|
|
}
|
|
|
|
// Close the open log file if we have one.
|
|
|
|
closeLogFile();
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
gamerules_c::clientCalculateWaver
|
|
==================
|
|
*/
|
|
|
|
float gamerules_c::clientCalculateWaver(edict_t &ent, int atkID)
|
|
{
|
|
bodyhuman_c *body = NULL;
|
|
float waverAmount = 0, speed = 0;
|
|
|
|
// check for arm damage.
|
|
if (body = (bodyhuman_c*)ent.client->body)
|
|
{ // IsAimImpeded returns -1 if left arm, 1 if right arm. for now, though, I think
|
|
//all we care about is the fact that we took an arm hit
|
|
if (body->IsAimImpeded())
|
|
{
|
|
waverAmount += 3; // totally guessing here. additionally, shouldn't be hardcoded.
|
|
}
|
|
}
|
|
|
|
// check speed
|
|
speed = DotProduct(ent.velocity, ent.velocity);
|
|
speed *= 3.0 / 80000.0;//max possible miss fire is 30 degrees
|
|
waverAmount += speed;
|
|
|
|
// check for Sniper rifle, add waver based on zoom
|
|
if((atkID == ATK_SNIPER)/* || (atkID == ATK_SNIPER_ALT)*/)
|
|
{
|
|
// This waver based on a desired maximum waver of 4.5 when not zoomed (fov == 95),
|
|
// then a linearly decreasing slope from 2 at minimum zoom (fov == 60),
|
|
// to no waver when maximally zoomed (fov == 6). Thus:
|
|
// (CurrentFOV - FOV@MaxZoom) * MaxWaver (CurrentFOV - 6) * 2
|
|
// waver = --------------------------------------- = --------------------------
|
|
// (FOV@MinZoom - FOV@MaxZoom) (60 - 6)
|
|
|
|
if(ent.client->ps.fov > 60)
|
|
{
|
|
waverAmount = 4.5;
|
|
}
|
|
else
|
|
{
|
|
waverAmount = 0;//((ent.client->ps.fov - 6) * .037);
|
|
}
|
|
}
|
|
|
|
return waverAmount;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
gamerules_c::clientGetMovescale
|
|
====================
|
|
*/
|
|
|
|
float gamerules_c::clientGetMovescale(edict_t *ent)
|
|
{
|
|
return(g_movescale->value-(g_movescale->value*ent->client->moveScale));
|
|
}
|
|
|
|
/*
|
|
====================
|
|
gamerules_c::IsUnprotectedGoreZone
|
|
====================
|
|
*/
|
|
|
|
bool gamerules_c::IsUnprotectedGoreZone(gz_code zone)
|
|
{
|
|
if ((zone == GZ_SHLDR_RIGHT_FRONT) || (zone == GZ_SHLDR_RIGHT_BACK) || (zone == GZ_SHLDR_LEFT_FRONT) ||
|
|
(zone == GZ_SHLDR_LEFT_BACK) || (zone == GZ_CHEST_FRONT) || (zone == GZ_CHEST_BACK) ||
|
|
(zone == GZ_GUT_FRONT_EXTRA) || (zone == GZ_GUT_BACK_EXTRA))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
gamerules_c::IsLimbZone
|
|
====================
|
|
*/
|
|
|
|
bool gamerules_c::IsLimbZone(gz_code zone)
|
|
{
|
|
if ((zone == GZ_ARM_UPPER_RIGHT) || (zone == GZ_ARM_UPPER_LEFT) || (zone == GZ_ARM_LOWER_RIGHT) ||
|
|
(zone == GZ_ARM_LOWER_LEFT) || (zone == GZ_LEG_UPPER_RIGHT_FRONT) || (zone == GZ_LEG_UPPER_RIGHT_BACK) ||
|
|
(zone == GZ_LEG_UPPER_LEFT_FRONT) || (zone == GZ_LEG_UPPER_LEFT_BACK) || (zone == GZ_LEG_LOWER_RIGHT) ||
|
|
(zone == GZ_LEG_LOWER_LEFT) || (zone == GZ_FOOT_RIGHT) || (zone == GZ_FOOT_LEFT))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
gamerules_c::IsShootableWeapon
|
|
====================
|
|
*/
|
|
|
|
bool gamerules_c::IsShootableWeapon(char *weaponname)
|
|
{
|
|
if(!strcmp(weaponname, "w_pistol1") || !strcmp(weaponname, "w_knife"))
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
gamemode_c::setDMMode
|
|
==================
|
|
*/
|
|
|
|
void gamemode_c::setDMMode(int newtype)
|
|
{
|
|
gametype = newtype;
|
|
rules = gamerules[newtype];
|
|
gi.configstring(CS_SHOWNAMES,rules->arePlayerNamesOn()?"1":"0");
|
|
gi.configstring(CS_SHOWTEAMS,rules->arePlayerTeamsOn()?"1":"0");
|
|
|
|
rules->registerPlayerIcons();
|
|
} |