openmohaa/code/fgame/g_utils.cpp
2024-11-11 19:06:05 +01:00

2446 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);
}
/*
==============
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;
}