openmohaa/code/fgame/g_utils.cpp
smallmodel 44d8adcfb0
Set the eyePos when loading from save
Server uses the playerState's vEyePos as the position, to add entities to snapshots, so make sure it's initialized before the next snapshot is sent
2024-11-21 20:26:12 +01:00

2451 lines
58 KiB
C++

/*
===========================================================================
Copyright (C) 2023 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "g_local.h"
#include "g_utils.h"
#include "ctype.h"
#include "worldspawn.h"
#include "scriptmaster.h"
#include "scriptthread.h"
#include "player.h"
#include "playerbot.h"
#include "playerstart.h"
#include "debuglines.h"
#include "smokesprite.h"
#include "../qcommon/tiki.h"
const char *means_of_death_strings[MOD_TOTAL_NUMBER] = {
"none",
"suicide",
"crush",
"crush_every_frame",
"telefrag",
"lava",
"slime",
"falling",
"last_self_inflicted",
"explosion",
"explodewall",
"electric",
"electricwater",
"thrownobject",
"grenade",
"beam",
"rocket",
"impact",
"bullet",
"fast_bullet",
"vehicle",
"fire",
"flashbang",
"on_fire",
"gib",
"impale",
"bash",
"shotgun",
"aagun",
"landmine"
};
int MOD_string_to_int(const str& immune_string)
{
int i;
for (i = 0; i < MOD_TOTAL_NUMBER; i++) {
if (!immune_string.icmp(means_of_death_strings[i])) {
return i;
}
}
gi.DPrintf("Unknown means of death - %s\n", immune_string.c_str());
return -1;
}
qboolean MOD_matches(int incoming_damage, int damage_type)
{
if (damage_type == -1) {
return true;
} else {
return incoming_damage == damage_type;
}
}
/*
============
G_TouchTriggers
============
*/
void G_TouchTriggers(Entity *ent)
{
int i;
int num;
int touch[MAX_GENTITIES];
gentity_t *hit;
Event *ev;
// dead things don't activate triggers!
if ((ent->client || (ent->edict->r.svFlags & SVF_MONSTER)) && (ent->IsDead())) {
return;
}
num = gi.AreaEntities(ent->absmin, ent->absmax, touch, MAX_GENTITIES);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i = 0; i < num; i++) {
hit = &g_entities[touch[i]];
if (!hit->inuse || (hit->entity == ent) || (hit->solid != SOLID_TRIGGER)) {
continue;
}
// Added in 2.0
// check if the ent is inside edict
if (hit->r.bmodel && !gi.HitEntity(ent->edict, hit)) {
continue;
}
assert(hit->entity);
ev = new Event(EV_Touch);
ev->AddEntity(ent);
hit->entity->ProcessEvent(ev);
}
}
/*
============
G_TouchSolids
Call after linking a new trigger in during gameplay
to force all entities it covers to immediately touch it
============
*/
void G_TouchSolids(Entity *ent)
{
int i;
int num;
int touch[MAX_GENTITIES];
gentity_t *hit;
Event *ev;
num = gi.AreaEntities(ent->absmin, ent->absmax, touch, MAX_GENTITIES);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i = 0; i < num; i++) {
hit = &g_entities[touch[i]];
if (!hit->inuse) {
continue;
}
assert(hit->entity);
//FIXME
// should we post the events so that we don't have to worry about any entities going away
ev = new Event(EV_Touch);
ev->AddEntity(ent);
hit->entity->ProcessEvent(ev);
}
}
void G_ShowTrace(trace_t *trace, const gentity_t *passent, const char *reason)
{
str text;
str pass;
str hit;
assert(reason);
assert(trace);
if (passent) {
pass = va("'%s'(%d)", passent->entname, passent->s.number);
} else {
pass = "NULL";
}
if (trace->ent) {
hit = va("'%s'(%d)", trace->ent->entname, trace->ent->s.number);
} else {
hit = "NULL";
}
text =
va("%0.2f : Pass %s Frac %f Hit %s : '%s'\n",
level.time,
pass.c_str(),
trace->fraction,
hit.c_str(),
reason ? reason : "");
if (sv_traceinfo->integer == 3) {
gi.DebugPrintf(text.c_str());
} else {
gi.DPrintf("%s", text.c_str());
}
}
void G_ShowSightTrace(gentity_t *passent1, gentity_t *passent2, const char *reason)
{
str text;
str pass1;
str pass2;
assert(reason);
if (passent1) {
pass1 = va("'%s'(%d)", passent1->entname, passent1->s.number);
} else {
pass1 = "NULL";
}
if (passent2) {
pass2 = va("'%s'(%d)", passent2->entname, passent2->s.number);
} else {
pass2 = "NULL";
}
text = va("%0.2f : Pass1 %s Pass2 %s : '%s'\n", level.time, pass1.c_str(), pass2.c_str(), reason ? reason : "");
if (sv_traceinfo->integer == 3) {
gi.DebugPrintf(text.c_str());
} else {
gi.DPrintf("%s", text.c_str());
}
}
void G_CalcBoundsOfMove(Vector& start, Vector& end, Vector& mins, Vector& maxs, Vector *minbounds, Vector *maxbounds)
{
Vector bmin;
Vector bmax;
ClearBounds(bmin, bmax);
AddPointToBounds(start, bmin, bmax);
AddPointToBounds(end, bmin, bmax);
bmin += mins;
bmax += maxs;
if (minbounds) {
*minbounds = bmin;
}
if (maxbounds) {
*maxbounds = bmax;
}
}
bool G_SightTrace(
const Vector& start,
const Vector& mins,
const Vector& maxs,
const Vector& end,
gentity_t *passent,
gentity_t *passent2,
int contentmask,
qboolean cylinder,
const char *reason
)
{
int entnum, entnum2;
bool result;
assert(reason);
if (passent == NULL) {
entnum = ENTITYNUM_NONE;
} else {
entnum = passent->s.number;
}
if (passent2 == NULL) {
entnum2 = ENTITYNUM_NONE;
} else {
entnum2 = passent2->s.number;
}
result = gi.SightTrace(start, mins, maxs, end, entnum, entnum2, contentmask, cylinder) ? true : false;
if (sv_traceinfo->integer > 1) {
G_ShowSightTrace(passent, passent2, reason);
}
sv_numtraces++;
if (sv_drawtrace->integer) {
G_DebugLine(start, end, 1, 1, 0, 1);
}
return result;
}
bool G_SightTrace(
const Vector& start,
const Vector& mins,
const Vector& maxs,
const Vector& end,
Entity *passent,
Entity *passent2,
int contentmask,
qboolean cylinder,
const char *reason
)
{
gentity_t *ent, *ent2;
int entnum, entnum2;
bool result;
assert(reason);
if (passent == NULL || !passent->isSubclassOf(Entity)) {
ent = NULL;
entnum = ENTITYNUM_NONE;
} else {
ent = passent->edict;
entnum = ent->s.number;
}
if (passent2 == NULL || !passent2->isSubclassOf(Entity)) {
ent2 = NULL;
entnum2 = ENTITYNUM_NONE;
} else {
ent2 = passent2->edict;
entnum2 = ent2->s.number;
}
result = gi.SightTrace(start, mins, maxs, end, entnum, entnum2, contentmask, cylinder);
if (sv_traceinfo->integer > 1) {
G_ShowSightTrace(ent, ent2, reason);
}
sv_numtraces++;
if (sv_drawtrace->integer) {
G_DebugLine(start, end, 1, 1, 0, 1);
}
return result == true;
}
void G_PMDrawTrace(
trace_t *results,
const vec3_t start,
const vec3_t mins,
const vec3_t maxs,
const vec3_t end,
int passEntityNum,
int contentMask,
qboolean cylinder,
qboolean traceDeep
)
{
gi.trace(results, start, mins, maxs, end, passEntityNum, contentMask, cylinder, traceDeep);
sv_numpmtraces++;
G_DebugLine(start, end, 1.f, 0.75f, 0.5f, 1.f);
}
trace_t G_Trace(
vec3_t start,
vec3_t mins,
vec3_t maxs,
vec3_t end,
const gentity_t *passent,
int contentmask,
qboolean cylinder,
const char *reason,
qboolean tracedeep
)
{
int entnum;
trace_t trace;
if (passent) {
entnum = passent->s.number;
} else {
entnum = ENTITYNUM_NONE;
}
gi.trace(&trace, start, mins, maxs, end, entnum, contentmask, cylinder, tracedeep);
if (trace.entityNum == ENTITYNUM_NONE) {
trace.ent = NULL;
} else {
trace.ent = &g_entities[trace.entityNum];
}
if (sv_traceinfo->integer > 1) {
G_ShowTrace(&trace, passent, reason);
}
sv_numtraces++;
if (sv_drawtrace->integer) {
G_DebugLine(Vector(start), Vector(end), 1, 1, 0, 1);
}
return trace;
}
trace_t G_Trace(
const Vector& start,
const Vector& mins,
const Vector& maxs,
const Vector& end,
const Entity *passent,
int contentmask,
qboolean cylinder,
const char *reason,
qboolean tracedeep
)
{
gentity_t *ent;
int entnum;
trace_t trace;
assert(reason);
if (passent == NULL) {
ent = NULL;
entnum = ENTITYNUM_NONE;
} else {
ent = passent->edict;
entnum = ent->s.number;
}
gi.trace(&trace, start, mins, maxs, end, entnum, contentmask, cylinder, tracedeep);
if (trace.entityNum == ENTITYNUM_NONE) {
trace.ent = NULL;
} else {
trace.ent = &g_entities[trace.entityNum];
}
if (sv_traceinfo->integer > 1) {
G_ShowTrace(&trace, ent, reason);
}
sv_numtraces++;
if (sv_drawtrace->integer) {
G_DebugLine(start, end, 1, 1, 0, 1);
}
return trace;
}
void G_TraceEntities(
Vector & start,
Vector & mins,
Vector & maxs,
Vector & end,
Container<Entity *> *victimlist,
int contentmask,
qboolean bIncludeTriggers
)
{
trace_t trace;
vec3_t boxmins;
vec3_t boxmaxs;
int num;
int touchlist[MAX_GENTITIES];
gentity_t *touch;
int i;
// Find the bounding box
for (i = 0; i < 3; i++) {
if (end[i] > start[i]) {
boxmins[i] = start[i] + mins[i] - 1;
boxmaxs[i] = end[i] + maxs[i] + 1;
} else {
boxmins[i] = end[i] + mins[i] - 1;
boxmaxs[i] = start[i] + maxs[i] + 1;
}
}
// Find the list of entites
num = gi.AreaEntities(boxmins, boxmaxs, touchlist, MAX_GENTITIES);
for (i = 0; i < num; i++) {
touch = &g_entities[touchlist[i]];
// see if we should ignore this entity
if (touch->solid == SOLID_NOT) {
continue;
}
if (touch->solid == SOLID_TRIGGER && !bIncludeTriggers) {
continue;
}
gi.ClipToEntity(&trace, start, mins, maxs, end, touchlist[i], contentmask);
if (trace.entityNum == touchlist[i]) {
victimlist->AddObject(touch->entity);
}
}
}
float G_VisualObfuscation(const Vector& start, const Vector& end)
{
float alpha;
if (start == end) {
// no obfuscation
return 0;
}
alpha = gi.CM_VisualObfuscation(start, end);
if (alpha >= 1.f) {
return alpha;
}
return G_ObfuscationForSmokeSprites(alpha, start, end);
}
/*
=======================================================================
SelectSpawnPoint
=======================================================================
*/
/*
================
PlayersRangeFromSpot
Returns the distance to the nearest player from the given spot
================
*/
float PlayersRangeFromSpot(Entity *spot)
{
Entity *player;
float bestplayerdistance;
Vector v;
int n;
float playerdistance;
bestplayerdistance = 9999999;
for (n = 0; n < maxclients->integer; n++) {
if (!g_entities[n].inuse || !g_entities[n].entity) {
continue;
}
player = g_entities[n].entity;
if (player->health <= 0) {
continue;
}
v = spot->origin - player->origin;
playerdistance = v.length();
if (playerdistance < bestplayerdistance) {
bestplayerdistance = playerdistance;
}
}
return bestplayerdistance;
}
/*
================
SelectRandomDeathmatchSpawnPoint
go to a random point, but NOT the two points closest
to other players
================
*/
Entity *SelectRandomDeathmatchSpawnPoint(void)
{
Entity *spot, *spot1, *spot2;
int count = 0;
int selection;
float range, range1, range2;
spot = NULL;
range1 = range2 = 99999;
spot1 = spot2 = NULL;
for (spot = G_FindClass(spot, "info_player_deathmatch"); spot; spot = G_FindClass(spot, "info_player_deathmatch")) {
count++;
range = PlayersRangeFromSpot(spot);
if (range < range1) {
range1 = range;
spot1 = spot;
} else if (range < range2) {
range2 = range;
spot2 = spot;
}
}
if (!count) {
return NULL;
}
if (count <= 2) {
spot1 = spot2 = NULL;
} else {
count -= 2;
}
selection = rand() % count;
spot = NULL;
do {
spot = G_FindClass(spot, "info_player_deathmatch");
// if there are no more, break out
if (!spot) {
break;
}
if (spot == spot1 || spot == spot2) {
selection++;
}
} while (selection--);
return spot;
}
/*
================
SelectFarthestDeathmatchSpawnPoint
================
*/
Entity *SelectFarthestDeathmatchSpawnPoint(void)
{
Entity *bestspot;
float bestdistance;
float bestplayerdistance;
Entity *spot;
spot = NULL;
bestspot = NULL;
bestdistance = 0;
for (spot = G_FindClass(spot, "info_player_deathmatch"); spot; spot = G_FindClass(spot, "info_player_deathmatch")) {
bestplayerdistance = PlayersRangeFromSpot(spot);
if (bestplayerdistance > bestdistance) {
bestspot = spot;
bestdistance = bestplayerdistance;
}
}
if (bestspot) {
return bestspot;
}
// if there is a player just spawned on each and every start spot
// we have no choice to turn one into a telefrag meltdown
spot = G_FindClass(NULL, "info_player_deathmatch");
return spot;
}
Entity *SelectDeathmatchSpawnPoint(void)
{
if (DM_FLAG(DF_SPAWN_FARTHEST)) {
return SelectFarthestDeathmatchSpawnPoint();
} else {
return SelectRandomDeathmatchSpawnPoint();
}
}
/*
=============
M_CheckBottom
Returns false if any part of the bottom of the entity is off an edge that
is not a staircase.
=============
*/
int c_yes, c_no;
qboolean M_CheckBottom(Entity *ent)
{
Vector mins, maxs, start, stop;
trace_t trace;
int x, y;
float mid, bottom;
mins = ent->origin + ent->mins * 0.5;
maxs = ent->origin + ent->maxs * 0.5;
// if all of the points under the corners are solid world, don't bother
// with the tougher checks
// the corners must be within 16 of the midpoint
start[2] = mins[2] - 1;
for (x = 0; x <= 1; x++) {
for (y = 0; y <= 1; y++) {
start[0] = x ? maxs[0] : mins[0];
start[1] = y ? maxs[1] : mins[1];
if (gi.pointcontents(start, 0) != CONTENTS_SOLID) {
goto realcheck;
}
}
}
c_yes++;
return true; // we got out easy
realcheck:
c_no++;
//
// check it for real...
//
start[2] = mins[2];
// the midpoint must be within 16 of the bottom
start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5;
start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5;
stop[2] = start[2] - 3 * STEPSIZE; //2 * STEPSIZE;
trace = G_Trace(start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 1");
if (trace.fraction == 1.0) {
return false;
}
mid = bottom = trace.endpos[2];
// the corners must be within 16 of the midpoint
for (x = 0; x <= 1; x++) {
for (y = 0; y <= 1; y++) {
start[0] = stop[0] = x ? maxs[0] : mins[0];
start[1] = stop[1] = y ? maxs[1] : mins[1];
trace = G_Trace(start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 2");
if (trace.fraction != 1.0 && trace.endpos[2] > bottom) {
bottom = trace.endpos[2];
}
if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) {
return false;
}
}
}
c_yes++;
return true;
}
Entity *G_FindClass(Entity *ent, const char *classname)
{
int entnum;
gentity_t *from;
if (ent) {
entnum = ent->entnum;
} else {
entnum = -1;
}
for (from = &g_entities[entnum + 1]; from < &g_entities[globals.num_entities]; from++) {
if (!from->inuse) {
continue;
}
if (!Q_stricmp(from->entity->getClassID(), classname)) {
return from->entity;
}
}
return NULL;
}
Entity *G_FindTarget(Entity *ent, const char *name)
{
SimpleEntity *next;
if (name && name[0]) {
next = world->GetNextEntity(str(name), ent);
if (next && next->IsSubclassOfEntity()) {
return static_cast<Entity *>(next);
}
}
return NULL;
}
SimpleEntity *G_FindRandomSimpleTarget(const char *name)
{
SimpleEntity *found = NULL, *ent = world;
int nFound = 0;
if (name && *name) {
while (true) {
ent = world->GetNextEntity(name, ent);
if (!ent) {
break;
}
if (++nFound * rand() <= 0x7FFF) {
found = ent;
}
}
}
return found;
}
Entity *G_FindRandomTarget(const char *name)
{
SimpleEntity *found = NULL, *ent = world;
int nFound = 0;
if (name && *name) {
while (true) {
ent = world->GetNextEntity(name, ent);
if (!ent) {
break;
}
if (ent->IsSubclassOfEntity() && ++nFound * rand() <= 0x7FFF) {
found = ent;
}
}
}
return (Entity *)found;
}
Entity *G_NextEntity(Entity *ent)
{
gentity_t *from;
if (!g_entities) {
return NULL;
}
if (!ent) {
from = g_entities;
} else {
from = ent->edict + 1;
}
if (!from) {
return NULL;
}
for (; from < &g_entities[globals.num_entities]; from++) {
if (!from->inuse || !from->entity) {
continue;
}
return from->entity;
}
return NULL;
}
//
// QuakeEd only writes a single float for angles (bad idea), so up and down are
// just constant angles.
//
Vector G_GetMovedir(float angle)
{
if (angle == -1) {
return Vector(0, 0, 1);
} else if (angle == -2) {
return Vector(0, 0, -1);
}
angle *= (M_PI * 2 / 360);
return Vector(cos(angle), sin(angle), 0);
}
/*
===============
G_SetMovedir
The editor only specifies a single value for angles (yaw),
but we have special constants to generate an up or down direction.
Angles will be cleared, because it is being used to represent a direction
instead of an orientation.
===============
*/
void G_SetMovedir(vec3_t angles, vec3_t movedir)
{
static vec3_t VEC_UP = {0, -1, 0};
static vec3_t MOVEDIR_UP = {0, 0, 1};
static vec3_t VEC_DOWN = {0, -2, 0};
static vec3_t MOVEDIR_DOWN = {0, 0, -1};
if (VectorCompare(angles, VEC_UP)) {
VectorCopy(MOVEDIR_UP, movedir);
} else if (VectorCompare(angles, VEC_DOWN)) {
VectorCopy(MOVEDIR_DOWN, movedir);
} else {
AngleVectors(angles, movedir, NULL, NULL);
}
VectorClear(angles);
}
/*
=================
G_GetAngle
Return the yaw angle
=================
*/
float G_GetAngle(Vector movedir)
{
float angle;
if (movedir == Vector(0, 0, 1)) {
return -1;
}
if (movedir == Vector(0, 0, -1)) {
return -2;
}
angle = RAD2DEG(atan(1.0) * 2 + atan(-movedir[0] / sqrt(-movedir[0] * movedir[0] + 1.0)));
if (movedir[1] < 0) {
return 360 - angle;
}
return angle;
}
/*
=================
KillBox
Kills all entities that would touch the proposed new positioning
of ent. Ent should be unlinked before calling this!
=================
*/
qboolean KillBox(Entity *ent)
{
int i;
int num;
int touch[MAX_GENTITIES];
gentity_t *hit;
Vector min;
Vector max;
int fail;
fail = 0;
min = ent->origin + ent->mins;
max = ent->origin + ent->maxs;
num = gi.AreaEntities(min, max, touch, MAX_GENTITIES);
for (i = 0; i < num; i++) {
hit = &g_entities[touch[i]];
if (!hit->inuse || (hit->entity == ent) || !hit->entity || (hit->entity == world)
|| (!hit->entity->edict->solid)) {
continue;
}
hit->entity->Damage(
ent,
ent,
hit->entity->health + 100000,
ent->origin,
vec_zero,
vec_zero,
0,
DAMAGE_NO_PROTECTION,
MOD_TELEFRAG
);
//
// if we didn't kill it, fail
//
if (hit->entity->getSolidType() != SOLID_NOT) {
fail++;
}
}
//
// all clear
//
return !fail;
}
qboolean IsNumeric(const char *str)
{
size_t len;
int i;
qboolean dot;
if (*str == '-') {
str++;
}
dot = false;
len = strlen(str);
for (i = 0; i < len; i++) {
if (!isdigit(str[i])) {
if ((str[i] == '.') && !dot) {
dot = true;
continue;
}
return false;
}
}
return true;
}
/*
=================
findradius
Returns entities that have origins within a spherical area
findradius (org, radius)
=================
*/
Entity *findradius(Entity *startent, Vector org, float rad)
{
Vector eorg;
gentity_t *from;
float r2, distance;
if (!startent) {
from = active_edicts.next;
} else {
from = startent->edict->next;
}
assert(from);
if (!from) {
return NULL;
}
assert((from == &active_edicts) || (from->inuse));
// square the radius so that we don't have to do a square root
r2 = rad * rad;
for (; from != &active_edicts; from = from->next) {
assert(from->inuse);
assert(from->entity);
eorg = org - from->entity->centroid;
// dot product returns length squared
distance = eorg * eorg;
if (distance <= r2) {
return from->entity;
} else {
// subtract the object's own radius from this distance
distance -= from->radius2;
if (distance <= r2) {
return from->entity;
}
}
}
return NULL;
}
/*
=================
findclientinradius
Returns clients that have origins within a spherical area
findclientinradius (org, radius)
=================
*/
Entity *findclientsinradius(Entity *startent, Vector org, float rad)
{
Vector eorg;
gentity_t *ed;
float r2;
int i;
// square the radius so that we don't have to do a square root
r2 = rad * rad;
if (!startent) {
i = 0;
} else {
i = startent->entnum + 1;
}
for (; i < game.maxclients; i++) {
ed = &g_entities[i];
if (!ed->inuse || !ed->entity) {
continue;
}
eorg = org - ed->entity->centroid;
// dot product returns length squared
if ((eorg * eorg) <= r2) {
return ed->entity;
}
}
return NULL;
}
Vector G_CalculateImpulse(Vector start, Vector end, float speed, float gravity)
{
float traveltime, vertical_speed;
Vector dir, xydir, velocity;
dir = end - start;
xydir = dir;
xydir.z = 0;
traveltime = xydir.length() / speed;
vertical_speed = (dir.z / traveltime) + (0.5f * gravity * sv_gravity->value * traveltime);
xydir.normalize();
velocity = speed * xydir;
velocity.z = vertical_speed;
return velocity;
}
Vector G_PredictPosition(Vector start, Vector target, Vector targetvelocity, float speed)
{
Vector projected;
float traveltime;
Vector dir, xydir;
dir = target - start;
xydir = dir;
xydir.z = 0;
traveltime = xydir.length() / speed;
projected = target + (targetvelocity * traveltime);
return projected;
}
/*
==============
G_ArchiveTrace
==============
*/
void G_ArchivePlayerState(Archiver& arc, playerState_t *ps)
{
int i;
// Movement
arc.ArchiveInteger(&ps->commandTime);
arc.ArchiveInteger(&ps->pm_type);
arc.ArchiveInteger(&ps->pm_flags);
arc.ArchiveInteger(&ps->pm_time);
arc.ArchiveVec3(ps->origin);
arc.ArchiveVec3(ps->velocity);
arc.ArchiveInteger(&ps->gravity);
arc.ArchiveInteger(&ps->speed);
arc.ArchiveInteger(&ps->delta_angles[0]);
arc.ArchiveInteger(&ps->delta_angles[1]);
arc.ArchiveInteger(&ps->delta_angles[2]);
// Trace
arc.ArchiveInteger(&ps->groundEntityNum);
arc.ArchiveBoolean(&ps->walking);
arc.ArchiveBoolean(&ps->groundPlane);
arc.ArchiveInteger(&ps->feetfalling);
arc.ArchiveVec3(ps->falldir);
G_ArchiveTrace(arc, &ps->groundTrace);
arc.ArchiveVec3(ps->viewangles);
arc.ArchiveInteger(&ps->viewheight);
// View
arc.ArchiveFloat(&ps->fLeanAngle);
arc.ArchiveInteger(&ps->iViewModelAnim);
arc.ArchiveInteger(&ps->iViewModelAnimChanged);
// Stats
for (i = 0; i < ARRAY_LEN(ps->stats); i++) {
arc.ArchiveInteger(&ps->stats[i]);
}
for (i = 0; i < ARRAY_LEN(ps->activeItems); i++) {
arc.ArchiveInteger(&ps->activeItems[i]);
}
for (i = 0; i < ARRAY_LEN(ps->ammo_name_index); i++) {
arc.ArchiveInteger(&ps->ammo_name_index[i]);
}
for (i = 0; i < ARRAY_LEN(ps->ammo_amount); i++) {
arc.ArchiveInteger(&ps->ammo_amount[i]);
}
for (i = 0; i < ARRAY_LEN(ps->max_ammo_amount); i++) {
arc.ArchiveInteger(&ps->max_ammo_amount[i]);
}
// Music
arc.ArchiveInteger(&ps->current_music_mood);
arc.ArchiveInteger(&ps->fallback_music_mood);
arc.ArchiveFloat(&ps->music_volume);
arc.ArchiveFloat(&ps->music_volume_fade_time);
arc.ArchiveInteger(&ps->reverb_type);
arc.ArchiveFloat(&ps->reverb_level);
// View
arc.ArchiveVec4(ps->blend);
arc.ArchiveFloat(&ps->fov);
// Camera
arc.ArchiveVec3(ps->camera_origin);
arc.ArchiveFloat(&ps->camera_time);
arc.ArchiveVec3(ps->camera_angles);
arc.ArchiveVec3(ps->camera_offset);
arc.ArchiveVec3(ps->camera_posofs);
arc.ArchiveInteger(&ps->camera_flags);
arc.ArchiveVec3(ps->damage_angles);
if (arc.Loading()) {
VectorCopy(ps->origin, ps->vEyePos);
ps->vEyePos[2] += ps->viewheight;
}
}
/*
==============
G_ArchiveTrace
==============
*/
void G_ArchiveTrace(Archiver& arc, trace_t *trace)
{
arc.ArchiveBoolean(&trace->allsolid);
arc.ArchiveBoolean(&trace->startsolid);
arc.ArchiveFloat(&trace->fraction);
arc.ArchiveVec3(trace->endpos);
arc.ArchiveVec3(trace->plane.normal);
arc.ArchiveFloat(&trace->plane.dist);
arc.ArchiveByte(&trace->plane.type);
arc.ArchiveByte(&trace->plane.signbits);
arc.ArchiveInteger(&trace->surfaceFlags);
arc.ArchiveInteger(&trace->shaderNum);
arc.ArchiveInteger(&trace->contents);
arc.ArchiveInteger(&trace->entityNum);
arc.ArchiveInteger(&trace->location);
}
/*
==============
G_ArchiveClient
==============
*/
void G_ArchiveClient(Archiver& arc, gclient_t *client)
{
G_ArchivePlayerState(arc, &client->ps);
arc.ArchiveVec3(client->cmd_angles);
arc.ArchiveInteger(&client->lastActiveTime);
arc.ArchiveInteger(&client->activeWarning);
}
/*
==============
G_ArchiveEdict
==============
*/
void G_ArchiveEdict(Archiver& arc, gentity_t *edict)
{
int i;
str tempStr;
assert(edict);
//
// this is written funny because it is used for both saving and loading
//
if (edict->client) {
// Removed in OPM
// Commented out because clients are already archived
// by Game::Archive()
//G_ArchiveClient(arc, edict->client);
}
arc.ArchiveInteger(&edict->s.beam_entnum);
for (i = 0; i < MAX_FRAMEINFOS; i++) {
arc.ArchiveInteger(&edict->s.frameInfo[i].index);
arc.ArchiveFloat(&edict->s.frameInfo[i].time);
arc.ArchiveFloat(&edict->s.frameInfo[i].weight);
}
arc.ArchiveFloat(&edict->s.actionWeight);
arc.ArchiveFloat(&edict->s.shader_data[0]);
arc.ArchiveFloat(&edict->s.shader_data[1]);
arc.ArchiveFloat(&edict->s.shader_time);
arc.ArchiveVec3(edict->s.eyeVector);
arc.ArchiveInteger(&edict->s.eType);
arc.ArchiveInteger(&edict->s.eFlags);
arc.ArchiveVec3(edict->s.netorigin);
arc.ArchiveVec3(edict->s.origin);
arc.ArchiveVec3(edict->s.origin2);
arc.ArchiveVec3(edict->s.netangles);
arc.ArchiveVec3(edict->s.angles);
arc.ArchiveInteger(&edict->s.constantLight);
if (arc.Saving()) {
if (edict->s.loopSound) {
tempStr = gi.getConfigstring(CS_SOUNDS + edict->s.loopSound);
} else {
tempStr = "";
}
arc.ArchiveString(&tempStr);
} else {
arc.ArchiveString(&tempStr);
if (tempStr.length()) {
qboolean streamed = tempStr[tempStr.length() - 1] != '0';
tempStr[tempStr.length() - 1] = 0;
edict->s.loopSound = gi.soundindex(tempStr.c_str(), streamed);
} else {
edict->s.loopSound = 0;
}
}
arc.ArchiveFloat(&edict->s.loopSoundVolume);
arc.ArchiveFloat(&edict->s.loopSoundMinDist);
arc.ArchiveFloat(&edict->s.loopSoundMaxDist);
arc.ArchiveFloat(&edict->s.loopSoundPitch);
arc.ArchiveInteger(&edict->s.loopSoundFlags);
arc.ArchiveInteger(&edict->s.parent);
arc.ArchiveInteger(&edict->s.tag_num);
arc.ArchiveBoolean(&edict->s.attach_use_angles);
arc.ArchiveVec3(edict->s.attach_offset);
arc.ArchiveInteger(&edict->s.skinNum);
arc.ArchiveInteger(&edict->s.wasframe);
for (i = 0; i < NUM_BONE_CONTROLLERS; i++) {
arc.ArchiveInteger(&edict->s.bone_tag[i]);
arc.ArchiveVec3(edict->s.bone_angles[i]);
arc.ArchiveVec4(edict->s.bone_quat[i]);
}
arc.ArchiveRaw(&edict->s.surfaces, sizeof(edict->s.surfaces));
arc.ArchiveInteger(&edict->s.clientNum);
arc.ArchiveInteger(&edict->s.groundEntityNum);
arc.ArchiveInteger(&edict->s.solid);
arc.ArchiveFloat(&edict->s.scale);
arc.ArchiveFloat(&edict->s.alpha);
arc.ArchiveInteger(&edict->s.renderfx);
arc.ArchiveVec4(edict->s.quat);
arc.ArchiveRaw(&edict->mat, sizeof(edict->mat));
arc.ArchiveInteger(&edict->r.svFlags);
arc.ArchiveVec3(edict->r.mins);
arc.ArchiveVec3(edict->r.maxs);
arc.ArchiveInteger(&edict->r.contents);
arc.ArchiveVec3(edict->r.absmin);
arc.ArchiveVec3(edict->r.absmax);
arc.ArchiveFloat(&edict->r.radius);
if (!arc.Saving()) {
edict->radius2 = edict->r.radius * edict->r.radius;
}
arc.ArchiveVec3(edict->r.centroid);
arc.ArchiveVec3(edict->r.currentAngles);
arc.ArchiveInteger(&edict->r.ownerNum);
ArchiveEnum(edict->solid, solid_t);
arc.ArchiveFloat(&edict->freetime);
arc.ArchiveFloat(&edict->spawntime);
tempStr = str(edict->entname);
arc.ArchiveString(&tempStr);
strncpy(edict->entname, tempStr.c_str(), sizeof(edict->entname) - 1);
arc.ArchiveInteger(&edict->clipmask);
arc.ArchiveBoolean(&edict->r.bmodel);
if (arc.Loading()) {
gi.linkentity(edict);
}
arc.ArchiveInteger(&edict->r.lastNetTime);
}
/*
=========================================================================
model / sound configstring indexes
=========================================================================
*/
/*
=======================
G_FindConfigstringIndex
=======================
*/
int G_FindConfigstringIndex(char *name, int start, int max, qboolean create)
{
int i;
char *s;
if (!name || !name[0]) {
return 0;
}
for (i = 1; i < max; i++) {
s = gi.getConfigstring(start + i);
if (!s || !s[0]) {
break;
}
if (!strcmp(s, name)) {
return i;
}
}
if (!create) {
return 0;
}
if (i == max) {
gi.Error(ERR_DROP, "G_FindConfigstringIndex: overflow");
}
gi.setConfigstring(start + i, name);
return i;
}
int G_ModelIndex(char *name)
{
return G_FindConfigstringIndex(name, CS_MODELS, MAX_MODELS, true);
}
int G_SoundIndex(char *name)
{
return G_FindConfigstringIndex(name, CS_SOUNDS, MAX_SOUNDS, true);
}
/*
===============
G_SetTrajectory
Sets the pos trajectory for a fixed position
===============
*/
void G_SetTrajectory(gentity_t *ent, vec3_t org)
{
ent->s.pos.trTime = 0;
VectorClear(ent->s.pos.trDelta);
VectorCopy(org, ent->s.origin);
}
/*
===============
G_SetConstantLight
Sets the encoded constant light parameter for entities
===============
*/
void G_SetConstantLight(int *constantlight, float *red, float *green, float *blue, float *radius, int *lightStyle)
{
int ir, ig, ib, iradius;
if (!constantlight) {
return;
}
ir = (*constantlight) & 255;
ig = ((*constantlight) >> 8) & 255;
ib = ((*constantlight) >> 16) & 255;
iradius = ((*constantlight) >> 24) & 255;
if (red) {
ir = *red * 255;
if (ir > 255) {
ir = 255;
}
}
if (green) {
ig = *green * 255;
if (ig > 255) {
ig = 255;
}
}
if (blue) {
ib = *blue * 255;
if (ib > 255) {
ib = 255;
}
}
if (radius) {
iradius = *radius / CONSTANTLIGHT_RADIUS_SCALE;
if (iradius > 255) {
iradius = 255;
}
}
if (lightStyle) {
ir = *lightStyle;
if (ir > 255) {
ir = 255;
}
}
*constantlight = (ir) + (ig << 8) + (ib << 16) + (iradius << 24);
}
char *CanonicalTikiName(const char *szInName)
{
static char filename[1024];
if (*szInName && Q_stricmpn("models/", szInName, 7)) {
Com_sprintf(filename, sizeof(filename), "models/%s", szInName);
} else {
Q_strncpyz(filename, szInName, sizeof(filename));
}
gi.FS_CanonicalFilename(filename);
return filename;
}
void G_ProcessCacheInitCommands(dtiki_t *tiki)
{
dtikicmd_t *pcmd;
if (tiki->a->num_server_initcmds) {
int i, j;
Event *event;
for (i = 0; i < tiki->a->num_server_initcmds; i++) {
pcmd = &tiki->a->server_initcmds[i];
event = new Event(pcmd->args[0], pcmd->num_args);
if (Director.GetFlags(event) & EV_CACHE) {
for (j = 1; j < pcmd->num_args; j++) {
event->AddToken(pcmd->args[j]);
}
if (!Director.ProcessEvent(event)) {
Com_Printf(
"^~^~^ Entity::G_ProcessCacheInitCommands: Bad init server command '%s' in '%s'\n",
pcmd->args[0],
tiki->name
);
}
} else {
delete event;
}
}
}
}
void CacheResource(const char *stuff)
{
AliasListNode_t *ret;
qboolean streamed = qfalse;
char filename[MAX_STRING_TOKENS];
assert(stuff);
if (!stuff) {
return;
}
if (gi.fsDebug->integer == 2) {
Com_Printf("server cache: %s\n", stuff);
}
if (!strchr(stuff, '.')) {
// must be a global alias
stuff = gi.GlobalAlias_FindRandom(stuff, &ret);
if (!stuff) {
if (gi.fsDebug->integer == 2) {
Com_Printf("alias not found\n");
}
return;
}
streamed = ret->streamed;
if (gi.fsDebug->integer == 2) {
Com_Printf("=> %s\n", stuff);
}
}
Q_strncpyz(filename, stuff, sizeof(filename));
gi.FS_CanonicalFilename(filename);
if (strstr(filename, ".wav") || strstr(filename, ".mp3")) {
gi.soundindex(filename, streamed);
} else if (strstr(filename, ".tik")) {
dtiki_t *tiki;
Q_strncpyz(filename, CanonicalTikiName(stuff), sizeof(filename));
tiki = gi.TIKI_RegisterModel(filename);
if (tiki) {
G_ProcessCacheInitCommands(tiki);
}
} else if (strstr(filename, ".scr")) {
Director.GetScript(filename);
}
}
void ChangeMusic(const char *current, const char *fallback, qboolean force)
{
int j;
gentity_t *other;
if (current || fallback) {
for (j = 0; j < game.maxclients; j++) {
other = &g_entities[j];
if (other->inuse && other->client) {
Player *client;
client = (Player *)other->entity;
client->ChangeMusic(current, fallback, force);
}
}
if (current && fallback) {
gi.DPrintf("music set to %s with fallback %s\n", current, fallback);
}
}
}
void ChangeMusicVolume(float volume, float fade_time)
{
int j;
gentity_t *other;
for (j = 0; j < game.maxclients; j++) {
other = &g_entities[j];
if (other->inuse && other->client) {
Player *client;
client = (Player *)other->entity;
client->ChangeMusicVolume(volume, fade_time);
}
}
gi.DPrintf("music volume set to %.2f, fade time %.2f\n", volume, fade_time);
}
void RestoreMusicVolume(float fade_time)
{
int j;
gentity_t *other;
for (j = 0; j < game.maxclients; j++) {
other = &g_entities[j];
if (other->inuse && other->client) {
Player *client;
client = (Player *)other->entity;
client->RestoreMusicVolume(fade_time);
}
}
}
void ChangeSoundtrack(const char *soundtrack)
{
level.saved_soundtrack = level.current_soundtrack;
level.current_soundtrack = soundtrack;
// Force the soundtrack to be sent again by setting it to empty first
// so it gets sent to clients again especially when loading
// from a saved game
gi.setConfigstring(CS_MUSIC, "");
gi.setConfigstring(CS_MUSIC, soundtrack);
gi.DPrintf("soundtrack switched to %s.\n", soundtrack);
}
void RestoreSoundtrack(void)
{
if (level.saved_soundtrack.length()) {
level.current_soundtrack = level.saved_soundtrack;
level.saved_soundtrack = "";
gi.setConfigstring(CS_MUSIC, level.current_soundtrack.c_str());
gi.DPrintf("soundtrack restored %s.\n", level.current_soundtrack.c_str());
}
}
const char *G_AIEventStringFromType(int iType)
{
switch (iType) {
case AI_EVENT_WEAPON_FIRE:
return "weapon_fire";
break;
case AI_EVENT_WEAPON_IMPACT:
return "weapon_impact";
break;
case AI_EVENT_EXPLOSION:
return "explosion";
break;
case AI_EVENT_AMERICAN_VOICE:
return "american_voice";
break;
case AI_EVENT_GERMAN_VOICE:
return "german_voice";
break;
case AI_EVENT_AMERICAN_URGENT:
return "american_urgent";
break;
case AI_EVENT_GERMAN_URGENT:
return "german_urgent";
break;
case AI_EVENT_MISC:
return "misc";
break;
case AI_EVENT_MISC_LOUD:
return "misc_loud";
break;
case AI_EVENT_FOOTSTEP:
return "footstep";
break;
case AI_EVENT_GRENADE:
return "grenade";
break;
default:
return "????";
break;
}
}
int G_AIEventTypeFromString(const char *pszType)
{
if (!Q_stricmp(pszType, "weapon_fire")) {
return AI_EVENT_WEAPON_FIRE;
} else if (!Q_stricmp(pszType, "weapon_impact")) {
return AI_EVENT_WEAPON_IMPACT;
} else if (!Q_stricmp(pszType, "explosion")) {
return AI_EVENT_EXPLOSION;
} else if (!Q_stricmp(pszType, "american_voice")) {
return AI_EVENT_AMERICAN_VOICE;
} else if (!Q_stricmp(pszType, "german_voice")) {
return AI_EVENT_GERMAN_VOICE;
} else if (!Q_stricmp(pszType, "american_urgent")) {
return AI_EVENT_AMERICAN_URGENT;
} else if (!Q_stricmp(pszType, "german_urgent")) {
return AI_EVENT_GERMAN_URGENT;
} else if (!Q_stricmp(pszType, "misc")) {
return AI_EVENT_MISC;
} else if (!Q_stricmp(pszType, "misc_loud")) {
return AI_EVENT_MISC_LOUD;
} else if (!Q_stricmp(pszType, "footstep")) {
return AI_EVENT_FOOTSTEP;
} else if (!Q_stricmp(pszType, "grenade")) {
return AI_EVENT_GRENADE;
} else {
return AI_EVENT_NONE;
}
}
float G_AIEventRadius(int iType)
{
static float fRadius[] = {
0,
2048,
384,
4096,
1024,
1024,
1536,
1536,
1500,
2250,
512,
384,
32768,
0,
0,
0
};
if (iType >= AI_EVENT_MAX) {
Com_Printf("G_AIEventRadius: invalid event type\n");
return 1500.0f;
}
return fRadius[iType];
}
void G_BroadcastAIEvent(Entity *originator, Vector origin, char *pszType)
{
G_BroadcastAIEvent(originator, origin, G_AIEventTypeFromString(pszType), -1.0f);
}
void G_BroadcastAIEvent(Entity *originator, Vector origin, int iType, float radius)
{
Sentient *ent;
Actor *act;
Vector delta;
str name;
float r2;
float dist2;
int i;
int iNumSentients;
int iAreaNum;
if (iType == AI_EVENT_MISC || iType == AI_EVENT_MISC_LOUD) {
ent = static_cast<Sentient *>(G_GetEntity(0));
if (ent && ent->m_bIsDisguised) {
return;
}
}
if (radius <= 0.0f) {
radius = G_AIEventRadius(iType);
}
assert(originator);
r2 = Square(radius);
iNumSentients = SentientList.NumObjects();
for (i = 1; i <= iNumSentients; i++) {
ent = SentientList.ObjectAt(i);
if ((ent == originator) || ent->deadflag) {
continue;
}
if (!ent->IsSubclassOfActor()) {
continue;
}
act = static_cast<Actor*>(ent);
if (act->IgnoreSound(iType)) {
continue;
}
delta = origin - ent->centroid;
// dot product returns length squared
dist2 = Square(delta);
if (originator) {
iAreaNum = originator->edict->r.areanum;
} else {
iAreaNum = gi.AreaForPoint(origin);
}
if (dist2 > r2) {
continue;
}
if (iAreaNum != ent->edict->r.areanum && !gi.AreasConnected(iAreaNum, ent->edict->r.areanum)) {
continue;
}
act->ReceiveAIEvent(origin, iType, originator, dist2, r2);
}
botManager.BroadcastEvent(originator, origin, iType, radius);
#if 0
gi.DPrintf("Broadcast event %s to %d entities\n", ev->getName(), count);
#endif
}
void CloneEntity(Entity *dest, Entity *src)
{
int i, num;
#if 0
dest->setModel(src->model);
// don't process our init commands
//dest->CancelEventsOfType( EV_ProcessInitCommands );
dest->setOrigin(src->origin);
dest->setAngles(src->angles);
#endif
dest->setScale(src->edict->s.scale);
dest->setAlpha(src->edict->s.alpha);
dest->health = src->health;
// copy the surfaces
memcpy(dest->edict->s.surfaces, src->edict->s.surfaces, sizeof(src->edict->s.surfaces));
dest->edict->s.constantLight = src->edict->s.constantLight;
//dest->edict->s.eFlags = src->edict->s.eFlags;
dest->edict->s.renderfx = src->edict->s.renderfx;
num = src->numchildren;
for (i = 0; (i < MAX_MODEL_CHILDREN) && num; i++) {
Entity *clone;
Entity *child;
// duplicate the children
if (!src->children[i]) {
continue;
}
child = G_GetEntity(src->children[i]);
if (child) {
clone = new Animate;
CloneEntity(clone, child);
clone->attach(dest->entnum, child->edict->s.tag_num);
}
num--;
}
}
weaponhand_t WeaponHandNameToNum(str side)
{
if (!side.length()) {
gi.DPrintf("WeaponHandNameToNum : Weapon hand not specified\n");
return WEAPON_ERROR;
}
if (!side.icmp("mainhand") || !side.icmp("main")) {
return WEAPON_MAIN;
} else if (!side.icmp("offhand") || !side.icmp("off")) {
return WEAPON_OFFHAND;
} else {
return (weaponhand_t)atoi(side);
}
}
const char *WeaponHandNumToName(weaponhand_t hand)
{
switch (hand) {
case WEAPON_MAIN:
return "mainhand";
case WEAPON_OFFHAND:
return "offhand";
default:
return "Invalid Hand";
}
}
firemode_t WeaponModeNameToNum(str mode)
{
if (!mode.length()) {
gi.DPrintf("WeaponModeNameToNum : Weapon mode not specified\n");
return FIRE_ERROR;
}
if (!mode.icmp("primary")) {
return FIRE_PRIMARY;
}
if (!mode.icmp("secondary")) {
return FIRE_SECONDARY;
} else {
return (firemode_t)atoi(mode);
}
}
int G_WeaponClassNameToNum(str name)
{
int weaponindex = 0;
if (!name.length()) {
gi.DPrintf("WeaponClassNameToNum: Weapon class not specified\n");
return 0;
}
if (!str::icmp(name, "pistol")) {
weaponindex = WEAPON_CLASS_PISTOL;
} else if (!str::icmp(name, "rifle")) {
weaponindex = WEAPON_CLASS_RIFLE;
} else if (!str::icmp(name, "smg")) {
weaponindex = WEAPON_CLASS_SMG;
} else if (!str::icmp(name, "mg")) {
weaponindex = WEAPON_CLASS_MG;
} else if (!str::icmp(name, "grenade")) {
weaponindex = WEAPON_CLASS_GRENADE;
} else if (!str::icmp(name, "heavy")) {
weaponindex = WEAPON_CLASS_HEAVY;
} else if (!str::icmp(name, "cannon")) {
weaponindex = WEAPON_CLASS_CANNON;
} else if (!str::icmp(name, "item")) {
weaponindex = WEAPON_CLASS_ITEM;
} else if (!str::icmp(name, "item1")) {
weaponindex = WEAPON_CLASS_ITEM1;
} else if (!str::icmp(name, "item2")) {
weaponindex = WEAPON_CLASS_ITEM2;
} else if (!str::icmp(name, "item3")) {
weaponindex = WEAPON_CLASS_ITEM3;
} else if (!str::icmp(name, "item4")) {
weaponindex = WEAPON_CLASS_ITEM4;
} else {
gi.DPrintf("WeaponClassNameToNum: Unknown Weapon class %s\n", name.c_str());
return 0;
}
return weaponindex;
}
str G_WeaponClassNumToName(int num)
{
if (num & WEAPON_CLASS_PISTOL) {
return "pistol";
} else if (num & WEAPON_CLASS_RIFLE) {
return "rifle";
} else if (num & WEAPON_CLASS_SMG) {
return "smg";
} else if (num & WEAPON_CLASS_MG) {
return "mg";
} else if (num & WEAPON_CLASS_GRENADE) {
return "grenade";
} else if (num & WEAPON_CLASS_HEAVY) {
return "heavy";
} else if (num & WEAPON_CLASS_CANNON) {
return "cannon";
} else if (num & WEAPON_CLASS_ITEM) {
return "item";
} else if (num & WEAPON_CLASS_ITEM1) {
return "item1";
} else if (num & WEAPON_CLASS_ITEM2) {
return "item2";
} else if (num & WEAPON_CLASS_ITEM3) {
return "item3";
} else if (num & WEAPON_CLASS_ITEM4) {
return "item4";
} else {
assert(0);
return "";
}
}
void G_DebugTargets(Entity *e, str from)
{
gi.DPrintf("DEBUGTARGETS:%s ", from.c_str());
if (e->TargetName() && strlen(e->TargetName())) {
gi.DPrintf("Targetname=\"%s\"\n", e->TargetName().c_str());
} else {
gi.DPrintf("Targetname=\"None\"\n");
}
if (e->Target() && strlen(e->Target())) {
gi.DPrintf("Target=\"%s\"\n", e->Target().c_str());
} else {
gi.DPrintf("Target=\"None\"\n");
}
}
void G_DebugDamage(float damage, Entity *victim, Entity *attacker, Entity *inflictor)
{
gi.DPrintf(
"Victim:%s Attacker:%s Inflictor:%s Damage:%f\n",
victim->getClassname(),
attacker->getClassname(),
inflictor->getClassname(),
damage
);
}
void G_FadeOut(float delaytime)
{
// Fade the screen out
level.m_fade_color = Vector(0, 0, 0);
level.m_fade_alpha = 1.0f;
level.m_fade_time = delaytime;
level.m_fade_time_start = delaytime;
level.m_fade_type = fadeout;
level.m_fade_style = alphablend;
}
void G_AutoFadeIn(void)
{
level.m_fade_time_start = 1;
level.m_fade_time = 1;
level.m_fade_color[0] = 0;
level.m_fade_color[1] = 0;
level.m_fade_color[2] = 0;
level.m_fade_alpha = 1;
level.m_fade_type = fadein;
level.m_fade_style = alphablend;
}
void G_ClearFade(void)
{
level.m_fade_time = -1;
level.m_fade_type = fadein;
}
void G_FadeSound(float delaytime)
{
float time;
// Fade the screen out
time = delaytime * 1000;
gi.SendServerCommand(0, va("fadesound %0.2f", time));
}
void G_RestartLevelWithDelay(float delaytime)
{
int i;
if (g_gametype->integer != GT_SINGLE_PLAYER) {
return;
}
if (level.died_already) {
return;
}
level.died_already = true;
// Restart the level soon
for (i = 0; i < game.maxclients; i++) {
if (g_entities[i].inuse) {
if (g_entities[i].entity) {
g_entities[i].entity->PostEvent(EV_Player_Respawn, delaytime);
}
}
}
G_FadeOut(delaytime);
G_FadeSound(delaytime);
}
void G_MissionFailed(void)
{
G_RestartLevelWithDelay(0);
level.mission_failed = true;
}
void G_StartCinematic(void)
{
level.cinematic = true;
gi.cvar_set("sv_cinematic", "1");
}
void G_StopCinematic(void)
{
// clear out the skip thread
level.cinematic = false;
gi.cvar_set("sv_cinematic", "0");
}
int G_NumClients(void)
{
gentity_t *ent;
int i;
int count = 0;
for (i = 0, ent = g_entities; i < game.maxclients; ent++, i++) {
if (!ent->inuse || !ent->entity) {
continue;
}
count++;
}
return count;
}
void G_PrintToAllClients(const char *pszString, int iType)
{
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
if (iType == 0) {
gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_YELLOW "%s\"", pszString);
} else if (iType == 1) {
gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_WHITE "%s\"", pszString);
} else if (iType == 2) {
gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_CHAT_WHITE "%s\"", pszString);
}
} else {
if (iType == 0) {
gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_YELLOW "%s\n\"", pszString);
} else {
gi.SendServerCommand(-1, "print \"" HUD_MESSAGE_WHITE "%s\n\"", pszString);
}
}
}
void G_CenterPrintToAllClients(const char *pszString)
{
gentity_t *ent;
int i;
for (i = 0, ent = g_entities; i < game.maxclients; ent++, i++) {
if (!ent->inuse || !ent->entity) {
continue;
}
gi.centerprintf(ent, va("%s\n", pszString));
}
}
void SanitizeName(const char *oldName, char *newName, size_t maxLen)
{
size_t i;
size_t j;
size_t len;
const char *p;
len = strlen(oldName);
j = 0;
p = oldName;
for (i = 0; i < len && i < maxLen - 1; i++, p++) {
if (i >= len - 1 && *p <= ' ') {
newName[j++] = '?';
} else {
newName[j++] = *p;
}
}
newName[j] = 0;
}
/*
=================
G_PrintDeathMessageEmulated
Emulate the client-side behavior of printdeathmessage.
So old clients can still have the string displayed.
=================
*/
const char *
G_PrintDeathMessageEmulated(const char *s1, const char *s2, char *attackerName, const char *victimName, char type)
{
const char *result1, *result2;
int hudColor;
result1 = NULL;
result2 = NULL;
if (type == tolower(type)) {
hudColor = 4;
} else {
hudColor = 5;
}
if (*s1 != 'x') {
result1 = gi.LV_ConvertString(s1);
}
if (*s2 != 'x') {
result2 = gi.LV_ConvertString(s2);
}
if (tolower(type) == 's') {
return va("%c%s %s\n", hudColor, victimName, result1);
} else if (tolower(type) == 'p') {
if (*s2 == 'x') {
if (s2[1] && s2[2]) {
return va("%c%s %s %s %s\n", hudColor, victimName, result1, attackerName, s2 + 2);
} else {
return va("%c%s %s %s\n", hudColor, victimName, result1, attackerName);
}
} else {
return va("%c%s %s %s%s\n", hudColor, victimName, result1, attackerName, result2);
}
} else if (tolower(type) == 'w') {
return va("%c%s %s\n", hudColor, victimName, result1);
} else {
return va("%s", s1);
}
}
void G_PrintDeathMessage(
const char *s1, const char *s2, const char *attackerName, const char *victimName, Player *victim, const char *type
)
{
gentity_t *ent;
Player *pPlayer;
int i;
char attackerNameSanitized[MAX_NAME_LENGTH];
char victimNameSanitized[MAX_NAME_LENGTH];
SanitizeName(attackerName, attackerNameSanitized, MAX_NAME_LENGTH);
SanitizeName(victimName, victimNameSanitized, MAX_NAME_LENGTH);
if (g_protocol >= protocol_e::PROTOCOL_MOHTA_MIN) {
for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
if (!ent->inuse || !ent->entity) {
continue;
}
pPlayer = static_cast<Player *>(ent->entity);
if ((pPlayer->GetTeam() == TEAM_ALLIES || pPlayer->GetTeam() == TEAM_AXIS)
&& pPlayer->GetTeam() == victim->GetTeam()
|| pPlayer->GetTeam() != TEAM_ALLIES && pPlayer->GetTeam() != TEAM_AXIS
&& victim->GetTeam() == TEAM_ALLIES) {
gi.SendServerCommand(
ent - g_entities,
"printdeathmsg \"%s\"\"%s\"\"%s\"\"%s\" %c",
s1,
s2,
attackerNameSanitized,
victimNameSanitized,
*type
);
} else {
gi.SendServerCommand(
ent - g_entities,
"printdeathmsg \"%s\"\"%s\"\"%s\"\"%s\" %c",
s1,
s2,
attackerNameSanitized,
victimNameSanitized,
toupper(*type)
);
}
}
} else {
const char *string =
G_PrintDeathMessageEmulated(s1, s2, attackerNameSanitized, victimNameSanitized, toupper(*type));
// Fallback to the old version
G_PrintDeathMessage_Old(string);
}
}
void G_PrintDeathMessage_Old(const char *pszString)
{
gentity_t *ent;
int i;
for (i = 0, ent = g_entities; i < game.maxclients; i++, ent++) {
if (!ent->inuse || !ent->entity) {
continue;
}
gi.SendServerCommand(ent - g_entities, "print \"" HUD_MESSAGE_CHAT_RED "%s\"", pszString);
}
}
char *G_TimeString(float fTime)
{
float fTmp;
static char szTime[32];
fTmp = fTime / 3600.0f;
if (fTmp >= 1.0f) {
Com_sprintf(
szTime,
sizeof(szTime),
"%i:%02i:%02i",
(int)(fTmp),
(int)(fmod(fTime / 60.0f, 60.0f)),
(int)(fmod(fTime, 60.0f))
);
} else {
Com_sprintf(szTime, sizeof(szTime), "%i:%02i", (int)(fTime / 60.0f), (int)(fmod(fTime, 60.0f)));
}
return szTime;
}
void G_WarnPlayer(Player *player, const char *format, ...)
{
char buffer[4100];
va_list va;
va_start(va, format);
Q_vsnprintf(buffer, sizeof(buffer), format, va);
va_end(va);
gi.SendServerCommand(player->client->ps.clientNum, "print \"%s\"\n", buffer);
gi.SendServerCommand(player->client->ps.clientNum, "print \"%c%s\"", HUD_MESSAGE_YELLOW, buffer);
}
int G_GetClientNumber(gentity_t *ent)
{
return ent->client->ps.clientNum;
}
int G_GetClientNumber(Entity *entity)
{
return 0;
}
SimpleArchivedEntity *G_FindArchivedClass(SimpleArchivedEntity *ent, const char *classname)
{
SimpleArchivedEntity *arcent;
int i;
if (ent) {
i = level.m_SimpleArchivedEntities.IndexOfObject((SimpleArchivedEntity *)ent) - 1;
} else {
i = level.m_SimpleArchivedEntities.NumObjects();
}
for (; i > 0; i--) {
arcent = level.m_SimpleArchivedEntities.ObjectAt(i);
if (!Q_stricmp(arcent->getClassID(), classname)) {
return arcent;
}
}
return NULL;
}
Entity *G_GetEntityByClient(int clientNum)
{
gentity_t *ent = globals.gentities;
if (clientNum < 0 || clientNum > globals.max_entities) {
gi.DPrintf("G_GetEntity: %d out of valid range.", clientNum);
return NULL;
}
for (int i = 0; i < globals.num_entities; i++, ent++) {
if (ent->s.clientNum == clientNum) {
break;
}
}
return ent->entity;
}
int G_GetEntityIndex(int number)
{
gentity_t *ent = globals.gentities;
if (number < 0 || number > globals.max_entities) {
return -1;
}
for (int i = 0; i < globals.num_entities; i++, ent++) {
if (ent->s.number == number) {
return i;
}
}
return -1;
}
int G_GetEntityIndex(gentity_t *ent)
{
return G_GetEntityIndex(ent->s.number);
}
gentity_t *G_GetGEntity(int ent_num)
{
gentity_t *ent = &globals.gentities[ent_num];
if (ent_num < 0 || ent_num > globals.max_entities) {
gi.DPrintf("G_GetEntity: %d out of valid range.\n", ent_num);
return NULL;
}
return ent;
}