2016-03-27 11:49:47 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
2023-08-15 01:27:35 +02:00
|
|
|
Copyright (C) 2023 the OpenMoHAA team
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
This file is part of OpenMoHAA source code.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
OpenMoHAA source code is free software; you can redistribute it
|
2016-03-27 11:49:47 +02:00
|
|
|
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.
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
OpenMoHAA source code is distributed in the hope that it will be
|
2016-03-27 11:49:47 +02:00
|
|
|
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
|
2023-08-15 01:27:35 +02:00
|
|
|
along with OpenMoHAA source code; if not, write to the Free Software
|
2016-03-27 11:49:47 +02:00
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "g_local.h"
|
2023-08-15 01:27:35 +02:00
|
|
|
#include "g_utils.h"
|
|
|
|
#include "ctype.h"
|
|
|
|
#include "world.h"
|
|
|
|
#include "scriptmaster.h"
|
|
|
|
#include "scriptthread.h"
|
2016-03-27 11:49:47 +02:00
|
|
|
#include "player.h"
|
|
|
|
#include "playerbot.h"
|
2023-08-15 01:27:35 +02:00
|
|
|
#include "PlayerStart.h"
|
2023-04-29 21:56:38 +02:00
|
|
|
#include "debuglines.h"
|
2023-08-15 01:27:35 +02:00
|
|
|
#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",
|
|
|
|
"electric_water",
|
|
|
|
"thrown_object",
|
|
|
|
"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;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
for ( i = 0 ; i < MOD_TOTAL_NUMBER ; i++ )
|
|
|
|
{
|
|
|
|
if ( !immune_string.icmp( means_of_death_strings[ i ] ) )
|
|
|
|
return i;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.DPrintf( "Unknown means of death - %s\n", immune_string.c_str() );
|
|
|
|
return -1;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
qboolean MOD_matches
|
|
|
|
(
|
|
|
|
int incoming_damage,
|
|
|
|
int damage_type
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
if (damage_type == -1) {
|
|
|
|
return true;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-08-15 01:27:35 +02:00
|
|
|
else {
|
|
|
|
return incoming_damage == damage_type;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
============
|
|
|
|
G_TouchTriggers
|
|
|
|
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_TouchTriggers
|
|
|
|
(
|
|
|
|
Entity *ent
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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++ )
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
hit = &g_entities[ touch[ i ] ];
|
|
|
|
if( !hit->inuse || ( hit->entity == ent ) || ( hit->solid != SOLID_TRIGGER ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert( hit->entity );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
ev = new Event( EV_Touch );
|
|
|
|
ev->AddEntity( ent );
|
|
|
|
hit->entity->ProcessEvent( ev );
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
2023-08-15 01:27:35 +02:00
|
|
|
============
|
|
|
|
G_TouchSolids
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Call after linking a new trigger in during gameplay
|
|
|
|
to force all entities it covers to immediately touch it
|
|
|
|
============
|
|
|
|
*/
|
|
|
|
void G_TouchSolids
|
|
|
|
(
|
|
|
|
Entity *ent
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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 );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_ShowTrace
|
|
|
|
(
|
|
|
|
trace_t *trace,
|
|
|
|
const gentity_t *passent,
|
|
|
|
const char *reason
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
str text;
|
|
|
|
str pass;
|
|
|
|
str hit;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert( reason );
|
|
|
|
assert( trace );
|
|
|
|
|
|
|
|
if ( passent )
|
|
|
|
{
|
|
|
|
pass = va( "'%s'(%d)", passent->entname, passent->s.number );
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
2023-08-15 01:27:35 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
pass = "NULL";
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace->ent )
|
|
|
|
{
|
|
|
|
hit = va( "'%s'(%d)", trace->ent->entname, trace->ent->s.number );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
hit = "NULL";
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
text = va( "%0.2f : Pass %s Frac %f Hit %s : '%s'\n",
|
|
|
|
level.time, pass.c_str(), trace->fraction, hit.c_str(), reason ? reason : "" );
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( sv_traceinfo->integer == 3 )
|
|
|
|
{
|
|
|
|
gi.DebugPrintf( text.c_str() );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gi.DPrintf( "%s", text.c_str() );
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_ShowSightTrace(gentity_t *passent1, gentity_t *passent2, const char *reason)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
str text;
|
|
|
|
str pass1;
|
|
|
|
str pass2;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert(reason);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent1) {
|
|
|
|
pass1 = va("'%s'(%d)", passent1->entname, passent1->s.number);
|
2023-08-10 21:26:28 +02:00
|
|
|
} else {
|
2023-08-15 01:27:35 +02:00
|
|
|
pass1 = "NULL";
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent2) {
|
|
|
|
pass2 = va("'%s'(%d)", passent2->entname, passent2->s.number);
|
|
|
|
} else {
|
|
|
|
pass2 = "NULL";
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
text = va("%0.2f : Pass1 %s Pass2 %s : '%s'\n", level.time, pass1.c_str(), pass2.c_str(), reason ? reason : "");
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (sv_traceinfo->integer == 3) {
|
|
|
|
gi.DebugPrintf(text.c_str());
|
|
|
|
} else {
|
|
|
|
gi.DPrintf("%s", text.c_str());
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_CalcBoundsOfMove
|
|
|
|
(
|
|
|
|
Vector &start,
|
|
|
|
Vector &end,
|
|
|
|
Vector &mins,
|
|
|
|
Vector &maxs,
|
|
|
|
Vector *minbounds,
|
|
|
|
Vector *maxbounds
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
Vector bmin;
|
|
|
|
Vector bmax;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
ClearBounds( bmin, bmax );
|
|
|
|
AddPointToBounds( start, bmin, bmax );
|
|
|
|
AddPointToBounds( end, bmin, bmax );
|
|
|
|
bmin += mins;
|
|
|
|
bmax += maxs;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( minbounds )
|
|
|
|
{
|
|
|
|
*minbounds = bmin;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( maxbounds )
|
|
|
|
{
|
|
|
|
*maxbounds = bmax;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert(reason);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent == NULL) {
|
|
|
|
entnum = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
entnum = passent->s.number;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent2 == NULL) {
|
|
|
|
entnum2 = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
entnum2 = passent2->s.number;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
result = gi.SightTrace(start, mins, maxs, end, entnum, entnum2, contentmask, cylinder) ? true : false;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (sv_traceinfo->integer > 1) {
|
|
|
|
G_ShowSightTrace(passent, passent2, reason);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
sv_numtraces++;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (sv_drawtrace->integer) {
|
|
|
|
G_DebugLine(start, end, 1, 1, 0, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert(reason);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent == NULL || !passent->isSubclassOf(Entity)) {
|
|
|
|
ent = NULL;
|
|
|
|
entnum = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ent = passent->edict;
|
|
|
|
entnum = ent->s.number;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (passent2 == NULL || !passent2->isSubclassOf(Entity)) {
|
|
|
|
ent2 = NULL;
|
|
|
|
entnum2 = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ent2 = passent2->edict;
|
|
|
|
entnum2 = ent2->s.number;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
result = gi.SightTrace(start, mins, maxs, end, entnum, entnum2, contentmask, cylinder) ? true : false;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (sv_traceinfo->integer > 1) {
|
|
|
|
G_ShowSightTrace(ent, ent2, reason);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
sv_numtraces++;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (sv_drawtrace->integer) {
|
|
|
|
G_DebugLine(start, end, 1, 1, 0, 1);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
)
|
2023-08-10 21:26:28 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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++;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( sv_drawtrace->integer )
|
|
|
|
{
|
|
|
|
G_DebugLine( Vector( start ), Vector( end ), 1, 1, 0, 1 );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return trace;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
gentity_t *ent;
|
|
|
|
int entnum;
|
|
|
|
trace_t trace;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert( reason );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( passent == NULL )
|
|
|
|
{
|
|
|
|
ent = NULL;
|
|
|
|
entnum = ENTITYNUM_NONE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ent = passent->edict;
|
|
|
|
entnum = ent->s.number;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.trace( &trace, start, mins, maxs, end, entnum, contentmask, cylinder, tracedeep );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace.entityNum == ENTITYNUM_NONE )
|
|
|
|
{
|
|
|
|
trace.ent = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
trace.ent = &g_entities[ trace.entityNum ];
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( sv_traceinfo->integer > 1 )
|
|
|
|
{
|
|
|
|
G_ShowTrace( &trace, ent, reason );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
sv_numtraces++;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( sv_drawtrace->integer )
|
|
|
|
{
|
|
|
|
G_DebugLine( start, end, 1, 1, 0, 1 );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return trace;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_TraceEntities
|
|
|
|
(
|
|
|
|
Vector &start,
|
|
|
|
Vector &mins,
|
|
|
|
Vector &maxs,
|
|
|
|
Vector &end,
|
|
|
|
Container<Entity *>*victimlist,
|
|
|
|
int contentmask,
|
|
|
|
qboolean bIncludeTriggers
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// Find the list of entites
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
num = gi.AreaEntities( boxmins, boxmaxs, touchlist, MAX_GENTITIES );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.ClipToEntity( &trace, start, mins, maxs, end, touchlist[i], contentmask );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace.entityNum == touchlist[i] )
|
|
|
|
victimlist->AddObject( touch->entity );
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
=======================================================================
|
|
|
|
|
|
|
|
SelectSpawnPoint
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
=======================================================================
|
|
|
|
*/
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
2023-08-15 01:27:35 +02:00
|
|
|
PlayersRangeFromSpot
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Returns the distance to the nearest player from the given spot
|
2016-03-27 11:49:47 +02:00
|
|
|
================
|
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
float PlayersRangeFromSpot
|
|
|
|
(
|
|
|
|
Entity *spot
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
player = g_entities[ n ].entity;
|
|
|
|
if ( player->health <= 0 )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
v = spot->origin - player->origin;
|
|
|
|
playerdistance = v.length();
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( playerdistance < bestplayerdistance )
|
|
|
|
{
|
|
|
|
bestplayerdistance = playerdistance;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return bestplayerdistance;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
SelectRandomDeathmatchSpawnPoint
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
go to a random point, but NOT the two points closest
|
|
|
|
to other players
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
Entity *SelectRandomDeathmatchSpawnPoint
|
|
|
|
(
|
|
|
|
void
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( !count )
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( count <= 2 )
|
|
|
|
{
|
|
|
|
spot1 = spot2 = NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
count -= 2;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
selection = rand() % count;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
spot = NULL;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
spot = G_FindClass( spot, "info_player_deathmatch" );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// if there are no more, break out
|
|
|
|
if ( !spot )
|
|
|
|
break;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( spot == spot1 || spot == spot2 )
|
|
|
|
{
|
|
|
|
selection++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while( selection-- );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return spot;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
================
|
|
|
|
SelectFarthestDeathmatchSpawnPoint
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
================
|
|
|
|
*/
|
|
|
|
Entity *SelectFarthestDeathmatchSpawnPoint
|
|
|
|
(
|
|
|
|
void
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
2023-08-15 01:27:35 +02:00
|
|
|
M_CheckBottom
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Returns false if any part of the bottom of the entity is off an edge that
|
|
|
|
is not a staircase.
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
=============
|
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
int c_yes, c_no;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
qboolean M_CheckBottom
|
|
|
|
(
|
|
|
|
Entity *ent
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
c_yes++;
|
|
|
|
return true; // we got out easy
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
realcheck:
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
c_no++;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
//
|
|
|
|
// check it for real...
|
|
|
|
//
|
|
|
|
start[ 2 ] = mins[ 2 ];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// 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;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 1" );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace.fraction == 1.0 )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
mid = bottom = trace.endpos[ 2 ];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// 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 ];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 2" );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace.fraction != 1.0 && trace.endpos[ 2 ] > bottom )
|
|
|
|
{
|
|
|
|
bottom = trace.endpos[ 2 ];
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( trace.fraction == 1.0 || mid - trace.endpos[ 2 ] > STEPSIZE )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
c_yes++;
|
|
|
|
return true;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Entity * G_FindClass
|
|
|
|
(
|
|
|
|
Entity * ent,
|
|
|
|
const char *classname
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Entity * G_FindTarget
|
|
|
|
(
|
|
|
|
Entity * ent,
|
|
|
|
const char *name
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
SimpleEntity *next;
|
|
|
|
|
|
|
|
if ( name && name[ 0 ] )
|
|
|
|
{
|
|
|
|
next = world->GetNextEntity( str( name ), ent );
|
|
|
|
if ( next && next->IsSubclassOfEntity() )
|
|
|
|
{
|
|
|
|
return static_cast<Entity*>(next);
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Entity *G_NextEntity
|
|
|
|
(
|
|
|
|
Entity *ent
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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 );
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
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.
|
|
|
|
===============
|
|
|
|
*/
|
2023-08-10 21:26:28 +02:00
|
|
|
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);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
2023-08-15 01:27:35 +02:00
|
|
|
KillBox
|
|
|
|
|
|
|
|
Kills all entities that would touch the proposed new positioning
|
|
|
|
of ent. Ent should be unlinked before calling this!
|
2016-03-27 11:49:47 +02:00
|
|
|
=================
|
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
qboolean KillBox
|
|
|
|
(
|
|
|
|
Entity *ent
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int num;
|
|
|
|
int touch[ MAX_GENTITIES ];
|
|
|
|
gentity_t *hit;
|
|
|
|
Vector min;
|
|
|
|
Vector max;
|
|
|
|
int fail;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
fail = 0;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
min = ent->origin + ent->mins;
|
|
|
|
max = ent->origin + ent->maxs;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
num = gi.AreaEntities( min, max, touch, MAX_GENTITIES );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
for( i = 0; i < num; i++ )
|
|
|
|
{
|
|
|
|
hit = &g_entities[ touch[ i ] ];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( !hit->inuse || ( hit->entity == ent ) || !hit->entity || ( hit->entity == world ) || ( !hit->entity->edict->solid ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
hit->entity->Damage( ent, ent, hit->entity->health + 100000, ent->origin, vec_zero, vec_zero,
|
|
|
|
0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG );
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
//
|
|
|
|
// if we didn't kill it, fail
|
|
|
|
//
|
|
|
|
if ( hit->entity->getSolidType() != SOLID_NOT )
|
|
|
|
{
|
|
|
|
fail++;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
//
|
|
|
|
// all clear
|
|
|
|
//
|
|
|
|
return !fail;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
qboolean IsNumeric
|
|
|
|
(
|
|
|
|
const char *str
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
int len;
|
|
|
|
int i;
|
|
|
|
qboolean dot;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ( *str == '-' )
|
|
|
|
{
|
|
|
|
str++;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
dot = false;
|
|
|
|
len = strlen( str );
|
|
|
|
for( i = 0; i < len; i++ )
|
|
|
|
{
|
|
|
|
if ( !isdigit( str[ i ] ) )
|
|
|
|
{
|
|
|
|
if ( ( str[ i ] == '.' ) && !dot )
|
|
|
|
{
|
|
|
|
dot = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return true;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
2023-08-15 01:27:35 +02:00
|
|
|
findradius
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Returns entities that have origins within a spherical area
|
|
|
|
|
|
|
|
findradius (org, radius)
|
2016-03-27 11:49:47 +02:00
|
|
|
=================
|
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
Entity *findradius
|
|
|
|
(
|
|
|
|
Entity *startent,
|
|
|
|
Vector org,
|
|
|
|
float rad
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
findclientinradius
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Returns clients that have origins within a spherical area
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
findclientinradius (org, radius)
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
Entity *findclientsinradius
|
|
|
|
(
|
|
|
|
Entity *startent,
|
|
|
|
Vector org,
|
|
|
|
float rad
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
eorg = org - ed->entity->centroid;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// dot product returns length squared
|
|
|
|
if ( ( eorg * eorg ) <= r2 )
|
|
|
|
{
|
|
|
|
return ed->entity;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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_ArchiveEdict
|
|
|
|
==============
|
2016-03-27 11:49:47 +02:00
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_ArchiveEdict
|
|
|
|
(
|
|
|
|
Archiver &arc,
|
|
|
|
gentity_t *edict
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
str tempStr;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert(edict);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
//
|
|
|
|
// this is written funny because it is used for both saving and loading
|
|
|
|
//
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (edict->client) {
|
|
|
|
arc.ArchiveRaw(edict->client, sizeof(*edict->client));
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.beam_entnum);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveFloat(&edict->s.actionWeight);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveFloat(&edict->s.shader_data[0]);
|
|
|
|
arc.ArchiveFloat(&edict->s.shader_data[1]);
|
|
|
|
arc.ArchiveFloat(&edict->s.shader_time);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveVec3(edict->s.eyeVector);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.eType);
|
|
|
|
arc.ArchiveInteger(&edict->s.eFlags);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.constantLight);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (arc.Saving()) {
|
|
|
|
if (edict->s.loopSound) {
|
|
|
|
tempStr = gi.getConfigstring(CS_SOUNDS + edict->s.loopSound);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
tempStr = "";
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveString(&tempStr);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
arc.ArchiveString(&tempStr);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (tempStr.length()) {
|
|
|
|
edict->s.loopSound = gi.soundindex(tempStr.c_str(), true);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
edict->s.loopSound = 0;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.parent);
|
|
|
|
arc.ArchiveInteger(&edict->s.tag_num);
|
|
|
|
arc.ArchiveBoolean(&edict->s.attach_use_angles);
|
|
|
|
arc.ArchiveVec3(edict->s.attach_offset);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.skinNum);
|
|
|
|
arc.ArchiveInteger(&edict->s.wasframe);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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]);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveRaw(&edict->s.surfaces, sizeof(edict->s.surfaces));
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->s.clientNum);
|
|
|
|
arc.ArchiveInteger(&edict->s.groundEntityNum);
|
|
|
|
arc.ArchiveInteger(&edict->s.solid);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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));
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->r.svFlags);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveVec3(edict->r.currentOrigin);
|
|
|
|
arc.ArchiveVec3(edict->r.currentAngles);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->r.ownerNum);
|
|
|
|
ArchiveEnum(edict->solid, solid_t);
|
|
|
|
arc.ArchiveFloat(&edict->freetime);
|
|
|
|
arc.ArchiveFloat(&edict->spawntime);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
tempStr = str(edict->entname);
|
|
|
|
arc.ArchiveString(&tempStr);
|
|
|
|
strncpy(edict->entname, tempStr.c_str(), sizeof(edict->entname) - 1);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->clipmask);
|
|
|
|
arc.ArchiveBoolean(&edict->r.bmodel);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (arc.Loading()) {
|
|
|
|
gi.linkentity(edict);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
arc.ArchiveInteger(&edict->r.lastNetTime);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
/*
|
2023-08-15 01:27:35 +02:00
|
|
|
=========================================================================
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
model / sound configstring indexes
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
=========================================================================
|
2016-03-27 11:49:47 +02:00
|
|
|
*/
|
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
/*
|
2023-08-15 01:27:35 +02:00
|
|
|
=======================
|
|
|
|
G_FindConfigstringIndex
|
|
|
|
=======================
|
2023-08-10 21:26:28 +02:00
|
|
|
*/
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
G_SetTrajectory
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
Sets the pos trajectory for a fixed position
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void G_SetTrajectory
|
|
|
|
(
|
|
|
|
gentity_t *ent,
|
|
|
|
vec3_t org
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
ent->s.pos.trTime = 0;
|
|
|
|
VectorClear(ent->s.pos.trDelta);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
VectorCopy(org, ent->s.origin);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
===============
|
|
|
|
G_SetConstantLight
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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)) {
|
|
|
|
sprintf(filename, "models/%s", szInName);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
strcpy(filename, szInName);
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.FS_CanonicalFilename(filename);
|
|
|
|
return filename;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_ProcessCacheInitCommands(dtiki_t* tiki)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
dtikicmd_t* pcmd;
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (tiki->a->num_server_initcmds) {
|
|
|
|
int i, j;
|
|
|
|
Event* event;
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
for (i = 0; i < tiki->a->num_server_initcmds; i++) {
|
|
|
|
pcmd = &tiki->a->server_initcmds[i];
|
|
|
|
event = new Event(pcmd->args[0]);
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (Director.GetFlags(event) & EV_CACHE) {
|
|
|
|
for (j = 1; j < pcmd->num_args; j++) {
|
|
|
|
event->AddToken(pcmd->args[j]);
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void CacheResource
|
|
|
|
(
|
|
|
|
const char * stuff
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
AliasListNode_t* ret;
|
|
|
|
qboolean streamed = qfalse;
|
|
|
|
char filename[MAX_STRING_TOKENS];
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (!stuff) {
|
|
|
|
return;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (!strchr(stuff, '.')) {
|
|
|
|
// must be a global alias
|
|
|
|
stuff = gi.GlobalAlias_FindRandom(stuff, &ret);
|
|
|
|
if (!stuff) {
|
|
|
|
if (gi.fsDebug->integer) {
|
|
|
|
Com_Printf("alias not found\n");
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
streamed = ret->streamed;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
strcpy(filename, stuff);
|
|
|
|
gi.FS_CanonicalFilename(filename);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (strstr(filename, ".wav")) {
|
|
|
|
gi.soundindex(filename, streamed);
|
|
|
|
}
|
|
|
|
else if (strstr(filename, ".mp3")) {
|
|
|
|
gi.soundindex(filename, streamed);
|
|
|
|
}
|
|
|
|
else if (strstr(filename, ".tik")) {
|
|
|
|
dtiki_t* tiki;
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (*stuff && strncmp("models/", stuff, 7)) {
|
|
|
|
sprintf(filename, "models/%s", stuff);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
strcpy(filename, stuff);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.FS_CanonicalFilename(filename);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
tiki = gi.TIKI_RegisterModel(filename);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (tiki) {
|
|
|
|
G_ProcessCacheInitCommands(tiki);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strstr(filename, ".scr")) {
|
|
|
|
Director.GetScript(filename);
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2023-08-10 21:26:28 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
|
|
|
|
|
|
|
gi.setConfigstring( CS_SOUNDTRACK, 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_SOUNDTRACK, level.current_soundtrack.c_str() );
|
|
|
|
gi.DPrintf( "soundtrack restored %s.\n", level.current_soundtrack.c_str() );
|
|
|
|
}
|
|
|
|
}
|
2018-08-19 08:26:59 +02:00
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
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;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
float G_AIEventRadius(int iType)
|
|
|
|
{
|
|
|
|
static float fRadius[] = {
|
|
|
|
2048.0f, 384.0f, 4096.0f, 1024.0f, 1024.0f, 1536.0f, 1536.0f, 1500.0f, 2250.0f, 512.0f, 384.0f, 0, 0, 0, 0};
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
if (iType <= AI_EVENT_GRENADE) {
|
|
|
|
return fRadius[iType];
|
|
|
|
} else {
|
|
|
|
Com_Printf("G_AIEventRadius: invalid event type\n");
|
2023-08-15 01:27:35 +02:00
|
|
|
return 1500.0f;
|
2023-08-01 19:31:08 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_BroadcastAIEvent(Entity *originator, Vector origin, char *pszType)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
G_BroadcastAIEvent(originator, origin, G_AIEventTypeFromString(pszType), -1.0f);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_BroadcastAIEvent(Entity *originator, Vector origin, int iType, float radius)
|
|
|
|
{
|
|
|
|
Sentient *ent;
|
|
|
|
Vector delta;
|
|
|
|
str name;
|
|
|
|
float r2;
|
|
|
|
float dist2;
|
|
|
|
int i;
|
|
|
|
int iNumSentients;
|
|
|
|
int iAreaNum;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (iType < AI_EVENT_FOOTSTEP) {
|
|
|
|
ent = (Sentient *)G_GetEntity(0);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (ent && ent->m_bIsDisguised) {
|
|
|
|
return;
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (radius <= 0.0f) {
|
|
|
|
radius = G_AIEventRadius(iType);
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
assert(originator);
|
|
|
|
if (originator && !(originator->flags & FL_NOTARGET)) {
|
|
|
|
r2 = Square(radius);
|
|
|
|
iNumSentients = SentientList.NumObjects();
|
|
|
|
for (i = 1; i <= iNumSentients; i++) {
|
|
|
|
ent = SentientList.ObjectAt(i);
|
|
|
|
if ((ent == originator) || ent->deadflag) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
delta = origin - ent->centroid;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
// dot product returns length squared
|
|
|
|
dist2 = Square(delta);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (originator) {
|
|
|
|
iAreaNum = originator->edict->r.areanum;
|
|
|
|
} else {
|
|
|
|
iAreaNum = gi.AreaForPoint(origin);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if ((dist2 <= r2)
|
|
|
|
&& ((iAreaNum == ent->edict->r.areanum) || (gi.AreasConnected(iAreaNum, ent->edict->r.areanum))))
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
{
|
|
|
|
if (ent->IsSubclassOfActor()) {
|
|
|
|
Actor *act = (Actor *)ent;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (!act->IgnoreSound(iType)) {
|
|
|
|
act->ReceiveAIEvent(origin, iType, originator, dist2, r2);
|
|
|
|
}
|
|
|
|
} else if (ent->IsSubclassOfBot()) {
|
|
|
|
PlayerBot *bot = (PlayerBot *)ent;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
bot->NoticeEvent(origin, iType, originator, dist2, r2);
|
|
|
|
}
|
|
|
|
}
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
#if 0
|
|
|
|
gi.DPrintf( "Broadcast event %s to %d entities\n", ev->getName(), count );
|
|
|
|
#endif
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2023-08-15 01:27:35 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void CloneEntity
|
|
|
|
(
|
|
|
|
Entity * dest,
|
|
|
|
Entity * src
|
|
|
|
)
|
|
|
|
{
|
|
|
|
int i, num;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
#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 );
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
int G_WeaponClassNameToNum(str name)
|
|
|
|
{
|
|
|
|
int weaponindex = 0;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
if (!name.length()) {
|
|
|
|
gi.DPrintf("WeaponClassNameToNum: Weapon class not specified\n");
|
|
|
|
return 0;
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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;
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
return weaponindex;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-10 21:26:28 +02:00
|
|
|
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 "";
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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) );
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// restarts the game after delaytime
|
|
|
|
//
|
|
|
|
void G_PlayerDied
|
|
|
|
(
|
|
|
|
float delaytime
|
|
|
|
)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
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
|
|
|
|
)
|
|
|
|
|
|
|
|
{
|
|
|
|
// Make the music system play the failure music for this level
|
|
|
|
ChangeMusic( "failure", "normal", true );
|
|
|
|
|
|
|
|
G_PlayerDied( 3 );
|
|
|
|
|
|
|
|
// tell the player they f'd up
|
|
|
|
gi.centerprintf( &g_entities[ 0 ], "@textures/menu/mission.tga" );
|
|
|
|
|
|
|
|
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" );
|
|
|
|
}
|
|
|
|
|
|
|
|
void G_PrintToAllClients(const char* pszString, int iType)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_CenterPrintToAllClients(const char* pszString)
|
|
|
|
{
|
|
|
|
gentity_t* ent;
|
|
|
|
int i;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
for (i = 0, ent = g_entities; i < game.maxclients; ent++, i++) {
|
|
|
|
if (!ent->inuse || !ent->entity) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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++) {
|
|
|
|
if (i >= len - 1 && *p <= ' ') {
|
|
|
|
newName[j++] = '?';
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
newName[j++] = *p;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newName[j] = 0;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
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)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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') {
|
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_PrintDeathMessage(
|
|
|
|
const char* s1, const char* s2, const char* attackerName, const char* victimName, Player* victim, const char* type
|
|
|
|
)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_PrintDeathMessage_Old(const char* pszString)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
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);
|
|
|
|
}
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
char *G_TimeString(float fTime)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
float fTmp;
|
|
|
|
static char szTime[32];
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
fTmp = fTime / 3600.0f;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
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)));
|
2023-08-10 21:26:28 +02:00
|
|
|
}
|
2023-08-15 01:27:35 +02:00
|
|
|
|
|
|
|
return szTime;
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
void G_WarnPlayer(Player* player, const char* format, ...)
|
2016-03-27 11:49:47 +02:00
|
|
|
{
|
2023-08-15 01:27:35 +02:00
|
|
|
char buffer[4100];
|
|
|
|
va_list va;
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
va_start(va, format);
|
|
|
|
vsprintf(buffer, format, va);
|
|
|
|
va_end(va);
|
2016-03-27 11:49:47 +02:00
|
|
|
|
2023-08-15 01:27:35 +02:00
|
|
|
gi.SendServerCommand(player->client->ps.clientNum, "print \"%s\"\n", buffer);
|
|
|
|
gi.SendServerCommand(player->client->ps.clientNum, "print \"%c%s\"", HUD_MESSAGE_YELLOW, buffer);
|
2016-03-27 11:49:47 +02:00
|
|
|
}
|