From 16dee61c78f408d7d9b13e59a64484e5b3b07431 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Fri, 3 Nov 2023 01:00:51 +0200 Subject: [PATCH] game: sync ctf g_{spawn,phys} --- Makefile | 5 +- src/ctf/g_phys.c | 1531 -------------------------------- src/ctf/g_spawn.c | 1776 -------------------------------------- src/ctf/player/client.c | 571 +++++++++++- src/game/g_phys.c | 17 + src/game/g_spawn.c | 21 +- src/game/header/local.h | 32 +- src/game/player/client.c | 4 + 8 files changed, 613 insertions(+), 3344 deletions(-) delete mode 100644 src/ctf/g_phys.c delete mode 100644 src/ctf/g_spawn.c diff --git a/Makefile b/Makefile index ea05edb3..c6683d48 100644 --- a/Makefile +++ b/Makefile @@ -1496,13 +1496,14 @@ CTF_OBJS_ = \ src/ctf/g_main.o \ src/game/g_misc.o \ src/game/g_monster.o \ - src/ctf/g_phys.o \ + src/game/g_phys.o \ src/ctf/g_save.o \ src/game/g_sphere.o \ - src/ctf/g_spawn.o \ + src/game/g_spawn.o \ src/game/g_svcmds.o \ src/game/g_target.o \ src/game/g_trigger.o \ + src/game/g_turret.o \ src/game/g_utils.o \ src/game/g_weapon.o \ src/game/menu/menu.o \ diff --git a/src/ctf/g_phys.c b/src/ctf/g_phys.c deleted file mode 100644 index 867ee0d5..00000000 --- a/src/ctf/g_phys.c +++ /dev/null @@ -1,1531 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Quake IIs legendary physic engine. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define STOP_EPSILON 0.1 -#define MAX_CLIP_PLANES 5 -#define FRICTION 6 -#define WATERFRICTION 1 - -void SV_Physics_NewToss(edict_t *ent); - -typedef struct -{ - edict_t *ent; - vec3_t origin; - vec3_t angles; - float deltayaw; -} pushed_t; - -static pushed_t pushed[MAX_EDICTS], *pushed_p; -static edict_t *obstacle; - -/* - * pushmove objects do not obey gravity, and do not interact with each other or - * trigger fields, but block normal movement and push normal objects when they move. - * - * onground is set for toss objects when they come to a complete rest. it is set for - * steping or walking objects - * - * - doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH - * - bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS - * - corpses are SOLID_NOT and MOVETYPE_TOSS - * - crates are SOLID_BBOX and MOVETYPE_TOSS - * - walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP - * - flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY - * - solid_edge items only clip against bsp models. - * - */ - -edict_t * -SV_TestEntityPosition(edict_t *ent) -{ - trace_t trace; - int mask; - - if (!ent) - { - return NULL; - } - - /* dead bodies are supposed to not be solid so lets - ensure they only collide with BSP during pushmoves - */ - if (ent->clipmask && !(ent->svflags & SVF_DEADMONSTER)) - { - mask = ent->clipmask; - } - else - { - mask = MASK_SOLID; - } - - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, - ent->s.origin, ent, mask); - - if (trace.startsolid) - { - return g_edicts; - } - - return NULL; -} - -void -SV_CheckVelocity(edict_t *ent) -{ - int i; - - /* bound velocity */ - for (i = 0; i < 3; i++) - { - if (ent->velocity[i] > sv_maxvelocity->value) - { - ent->velocity[i] = sv_maxvelocity->value; - } - else if (ent->velocity[i] < -sv_maxvelocity->value) - { - ent->velocity[i] = -sv_maxvelocity->value; - } - } -} - -/* - * Runs thinking code for - * this frame if necessary - */ -qboolean -SV_RunThink(edict_t *ent) -{ - float thinktime; - - if (!ent) - { - return false; - } - - thinktime = ent->nextthink; - - if (thinktime <= 0) - { - return true; - } - - if (thinktime > level.time + 0.001) - { - return true; - } - - ent->nextthink = 0; - - if (!ent->think) - { - gi.error("NULL ent->think"); - } - - ent->think(ent); - - return false; -} - -/* - * Two entities have touched, so - * run their touch functions - */ -void -SV_Impact(edict_t *e1, trace_t *trace) -{ - edict_t *e2; - - if (!e1 || !trace) - { - return; - } - - e2 = trace->ent; - - if (e1->touch && (e1->solid != SOLID_NOT)) - { - e1->touch(e1, e2, &trace->plane, trace->surface); - } - - if (e2->touch && (e2->solid != SOLID_NOT)) - { - e2->touch(e2, e1, NULL, NULL); - } -} - -/* - * Slide off of the impacting object - * returns the blocked flags: - * 1 = floor - * 2 = step / wall - */ -int -ClipVelocity(vec3_t in, vec3_t normal, vec3_t out, float overbounce) -{ - float backoff; - float change; - int i, blocked; - - blocked = 0; - - if (normal[2] > 0) - { - blocked |= 1; /* floor */ - } - - if (!normal[2]) - { - blocked |= 2; /* step */ - } - - backoff = DotProduct(in, normal) * overbounce; - - for (i = 0; i < 3; i++) - { - change = normal[i] * backoff; - out[i] = in[i] - change; - - if ((out[i] > -STOP_EPSILON) && (out[i] < STOP_EPSILON)) - { - out[i] = 0; - } - } - - return blocked; -} - -/* - * The basic solid body movement clip - * that slides along multiple planes - * Returns the clipflags if the velocity - * was modified (hit something solid) - * - * 1 = floor - * 2 = wall / step - * 4 = dead stop - */ -int -SV_FlyMove(edict_t *ent, float time, int mask) -{ - edict_t *hit; - int bumpcount, numbumps; - vec3_t dir; - float d; - int numplanes; - vec3_t planes[MAX_CLIP_PLANES]; - vec3_t primal_velocity, original_velocity, new_velocity; - int i, j; - trace_t trace; - vec3_t end; - float time_left; - int blocked; - - if (!ent) - { - return 0; - } - - numbumps = 4; - - blocked = 0; - VectorCopy(ent->velocity, original_velocity); - VectorCopy(ent->velocity, primal_velocity); - numplanes = 0; - - time_left = time; - - ent->groundentity = NULL; - - for (bumpcount = 0; bumpcount < numbumps; bumpcount++) - { - for (i = 0; i < 3; i++) - { - end[i] = ent->s.origin[i] + time_left * ent->velocity[i]; - } - - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, mask); - - if (trace.allsolid) - { - /* entity is trapped in another solid */ - VectorCopy(vec3_origin, ent->velocity); - return 3; - } - - if (trace.fraction > 0) - { - /* actually covered some distance */ - VectorCopy(trace.endpos, ent->s.origin); - VectorCopy(ent->velocity, original_velocity); - numplanes = 0; - } - - if (trace.fraction == 1) - { - break; /* moved the entire distance */ - } - - hit = trace.ent; - - if (trace.plane.normal[2] > 0.7) - { - blocked |= 1; /* floor */ - - if (hit->solid == SOLID_BSP) - { - ent->groundentity = hit; - ent->groundentity_linkcount = hit->linkcount; - } - } - - if (!trace.plane.normal[2]) - { - blocked |= 2; /* step */ - } - - /* run the impact function */ - SV_Impact(ent, &trace); - - if (!ent->inuse) - { - break; /* removed by the impact function */ - } - - time_left -= time_left * trace.fraction; - - /* cliped to another plane */ - if (numplanes >= MAX_CLIP_PLANES) - { - /* this shouldn't really happen */ - VectorCopy(vec3_origin, ent->velocity); - return 3; - } - - VectorCopy(trace.plane.normal, planes[numplanes]); - numplanes++; - - /* modify original_velocity so it - parallels all of the clip planes */ - for (i = 0; i < numplanes; i++) - { - ClipVelocity(original_velocity, planes[i], new_velocity, 1); - - for (j = 0; j < numplanes; j++) - { - if ((j != i) && !VectorCompare(planes[i], planes[j])) - { - if (DotProduct(new_velocity, planes[j]) < 0) - { - break; /* not ok */ - } - } - } - - if (j == numplanes) - { - break; - } - } - - if (i != numplanes) - { - /* go along this plane */ - VectorCopy(new_velocity, ent->velocity); - } - else - { - /* go along the crease */ - if (numplanes != 2) - { - VectorCopy(vec3_origin, ent->velocity); - return 7; - } - - CrossProduct(planes[0], planes[1], dir); - d = DotProduct(dir, ent->velocity); - VectorScale(dir, d, ent->velocity); - } - - /* if original velocity is against the original - velocity, stop dead to avoid tiny occilations - in sloping corners */ - if (DotProduct(ent->velocity, primal_velocity) <= 0) - { - VectorCopy(vec3_origin, ent->velocity); - return blocked; - } - } - - return blocked; -} - -void -SV_AddGravity(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (ent->gravityVector[2] > 0) - { - VectorMA(ent->velocity, ent->gravity * sv_gravity->value * FRAMETIME, - ent->gravityVector, ent->velocity); - } - else - { - ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; - } -} - -/* - * Returns the actual bounding box of a bmodel. - * This is a big improvement over what q2 normally - * does with rotating bmodels - q2 sets absmin, - * absmax to a cube that will completely contain - * the bmodel at *any* rotation on *any* axis, whether - * the bmodel can actually rotate to that angle or not. - * This leads to a lot of false block tests in SV_Push - * if another bmodel is in the vicinity. - */ -void -RealBoundingBox(edict_t *ent, vec3_t mins, vec3_t maxs) -{ - vec3_t forward, left, up, f1, l1, u1; - vec3_t p[8]; - int i, j, k, j2, k4; - - if (!ent) - { - return; - } - - for (k = 0; k < 2; k++) - { - k4 = k * 4; - - if (k) - { - p[k4][2] = ent->maxs[2]; - } - else - { - p[k4][2] = ent->mins[2]; - } - - p[k4 + 1][2] = p[k4][2]; - p[k4 + 2][2] = p[k4][2]; - p[k4 + 3][2] = p[k4][2]; - - for (j = 0; j < 2; j++) - { - j2 = j * 2; - - if (j) - { - p[j2 + k4][1] = ent->maxs[1]; - } - else - { - p[j2 + k4][1] = ent->mins[1]; - } - - p[j2 + k4 + 1][1] = p[j2 + k4][1]; - - for (i = 0; i < 2; i++) - { - if (i) - { - p[i + j2 + k4][0] = ent->maxs[0]; - } - else - { - p[i + j2 + k4][0] = ent->mins[0]; - } - } - } - } - - AngleVectors(ent->s.angles, forward, left, up); - - for (i = 0; i < 8; i++) - { - VectorScale(forward, p[i][0], f1); - VectorScale(left, -p[i][1], l1); - VectorScale(up, p[i][2], u1); - VectorAdd(ent->s.origin, f1, p[i]); - VectorAdd(p[i], l1, p[i]); - VectorAdd(p[i], u1, p[i]); - } - - VectorCopy(p[0], mins); - VectorCopy(p[0], maxs); - - for (i = 1; i < 8; i++) - { - if (mins[0] > p[i][0]) - { - mins[0] = p[i][0]; - } - - if (mins[1] > p[i][1]) - { - mins[1] = p[i][1]; - } - - if (mins[2] > p[i][2]) - { - mins[2] = p[i][2]; - } - - if (maxs[0] < p[i][0]) - { - maxs[0] = p[i][0]; - } - - if (maxs[1] < p[i][1]) - { - maxs[1] = p[i][1]; - } - - if (maxs[2] < p[i][2]) - { - maxs[2] = p[i][2]; - } - } -} - -/* ================================================================== */ - -/* PUSHMOVE */ - -/* - * Does not change the entities velocity at all - */ -trace_t -SV_PushEntity(edict_t *ent, vec3_t push) -{ - trace_t trace; - vec3_t start; - vec3_t end; - int mask; - - VectorCopy(ent->s.origin, start); - VectorAdd(start, push, end); - -retry: - - if (ent->clipmask) - { - mask = ent->clipmask; - } - else - { - mask = MASK_SOLID; - } - - trace = gi.trace(start, ent->mins, ent->maxs, end, ent, mask); - - /* startsolid treats different-content volumes - as continuous, like the bbox of a monster/player - and the floor of an elevator. So do another trace - that only collides with BSP so that we make a best - effort to keep these entities inside non-solid space - */ - if (trace.startsolid && (mask & ~MASK_SOLID)) - { - trace = gi.trace (start, ent->mins, ent->maxs, end, ent, MASK_SOLID); - } - - VectorCopy(trace.endpos, ent->s.origin); - gi.linkentity(ent); - - /* Push slightly away from non-horizontal surfaces, - prevent origin stuck in the plane which causes - the entity to be rendered in full black. */ - if (trace.plane.type != 2) - { - /* Limit the fix to gibs, debris and dead monsters. - Everything else may break existing maps. Items - may slide to unreachable locations, monsters may - get stuck, etc. */ - if (((strncmp(ent->classname, "monster_", 8) == 0) && ent->health < 1) || - (strcmp(ent->classname, "debris") == 0) || (ent->s.effects & EF_GIB)) - { - VectorAdd(ent->s.origin, trace.plane.normal, ent->s.origin); - } - } - - if (trace.fraction != 1.0) - { - SV_Impact(ent, &trace); - - /* if the pushed entity went away - and the pusher is still there */ - if (!trace.ent->inuse && ent->inuse) - { - /* move the pusher back and try again */ - VectorCopy(start, ent->s.origin); - gi.linkentity(ent); - goto retry; - } - } - - ent->gravity = 1.0; - - if (ent->inuse) - { - G_TouchTriggers(ent); - } - - return trace; -} - -/* - * Objects need to be moved back on a failed push, - * otherwise riders would continue to slide. - */ -qboolean -SV_Push(edict_t *pusher, vec3_t move, vec3_t amove) -{ - int i, e; - edict_t *check, *block; - pushed_t *p; - vec3_t org, org2, move2, forward, right, up; - vec3_t realmins, realmaxs; - - if (!pusher) - { - return false; - } - - /* clamp the move to 1/8 units, so the position will - be accurate for client side prediction */ - for (i = 0; i < 3; i++) - { - float temp; - temp = move[i] * 8.0; - - if (temp > 0.0) - { - temp += 0.5; - } - else - { - temp -= 0.5; - } - - move[i] = 0.125 * (int)temp; - } - - /* we need this for pushing things later */ - VectorSubtract(vec3_origin, amove, org); - AngleVectors(org, forward, right, up); - - /* save the pusher's original position */ - pushed_p->ent = pusher; - VectorCopy(pusher->s.origin, pushed_p->origin); - VectorCopy(pusher->s.angles, pushed_p->angles); - - if (pusher->client) - { - pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; - } - - pushed_p++; - - /* move the pusher to it's final position */ - VectorAdd(pusher->s.origin, move, pusher->s.origin); - VectorAdd(pusher->s.angles, amove, pusher->s.angles); - gi.linkentity(pusher); - - /* Create a real bounding box for - rotating brush models. */ - RealBoundingBox(pusher, realmins, realmaxs); - - /* see if any solid entities - are inside the final position */ - check = g_edicts + 1; - - for (e = 1; e < globals.num_edicts; e++, check++) - { - if (!check->inuse) - { - continue; - } - - if ((check->movetype == MOVETYPE_PUSH) || - (check->movetype == MOVETYPE_STOP) || - (check->movetype == MOVETYPE_NONE) || - (check->movetype == MOVETYPE_NOCLIP)) - { - continue; - } - - if (!check->area.prev) - { - continue; /* not linked in anywhere */ - } - - /* if the entity is standing on the pusher, - it will definitely be moved */ - if (check->groundentity != pusher) - { - /* see if the ent needs to be tested */ - if ((check->absmin[0] >= realmaxs[0]) || - (check->absmin[1] >= realmaxs[1]) || - (check->absmin[2] >= realmaxs[2]) || - (check->absmax[0] <= realmins[0]) || - (check->absmax[1] <= realmins[1]) || - (check->absmax[2] <= realmins[2])) - { - continue; - } - - /* see if the ent's bbox is inside - the pusher's final position */ - if (!SV_TestEntityPosition(check)) - { - continue; - } - } - - if ((pusher->movetype == MOVETYPE_PUSH) || - (check->groundentity == pusher)) - { - /* move this entity */ - pushed_p->ent = check; - VectorCopy(check->s.origin, pushed_p->origin); - VectorCopy(check->s.angles, pushed_p->angles); - pushed_p++; - - /* try moving the contacted entity */ - VectorAdd(check->s.origin, move, check->s.origin); - - if (check->client) - { - check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; - } - - /* figure movement due to the pusher's amove */ - VectorSubtract(check->s.origin, pusher->s.origin, org); - org2[0] = DotProduct(org, forward); - org2[1] = -DotProduct(org, right); - org2[2] = DotProduct(org, up); - VectorSubtract(org2, org, move2); - VectorAdd(check->s.origin, move2, check->s.origin); - - /* may have pushed them off an edge */ - if (check->groundentity != pusher) - { - check->groundentity = NULL; - } - - block = SV_TestEntityPosition(check); - - if (!block) - { - /* pushed ok */ - gi.linkentity(check); - - /* impact? */ - continue; - } - - /* if it is ok to leave in the old position, do it - this is only relevent for riding entities, not - pushed */ - VectorSubtract(check->s.origin, move, check->s.origin); - block = SV_TestEntityPosition(check); - - if (!block) - { - pushed_p--; - continue; - } - } - - /* save off the obstacle so we can - call the block function */ - obstacle = check; - - /* move back any entities we already moved - go backwards, so if the same entity was pushed - twice, it goes back to the original position */ - for (p = pushed_p - 1; p >= pushed; p--) - { - VectorCopy(p->origin, p->ent->s.origin); - VectorCopy(p->angles, p->ent->s.angles); - - if (p->ent->client) - { - p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; - } - - gi.linkentity(p->ent); - } - - return false; - } - - /* see if anything we moved has touched a trigger */ - for (p = pushed_p - 1; p >= pushed; p--) - { - G_TouchTriggers(p->ent); - } - - return true; -} - -/* - * Bmodel objects don't interact with each - * other, but push all box objects - */ -void -SV_Physics_Pusher(edict_t *ent) -{ - vec3_t move, amove; - edict_t *part, *mv; - - if (!ent) - { - return; - } - - /* if not a team captain, so movement - will be handled elsewhere */ - if (ent->flags & FL_TEAMSLAVE) - { - return; - } - - /* make sure all team slaves can move before commiting - any moves or calling any think functions if the move - is blocked, all moved objects will be backed out */ - pushed_p = pushed; - - for (part = ent; part; part = part->teamchain) - { - if (part->velocity[0] || part->velocity[1] || part->velocity[2] || - part->avelocity[0] || part->avelocity[1] || part->avelocity[2]) - { - /* object is moving */ - VectorScale(part->velocity, FRAMETIME, move); - VectorScale(part->avelocity, FRAMETIME, amove); - - if (!SV_Push(part, move, amove)) - { - break; /* move was blocked */ - } - } - } - - if (pushed_p > &pushed[MAX_EDICTS - 1]) - { - gi.error("pushed_p > &pushed[MAX_EDICTS - 1], memory corrupted"); - } - - if (part) - { - /* the move failed, bump all nextthink - times and back out moves */ - for (mv = ent; mv; mv = mv->teamchain) - { - if (mv->nextthink > 0) - { - mv->nextthink += FRAMETIME; - } - } - - /* if the pusher has a "blocked" function, call it - otherwise, just stay in place until the obstacle - is gone */ - if (part->blocked) - { - part->blocked(part, obstacle); - } - } - else - { - /* the move succeeded, so call all think functions */ - for (part = ent; part; part = part->teamchain) - { - /* prevent entities that are on trains that have gone away from thinking! */ - if (part->inuse) - { - SV_RunThink(part); - } - } - } -} - -/* ================================================================== */ - -/* - * Non moving objects can only think - */ -void -SV_Physics_None(edict_t *ent) -{ - if (!ent) - { - return; - } - - /* regular thinking */ - SV_RunThink(ent); -} - -/* - * A moving object that doesn't obey physics - */ -void -SV_Physics_Noclip(edict_t *ent) -{ - if (!ent) - { - return; - } - - /* regular thinking */ - if (!SV_RunThink(ent)) - { - return; - } - - VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); - VectorMA(ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); - - gi.linkentity(ent); -} - -/* ================================================================== */ - -/* TOSS / BOUNCE */ - -/* - * Toss, bounce, and fly movement. - * When onground, do nothing. - */ -void -SV_Physics_Toss(edict_t *ent) -{ - trace_t trace; - vec3_t move; - float backoff; - edict_t *slave; - qboolean wasinwater; - qboolean isinwater; - vec3_t old_origin; - - if (!ent) - { - return; - } - - /* regular thinking */ - SV_RunThink(ent); - - /* entities are very often freed during thinking */ - if (!ent->inuse) - { - return; - } - - /* if not a team captain, so movement - will be handled elsewhere */ - if (ent->flags & FL_TEAMSLAVE) - { - return; - } - - if (ent->velocity[2] > 0) - { - ent->groundentity = NULL; - } - - /* check for the groundentity going away */ - if (ent->groundentity) - { - if (!ent->groundentity->inuse) - { - ent->groundentity = NULL; - } - } - - /* if onground, return without moving */ - if (ent->groundentity && (ent->gravity > 0.0)) - { - return; - } - - VectorCopy(ent->s.origin, old_origin); - - SV_CheckVelocity(ent); - - /* add gravity */ - if ((ent->movetype != MOVETYPE_FLY) && - (ent->movetype != MOVETYPE_FLYMISSILE) - && (ent->movetype != MOVETYPE_WALLBOUNCE)) - { - SV_AddGravity(ent); - } - - /* move angles */ - VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); - - /* move origin */ - VectorScale(ent->velocity, FRAMETIME, move); - trace = SV_PushEntity(ent, move); - - if (!ent->inuse) - { - return; - } - - if (trace.fraction < 1) - { - if (ent->movetype == MOVETYPE_WALLBOUNCE) - { - backoff = 2.0; - } - else if (ent->movetype == MOVETYPE_BOUNCE) - { - backoff = 1.5; - } - else - { - backoff = 1; - } - - ClipVelocity(ent->velocity, trace.plane.normal, ent->velocity, backoff); - - if (ent->movetype == MOVETYPE_WALLBOUNCE) - { - vectoangles(ent->velocity, ent->s.angles); - } - - /* stop if on ground */ - if ((trace.plane.normal[2] > 0.7) && - (ent->movetype != MOVETYPE_WALLBOUNCE)) - { - if ((ent->velocity[2] < 60) || (ent->movetype != MOVETYPE_BOUNCE)) - { - ent->groundentity = trace.ent; - ent->groundentity_linkcount = trace.ent->linkcount; - VectorCopy(vec3_origin, ent->velocity); - VectorCopy(vec3_origin, ent->avelocity); - } - } - } - - /* check for water transition */ - wasinwater = (ent->watertype & MASK_WATER); - ent->watertype = gi.pointcontents(ent->s.origin); - isinwater = ent->watertype & MASK_WATER; - - if (isinwater) - { - ent->waterlevel = 1; - } - else - { - ent->waterlevel = 0; - } - - if (!wasinwater && isinwater) - { - /* don't play splash sound for entities already in water on level start */ - if (level.framenum > 3) - { - gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, - gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); - } - } - else if (wasinwater && !isinwater) - { - gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, - gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); - } - - /* move teamslaves */ - for (slave = ent->teamchain; slave; slave = slave->teamchain) - { - VectorCopy(ent->s.origin, slave->s.origin); - gi.linkentity(slave); - } -} - -/* ================================================================== */ - -/* STEPPING MOVEMENT */ - -/* - * Monsters freefall when they don't have a ground - * entity, otherwise all movement is done with - * discrete steps. - * - * This is also used for objects that have become - * still on the ground, but will fall if the floor - * is pulled out from under them. - */ -void -SV_AddRotationalFriction(edict_t *ent) -{ - int n; - float adjustment; - - if (!ent) - { - return; - } - - VectorMA(ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); - adjustment = FRAMETIME * sv_stopspeed->value * FRICTION; - - for (n = 0; n < 3; n++) - { - if (ent->avelocity[n] > 0) - { - ent->avelocity[n] -= adjustment; - - if (ent->avelocity[n] < 0) - { - ent->avelocity[n] = 0; - } - } - else - { - ent->avelocity[n] += adjustment; - - if (ent->avelocity[n] > 0) - { - ent->avelocity[n] = 0; - } - } - } -} - -void -SV_Physics_Step(edict_t *ent) -{ - qboolean wasonground; - qboolean hitsound = false; - float *vel; - float speed, newspeed, control; - float friction; - edict_t *groundentity; - int mask; - vec3_t oldorig; - trace_t tr; - - if (!ent) - { - return; - } - - /* airborn monsters should always check for ground */ - if (!ent->groundentity) - { - M_CheckGround(ent); - } - - groundentity = ent->groundentity; - - SV_CheckVelocity(ent); - - if (groundentity) - { - wasonground = true; - } - else - { - wasonground = false; - } - - if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) - { - SV_AddRotationalFriction(ent); - } - - /* add gravity except: - - flying monsters - - swimming monsters who are in the water */ - if (!wasonground) - { - if (!(ent->flags & FL_FLY)) - { - if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) - { - if (ent->velocity[2] < sv_gravity->value * -0.1) - { - hitsound = true; - } - - if (ent->waterlevel == 0) - { - SV_AddGravity(ent); - } - } - } - } - - /* friction for flying monsters that have been given vertical velocity */ - if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) - { - speed = fabs(ent->velocity[2]); - control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; - friction = FRICTION / 3; - newspeed = speed - (FRAMETIME * control * friction); - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - ent->velocity[2] *= newspeed; - } - - /* friction for flying monsters that have been given vertical velocity */ - if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) - { - speed = fabs(ent->velocity[2]); - control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; - newspeed = speed - (FRAMETIME * control * WATERFRICTION * ent->waterlevel); - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - ent->velocity[2] *= newspeed; - } - - if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) - { - /* apply friction: let dead monsters who - aren't completely onground slide */ - if ((wasonground) || (ent->flags & (FL_SWIM | FL_FLY))) - { - if (!((ent->health <= 0.0) && !M_CheckBottom(ent))) - { - vel = ent->velocity; - speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); - - if (speed) - { - friction = FRICTION; - - control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; - newspeed = speed - FRAMETIME * control * friction; - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - - vel[0] *= newspeed; - vel[1] *= newspeed; - } - } - } - - if (ent->svflags & SVF_MONSTER) - { - mask = MASK_MONSTERSOLID; - } - else - { - mask = MASK_SOLID; - } - - VectorCopy(ent->s.origin, oldorig); - SV_FlyMove(ent, FRAMETIME, mask); - - /* Evil hack to work around dead parasites (and maybe other monster) - falling through the worldmodel into the void. We copy the current - origin (see above) and after the SV_FlyMove() was performend we - checl if we're stuck in the world model. If yes we're undoing the - move. */ - if (!VectorCompare(ent->s.origin, oldorig)) - { - tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); - - if (tr.startsolid) - { - VectorCopy(oldorig, ent->s.origin); - } - } - - gi.linkentity(ent); - ent->gravity = 1.0; - G_TouchTriggers(ent); - - if (!ent->inuse) - { - return; - } - - if (ent->groundentity) - { - if (!wasonground) - { - if (hitsound) - { - gi.sound(ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); - } - } - } - } - - if (!ent->inuse) /* g_touchtrigger free problem */ - { - return; - } - - /* regular thinking */ - SV_RunThink(ent); -} - -/* ================================================================== */ - -void -G_RunEntity(edict_t *ent) -{ - trace_t trace; - vec3_t previous_origin; - qboolean saved_origin; - - if (!ent) - { - return; - } - - if (ent->movetype == MOVETYPE_STEP) - { - VectorCopy(ent->s.origin, previous_origin); - saved_origin = true; - } - else - { - saved_origin = false; - } - - if (ent->prethink) - { - ent->prethink(ent); - } - - switch ((int)ent->movetype) - { - case MOVETYPE_PUSH: - case MOVETYPE_STOP: - SV_Physics_Pusher(ent); - break; - case MOVETYPE_NONE: - SV_Physics_None(ent); - break; - case MOVETYPE_NOCLIP: - SV_Physics_Noclip(ent); - break; - case MOVETYPE_STEP: - SV_Physics_Step(ent); - break; - case MOVETYPE_TOSS: - case MOVETYPE_BOUNCE: - case MOVETYPE_FLY: - case MOVETYPE_FLYMISSILE: - case MOVETYPE_WALLBOUNCE: - SV_Physics_Toss(ent); - break; - case MOVETYPE_NEWTOSS: - SV_Physics_NewToss(ent); - break; - default: - gi.error("SV_Physics: bad movetype %i", (int)ent->movetype); - } - - /* if we moved, check and fix origin if needed */ - /* also check inuse since entities are very often freed while thinking */ - if (saved_origin && ent->inuse && !VectorCompare(ent->s.origin, previous_origin)) - { - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, - previous_origin, ent, MASK_MONSTERSOLID); - - if (trace.allsolid || trace.startsolid) - { - VectorCopy(previous_origin, ent->s.origin); - } - } -} - -/* - * Toss, bounce, and fly movement. When on ground and - * no velocity, do nothing. With velocity, slide. - */ -void -SV_Physics_NewToss(edict_t *ent) -{ - trace_t trace; - vec3_t move; - edict_t *slave; - qboolean wasinwater; - qboolean isinwater; - float speed, newspeed; - vec3_t old_origin; - - if (!ent) - { - return; - } - - /* regular thinking */ - SV_RunThink(ent); - - /* if not a team captain, so movement will be handled elsewhere */ - if (ent->flags & FL_TEAMSLAVE) - { - return; - } - - /* find out what we're sitting on. */ - VectorCopy(ent->s.origin, move); - move[2] -= 0.25; - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, - move, ent, ent->clipmask); - - if (ent->groundentity && ent->groundentity->inuse) - { - ent->groundentity = trace.ent; - } - else - { - ent->groundentity = NULL; - } - - /* if we're sitting on something flat and have no velocity of our own, return. */ - if (ent->groundentity && (trace.plane.normal[2] == 1.0) && - !ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2]) - { - return; - } - - /* store the old origin */ - VectorCopy(ent->s.origin, old_origin); - - SV_CheckVelocity(ent); - - /* add gravity */ - SV_AddGravity(ent); - - if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) - { - SV_AddRotationalFriction(ent); - } - - /* add friction */ - speed = VectorLength(ent->velocity); - - if (ent->waterlevel) /* friction for water movement */ - { - newspeed = speed - (WATERFRICTION * 6 * ent->waterlevel); - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - VectorScale(ent->velocity, newspeed, ent->velocity); - } - else if (!ent->groundentity) /* friction for air movement */ - { - newspeed = speed - ((FRICTION)); - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - VectorScale(ent->velocity, newspeed, ent->velocity); - } - else /* use ground friction */ - { - newspeed = speed - (FRICTION * 6); - - if (newspeed < 0) - { - newspeed = 0; - } - - newspeed /= speed; - VectorScale(ent->velocity, newspeed, ent->velocity); - } - - SV_FlyMove(ent, FRAMETIME, ent->clipmask); - gi.linkentity(ent); - - G_TouchTriggers(ent); - - /* check for water transition */ - wasinwater = (ent->watertype & MASK_WATER); - ent->watertype = gi.pointcontents(ent->s.origin); - isinwater = ent->watertype & MASK_WATER; - - if (isinwater) - { - ent->waterlevel = 1; - } - else - { - ent->waterlevel = 0; - } - - if (!wasinwater && isinwater) - { - gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); - } - else if (wasinwater && !isinwater) - { - gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); - } - - /* move teamslaves */ - for (slave = ent->teamchain; slave; slave = slave->teamchain) - { - VectorCopy(ent->s.origin, slave->s.origin); - gi.linkentity(slave); - } -} diff --git a/src/ctf/g_spawn.c b/src/ctf/g_spawn.c deleted file mode 100644 index 58dbdbc7..00000000 --- a/src/ctf/g_spawn.c +++ /dev/null @@ -1,1776 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Item spawning. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define LEG_WAIT_TIME 1 -#define MAX_LEGSFRAME 23 - -#define SPAWNGROW_LIFESPAN 0.3 -#define STEPSIZE 18 - -typedef struct -{ - char *name; - void (*spawn)(edict_t *ent); -} spawn_t; - -void SP_item_health(edict_t *self); -void SP_item_health_small(edict_t *self); -void SP_item_health_large(edict_t *self); -void SP_item_health_mega(edict_t *self); - -void SP_info_player_start(edict_t *ent); -void SP_info_player_deathmatch(edict_t *ent); -void SP_info_player_coop(edict_t *ent); -void SP_info_player_intermission(edict_t *ent); - -void SP_func_plat(edict_t *ent); -void SP_func_rotating(edict_t *ent); -void SP_func_button(edict_t *ent); -void SP_func_door(edict_t *ent); -void SP_func_door_secret(edict_t *ent); -void SP_func_door_rotating(edict_t *ent); -void SP_func_water(edict_t *ent); -void SP_func_train(edict_t *ent); -void SP_func_conveyor(edict_t *self); -void SP_func_wall(edict_t *self); -void SP_func_object(edict_t *self); -void SP_func_explosive(edict_t *self); -void SP_func_timer(edict_t *self); -void SP_func_areaportal(edict_t *ent); -void SP_func_clock(edict_t *ent); -void SP_func_killbox(edict_t *ent); - -void SP_trigger_always(edict_t *ent); -void SP_trigger_once(edict_t *ent); -void SP_trigger_multiple(edict_t *ent); -void SP_trigger_relay(edict_t *ent); -void SP_trigger_push(edict_t *ent); -void SP_trigger_hurt(edict_t *ent); -void SP_trigger_key(edict_t *ent); -void SP_trigger_counter(edict_t *ent); -void SP_trigger_elevator(edict_t *ent); -void SP_trigger_gravity(edict_t *ent); -void SP_trigger_monsterjump(edict_t *ent); - -void SP_target_temp_entity(edict_t *ent); -void SP_target_speaker(edict_t *ent); -void SP_target_explosion(edict_t *ent); -void SP_target_changelevel(edict_t *ent); -void SP_target_secret(edict_t *ent); -void SP_target_goal(edict_t *ent); -void SP_target_splash(edict_t *ent); -void SP_target_spawner(edict_t *ent); -void SP_target_blaster(edict_t *ent); -void SP_target_crosslevel_trigger(edict_t *ent); -void SP_target_crosslevel_target(edict_t *ent); -void SP_target_laser(edict_t *self); -void SP_target_help(edict_t *ent); -void SP_target_actor(edict_t *ent); -void SP_target_lightramp(edict_t *self); -void SP_target_earthquake(edict_t *ent); -void SP_target_music(edict_t *ent); -void SP_target_sky(edict_t *ent); -void SP_target_character(edict_t *ent); -void SP_target_string(edict_t *ent); - -void SP_worldspawn(edict_t *ent); -void SP_viewthing(edict_t *ent); - -void SP_light(edict_t *self); -void SP_light_mine1(edict_t *ent); -void SP_light_mine2(edict_t *ent); -void SP_info_null(edict_t *self); -void SP_info_notnull(edict_t *self); -void SP_path_corner(edict_t *self); -void SP_point_combat(edict_t *self); - -void SP_misc_explobox(edict_t *self); -void SP_misc_banner(edict_t *self); -void SP_misc_satellite_dish(edict_t *self); -void SP_misc_actor(edict_t *self); -void SP_misc_gib_arm(edict_t *self); -void SP_misc_gib_leg(edict_t *self); -void SP_misc_gib_head(edict_t *self); -void SP_misc_insane(edict_t *self); -void SP_misc_deadsoldier(edict_t *self); -void SP_misc_viper(edict_t *self); -void SP_misc_viper_bomb(edict_t *self); -void SP_misc_bigviper(edict_t *self); -void SP_misc_strogg_ship(edict_t *self); -void SP_misc_teleporter(edict_t *self); -void SP_misc_teleporter_dest(edict_t *self); -void SP_misc_blackhole(edict_t *self); -void SP_misc_eastertank(edict_t *self); -void SP_misc_easterchick(edict_t *self); -void SP_misc_easterchick2(edict_t *self); - -void SP_monster_berserk(edict_t *self); -void SP_monster_gladiator(edict_t *self); -void SP_monster_gunner(edict_t *self); -void SP_monster_guncmdr(edict_t *self); -void SP_monster_infantry(edict_t *self); -void SP_monster_soldier_light(edict_t *self); -void SP_monster_soldier(edict_t *self); -void SP_monster_soldier_ss(edict_t *self); -void SP_monster_tank(edict_t *self); -void SP_monster_medic(edict_t *self); -void SP_monster_flipper(edict_t *self); -void SP_monster_chick(edict_t *self); -void SP_monster_parasite(edict_t *self); -void SP_monster_flyer(edict_t *self); -void SP_monster_brain(edict_t *self); -void SP_monster_floater(edict_t *self); -void SP_monster_hover(edict_t *self); -void SP_monster_mutant(edict_t *self); -void SP_monster_supertank(edict_t *self); -void SP_monster_boss2(edict_t *self); -void SP_monster_jorg(edict_t *self); -void SP_monster_makron(edict_t *self); -void SP_monster_boss3_stand(edict_t *self); - -void SP_monster_commander_body(edict_t *self); - -void SP_turret_breach(edict_t *self); -void SP_turret_base(edict_t *self); -void SP_turret_driver(edict_t *self); - -void SP_monster_soldier_hypergun(edict_t *self); -void SP_monster_soldier_lasergun(edict_t *self); -void SP_monster_soldier_ripper(edict_t *self); -void SP_monster_fixbot(edict_t *self); -void SP_monster_gekk(edict_t *self); -void SP_monster_chick_heat(edict_t *self); -void SP_monster_gladb(edict_t *self); -void SP_monster_boss5(edict_t *self); -void SP_rotating_light(edict_t *self); -void SP_object_repair(edict_t *self); -void SP_misc_crashviper(edict_t *ent); -void SP_misc_viper_missile(edict_t *self); -void SP_misc_amb4(edict_t *ent); -void SP_target_mal_laser(edict_t *ent); -void SP_misc_transport(edict_t *ent); - -void SP_misc_nuke(edict_t *ent); -void SP_func_plat2(edict_t *ent); -void SP_func_door_secret2(edict_t *ent); -void SP_func_force_wall(edict_t *ent); -void SP_info_player_coop_lava(edict_t *self); -void SP_info_teleport_destination(edict_t *self); -void SP_trigger_teleport(edict_t *self); -void SP_trigger_disguise(edict_t *self); -void SP_monster_stalker(edict_t *self); -void SP_monster_turret(edict_t *self); -void SP_target_steam(edict_t *self); -void SP_target_anger(edict_t *self); -void SP_target_killplayers(edict_t *self); - -void SP_target_blacklight(edict_t *self); -void SP_target_orb(edict_t *self); - -void SP_hint_path(edict_t *self); -void SP_monster_carrier(edict_t *self); -void SP_monster_widow(edict_t *self); -void SP_monster_widow2(edict_t *self); -void SP_dm_tag_token(edict_t *self); -void SP_dm_dball_goal(edict_t *self); -void SP_dm_dball_ball(edict_t *self); -void SP_dm_dball_team1_start(edict_t *self); -void SP_dm_dball_team2_start(edict_t *self); -void SP_dm_dball_ball_start(edict_t *self); -void SP_dm_dball_speed_change(edict_t *self); -void SP_monster_kamikaze(edict_t *self); -void SP_turret_invisible_brain(edict_t *self); -void SP_xatrix_item(edict_t *self); -void SP_misc_nuke_core(edict_t *self); - -void ThrowMoreStuff(edict_t *self, vec3_t point); -void ThrowSmallStuff(edict_t *self, vec3_t point); -void ThrowWidowGibLoc(edict_t *self, char *gibname, int damage, - int type, vec3_t startpos, qboolean fade); -void ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type, - vec3_t startpos, int hitsound, qboolean fade); - -static spawn_t spawns[] = { - {"item_health", SP_item_health}, - {"item_health_small", SP_item_health_small}, - {"item_health_large", SP_item_health_large}, - {"item_health_mega", SP_item_health_mega}, - - {"info_player_start", SP_info_player_start}, - {"info_player_deathmatch", SP_info_player_deathmatch}, - {"info_player_coop", SP_info_player_coop}, - {"info_player_intermission", SP_info_player_intermission}, - {"info_player_team1", SP_info_player_team1}, - {"info_player_team2", SP_info_player_team2}, - - {"func_plat", SP_func_plat}, - {"func_button", SP_func_button}, - {"func_door", SP_func_door}, - {"func_door_secret", SP_func_door_secret}, - {"func_door_rotating", SP_func_door_rotating}, - {"func_rotating", SP_func_rotating}, - {"func_train", SP_func_train}, - {"func_water", SP_func_water}, - {"func_conveyor", SP_func_conveyor}, - {"func_areaportal", SP_func_areaportal}, - {"func_clock", SP_func_clock}, - {"func_wall", SP_func_wall}, - {"func_object", SP_func_object}, - {"func_timer", SP_func_timer}, - {"func_explosive", SP_func_explosive}, - {"func_killbox", SP_func_killbox}, - - {"trigger_always", SP_trigger_always}, - {"trigger_once", SP_trigger_once}, - {"trigger_multiple", SP_trigger_multiple}, - {"trigger_relay", SP_trigger_relay}, - {"trigger_push", SP_trigger_push}, - {"trigger_hurt", SP_trigger_hurt}, - {"trigger_key", SP_trigger_key}, - {"trigger_counter", SP_trigger_counter}, - {"trigger_elevator", SP_trigger_elevator}, - {"trigger_gravity", SP_trigger_gravity}, - {"trigger_monsterjump", SP_trigger_monsterjump}, - - {"target_temp_entity", SP_target_temp_entity}, - {"target_speaker", SP_target_speaker}, - {"target_explosion", SP_target_explosion}, - {"target_changelevel", SP_target_changelevel}, - {"target_secret", SP_target_secret}, - {"target_goal", SP_target_goal}, - {"target_splash", SP_target_splash}, - {"target_spawner", SP_target_spawner}, - {"target_blaster", SP_target_blaster}, - {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, - {"target_crosslevel_target", SP_target_crosslevel_target}, - {"target_laser", SP_target_laser}, - {"target_help", SP_target_help}, - {"target_lightramp", SP_target_lightramp}, - {"target_earthquake", SP_target_earthquake}, - {"target_character", SP_target_character}, - {"target_string", SP_target_string}, - - {"worldspawn", SP_worldspawn}, - {"viewthing", SP_viewthing}, - - {"light", SP_light}, - {"light_mine1", SP_light_mine1}, - {"light_mine2", SP_light_mine2}, - {"info_null", SP_info_null}, - {"func_group", SP_info_null}, - {"info_notnull", SP_info_notnull}, - {"path_corner", SP_path_corner}, - {"point_combat", SP_point_combat}, - - {"misc_explobox", SP_misc_explobox}, - {"misc_banner", SP_misc_banner}, - {"misc_ctf_banner", SP_misc_ctf_banner}, - {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, - {"misc_satellite_dish", SP_misc_satellite_dish}, - {"misc_gib_arm", SP_misc_gib_arm}, - {"misc_gib_leg", SP_misc_gib_leg}, - {"misc_gib_head", SP_misc_gib_head}, - {"misc_viper", SP_misc_viper}, - {"misc_viper_bomb", SP_misc_viper_bomb}, - {"misc_bigviper", SP_misc_bigviper}, - {"misc_strogg_ship", SP_misc_strogg_ship}, - {"misc_teleporter", SP_misc_teleporter}, - {"misc_teleporter_dest", SP_misc_teleporter_dest}, - {"trigger_teleport", SP_trigger_teleport}, - {"info_teleport_destination", SP_info_teleport_destination}, - {"misc_blackhole", SP_misc_blackhole}, - {"misc_eastertank", SP_misc_eastertank}, - {"misc_easterchick", SP_misc_easterchick}, - {"misc_easterchick2", SP_misc_easterchick2}, - - {NULL, NULL} -}; - -static qboolean -Spawn_CheckCoop_MapHacks(edict_t *ent) -{ - if(!coop->value || !ent) - { - return false; - } - - if(!Q_stricmp(level.mapname, "xsewer1")) - { - if(ent->classname && !Q_stricmp(ent->classname, "trigger_relay") && ent->target && !Q_stricmp(ent->target, "t3") && ent->targetname && !Q_stricmp(ent->targetname, "t2")) - { - return true; - } - if(ent->classname && !Q_stricmp(ent->classname, "func_button") && ent->target && !Q_stricmp(ent->target, "t16") && ent->model && !Q_stricmp(ent->model, "*71")) - { - ent->message = "Overflow valve maintenance\nhatch A opened."; - return false; - } - - if(ent->classname && !Q_stricmp(ent->classname, "trigger_once") && ent->model && !Q_stricmp(ent->model, "*3")) - { - ent->message = "Overflow valve maintenance\nhatch B opened."; - return false; - } - } - - return false; -} - -/* - * Finds the spawn function for - * the entity and calls it - */ -void -ED_CallSpawn(edict_t *ent) -{ - spawn_t *s; - gitem_t *item; - int i; - - if (!ent) - { - return; - } - - if (!ent->classname) - { - gi.dprintf("ED_CallSpawn: NULL classname\n"); - G_FreeEdict(ent); - return; - } - - ent->gravityVector[0] = 0.0; - ent->gravityVector[1] = 0.0; - ent->gravityVector[2] = -1.0; - - if (!strcmp(ent->classname, "weapon_nailgun")) - { - ent->classname = (FindItem("ETF Rifle"))->classname; - } - - if (!strcmp(ent->classname, "ammo_nails")) - { - ent->classname = (FindItem("Flechettes"))->classname; - } - - if (!strcmp(ent->classname, "weapon_heatbeam")) - { - ent->classname = (FindItem("Plasma Beam"))->classname; - } - - /* check item spawn functions */ - for (i = 0, item = itemlist; i < game.num_items; i++, item++) - { - if (!item->classname) - { - continue; - } - - if (!strcmp(item->classname, ent->classname)) - { - /* found it */ - SpawnItem(ent, item); - return; - } - } - - /* check normal spawn functions */ - for (s = spawns; s->name; s++) - { - if (!strcmp(s->name, ent->classname)) - { - /* found it */ - s->spawn(ent); - return; - } - } - - gi.dprintf("%s doesn't have a spawn function\n", ent->classname); -} - -char * -ED_NewString(const char *string) -{ - char *newb, *new_p; - int i, l; - - if (!string) - { - return NULL; - } - - l = strlen(string) + 1; - - newb = gi.TagMalloc(l, TAG_LEVEL); - - new_p = newb; - - for (i = 0; i < l; i++) - { - if ((string[i] == '\\') && (i < l - 1)) - { - i++; - - if (string[i] == 'n') - { - *new_p++ = '\n'; - } - else - { - *new_p++ = '\\'; - } - } - else - { - *new_p++ = string[i]; - } - } - - return newb; -} - -/* - * Takes a key/value pair and sets - * the binary values in an edict - */ -void -ED_ParseField(const char *key, const char *value, edict_t *ent) -{ - field_t *f; - byte *b; - float v; - vec3_t vec; - - if (!ent || !value || !key) - { - return; - } - - for (f = fields; f->name; f++) - { - if (!(f->flags & FFL_NOSPAWN) && !Q_strcasecmp(f->name, (char *)key)) - { - /* found it */ - if (f->flags & FFL_SPAWNTEMP) - { - b = (byte *)&st; - } - else - { - b = (byte *)ent; - } - - switch (f->type) - { - case F_LSTRING: - *(char **)(b + f->ofs) = ED_NewString(value); - break; - case F_VECTOR: - sscanf(value, "%f %f %f", &vec[0], &vec[1], &vec[2]); - ((float *)(b + f->ofs))[0] = vec[0]; - ((float *)(b + f->ofs))[1] = vec[1]; - ((float *)(b + f->ofs))[2] = vec[2]; - break; - case F_INT: - *(int *)(b + f->ofs) = (int)strtol(value, (char **)NULL, 10); - break; - case F_FLOAT: - *(float *)(b + f->ofs) = (float)strtod(value, (char **)NULL); - break; - case F_ANGLEHACK: - v = (float)strtod(value, (char **)NULL); - ((float *)(b + f->ofs))[0] = 0; - ((float *)(b + f->ofs))[1] = v; - ((float *)(b + f->ofs))[2] = 0; - break; - case F_IGNORE: - break; - default: - break; - } - - return; - } - } - - gi.dprintf("%s is not a field\n", key); -} - -/* - * Parses an edict out of the given string, - * returning the new position. ed should be - * a properly initialized empty edict. - */ -char * -ED_ParseEdict(char *data, edict_t *ent) -{ - qboolean init; - char keyname[256]; - const char *com_token; - - if (!ent) - { - return NULL; - } - - init = false; - memset(&st, 0, sizeof(st)); - st.skyautorotate = 1; - - /* go through all the dictionary pairs */ - while (1) - { - /* parse key */ - com_token = COM_Parse(&data); - - if (com_token[0] == '}') - { - break; - } - - if (!data) - { - gi.error("ED_ParseEntity: EOF without closing brace"); - } - - Q_strlcpy(keyname, com_token, sizeof(keyname)); - - /* parse value */ - com_token = COM_Parse(&data); - - if (!data) - { - gi.error("ED_ParseEntity: EOF without closing brace"); - } - - if (com_token[0] == '}') - { - gi.error("ED_ParseEntity: closing brace without data"); - } - - init = true; - - /* keynames with a leading underscore are - used for utility comments, and are - immediately discarded by quake */ - if (keyname[0] == '_') - { - continue; - } - - ED_ParseField(keyname, com_token, ent); - } - - if (!init) - { - memset(ent, 0, sizeof(*ent)); - } - - return data; -} - -/* - * Chain together all entities with a matching team field. - * - * All but the first will have the FL_TEAMSLAVE flag set. - * All but the last will have the teamchain field set to the next one - */ -static void -G_FixTeams(void) -{ - edict_t *e, *e2, *chain; - int i, j; - int c, c2; - - c = 0; - c2 = 0; - - for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) - { - if (!e->inuse) - { - continue; - } - - if (!e->team) - { - continue; - } - - if (!strcmp(e->classname, "func_train")) - { - if (e->flags & FL_TEAMSLAVE) - { - chain = e; - e->teammaster = e; - e->teamchain = NULL; - e->flags &= ~FL_TEAMSLAVE; - c++; - c2++; - - for (j = 1, e2 = g_edicts + j; - j < globals.num_edicts; - j++, e2++) - { - if (e2 == e) - { - continue; - } - - if (!e2->inuse) - { - continue; - } - - if (!e2->team) - { - continue; - } - - if (!strcmp(e->team, e2->team)) - { - c2++; - chain->teamchain = e2; - e2->teammaster = e; - e2->teamchain = NULL; - chain = e2; - e2->flags |= FL_TEAMSLAVE; - e2->movetype = MOVETYPE_PUSH; - e2->speed = e->speed; - } - } - } - } - } - - gi.dprintf("%i teams repaired\n", c); -} - -void -G_FindTeams(void) -{ - edict_t *e, *e2, *chain; - int i, j; - int c, c2; - - c = 0; - c2 = 0; - - for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) - { - if (!e->inuse) - { - continue; - } - - if (!e->team) - { - continue; - } - - if (e->flags & FL_TEAMSLAVE) - { - continue; - } - - chain = e; - e->teammaster = e; - c++; - c2++; - - for (j = i + 1, e2 = e + 1; j < globals.num_edicts; j++, e2++) - { - if (!e2->inuse) - { - continue; - } - - if (!e2->team) - { - continue; - } - - if (e2->flags & FL_TEAMSLAVE) - { - continue; - } - - if (!strcmp(e->team, e2->team)) - { - c2++; - chain->teamchain = e2; - e2->teammaster = e; - chain = e2; - e2->flags |= FL_TEAMSLAVE; - } - } - } - - G_FixTeams(); - - gi.dprintf("%i teams with %i entities.\n", c, c2); -} - -/* - * Creates a server's entity / program execution context by - * parsing textual entity definitions out of an ent file. - */ -void -SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) -{ - edict_t *ent; - int inhibit; - const char *com_token; - int i; - float skill_level; - - if (!mapname || !entities || !spawnpoint) - { - return; - } - - skill_level = floor(skill->value); - - if (skill_level < 0) - { - skill_level = 0; - } - - if (skill_level > 3) - { - skill_level = 3; - } - - if (skill->value != skill_level) - { - gi.cvar_forceset("skill", va("%f", skill_level)); - } - - SaveClientData(); - - gi.FreeTags(TAG_LEVEL); - - memset(&level, 0, sizeof(level)); - memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); - - Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); - Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); - - /* set client fields on player ents */ - for (i = 0; i < game.maxclients; i++) - { - g_edicts[i + 1].client = game.clients + i; - } - - ent = NULL; - inhibit = 0; - - /* parse ents */ - while (1) - { - /* parse the opening brace */ - com_token = COM_Parse(&entities); - - if (!entities) - { - break; - } - - if (com_token[0] != '{') - { - gi.error("ED_LoadFromFile: found %s when expecting {", com_token); - } - - if (!ent) - { - ent = g_edicts; - } - else - { - ent = G_Spawn(); - } - - entities = ED_ParseEdict(entities, ent); - - /* yet another map hack */ - if (!Q_stricmp(level.mapname, "command") && - !Q_stricmp(ent->classname, "trigger_once") && - !Q_stricmp(ent->model, "*27")) - { - ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; - } - - /* ahh, the joys of map hacks .. */ - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "func_door_rotating") && - ent->targetname && !Q_stricmp(ent->targetname, "t265")) - { - ent->spawnflags &= ~SPAWNFLAG_NOT_COOP; - } - - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "trigger_always") && - ent->target && !Q_stricmp(ent->target, "t265")) - { - ent->spawnflags |= SPAWNFLAG_NOT_COOP; - } - - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "func_wall") && - !Q_stricmp(ent->model, "*15")) - { - ent->spawnflags |= SPAWNFLAG_NOT_COOP; - } - - /* remove things (except the world) from - different skill levels or deathmatch */ - if (ent != g_edicts) - { - if (deathmatch->value) - { - if (ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - } - else - { - if (((skill->value == 0) && - (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || - ((skill->value == 1) && - (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || - (((skill->value == 2) || - (skill->value == 3)) && - (ent->spawnflags & SPAWNFLAG_NOT_HARD)) - ) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - } - - ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM | - SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH); - } - - ent->gravityVector[0] = 0.0; - ent->gravityVector[1] = 0.0; - ent->gravityVector[2] = -1.0; - - ED_CallSpawn(ent); - - ent->s.renderfx |= RF_IR_VISIBLE; - } - - gi.dprintf("%i entities inhibited.\n", inhibit); - - G_FindTeams(); - - PlayerTrail_Init(); - - CTFSpawn(); -} - -/* =================================================================== */ - -static char *single_statusbar = - "yb -24 " - -/* health */ - "xv 0 " - "hnum " - "xv 50 " - "pic 0 " - -/* ammo */ - "if 2 " - " xv 100 " - " anum " - " xv 150 " - " pic 2 " - "endif " - -/* armor */ - "if 4 " - " xv 200 " - " rnum " - " xv 250 " - " pic 4 " - "endif " - -/* selected item */ - "if 6 " - " xv 296 " - " pic 6 " - "endif " - - "yb -50 " - -/* picked up item */ - "if 7 " - " xv 0 " - " pic 7 " - " xv 26 " - " yb -42 " - " stat_string 8 " - " yb -50 " - "endif " - -/* timer */ - "if 9 " - " xv 262 " - " num 2 10 " - " xv 296 " - " pic 9 " - "endif " - -/* help / weapon icon */ - "if 11 " - " xv 148 " - " pic 11 " - "endif " -; - -static char *dm_statusbar = - "yb -24 " - -/* health */ - "xv 0 " - "hnum " - "xv 50 " - "pic 0 " - -/* ammo */ - "if 2 " - " xv 100 " - " anum " - " xv 150 " - " pic 2 " - "endif " - -/* armor */ - "if 4 " - " xv 200 " - " rnum " - " xv 250 " - " pic 4 " - "endif " - -/* selected item */ - "if 6 " - " xv 296 " - " pic 6 " - "endif " - - "yb -50 " - -/* picked up item */ - "if 7 " - " xv 0 " - " pic 7 " - " xv 26 " - " yb -42 " - " stat_string 8 " - " yb -50 " - "endif " - -/* timer */ - "if 9 " - " xv 246 " - " num 2 10 " - " xv 296 " - " pic 9 " - "endif " - -/* help / weapon icon */ - "if 11 " - " xv 148 " - " pic 11 " - "endif " - -/* frags */ - "xr -50 " - "yt 2 " - "num 3 14" -; - -/* - * QUAKED worldspawn (0 0 0) ? - * - * Only used for the world. - * "sky" environment map name - * "skyaxis" vector axis for rotating sky - * "skyrotate" speed of rotation in degrees/second - * "sounds" music cd track number - * "gravity" 800 is default gravity - * "message" text to print at user logon - */ -void -SP_worldspawn(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_PUSH; - ent->solid = SOLID_BSP; - ent->inuse = true; /* since the world doesn't use G_Spawn() */ - ent->s.modelindex = 1; /* world model is always index 1 */ - - /* --------------- */ - - /* reserve some spots for dead - player bodies for coop / deathmatch */ - InitBodyQue(); - - /* set configstrings for items */ - SetItemNames(); - - if (st.nextmap) - { - strcpy(level.nextmap, st.nextmap); - } - - /* make some data visible to the server */ - if (ent->message && ent->message[0]) - { - gi.configstring(CS_NAME, ent->message); - Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name)); - } - else - { - Q_strlcpy(level.level_name, level.mapname, sizeof(level.level_name)); - } - - if (st.sky && st.sky[0]) - { - gi.configstring(CS_SKY, st.sky); - } - else - { - gi.configstring(CS_SKY, "unit1_"); - } - - gi.configstring(CS_SKYROTATE, va("%f", st.skyrotate)); - - gi.configstring(CS_SKYAXIS, va("%f %f %f", st.skyaxis[0], st.skyaxis[1], st.skyaxis[2])); - - gi.configstring(CS_CDTRACK, va("%i", ent->sounds)); - - gi.configstring(CS_MAXCLIENTS, va("%i", (int)(maxclients->value))); - - /* status bar program */ - if (deathmatch->value) - { - if (ctf->value) - { - gi.configstring(CS_STATUSBAR, ctf_statusbar); - CTFPrecache(); - } - else - { - gi.configstring(CS_STATUSBAR, dm_statusbar); - } - } - else - { - gi.configstring(CS_STATUSBAR, single_statusbar); - } - - /* --------------- */ - - /* help icon for statusbar */ - gi.imageindex("i_help"); - level.pic_health = gi.imageindex("i_health"); - gi.imageindex("help"); - gi.imageindex("field_3"); - - if (!st.gravity) - { - gi.cvar_set("sv_gravity", "800"); - } - else - { - gi.cvar_set("sv_gravity", st.gravity); - } - - snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */ - - PrecacheItem(FindItem("Blaster")); - - gi.soundindex("player/lava1.wav"); - gi.soundindex("player/lava2.wav"); - - gi.soundindex("misc/pc_up.wav"); - gi.soundindex("misc/talk1.wav"); - - gi.soundindex("misc/udeath.wav"); - - /* gibs */ - gi.soundindex("items/respawn1.wav"); - - /* sexed sounds */ - gi.soundindex("*death1.wav"); - gi.soundindex("*death2.wav"); - gi.soundindex("*death3.wav"); - gi.soundindex("*death4.wav"); - gi.soundindex("*fall1.wav"); - gi.soundindex("*fall2.wav"); - gi.soundindex("*gurp1.wav"); /* drowning damage */ - gi.soundindex("*gurp2.wav"); - gi.soundindex("*jump1.wav"); /* player jump */ - gi.soundindex("*pain25_1.wav"); - gi.soundindex("*pain25_2.wav"); - gi.soundindex("*pain50_1.wav"); - gi.soundindex("*pain50_2.wav"); - gi.soundindex("*pain75_1.wav"); - gi.soundindex("*pain75_2.wav"); - gi.soundindex("*pain100_1.wav"); - gi.soundindex("*pain100_2.wav"); - - /* sexed models */ - gi.modelindex("#w_blaster.md2"); - gi.modelindex("#w_shotgun.md2"); - gi.modelindex("#w_sshotgun.md2"); - gi.modelindex("#w_machinegun.md2"); - gi.modelindex("#w_chaingun.md2"); - gi.modelindex("#a_grenades.md2"); - gi.modelindex("#w_glauncher.md2"); - gi.modelindex("#w_rlauncher.md2"); - gi.modelindex("#w_hyperblaster.md2"); - gi.modelindex("#w_railgun.md2"); - gi.modelindex("#w_bfg.md2"); - gi.modelindex("#w_grapple.md2"); - - /* ------------------- */ - - gi.soundindex("player/gasp1.wav"); /* gasping for air */ - gi.soundindex("player/gasp2.wav"); /* head breaking surface, not gasping */ - - gi.soundindex("player/watr_in.wav"); /* feet hitting water */ - gi.soundindex("player/watr_out.wav"); /* feet leaving water */ - - gi.soundindex("player/watr_un.wav"); /* head going underwater */ - - gi.soundindex("player/u_breath1.wav"); - gi.soundindex("player/u_breath2.wav"); - - gi.soundindex("items/pkup.wav"); /* bonus item pickup */ - gi.soundindex("world/land.wav"); /* landing thud */ - gi.soundindex("misc/h2ohit1.wav"); /* landing splash */ - - gi.soundindex("items/damage.wav"); - gi.soundindex("misc/ddamage1.wav"); - gi.soundindex("items/protect.wav"); - gi.soundindex("items/protect4.wav"); - gi.soundindex("weapons/noammo.wav"); - - gi.soundindex("infantry/inflies1.wav"); - - sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2"); - gi.modelindex("models/objects/gibs/arm/tris.md2"); - gi.modelindex("models/objects/gibs/bone/tris.md2"); - gi.modelindex("models/objects/gibs/bone2/tris.md2"); - gi.modelindex("models/objects/gibs/chest/tris.md2"); - gi.modelindex("models/objects/gibs/skull/tris.md2"); - gi.modelindex("models/objects/gibs/head2/tris.md2"); - - /* Setup light animation tables. 'a' - is total darkness, 'z' is doublebright. */ - - /* 0 normal */ - gi.configstring(CS_LIGHTS + 0, "m"); - - /* 1 FLICKER (first variety) */ - gi.configstring(CS_LIGHTS + 1, "mmnmmommommnonmmonqnmmo"); - - /* 2 SLOW STRONG PULSE */ - gi.configstring(CS_LIGHTS + 2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); - - /* 3 CANDLE (first variety) */ - gi.configstring(CS_LIGHTS + 3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); - - /* 4 FAST STROBE */ - gi.configstring(CS_LIGHTS + 4, "mamamamamama"); - - /* 5 GENTLE PULSE 1 */ - gi.configstring(CS_LIGHTS + 5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj"); - - /* 6 FLICKER (second variety) */ - gi.configstring(CS_LIGHTS + 6, "nmonqnmomnmomomno"); - - /* 7 CANDLE (second variety) */ - gi.configstring(CS_LIGHTS + 7, "mmmaaaabcdefgmmmmaaaammmaamm"); - - /* 8 CANDLE (third variety) */ - gi.configstring(CS_LIGHTS + 8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); - - /* 9 SLOW STROBE (fourth variety) */ - gi.configstring(CS_LIGHTS + 9, "aaaaaaaazzzzzzzz"); - - /* 10 FLUORESCENT FLICKER */ - gi.configstring(CS_LIGHTS + 10, "mmamammmmammamamaaamammma"); - - /* 11 SLOW PULSE NOT FADE TO BLACK */ - gi.configstring(CS_LIGHTS + 11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); - - /* styles 32-62 are assigned by the light program for switchable lights */ - - /* 63 testing */ - gi.configstring(CS_LIGHTS + 63, "a"); -} - -/* - * Monster spawning code: - * Used by the carrier, the medic_commander, and the black widow - * - * The sequence to create a flying monster is: - * FindSpawnPoint - tries to find suitable spot to spawn the monster in - * CreateFlyMonster - this verifies the point as good and creates the monster - * - * To create a ground walking monster: - * FindSpawnPoint - same thing - * CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable - */ - -edict_t * -CreateMonster(vec3_t origin, vec3_t angles, char *classname) -{ - edict_t *newEnt; - - if (!classname) - { - return NULL; - } - - newEnt = G_Spawn(); - - VectorCopy(origin, newEnt->s.origin); - VectorCopy(angles, newEnt->s.angles); - newEnt->classname = ED_NewString(classname); - newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; - - VectorSet(newEnt->gravityVector, 0, 0, -1); - ED_CallSpawn(newEnt); - newEnt->s.renderfx |= RF_IR_VISIBLE; - - return newEnt; -} - -edict_t * -CreateFlyMonster(vec3_t origin, vec3_t angles, vec3_t mins, - vec3_t maxs, char *classname) -{ - if (!classname) - { - return NULL; - } - - if (!mins || !maxs || - VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin)) - { - DetermineBBox(classname, mins, maxs); - } - - if (!CheckSpawnPoint(origin, mins, maxs)) - { - return NULL; - } - - return CreateMonster(origin, angles, classname); -} - -edict_t * -CreateGroundMonster(vec3_t origin, vec3_t angles, vec3_t entMins, - vec3_t entMaxs, char *classname, int height) -{ - edict_t *newEnt; - vec3_t mins, maxs; - - if (!classname) - { - return NULL; - } - - /* if they don't provide us a bounding box, figure it out */ - if (!entMins || !entMaxs || VectorCompare(entMins, - vec3_origin) || VectorCompare(entMaxs, vec3_origin)) - { - DetermineBBox(classname, mins, maxs); - } - else - { - VectorCopy(entMins, mins); - VectorCopy(entMaxs, maxs); - } - - /* check the ground to make sure it's there, it's relatively flat, and it's not toxic */ - if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1)) - { - return NULL; - } - - newEnt = CreateMonster(origin, angles, classname); - - if (!newEnt) - { - return NULL; - } - - return newEnt; -} - -qboolean -FindSpawnPoint(vec3_t startpoint, vec3_t mins, vec3_t maxs, - vec3_t spawnpoint, float maxMoveUp) -{ - trace_t tr; - vec3_t top; - - tr = gi.trace(startpoint, mins, maxs, startpoint, - NULL, MASK_MONSTERSOLID | CONTENTS_PLAYERCLIP); - - if ((tr.startsolid || tr.allsolid) || (tr.ent != world)) - { - VectorCopy(startpoint, top); - top[2] += maxMoveUp; - - tr = gi.trace(top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID); - - if (tr.startsolid || tr.allsolid) - { - return false; - } - else - { - VectorCopy(tr.endpos, spawnpoint); - return true; - } - } - else - { - VectorCopy(startpoint, spawnpoint); - return true; - } -} - -qboolean -CheckSpawnPoint(vec3_t origin, vec3_t mins, vec3_t maxs) -{ - trace_t tr; - - if (!mins || !maxs || - VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin)) - { - return false; - } - - tr = gi.trace(origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID); - - if (tr.startsolid || tr.allsolid) - { - return false; - } - - if (tr.ent != world) - { - return false; - } - - return true; -} - -qboolean -CheckGroundSpawnPoint(vec3_t origin, vec3_t entMins, vec3_t entMaxs, - float height, float gravity) -{ - trace_t tr; - vec3_t start, stop; - vec3_t mins, maxs; - int x, y; - float mid, bottom; - - if (!CheckSpawnPoint(origin, entMins, entMaxs)) - { - return false; - } - - - VectorCopy(origin, stop); - stop[2] = origin[2] + entMins[2] - height; - - tr = gi.trace(origin, entMins, entMaxs, stop, - NULL, MASK_MONSTERSOLID | MASK_WATER); - - if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID)) - { - /* first, do the midpoint trace */ - VectorAdd(tr.endpos, entMins, mins); - VectorAdd(tr.endpos, entMaxs, maxs); - - /* first, do the easy flat check */ - if (gravity > 0) - { - start[2] = maxs[2] + 1; - } - else - { - 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) != CONTENTS_SOLID) - { - goto realcheck; - } - } - } - - /* if it passed all four above checks, we're done */ - return true; - - realcheck: - - /* check it for real */ - start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5; - start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5; - start[2] = mins[2]; - - tr = gi.trace(start, vec3_origin, vec3_origin, - stop, NULL, MASK_MONSTERSOLID); - - if (tr.fraction == 1.0) - { - return false; - } - - if (gravity < 0) - { - start[2] = mins[2]; - stop[2] = start[2] - STEPSIZE - STEPSIZE; - mid = bottom = tr.endpos[2] + entMins[2]; - } - else - { - start[2] = maxs[2]; - stop[2] = start[2] + STEPSIZE + STEPSIZE; - mid = bottom = tr.endpos[2] - entMaxs[2]; - } - - 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]; - - tr = gi.trace(start, vec3_origin, vec3_origin, - stop, NULL, MASK_MONSTERSOLID); - - if (gravity > 0) - { - if ((tr.fraction != 1.0) && (tr.endpos[2] < bottom)) - { - bottom = tr.endpos[2]; - } - - if ((tr.fraction == 1.0) || (tr.endpos[2] - mid > STEPSIZE)) - { - return false; - } - } - else - { - if ((tr.fraction != 1.0) && (tr.endpos[2] > bottom)) - { - bottom = tr.endpos[2]; - } - - if ((tr.fraction == 1.0) || (mid - tr.endpos[2] > STEPSIZE)) - { - return false; - } - } - } - } - - return true; /* we can land on it, it's ok */ - } - - /* otherwise, it's either water (bad) or not - * there (too far) if we're here, it's bad below */ - return false; -} - -void -DetermineBBox(char *classname, vec3_t mins, vec3_t maxs) -{ - edict_t *newEnt; - - if (!classname) - { - return; - } - - newEnt = G_Spawn(); - - VectorCopy(vec3_origin, newEnt->s.origin); - VectorCopy(vec3_origin, newEnt->s.angles); - newEnt->classname = ED_NewString(classname); - newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; - - ED_CallSpawn(newEnt); - - VectorCopy(newEnt->mins, mins); - VectorCopy(newEnt->maxs, maxs); - - G_FreeEdict(newEnt); -} - - -void -spawngrow_think(edict_t *self) -{ - int i; - - if (!self) - { - return; - } - - for (i = 0; i < 2; i++) - { - self->s.angles[0] = rand() % 360; - self->s.angles[1] = rand() % 360; - self->s.angles[2] = rand() % 360; - } - - if ((level.time < self->wait) && (self->s.frame < 2)) - { - self->s.frame++; - } - - if (level.time >= self->wait) - { - if (self->s.effects & EF_SPHERETRANS) - { - G_FreeEdict(self); - return; - } - else if (self->s.frame > 0) - { - self->s.frame--; - } - else - { - G_FreeEdict(self); - return; - } - } - - self->nextthink += FRAMETIME; -} - -void -SpawnGrow_Spawn(vec3_t startpos, int size) -{ - edict_t *ent; - int i; - float lifespan; - - ent = G_Spawn(); - VectorCopy(startpos, ent->s.origin); - - for (i = 0; i < 2; i++) - { - ent->s.angles[0] = rand() % 360; - ent->s.angles[1] = rand() % 360; - ent->s.angles[2] = rand() % 360; - } - - ent->solid = SOLID_NOT; - ent->s.renderfx = RF_IR_VISIBLE; - ent->movetype = MOVETYPE_NONE; - ent->classname = "spawngro"; - - if (size <= 1) - { - lifespan = SPAWNGROW_LIFESPAN; - ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2"); - } - else if (size == 2) - { - ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); - lifespan = 2; - } - else - { - ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2"); - lifespan = SPAWNGROW_LIFESPAN; - } - - ent->think = spawngrow_think; - - ent->wait = level.time + lifespan; - ent->nextthink = level.time + FRAMETIME; - - if (size != 2) - { - ent->s.effects |= EF_SPHERETRANS; - } - - gi.linkentity(ent); -} - -void -widowlegs_think(edict_t *self) -{ - vec3_t offset; - vec3_t point; - vec3_t f, r, u; - - if (!self) - { - return; - } - - if (self->s.frame == 17) - { - VectorSet(offset, 11.77, -7.24, 23.31); - AngleVectors(self->s.angles, f, r, u); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - } - - if (self->s.frame < MAX_LEGSFRAME) - { - self->s.frame++; - self->nextthink = level.time + FRAMETIME; - return; - } - else if (self->wait == 0) - { - self->wait = level.time + LEG_WAIT_TIME; - } - - if (level.time > self->wait) - { - AngleVectors(self->s.angles, f, r, u); - - VectorSet(offset, -65.6, -8.44, 28.59); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - - VectorSet(offset, -1.04, -51.18, 7.04); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - - G_FreeEdict(self); - return; - } - - if ((level.time > (self->wait - 0.5)) && (self->count == 0)) - { - self->count = 1; - AngleVectors(self->s.angles, f, r, u); - - VectorSet(offset, 31, -88.7, 10.96); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - - VectorSet(offset, -12.67, -4.39, 15.68); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - - self->nextthink = level.time + FRAMETIME; - return; - } - - self->nextthink = level.time + FRAMETIME; -} - -void -Widowlegs_Spawn(vec3_t startpos, vec3_t angles) -{ - edict_t *ent; - - ent = G_Spawn(); - VectorCopy(startpos, ent->s.origin); - VectorCopy(angles, ent->s.angles); - ent->solid = SOLID_NOT; - ent->s.renderfx = RF_IR_VISIBLE; - ent->movetype = MOVETYPE_NONE; - ent->classname = "widowlegs"; - - ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2"); - ent->think = widowlegs_think; - - ent->nextthink = level.time + FRAMETIME; - gi.linkentity(ent); -} diff --git a/src/ctf/player/client.c b/src/ctf/player/client.c index a2e7cbc2..3b5a2fec 100644 --- a/src/ctf/player/client.c +++ b/src/ctf/player/client.c @@ -29,6 +29,268 @@ #include "../monster/misc/player.h" void SP_misc_teleporter_dest(edict_t *ent); +void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +/* + * The ugly as hell coop spawnpoint fixup function. + * While coop was planed by id, it wasn't part of + * the initial release and added later with patch + * to version 2.00. The spawnpoints in some maps + * were SNAFU, some have wrong targets and some + * no name at all. Fix this by matching the coop + * spawnpoint target names to the nearest named + * single player spot. + */ +void +SP_FixCoopSpots(edict_t *self) +{ + edict_t *spot; + vec3_t d; + + if (!self) + { + return; + } + + /* Entity number 292 is an unnamed info_player_start + next to a named info_player_start. Delete it, if + we're in coop since it screws up the spawnpoint + selection heuristic in SelectCoopSpawnPoint(). + This unnamed info_player_start is selected as + spawnpoint for player 0, therefor none of the + named info_coop_start() matches... */ + if(Q_stricmp(level.mapname, "xware") == 0) + { + if (self->s.number == 292) + { + G_FreeEdict(self); + self = NULL; + } + } + + spot = NULL; + + while (1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + + if (!spot) + { + return; + } + + if (!spot->targetname) + { + continue; + } + + VectorSubtract(self->s.origin, spot->s.origin, d); + + if (VectorLength(d) < 550) + { + if ((!self->targetname) || (Q_stricmp(self->targetname, spot->targetname) != 0)) + { + self->targetname = spot->targetname; + } + + return; + } + } +} + +/* + * Some maps have no coop spawnpoints at + * all. Add these by injecting entities + * into the map where they should have + * been + */ +void +SP_CreateCoopSpots(edict_t *self) +{ + edict_t *spot; + + if (!self) + { + return; + } + + if (Q_stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + +/* + * Some maps have no unnamed (e.g. generic) + * info_player_start. This is no problem in + * normal gameplay, but if the map is loaded + * via console there is a huge chance that + * the player will spawn in the wrong point. + * Therefore create an unnamed info_player_start + * at the correct point. + */ +void +SP_CreateUnnamedSpawn(edict_t *self) +{ + edict_t *spot = G_Spawn(); + + if (!self) + { + return; + } + + /* mine1 */ + if (Q_stricmp(level.mapname, "mine1") == 0) + { + if (Q_stricmp(self->targetname, "mintro") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* mine2 */ + if (Q_stricmp(level.mapname, "mine2") == 0) + { + if (Q_stricmp(self->targetname, "mine1") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* mine3 */ + if (Q_stricmp(level.mapname, "mine3") == 0) + { + if (Q_stricmp(self->targetname, "mine2a") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* mine4 */ + if (Q_stricmp(level.mapname, "mine4") == 0) + { + if (Q_stricmp(self->targetname, "mine3") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* power2 */ + if (Q_stricmp(level.mapname, "power2") == 0) + { + if (Q_stricmp(self->targetname, "power1") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* waste1 */ + if (Q_stricmp(level.mapname, "waste1") == 0) + { + if (Q_stricmp(self->targetname, "power2") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* waste2 */ + if (Q_stricmp(level.mapname, "waste2") == 0) + { + if (Q_stricmp(self->targetname, "waste1") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } + + /* city3 */ + if (Q_stricmp(level.mapname, "city2") == 0) + { + if (Q_stricmp(self->targetname, "city2NL") == 0) + { + spot->classname = self->classname; + spot->s.origin[0] = self->s.origin[0]; + spot->s.origin[1] = self->s.origin[1]; + spot->s.origin[2] = self->s.origin[2]; + spot->s.angles[1] = self->s.angles[1]; + spot->targetname = NULL; + + return; + } + } +} /* * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) @@ -63,10 +325,59 @@ SP_info_player_deathmatch(edict_t *self) * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) * potential spawning position for coop games */ - void SP_info_player_coop(edict_t *self) { + if (!self) + { + return; + } + + if (!coop->value) + { + G_FreeEdict(self); + return; + } + + if ((Q_stricmp(level.mapname, "jail2") == 0) || + (Q_stricmp(level.mapname, "jail4") == 0) || + (Q_stricmp(level.mapname, "mintro") == 0) || + (Q_stricmp(level.mapname, "mine1") == 0) || + (Q_stricmp(level.mapname, "mine2") == 0) || + (Q_stricmp(level.mapname, "mine3") == 0) || + (Q_stricmp(level.mapname, "mine4") == 0) || + (Q_stricmp(level.mapname, "lab") == 0) || + (Q_stricmp(level.mapname, "boss1") == 0) || + (Q_stricmp(level.mapname, "fact1") == 0) || + (Q_stricmp(level.mapname, "fact3") == 0) || + (Q_stricmp(level.mapname, "waste1") == 0) || /* really? */ + (Q_stricmp(level.mapname, "biggun") == 0) || + (Q_stricmp(level.mapname, "space") == 0) || + (Q_stricmp(level.mapname, "command") == 0) || + (Q_stricmp(level.mapname, "power2") == 0) || + (Q_stricmp(level.mapname, "strike") == 0) || + (Q_stricmp(level.mapname, "city2") == 0)) + { + /* invoke one of our gross, ugly, disgusting hacks */ + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/* + * QUAKED info_player_coop_lava (1 0 1) (-16 -16 -24) (16 16 32) + * + * potential spawning position for coop games on rmine2 where lava level + * needs to be checked + */ +void +SP_info_player_coop_lava(edict_t *self) +{ + if (!self) + { + return; + } + if (!coop->value) { G_FreeEdict(self); @@ -76,22 +387,31 @@ SP_info_player_coop(edict_t *self) /* * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) + * * The deathmatch intermission point will be at one of these * Use 'angles' instead of 'angle', so you can set pitch or * roll as well as yaw. 'pitch yaw roll' */ void -SP_info_player_intermission(edict_t *ent) +SP_info_player_intermission(edict_t *self) { + /* This function cannot be removed + * since the info_player_intermission + * needs a callback function. Like + * every entity. */ } /* ======================================================================= */ void -player_pain(edict_t *self, edict_t *other, float kick, int damage) +player_pain(edict_t *self /* unused */, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) { - /* player pain is handled at the - end of the frame in P_DamageFeedback */ + /* Player pain is handled at the end + * of the frame in P_DamageFeedback. + * This function is still here since + * the player is an entity and needs + * a pain callback */ } qboolean @@ -99,12 +419,22 @@ IsFemale(edict_t *ent) { char *info; + if (!ent) + { + return false; + } + if (!ent->client) { return false; } - info = Info_ValueForKey(ent->client->pers.userinfo, "skin"); + info = Info_ValueForKey(ent->client->pers.userinfo, "gender"); + + if (strstr(info, "crakhor")) + { + return true; + } if ((info[0] == 'f') || (info[0] == 'F')) { @@ -114,15 +444,52 @@ IsFemale(edict_t *ent) return false; } +qboolean +IsNeutral(edict_t *ent) +{ + char *info; + + if (!ent) + { + return false; + } + + if (!ent->client) + { + return false; + } + + info = Info_ValueForKey(ent->client->pers.userinfo, "gender"); + + if (strstr(info, "crakhor")) + { + return false; + } + + if ((info[0] != 'f') && (info[0] != 'F') && (info[0] != 'm') && + (info[0] != 'M')) + { + return true; + } + + return false; +} + void -ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) +ClientObituary(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker) { int mod; char *message; char *message2; qboolean ff; - if (coop->value && attacker->client) + if (!self || !attacker || !inflictor) + { + return; + } + + if (coop->value && attacker && attacker->client) { meansOfDeath |= MOD_FRIENDLY_FIRE; } @@ -172,6 +539,12 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) case MOD_TRIGGER_HURT: message = "was in the wrong place"; break; + case MOD_GEKK: + case MOD_BRAINTENTACLE: + message = "that's gotta hurt"; + break; + default: + break; } if (attacker == self) @@ -184,7 +557,11 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) case MOD_HG_SPLASH: case MOD_G_SPLASH: - if (IsFemale(self)) + if (IsNeutral(self)) + { + message = "tripped on its own grenade"; + } + else if (IsFemale(self)) { message = "tripped on her own grenade"; } @@ -196,7 +573,11 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) break; case MOD_R_SPLASH: - if (IsFemale(self)) + if (IsNeutral(self)) + { + message = "blew itself up"; + } + else if (IsFemale(self)) { message = "blew herself up"; } @@ -209,9 +590,31 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) case MOD_BFG_BLAST: message = "should have used a smaller gun"; break; + case MOD_DOPPLE_EXPLODE: + + if (IsNeutral(self)) + { + message = "got caught in it's own trap"; + } + else if (IsFemale(self)) + { + message = "got caught in her own trap"; + } + else + { + message = "got caught in his own trap"; + } + + case MOD_TRAP: + message = "sucked into his own trap"; + break; default: - if (IsFemale(self)) + if (IsNeutral(self)) + { + message = "killed itself"; + } + else if (IsFemale(self)) { message = "killed herself"; } @@ -317,12 +720,98 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) message = "was caught by"; message2 = "'s grapple"; break; + case MOD_RIPPER: + message = "ripped to shreds by"; + message2 = "'s ripper gun"; + break; + case MOD_PHALANX: + message = "was evaporated by"; + break; + case MOD_TRAP: + message = "caught in trap by"; + break; + case MOD_CHAINFIST: + message = "was shredded by"; + message2 = "'s ripsaw"; + break; + case MOD_DISINTEGRATOR: + message = "lost his grip courtesy of"; + message2 = "'s disintegrator"; + break; + case MOD_ETF_RIFLE: + message = "was perforated by"; + break; + case MOD_HEATBEAM: + message = "was scorched by"; + message2 = "'s plasma beam"; + break; + case MOD_TESLA: + message = "was enlightened by"; + message2 = "'s tesla mine"; + break; + case MOD_PROX: + message = "got too close to"; + message2 = "'s proximity mine"; + break; + case MOD_NUKE: + message = "was nuked by"; + message2 = "'s antimatter bomb"; + break; + case MOD_VENGEANCE_SPHERE: + message = "was purged by"; + message2 = "'s vengeance sphere"; + break; + case MOD_DEFENDER_SPHERE: + message = "had a blast with"; + message2 = "'s defender sphere"; + break; + case MOD_HUNTER_SPHERE: + message = "was killed like a dog by"; + message2 = "'s hunter sphere"; + break; + case MOD_TRACKER: + message = "was annihilated by"; + message2 = "'s disruptor"; + break; + case MOD_DOPPLE_EXPLODE: + message = "was blown up by"; + message2 = "'s doppleganger"; + break; + case MOD_DOPPLE_VENGEANCE: + message = "was purged by"; + message2 = "'s doppleganger"; + break; + case MOD_DOPPLE_HUNTER: + message = "was hunted down by"; + message2 = "'s doppleganger"; + break; + default: + break; } if (message) { - gi.bprintf(PRINT_MEDIUM, "%s %s %s%s\n", self->client->pers.netname, - message, attacker->client->pers.netname, message2); + gi.bprintf(PRINT_MEDIUM, "%s %s %s%s\n", + self->client->pers.netname, + message, attacker->client->pers.netname, + message2); + + if (gamerules && gamerules->value) + { + if (DMGame.Score) + { + if (ff) + { + DMGame.Score(attacker, self, -1); + } + else + { + DMGame.Score(attacker, self, 1); + } + } + + return; + } if (deathmatch->value) { @@ -345,20 +834,36 @@ ClientObituary(edict_t *self, edict_t *inflictor, edict_t *attacker) if (deathmatch->value) { - self->client->resp.score--; + if (gamerules && gamerules->value) + { + if (DMGame.Score) + { + DMGame.Score(self, self, -1); + } + + return; + } + else + { + self->client->resp.score--; + } } } -void Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); - void TossClientWeapon(edict_t *self) { gitem_t *item; edict_t *drop; qboolean quad; + qboolean quadfire; float spread; + if (!self) + { + return; + } + if (!deathmatch->value) { return; @@ -385,10 +890,23 @@ TossClientWeapon(edict_t *self) quad = (self->client->quad_framenum > (level.framenum + 10)); } + if (!((int)(dmflags->value) & DF_QUADFIRE_DROP)) + { + quadfire = false; + } + else + { + quadfire = (self->client->quadfire_framenum > (level.framenum + 10)); + } + if (item && quad) { spread = 22.5; } + else if (item && quadfire) + { + spread = 12.5; + } else { spread = 0.0; @@ -410,7 +928,21 @@ TossClientWeapon(edict_t *self) drop->spawnflags |= DROPPED_PLAYER_ITEM; drop->touch = Touch_Item; - drop->nextthink = level.time + (self->client->quad_framenum - + drop->nextthink = level.time + + (self->client->quad_framenum - + level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } + + if (quadfire) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item(self, FindItemByClassname("item_quadfire")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quadfire_framenum - level.framenum) * FRAMETIME; drop->think = G_FreeEdict; } @@ -421,6 +953,11 @@ LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker) { vec3_t dir; + if (!self) + { + return; + } + if (attacker && (attacker != world) && (attacker != self)) { VectorSubtract(attacker->s.origin, self->s.origin, dir); diff --git a/src/game/g_phys.c b/src/game/g_phys.c index 0d879eb2..17147e9f 100644 --- a/src/game/g_phys.c +++ b/src/game/g_phys.c @@ -39,6 +39,7 @@ typedef struct edict_t *ent; vec3_t origin; vec3_t angles; + float deltayaw; } pushed_t; static pushed_t pushed[MAX_EDICTS], *pushed_p; @@ -648,6 +649,12 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove) pushed_p->ent = pusher; VectorCopy(pusher->s.origin, pushed_p->origin); VectorCopy(pusher->s.angles, pushed_p->angles); + + if (pusher->client) + { + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + } + pushed_p++; /* move the pusher to it's final position */ @@ -718,6 +725,11 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove) /* try moving the contacted entity */ VectorAdd(check->s.origin, move, check->s.origin); + if (check->client) + { + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + /* figure movement due to the pusher's amove */ VectorSubtract(check->s.origin, pusher->s.origin, org); org2[0] = DotProduct(org, forward); @@ -784,6 +796,11 @@ SV_Push(edict_t *pusher, vec3_t move, vec3_t amove) VectorCopy(p->origin, p->ent->s.origin); VectorCopy(p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity(p->ent); } diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index b9e70f04..7709c155 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -226,6 +226,8 @@ static spawn_t spawns[] = { {"info_player_deathmatch", SP_info_player_deathmatch}, {"info_player_coop", SP_info_player_coop}, {"info_player_intermission", SP_info_player_intermission}, + {"info_player_team1", SP_info_player_team1}, + {"info_player_team2", SP_info_player_team2}, {"func_plat", SP_func_plat}, {"func_button", SP_func_button}, @@ -294,6 +296,8 @@ static spawn_t spawns[] = { {"misc_explobox", SP_misc_explobox}, {"misc_banner", SP_misc_banner}, + {"misc_ctf_banner", SP_misc_ctf_banner}, + {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, {"misc_satellite_dish", SP_misc_satellite_dish}, {"misc_gib_arm", SP_misc_gib_arm}, {"misc_gib_leg", SP_misc_gib_leg}, @@ -1026,6 +1030,11 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) DMGame.PostInitSetup(); } } + + if (ctf->value) + { + CTFSpawn(); + } } /* =================================================================== */ @@ -1237,7 +1246,15 @@ SP_worldspawn(edict_t *ent) /* status bar program */ if (deathmatch->value) { - gi.configstring(CS_STATUSBAR, dm_statusbar); + if (ctf->value) + { + gi.configstring(CS_STATUSBAR, ctf_statusbar); + CTFPrecache(); + } + else + { + gi.configstring(CS_STATUSBAR, dm_statusbar); + } } else { @@ -1298,7 +1315,7 @@ SP_worldspawn(edict_t *ent) /* sexed models: THIS ORDER MUST MATCH THE DEFINES IN g_local.h you can add more, max 19 (pete change)these models are only loaded in coop or deathmatch. not singleplayer. */ - if (coop->value || deathmatch->value) + if (coop->value || deathmatch->value || ctf->value) { gi.modelindex("#w_blaster.md2"); gi.modelindex("#w_shotgun.md2"); diff --git a/src/game/header/local.h b/src/game/header/local.h index 88aebfc3..92fba34f 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -577,22 +577,22 @@ extern int gibsthisframe; #define MOD_TRAP 39 #define MOD_GRAPPLE 40 #define MOD_FRIENDLY_FIRE 0x8000000 -#define MOD_CHAINFIST 40 -#define MOD_DISINTEGRATOR 41 -#define MOD_ETF_RIFLE 42 -#define MOD_BLASTER2 43 -#define MOD_HEATBEAM 44 -#define MOD_TESLA 45 -#define MOD_PROX 46 -#define MOD_NUKE 47 -#define MOD_VENGEANCE_SPHERE 48 -#define MOD_HUNTER_SPHERE 49 -#define MOD_DEFENDER_SPHERE 50 -#define MOD_TRACKER 51 -#define MOD_DBALL_CRUSH 52 -#define MOD_DOPPLE_EXPLODE 53 -#define MOD_DOPPLE_VENGEANCE 54 -#define MOD_DOPPLE_HUNTER 55 +#define MOD_CHAINFIST 41 +#define MOD_DISINTEGRATOR 42 +#define MOD_ETF_RIFLE 43 +#define MOD_BLASTER2 44 +#define MOD_HEATBEAM 45 +#define MOD_TESLA 46 +#define MOD_PROX 47 +#define MOD_NUKE 48 +#define MOD_VENGEANCE_SPHERE 49 +#define MOD_HUNTER_SPHERE 50 +#define MOD_DEFENDER_SPHERE 51 +#define MOD_TRACKER 52 +#define MOD_DBALL_CRUSH 53 +#define MOD_DOPPLE_EXPLODE 54 +#define MOD_DOPPLE_VENGEANCE 55 +#define MOD_DOPPLE_HUNTER 56 /* Easier handling of AI skill levels */ #define SKILL_EASY 0 diff --git a/src/game/player/client.c b/src/game/player/client.c index c326fa01..52a9fbea 100644 --- a/src/game/player/client.c +++ b/src/game/player/client.c @@ -745,6 +745,10 @@ ClientObituary(edict_t *self, edict_t *inflictor /* unused */, message = "tried to invade"; message2 = "'s personal space"; break; + case MOD_GRAPPLE: + message = "was caught by"; + message2 = "'s grapple"; + break; case MOD_RIPPER: message = "ripped to shreds by"; message2 = "'s ripper gun";