From d27fc1f33330af0d2edd37b2c834929655255081 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 4 Nov 2023 18:30:46 +0200 Subject: [PATCH] game: sync ctf player --- Makefile | 8 +- src/ctf/g_save.c | 819 ------------ src/ctf/player/client.c | 2406 ---------------------------------- src/ctf/player/view.c | 1381 ------------------- src/game/player/client.c | 121 +- src/game/player/view.c | 70 +- src/game/savegame/savegame.c | 4 +- 7 files changed, 179 insertions(+), 4630 deletions(-) delete mode 100644 src/ctf/g_save.c delete mode 100644 src/ctf/player/client.c delete mode 100644 src/ctf/player/view.c diff --git a/Makefile b/Makefile index f2ad1e5c..aa4ebaf6 100644 --- a/Makefile +++ b/Makefile @@ -1497,7 +1497,6 @@ CTF_OBJS_ = \ src/game/g_misc.o \ src/game/g_monster.o \ src/game/g_phys.o \ - src/ctf/g_save.o \ src/game/g_sphere.o \ src/game/g_spawn.o \ src/game/g_svcmds.o \ @@ -1540,11 +1539,12 @@ CTF_OBJS_ = \ src/game/monster/turret/turret.o \ src/game/monster/widow/widow2.o \ src/game/monster/widow/widow.o \ - src/ctf/player/client.o \ + src/game/player/client.o \ src/game/player/hud.o \ src/game/player/trail.o \ - src/ctf/player/view.o \ - src/game/player/weapon.o + src/game/player/view.o \ + src/game/player/weapon.o \ + src/game/savegame/savegame.o # ---------- diff --git a/src/ctf/g_save.c b/src/ctf/g_save.c deleted file mode 100644 index 8020a3bc..00000000 --- a/src/ctf/g_save.c +++ /dev/null @@ -1,819 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (C) 2011 Knightmare - * Copyright (C) 2011 Yamagi Burmeister - * 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. - * - * ======================================================================= - * - * The savegame system. Unused by the CTF game but nevertheless called - * during game initialization. Therefor no new savegame code ist - * imported. - * - * ======================================================================= - */ - -#include "header/local.h" - -#ifndef BUILD_DATE -#define BUILD_DATE __DATE__ -#endif - -field_t fields[] = { - {"classname", FOFS(classname), F_LSTRING}, - {"origin", FOFS(s.origin), F_VECTOR}, - {"model", FOFS(model), F_LSTRING}, - {"spawnflags", FOFS(spawnflags), F_INT}, - {"speed", FOFS(speed), F_FLOAT}, - {"accel", FOFS(accel), F_FLOAT}, - {"decel", FOFS(decel), F_FLOAT}, - {"target", FOFS(target), F_LSTRING}, - {"targetname", FOFS(targetname), F_LSTRING}, - {"pathtarget", FOFS(pathtarget), F_LSTRING}, - {"deathtarget", FOFS(deathtarget), F_LSTRING}, - {"killtarget", FOFS(killtarget), F_LSTRING}, - {"combattarget", FOFS(combattarget), F_LSTRING}, - {"message", FOFS(message), F_LSTRING}, - {"team", FOFS(team), F_LSTRING}, - {"wait", FOFS(wait), F_FLOAT}, - {"delay", FOFS(delay), F_FLOAT}, - {"random", FOFS(random), F_FLOAT}, - {"move_origin", FOFS(move_origin), F_VECTOR}, - {"move_angles", FOFS(move_angles), F_VECTOR}, - {"style", FOFS(style), F_INT}, - {"count", FOFS(count), F_INT}, - {"health", FOFS(health), F_INT}, - {"sounds", FOFS(sounds), F_INT}, - {"light", 0, F_IGNORE}, - {"dmg", FOFS(dmg), F_INT}, - {"angles", FOFS(s.angles), F_VECTOR}, - {"angle", FOFS(s.angles), F_ANGLEHACK}, - {"mass", FOFS(mass), F_INT}, - {"volume", FOFS(volume), F_FLOAT}, - {"attenuation", FOFS(attenuation), F_FLOAT}, - {"map", FOFS(map), F_LSTRING}, - - /* temp spawn vars -- only valid when the spawn function is called */ - {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, - {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, - {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, - {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, - {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, - {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, - {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, - {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, - {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, - {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, - {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, - {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, - {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, - {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, - {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP} -}; - -field_t savefields[] = { - {"", FOFS(classname), F_LSTRING}, - {"", FOFS(target), F_LSTRING}, - {"", FOFS(targetname), F_LSTRING}, - {"", FOFS(killtarget), F_LSTRING}, - {"", FOFS(team), F_LSTRING}, - {"", FOFS(pathtarget), F_LSTRING}, - {"", FOFS(deathtarget), F_LSTRING}, - {"", FOFS(combattarget), F_LSTRING}, - {"", FOFS(model), F_LSTRING}, - {"", FOFS(map), F_LSTRING}, - {"", FOFS(message), F_LSTRING}, - - {"", FOFS(client), F_CLIENT}, - {"", FOFS(item), F_ITEM}, - - {"", FOFS(goalentity), F_EDICT}, - {"", FOFS(movetarget), F_EDICT}, - {"", FOFS(enemy), F_EDICT}, - {"", FOFS(oldenemy), F_EDICT}, - {"", FOFS(activator), F_EDICT}, - {"", FOFS(groundentity), F_EDICT}, - {"", FOFS(teamchain), F_EDICT}, - {"", FOFS(teammaster), F_EDICT}, - {"", FOFS(owner), F_EDICT}, - {"", FOFS(mynoise), F_EDICT}, - {"", FOFS(mynoise2), F_EDICT}, - {"", FOFS(target_ent), F_EDICT}, - {"", FOFS(chain), F_EDICT}, - - {NULL, 0, F_INT} -}; - -field_t levelfields[] = { - {"", LLOFS(changemap), F_LSTRING}, - - {"", LLOFS(sight_client), F_EDICT}, - {"", LLOFS(sight_entity), F_EDICT}, - {"", LLOFS(sound_entity), F_EDICT}, - {"", LLOFS(sound2_entity), F_EDICT}, - - {NULL, 0, F_INT} -}; - -field_t clientfields[] = { - {"", CLOFS(pers.weapon), F_ITEM}, - {"", CLOFS(pers.lastweapon), F_ITEM}, - {"", CLOFS(newweapon), F_ITEM}, - - {NULL, 0, F_INT} -}; - -/* - * This will be called when the dll is first loaded, which - * only happens when a new game is started or a save game - * is loaded. - */ -void -InitGame(void) -{ - gi.dprintf("Game is starting up.\n"); - gi.dprintf("Game is ctf built on %s.\n", GAMEVERSION, BUILD_DATE); - - gun_x = gi.cvar("gun_x", "0", 0); - gun_y = gi.cvar("gun_y", "0", 0); - gun_z = gi.cvar("gun_z", "0", 0); - sv_rollspeed = gi.cvar("sv_rollspeed", "200", 0); - sv_rollangle = gi.cvar("sv_rollangle", "2", 0); - sv_maxvelocity = gi.cvar("sv_maxvelocity", "2000", 0); - sv_gravity = gi.cvar("sv_gravity", "800", 0); - - /* noset vars */ - dedicated = gi.cvar("dedicated", "0", CVAR_NOSET); - - /* latched vars */ - sv_cheats = gi.cvar("cheats", "0", CVAR_SERVERINFO | CVAR_LATCH); - gi.cvar("gamename", GAMEVERSION, CVAR_SERVERINFO | CVAR_LATCH); - gi.cvar("gamedate", BUILD_DATE, CVAR_SERVERINFO | CVAR_LATCH); - maxclients = gi.cvar("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); - deathmatch = gi.cvar("deathmatch", "0", CVAR_LATCH); - coop = gi.cvar("coop", "0", CVAR_LATCH); - skill = gi.cvar("skill", "1", CVAR_LATCH); - maxentities = gi.cvar("maxentities", "1024", CVAR_LATCH); - - /* This game.dll only supports deathmatch */ - if (!deathmatch->value) - { - gi.dprintf("Forcing deathmatch.\n"); - gi.cvar_set("deathmatch", "1"); - } - - /* force coop off */ - if (coop->value) - { - gi.cvar_set("coop", "0"); - } - - /* change anytime vars */ - dmflags = gi.cvar("dmflags", "0", CVAR_SERVERINFO); - fraglimit = gi.cvar("fraglimit", "0", CVAR_SERVERINFO); - timelimit = gi.cvar("timelimit", "0", CVAR_SERVERINFO); - capturelimit = gi.cvar("capturelimit", "0", CVAR_SERVERINFO); - instantweap = gi.cvar("instantweap", "0", CVAR_SERVERINFO); - password = gi.cvar("password", "", CVAR_USERINFO); - spectator_password = gi.cvar("spectator_password", "", CVAR_USERINFO); - needpass = gi.cvar("needpass", "0", CVAR_SERVERINFO); - filterban = gi.cvar("filterban", "1", 0); - g_select_empty = gi.cvar("g_select_empty", "0", CVAR_ARCHIVE); - run_pitch = gi.cvar("run_pitch", "0.002", 0); - run_roll = gi.cvar("run_roll", "0.005", 0); - bob_up = gi.cvar("bob_up", "0.005", 0); - bob_pitch = gi.cvar("bob_pitch", "0.002", 0); - bob_roll = gi.cvar("bob_roll", "0.002", 0); - - /* flood control */ - flood_msgs = gi.cvar("flood_msgs", "4", 0); - flood_persecond = gi.cvar("flood_persecond", "4", 0); - flood_waitdelay = gi.cvar("flood_waitdelay", "10", 0); - - /* dm map list */ - sv_maplist = gi.cvar("sv_maplist", "", 0); - - /* disruptor availability */ - g_disruptor = gi.cvar("g_disruptor", "0", 0); - - /* others */ - aimfix = gi.cvar("aimfix", "0", CVAR_ARCHIVE); - g_machinegun_norecoil = gi.cvar("g_machinegun_norecoil", "0", CVAR_ARCHIVE); - g_swap_speed = gi.cvar("g_swap_speed", "1", 0); - - /* items */ - InitItems(); - - Com_sprintf(game.helpmessage1, sizeof(game.helpmessage1), ""); - Com_sprintf(game.helpmessage2, sizeof(game.helpmessage2), ""); - - /* initialize all entities for this game */ - game.maxentities = maxentities->value; - g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); - globals.edicts = g_edicts; - globals.max_edicts = game.maxentities; - - /* initialize all clients for this game */ - game.maxclients = maxclients->value; - game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); - globals.num_edicts = game.maxclients + 1; - - CTFInit(); -} - -/* ========================================================= */ - -void -WriteField1(FILE *f, field_t *field, byte *base) -{ - void *p; - int len; - int index; - - p = (void *)(base + field->ofs); - - switch (field->type) - { - case F_INT: - case F_FLOAT: - case F_ANGLEHACK: - case F_VECTOR: - case F_IGNORE: - break; - - case F_LSTRING: - case F_GSTRING: - - if (*(char **)p) - { - len = strlen(*(char **)p) + 1; - } - else - { - len = 0; - } - - *(int *)p = len; - break; - case F_EDICT: - - if (*(edict_t **)p == NULL) - { - index = -1; - } - else - { - index = *(edict_t **)p - g_edicts; - } - - *(int *)p = index; - break; - case F_CLIENT: - - if (*(gclient_t **)p == NULL) - { - index = -1; - } - else - { - index = *(gclient_t **)p - game.clients; - } - - *(int *)p = index; - break; - case F_ITEM: - - if (*(edict_t **)p == NULL) - { - index = -1; - } - else - { - index = *(gitem_t **)p - itemlist; - } - - *(int *)p = index; - break; - - default: - gi.error("WriteEdict: unknown field type"); - } -} - -void -WriteField2(FILE *f, field_t *field, byte *base) -{ - int len; - void *p; - - p = (void *)(base + field->ofs); - - switch (field->type) - { - case F_LSTRING: - case F_GSTRING: - - if (*(char **)p) - { - len = strlen(*(char **)p) + 1; - fwrite(*(char **)p, len, 1, f); - } - - break; - default: - break; - } -} - -void -ReadField(FILE *f, field_t *field, byte *base) -{ - void *p; - int len; - int index; - - p = (void *)(base + field->ofs); - - switch (field->type) - { - case F_INT: - case F_FLOAT: - case F_ANGLEHACK: - case F_VECTOR: - case F_IGNORE: - break; - - case F_LSTRING: - len = *(int *)p; - - if (!len) - { - *(char **)p = NULL; - } - else - { - *(char **)p = gi.TagMalloc(len, TAG_LEVEL); - fread(*(char **)p, len, 1, f); - } - - break; - case F_GSTRING: - len = *(int *)p; - - if (!len) - { - *(char **)p = NULL; - } - else - { - *(char **)p = gi.TagMalloc(len, TAG_GAME); - fread(*(char **)p, len, 1, f); - } - - break; - case F_EDICT: - index = *(int *)p; - - if (index == -1) - { - *(edict_t **)p = NULL; - } - else - { - *(edict_t **)p = &g_edicts[index]; - } - - break; - case F_CLIENT: - index = *(int *)p; - - if (index == -1) - { - *(gclient_t **)p = NULL; - } - else - { - *(gclient_t **)p = &game.clients[index]; - } - - break; - case F_ITEM: - index = *(int *)p; - - if (index == -1) - { - *(gitem_t **)p = NULL; - } - else - { - *(gitem_t **)p = &itemlist[index]; - } - - break; - - default: - gi.error("ReadEdict: unknown field type"); - } -} - -/* ========================================================= */ - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -WriteClient(FILE *f, gclient_t *client) -{ - field_t *field; - gclient_t temp; - - /* all of the ints, floats, and vectors stay as they are */ - temp = *client; - - /* change the pointers to lengths or indexes */ - for (field = clientfields; field->name; field++) - { - WriteField1(f, field, (byte *)&temp); - } - - /* write the block */ - fwrite(&temp, sizeof(temp), 1, f); - - /* now write any allocated data following the edict */ - for (field = clientfields; field->name; field++) - { - WriteField2(f, field, (byte *)client); - } -} - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -ReadClient(FILE *f, gclient_t *client, short save_ver ) -{ - field_t *field; - - fread(client, sizeof(*client), 1, f); - - for (field = clientfields; field->name; field++) - { - ReadField(f, field, (byte *)client); - } -} - -/* - * This will be called whenever the game goes to a new level, - * and when the user explicitly saves the game. - * - * Game information include cross level data, like multi level - * triggers, help computer info, and all client states. - * - * A single player death will automatically restore from the - * last save position. - */ -void -WriteGame(const char *filename, qboolean autosave) -{ - FILE *f; - int i; - char str[16]; - - if (!autosave) - { - SaveClientData(); - } - - f = fopen(filename, "wb"); - - if (!f) - { - gi.error("Couldn't open %s", filename); - } - - memset(str, 0, sizeof(str)); - strcpy(str, BUILD_DATE); - fwrite(str, sizeof(str), 1, f); - - game.autosaved = autosave; - fwrite(&game, sizeof(game), 1, f); - game.autosaved = false; - - for (i = 0; i < game.maxclients; i++) - { - WriteClient(f, &game.clients[i]); - } - - fclose(f); -} - -void -ReadGame(const char *filename) -{ - FILE *f; - int i; - char str[16]; - - gi.FreeTags(TAG_GAME); - - f = fopen(filename, "rb"); - - if (!f) - { - gi.error("Couldn't open %s", filename); - } - - fread(str, sizeof(str), 1, f); - - if (strcmp(str, BUILD_DATE)) - { - fclose(f); - gi.error("Savegame from an older version.\n"); - } - - g_edicts = gi.TagMalloc(game.maxentities * sizeof(g_edicts[0]), TAG_GAME); - globals.edicts = g_edicts; - - fread(&game, sizeof(game), 1, f); - game.clients = gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); - - for (i = 0; i < game.maxclients; i++) - { - ReadClient(f, &game.clients[i], 0); - } - - fclose(f); -} - -/* ========================================================== */ - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -WriteEdict(FILE *f, edict_t *ent) -{ - field_t *field; - edict_t temp; - - /* all of the ints, floats, and vectors stay as they are */ - temp = *ent; - - /* change the pointers to lengths or indexes */ - for (field = savefields; field->name; field++) - { - WriteField1(f, field, (byte *)&temp); - } - - /* write the block */ - fwrite(&temp, sizeof(temp), 1, f); - - /* now write any allocated data following the edict */ - for (field = savefields; field->name; field++) - { - WriteField2(f, field, (byte *)ent); - } -} - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -WriteLevelLocals(FILE *f) -{ - field_t *field; - level_locals_t temp; - - /* all of the ints, floats, and vectors stay as they are */ - temp = level; - - /* change the pointers to lengths or indexes */ - for (field = levelfields; field->name; field++) - { - WriteField1(f, field, (byte *)&temp); - } - - /* write the block */ - fwrite(&temp, sizeof(temp), 1, f); - - /* now write any allocated data following the edict */ - for (field = levelfields; field->name; field++) - { - WriteField2(f, field, (byte *)&level); - } -} - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -ReadEdict(FILE *f, edict_t *ent) -{ - field_t *field; - - fread(ent, sizeof(*ent), 1, f); - - for (field = savefields; field->name; field++) - { - ReadField(f, field, (byte *)ent); - } -} - -/* - * All pointer variables (except function - * pointers) must be handled specially. - */ -void -ReadLevelLocals(FILE *f) -{ - field_t *field; - - fread(&level, sizeof(level), 1, f); - - for (field = levelfields; field->name; field++) - { - ReadField(f, field, (byte *)&level); - } -} - -void -WriteLevel(const char *filename) -{ - int i; - edict_t *ent; - FILE *f; - void *base; - - f = fopen(filename, "wb"); - - if (!f) - { - gi.error("Couldn't open %s", filename); - } - - /* write out edict size for checking */ - i = sizeof(edict_t); - fwrite(&i, sizeof(i), 1, f); - - /* write out a function pointer for checking */ - base = (void *)InitGame; - fwrite(&base, sizeof(base), 1, f); - - /* write out level_locals_t */ - WriteLevelLocals(f); - - /* write out all the entities */ - for (i = 0; i < globals.num_edicts; i++) - { - ent = &g_edicts[i]; - - if (!ent->inuse) - { - continue; - } - - fwrite(&i, sizeof(i), 1, f); - WriteEdict(f, ent); - } - - i = -1; - fwrite(&i, sizeof(i), 1, f); - - fclose(f); -} - -/* - * SpawnEntities will allready have been called on the - * level the same way it was when the level was saved. - * - * That is necessary to get the baselines - * set up identically. - * - * The server will have cleared all of the world links before - * calling ReadLevel. - * - * No clients are connected yet. - */ -void -ReadLevel(const char *filename) -{ - int entnum; - FILE *f; - int i; - void *base; - edict_t *ent; - - f = fopen(filename, "rb"); - - if (!f) - { - gi.error("Couldn't open %s", filename); - } - - /* free any dynamic memory allocated by - loading the level base state */ - gi.FreeTags(TAG_LEVEL); - - /* wipe all the entities */ - memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); - globals.num_edicts = maxclients->value + 1; - - /* check edict size */ - fread(&i, sizeof(i), 1, f); - - if (i != sizeof(edict_t)) - { - fclose(f); - gi.error("ReadLevel: mismatched edict size"); - } - - /* check function pointer base address */ - fread(&base, sizeof(base), 1, f); - - if (base != (void *)InitGame) - { - fclose(f); - gi.error("ReadLevel: function pointers have moved"); - } - - /* load the level locals */ - ReadLevelLocals(f); - - /* load all the entities */ - while (1) - { - if (fread(&entnum, sizeof(entnum), 1, f) != 1) - { - fclose(f); - gi.error("ReadLevel: failed to read entnum"); - } - - if (entnum == -1) - { - break; - } - - if (entnum >= globals.num_edicts) - { - globals.num_edicts = entnum + 1; - } - - ent = &g_edicts[entnum]; - ReadEdict(f, ent); - - /* let the server rebuild world links for this ent */ - memset(&ent->area, 0, sizeof(ent->area)); - gi.linkentity(ent); - } - - fclose(f); - - /* mark all clients as unconnected */ - for (i = 0; i < maxclients->value; i++) - { - ent = &g_edicts[i + 1]; - ent->client = game.clients + i; - ent->client->pers.connected = false; - } - - /* do any load time things at this point */ - for (i = 0; i < globals.num_edicts; i++) - { - ent = &g_edicts[i]; - - if (!ent->inuse) - { - continue; - } - - /* fire any cross-level triggers */ - if (ent->classname) - { - if (strcmp(ent->classname, "target_crosslevel_target") == 0) - { - ent->nextthink = level.time + ent->delay; - } - } - } -} diff --git a/src/ctf/player/client.c b/src/ctf/player/client.c deleted file mode 100644 index 221a5199..00000000 --- a/src/ctf/player/client.c +++ /dev/null @@ -1,2406 +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. - * - * ======================================================================= - * - * Interface between client <-> game and client calculations. - * - * ======================================================================= - */ - -#include "../header/local.h" -#include "../monster/misc/player.h" - -edict_t *pm_passent; - -void ClientUserinfoChanged(edict_t *ent, char *userinfo); -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) - * The normal starting point for a level. - */ -void -SP_info_player_start(edict_t *self) -{ - if (!coop->value) - { - return; - } -} - -/* - * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) - * potential spawning position for deathmatch games - */ -void -SP_info_player_deathmatch(edict_t *self) -{ - if (!deathmatch->value) - { - G_FreeEdict(self); - return; - } - - SP_misc_teleporter_dest(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); - return; - } -} - -/* - * 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 *self) -{ - /* This function cannot be removed - * since the info_player_intermission - * needs a callback function. Like - * every entity. */ -} - -/* ======================================================================= */ - -void -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. - * This function is still here since - * the player is an entity and needs - * a pain callback */ -} - -qboolean -IsFemale(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 true; - } - - if ((info[0] == 'f') || (info[0] == 'F')) - { - return true; - } - - 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 /* unused */, - edict_t *attacker) -{ - int mod; - char *message; - char *message2; - qboolean ff; - - if (!self || !attacker || !inflictor) - { - return; - } - - if (coop->value && attacker && attacker->client) - { - meansOfDeath |= MOD_FRIENDLY_FIRE; - } - - if (deathmatch->value || coop->value) - { - ff = meansOfDeath & MOD_FRIENDLY_FIRE; - mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; - message = NULL; - message2 = ""; - - switch (mod) - { - case MOD_SUICIDE: - message = "suicides"; - break; - case MOD_FALLING: - message = "cratered"; - break; - case MOD_CRUSH: - message = "was squished"; - break; - case MOD_WATER: - message = "sank like a rock"; - break; - case MOD_SLIME: - message = "melted"; - break; - case MOD_LAVA: - message = "does a back flip into the lava"; - break; - case MOD_EXPLOSIVE: - case MOD_BARREL: - message = "blew up"; - break; - case MOD_EXIT: - message = "found a way out"; - break; - case MOD_TARGET_LASER: - message = "saw the light"; - break; - case MOD_TARGET_BLASTER: - message = "got blasted"; - break; - case MOD_BOMB: - case MOD_SPLASH: - 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) - { - switch (mod) - { - case MOD_HELD_GRENADE: - message = "tried to put the pin back in"; - break; - case MOD_HG_SPLASH: - case MOD_G_SPLASH: - - if (IsNeutral(self)) - { - message = "tripped on its own grenade"; - } - else if (IsFemale(self)) - { - message = "tripped on her own grenade"; - } - else - { - message = "tripped on his own grenade"; - } - - break; - case MOD_R_SPLASH: - - if (IsNeutral(self)) - { - message = "blew itself up"; - } - else if (IsFemale(self)) - { - message = "blew herself up"; - } - else - { - message = "blew himself up"; - } - - break; - 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 (IsNeutral(self)) - { - message = "killed itself"; - } - else if (IsFemale(self)) - { - message = "killed herself"; - } - else - { - message = "killed himself"; - } - - break; - } - } - - if (message) - { - gi.bprintf(PRINT_MEDIUM, "%s %s.\n", - self->client->pers.netname, - message); - - if (deathmatch->value) - { - self->client->resp.score--; - } - - self->enemy = NULL; - return; - } - - self->enemy = attacker; - - if (attacker && attacker->client) - { - switch (mod) - { - case MOD_BLASTER: - message = "was blasted by"; - break; - case MOD_SHOTGUN: - message = "was gunned down by"; - break; - case MOD_SSHOTGUN: - message = "was blown away by"; - message2 = "'s super shotgun"; - break; - case MOD_MACHINEGUN: - message = "was machinegunned by"; - break; - case MOD_CHAINGUN: - message = "was cut in half by"; - message2 = "'s chaingun"; - break; - case MOD_GRENADE: - message = "was popped by"; - message2 = "'s grenade"; - break; - case MOD_G_SPLASH: - message = "was shredded by"; - message2 = "'s shrapnel"; - break; - case MOD_ROCKET: - message = "ate"; - message2 = "'s rocket"; - break; - case MOD_R_SPLASH: - message = "almost dodged"; - message2 = "'s rocket"; - break; - case MOD_HYPERBLASTER: - message = "was melted by"; - message2 = "'s hyperblaster"; - break; - case MOD_RAILGUN: - message = "was railed by"; - break; - case MOD_BFG_LASER: - message = "saw the pretty lights from"; - message2 = "'s BFG"; - break; - case MOD_BFG_BLAST: - message = "was disintegrated by"; - message2 = "'s BFG blast"; - break; - case MOD_BFG_EFFECT: - message = "couldn't hide from"; - message2 = "'s BFG"; - break; - case MOD_HANDGRENADE: - message = "caught"; - message2 = "'s handgrenade"; - break; - case MOD_HG_SPLASH: - message = "didn't see"; - message2 = "'s handgrenade"; - break; - case MOD_HELD_GRENADE: - message = "feels"; - message2 = "'s pain"; - break; - case MOD_TELEFRAG: - 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"; - 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); - - if (gamerules && gamerules->value) - { - if (DMGame.Score) - { - if (ff) - { - DMGame.Score(attacker, self, -1); - } - else - { - DMGame.Score(attacker, self, 1); - } - } - - return; - } - - if (deathmatch->value) - { - if (ff) - { - attacker->client->resp.score--; - } - else - { - attacker->client->resp.score++; - } - } - - return; - } - } - } - - gi.bprintf(PRINT_MEDIUM, "%s died.\n", self->client->pers.netname); - - if (deathmatch->value) - { - if (gamerules && gamerules->value) - { - if (DMGame.Score) - { - DMGame.Score(self, self, -1); - } - - return; - } - else - { - self->client->resp.score--; - } - } -} - -void -TossClientWeapon(edict_t *self) -{ - gitem_t *item; - edict_t *drop; - qboolean quad; - qboolean quadfire; - float spread; - - if (!self) - { - return; - } - - if (!deathmatch->value) - { - return; - } - - item = self->client->pers.weapon; - - if (!self->client->pers.inventory[self->client->ammo_index]) - { - item = NULL; - } - - if (item && (strcmp(item->pickup_name, "Blaster") == 0)) - { - item = NULL; - } - - if (!((int)(dmflags->value) & DF_QUAD_DROP)) - { - quad = false; - } - else - { - 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; - } - - if (item) - { - self->client->v_angle[YAW] -= spread; - drop = Drop_Item(self, item); - self->client->v_angle[YAW] += spread; - drop->spawnflags = DROPPED_PLAYER_ITEM; - } - - if (quad) - { - self->client->v_angle[YAW] += spread; - drop = Drop_Item(self, FindItemByClassname("item_quad")); - self->client->v_angle[YAW] -= spread; - drop->spawnflags |= DROPPED_PLAYER_ITEM; - - drop->touch = Touch_Item; - 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; - } -} - -void -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); - } - else if (inflictor && (inflictor != world) && (inflictor != self)) - { - VectorSubtract(inflictor->s.origin, self->s.origin, dir); - } - else - { - self->client->killer_yaw = self->s.angles[YAW]; - return; - } - - if (dir[0]) - { - self->client->killer_yaw = 180 / M_PI *atan2(dir[1], dir[0]); - } - else - { - self->client->killer_yaw = 0; - - if (dir[1] > 0) - { - self->client->killer_yaw = 90; - } - else if (dir[1] < 0) - { - self->client->killer_yaw = -90; - } - } - - if (self->client->killer_yaw < 0) - { - self->client->killer_yaw += 360; - } -} - -void -player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, - int damage, vec3_t point) -{ - int n; - - VectorClear(self->avelocity); - - self->takedamage = DAMAGE_YES; - self->movetype = MOVETYPE_TOSS; - - self->s.modelindex2 = 0; /* remove linked weapon model */ - self->s.modelindex3 = 0; /* remove linked ctf flag */ - - self->s.angles[0] = 0; - self->s.angles[2] = 0; - - self->s.sound = 0; - self->client->weapon_sound = 0; - - self->maxs[2] = -8; - - self->svflags |= SVF_DEADMONSTER; - - if (!self->deadflag) - { - self->client->respawn_time = level.time + 1.0; - LookAtKiller(self, inflictor, attacker); - self->client->ps.pmove.pm_type = PM_DEAD; - ClientObituary(self, inflictor, attacker); - - /* if at start and same team, clear */ - if (ctf->value && (meansOfDeath == MOD_TELEFRAG) && - (self->client->resp.ctf_state < 2) && - (self->client->resp.ctf_team == attacker->client->resp.ctf_team)) - { - attacker->client->resp.score--; - self->client->resp.ctf_state = 0; - } - - CTFFragBonuses(self, inflictor, attacker); - TossClientWeapon(self); - CTFPlayerResetGrapple(self); - CTFDeadDropFlag(self); - CTFDeadDropTech(self); - - if (deathmatch->value && !self->client->showscores) - { - Cmd_Help_f(self); /* show scores */ - } - } - - /* remove powerups */ - self->client->quad_framenum = 0; - self->client->invincible_framenum = 0; - self->client->breather_framenum = 0; - self->client->enviro_framenum = 0; - self->flags &= ~FL_POWER_ARMOR; - - /* clear inventory */ - memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); - - if (self->health < -40) - { - /* gib */ - gi.sound(self, CHAN_BODY, gi.soundindex( - "misc/udeath.wav"), 1, ATTN_NORM, 0); - - for (n = 0; n < 4; n++) - { - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", - damage, GIB_ORGANIC); - } - - ThrowClientHead(self, damage); - self->client->anim_priority = ANIM_DEATH; - self->client->anim_end = 0; - self->takedamage = DAMAGE_NO; - } - else - { - /* normal death */ - if (!self->deadflag) - { - static int i; - - i = (i + 1) % 3; - - /* start a death animation */ - self->client->anim_priority = ANIM_DEATH; - - if (self->client->ps.pmove.pm_flags & PMF_DUCKED) - { - self->s.frame = FRAME_crdeath1 - 1; - self->client->anim_end = FRAME_crdeath5; - } - else - { - switch (i) - { - case 0: - self->s.frame = FRAME_death101 - 1; - self->client->anim_end = FRAME_death106; - break; - case 1: - self->s.frame = FRAME_death201 - 1; - self->client->anim_end = FRAME_death206; - break; - case 2: - self->s.frame = FRAME_death301 - 1; - self->client->anim_end = FRAME_death308; - break; - } - } - - gi.sound(self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", - (rand() % 4) + 1)), 1, ATTN_NORM, 0); - } - } - - self->deadflag = DEAD_DEAD; - - gi.linkentity(self); -} - -/* ======================================================================= */ - -/* - * This is only called when the game - * first initializes in single player, - * but is called after each death and - * level change in deathmatch - */ -void -InitClientPersistant(gclient_t *client) -{ - gitem_t *item; - - memset(&client->pers, 0, sizeof(client->pers)); - - item = FindItem("Blaster"); - client->pers.selected_item = ITEM_INDEX(item); - client->pers.inventory[client->pers.selected_item] = 1; - - client->pers.weapon = item; - client->pers.lastweapon = item; - - item = FindItem("Grapple"); - client->pers.inventory[ITEM_INDEX(item)] = 1; - - client->pers.health = 100; - client->pers.max_health = 100; - - client->pers.max_bullets = 200; - client->pers.max_shells = 100; - client->pers.max_rockets = 50; - client->pers.max_grenades = 50; - client->pers.max_cells = 200; - client->pers.max_slugs = 50; - - client->pers.connected = true; -} - -void -InitClientResp(gclient_t *client) -{ - int ctf_team = client->resp.ctf_team; - qboolean id_state = client->resp.id_state; - - memset(&client->resp, 0, sizeof(client->resp)); - - client->resp.ctf_team = ctf_team; - client->resp.id_state = id_state; - - client->resp.enterframe = level.framenum; - client->resp.coop_respawn = client->pers; - - if (ctf->value && (client->resp.ctf_team < CTF_TEAM1)) - { - CTFAssignTeam(client); - } -} - -/* - * Some information that should be persistant, like health, - * is still stored in the edict structure, so it needs to - * be mirrored out to the client structure before all the - * edicts are wiped. - */ -void -SaveClientData(void) -{ - int i; - edict_t *ent; - - for (i = 0; i < game.maxclients; i++) - { - ent = &g_edicts[1 + i]; - - if (!ent->inuse) - { - continue; - } - - game.clients[i].pers.health = ent->health; - game.clients[i].pers.max_health = ent->max_health; - game.clients[i].pers.savedFlags = - (ent->flags & (FL_GODMODE | FL_NOTARGET | FL_POWER_ARMOR)); - - if (coop->value) - { - game.clients[i].pers.score = ent->client->resp.score; - } - } -} - -void -FetchClientEntData(edict_t *ent) -{ - ent->health = ent->client->pers.health; - ent->max_health = ent->client->pers.max_health; - ent->flags |= ent->client->pers.savedFlags; - - if (coop->value) - { - ent->client->resp.score = ent->client->pers.score; - } -} - -/* SelectSpawnPoint */ - -/* - * Returns the distance to the - * nearest player from the given spot - */ -float -PlayersRangeFromSpot(edict_t *spot) -{ - edict_t *player; - float bestplayerdistance; - vec3_t v; - int n; - float playerdistance; - - bestplayerdistance = 9999999; - - for (n = 1; n <= maxclients->value; n++) - { - player = &g_edicts[n]; - - if (!player->inuse) - { - continue; - } - - if (player->health <= 0) - { - continue; - } - - VectorSubtract(spot->s.origin, player->s.origin, v); - playerdistance = VectorLength(v); - - if (playerdistance < bestplayerdistance) - { - bestplayerdistance = playerdistance; - } - } - - return bestplayerdistance; -} - -/* - * go to a random point, but NOT the two - * points closest to other players - */ -edict_t * -SelectRandomDeathmatchSpawnPoint(void) -{ - edict_t *spot, *spot1, *spot2; - int count = 0; - int selection; - float range, range1, range2; - - spot = NULL; - range1 = range2 = 99999; - spot1 = spot2 = NULL; - - while ((spot = G_Find(spot, FOFS(classname), - "info_player_deathmatch")) != NULL) - { - count++; - range = PlayersRangeFromSpot(spot); - - if (range < range1) - { - range1 = range; - spot1 = spot; - } - else if (range < range2) - { - range2 = range; - spot2 = spot; - } - } - - if (!count) - { - return NULL; - } - - if (count <= 2) - { - spot1 = spot2 = NULL; - } - else - { - count -= 2; - } - - selection = rand() % count; - - spot = NULL; - - do - { - spot = G_Find(spot, FOFS(classname), "info_player_deathmatch"); - - if ((spot == spot1) || (spot == spot2)) - { - selection++; - } - } - while (selection--); - - return spot; -} - -edict_t * -SelectFarthestDeathmatchSpawnPoint(void) -{ - edict_t *bestspot; - float bestdistance, bestplayerdistance; - edict_t *spot; - - spot = NULL; - bestspot = NULL; - bestdistance = 0; - - while ((spot = G_Find(spot, FOFS(classname), - "info_player_deathmatch")) != NULL) - { - 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_Find(NULL, FOFS(classname), "info_player_deathmatch"); - - return spot; -} - -edict_t * -SelectDeathmatchSpawnPoint(void) -{ - if ((int)(dmflags->value) & DF_SPAWN_FARTHEST) - { - return SelectFarthestDeathmatchSpawnPoint(); - } - else - { - return SelectRandomDeathmatchSpawnPoint(); - } -} - -edict_t * -SelectCoopSpawnPoint(edict_t *ent) -{ - int index; - edict_t *spot = NULL; - char *target; - - index = ent->client - game.clients; - - /* player 0 starts in normal player spawn point */ - if (!index) - { - return NULL; - } - - spot = NULL; - - /* assume there are four coop spots at each spawnpoint */ - while (1) - { - spot = G_Find(spot, FOFS(classname), "info_player_coop"); - - if (!spot) - { - return NULL; /* we didn't have enough... */ - } - - target = spot->targetname; - - if (!target) - { - target = ""; - } - - if (Q_stricmp(game.spawnpoint, target) == 0) - { - /* this is a coop spawn point for one of the clients here */ - index--; - - if (!index) - { - return spot; /* this is it */ - } - } - } - - return spot; -} - -/* - * Chooses a player start, deathmatch start, coop start, etc - */ -void -SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles) -{ - edict_t *spot = NULL; - - if (deathmatch->value) - { - if (ctf->value) - { - spot = SelectCTFSpawnPoint(ent); - } - else - { - spot = SelectDeathmatchSpawnPoint(); - } - } - else if (coop->value) - { - spot = SelectCoopSpawnPoint(ent); - } - - /* find a single player start spot */ - if (!spot) - { - while ((spot = G_Find(spot, FOFS(classname), "info_player_start")) != NULL) - { - if (!game.spawnpoint[0] && !spot->targetname) - { - break; - } - - if (!game.spawnpoint[0] || !spot->targetname) - { - continue; - } - - if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) - { - break; - } - } - - if (!spot) - { - if (!game.spawnpoint[0]) - { - /* there wasn't a spawnpoint without a target, so use any */ - spot = G_Find(spot, FOFS(classname), "info_player_start"); - } - - if (!spot) - { - gi.error("Couldn't find spawn point %s\n", game.spawnpoint); - } - } - } - - VectorCopy(spot->s.origin, origin); - origin[2] += 9; - VectorCopy(spot->s.angles, angles); -} - -/* ====================================================================== */ - -void -InitBodyQue(void) -{ - int i; - edict_t *ent; - - level.body_que = 0; - - for (i = 0; i < BODY_QUEUE_SIZE; i++) - { - ent = G_Spawn(); - ent->classname = "bodyque"; - } -} - -void -body_die(edict_t *self, edict_t *inflictor, edict_t *attacker, - int damage, vec3_t point) -{ - int n; - - if (self->health < -40) - { - gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - for (n = 0; n < 4; n++) - { - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", - damage, GIB_ORGANIC); - } - - self->s.origin[2] -= 48; - ThrowClientHead(self, damage); - self->takedamage = DAMAGE_NO; - } -} - -void -CopyToBodyQue(edict_t *ent) -{ - edict_t *body; - - /* grab a body que and cycle to the next one */ - body = &g_edicts[(int)maxclients->value + level.body_que + 1]; - level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; - - gi.unlinkentity(ent); - - gi.unlinkentity(body); - body->s = ent->s; - body->s.number = body - g_edicts; - - body->svflags = ent->svflags; - VectorCopy(ent->mins, body->mins); - VectorCopy(ent->maxs, body->maxs); - VectorCopy(ent->absmin, body->absmin); - VectorCopy(ent->absmax, body->absmax); - VectorCopy(ent->size, body->size); - body->solid = ent->solid; - body->clipmask = ent->clipmask; - body->owner = ent->owner; - body->movetype = ent->movetype; - - body->die = body_die; - body->takedamage = DAMAGE_YES; - - gi.linkentity(body); -} - -void -respawn(edict_t *self) -{ - if (deathmatch->value || coop->value) - { - if (self->movetype != MOVETYPE_NOCLIP) - { - CopyToBodyQue(self); - } - - self->svflags &= ~SVF_NOCLIENT; - PutClientInServer(self); - - /* add a teleportation effect */ - self->s.event = EV_PLAYER_TELEPORT; - - /* hold in place briefly */ - self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; - self->client->ps.pmove.pm_time = 14; - - self->client->respawn_time = level.time; - - return; - } - - /* restart the entire server */ - gi.AddCommandString("menu_loadgame\n"); -} - -/* ============================================================== */ - -/* - * Called when a player connects - * to a server or respawns in - * a deathmatch. - */ -void -PutClientInServer(edict_t *ent) -{ - vec3_t mins = {-16, -16, -24}; - vec3_t maxs = {16, 16, 32}; - int index; - vec3_t spawn_origin, spawn_angles; - gclient_t *client; - int i; - client_persistant_t saved; - client_respawn_t resp; - - /* find a spawn point do it before setting health - back up, so farthest ranging doesn't count this - client */ - SelectSpawnPoint(ent, spawn_origin, spawn_angles); - - index = ent - g_edicts - 1; - client = ent->client; - - /* deathmatch wipes most client data every spawn */ - if (deathmatch->value) - { - char userinfo[MAX_INFO_STRING]; - - resp = client->resp; - memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); - InitClientPersistant(client); - ClientUserinfoChanged(ent, userinfo); - } - else if (coop->value) - { - int n; - char userinfo[MAX_INFO_STRING]; - - resp = client->resp; - memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); - - /* this is kind of ugly, but it's how we want to handle keys in coop */ - for (n = 0; n < MAX_ITEMS; n++) - { - if (itemlist[n].flags & IT_KEY) - { - resp.coop_respawn.inventory[n] = client->pers.inventory[n]; - } - } - - client->pers = resp.coop_respawn; - ClientUserinfoChanged(ent, userinfo); - - if (resp.score > client->pers.score) - { - client->pers.score = resp.score; - } - } - else - { - memset(&resp, 0, sizeof(resp)); - } - - /* clear everything but the persistant data */ - saved = client->pers; - memset(client, 0, sizeof(*client)); - client->pers = saved; - - if (client->pers.health <= 0) - { - InitClientPersistant(client); - } - - client->resp = resp; - - /* copy some data from the client to the entity */ - FetchClientEntData(ent); - - /* clear entity values */ - ent->groundentity = NULL; - ent->client = &game.clients[index]; - ent->takedamage = DAMAGE_AIM; - ent->movetype = MOVETYPE_WALK; - ent->viewheight = 22; - ent->inuse = true; - ent->classname = "player"; - ent->mass = 200; - ent->solid = SOLID_BBOX; - ent->deadflag = DEAD_NO; - ent->air_finished = level.time + 12; - ent->clipmask = MASK_PLAYERSOLID; - ent->model = "players/male/tris.md2"; - ent->pain = player_pain; - ent->die = player_die; - ent->waterlevel = 0; - ent->watertype = 0; - ent->flags &= ~FL_NO_KNOCKBACK; - ent->svflags &= ~SVF_DEADMONSTER; - - VectorCopy(mins, ent->mins); - VectorCopy(maxs, ent->maxs); - VectorClear(ent->velocity); - - /* clear playerstate values */ - memset(&ent->client->ps, 0, sizeof(client->ps)); - - client->ps.pmove.origin[0] = spawn_origin[0] * 8; - client->ps.pmove.origin[1] = spawn_origin[1] * 8; - client->ps.pmove.origin[2] = spawn_origin[2] * 8; - client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; - - if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) - { - client->ps.fov = 90; - } - else - { - client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); - - if (client->ps.fov < 1) - { - client->ps.fov = 90; - } - else if (client->ps.fov > 160) - { - client->ps.fov = 160; - } - } - - client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); - - /* clear entity state values */ - ent->s.effects = 0; - ent->s.skinnum = ent - g_edicts - 1; - ent->s.modelindex = 255; /* will use the skin specified model */ - ent->s.modelindex2 = 255; /* custom gun model */ - ent->s.skinnum = ent - g_edicts - 1; - - ent->s.frame = 0; - VectorCopy(spawn_origin, ent->s.origin); - ent->s.origin[2] += 1; /* make sure off ground */ - VectorCopy(ent->s.origin, ent->s.old_origin); - - /* set the delta angle */ - for (i = 0; i < 3; i++) - { - client->ps.pmove.delta_angles[i] = ANGLE2SHORT( - spawn_angles[i] - client->resp.cmd_angles[i]); - } - - ent->s.angles[PITCH] = 0; - ent->s.angles[YAW] = spawn_angles[YAW]; - ent->s.angles[ROLL] = 0; - VectorCopy(ent->s.angles, client->ps.viewangles); - VectorCopy(ent->s.angles, client->v_angle); - - if (CTFStartClient(ent)) - { - return; - } - - if (!KillBox(ent)) - { - /* could't spawn in? */ - } - - gi.linkentity(ent); - - /* force the current weapon up */ - client->newweapon = client->pers.weapon; - ChangeWeapon(ent); -} - -/* - * A client has just connected to - * the server in deathmatch mode, - * so clear everything out before - * starting them. - */ -void -ClientBeginDeathmatch(edict_t *ent) -{ - G_InitEdict(ent); - - InitClientResp(ent->client); - - /* locate ent at a spawn point */ - PutClientInServer(ent); - - if (level.intermissiontime) - { - MoveClientToIntermission(ent); - } - else - { - /* send effect */ - gi.WriteByte(svc_muzzleflash); - gi.WriteShort(ent - g_edicts); - gi.WriteByte(MZ_LOGIN); - gi.multicast(ent->s.origin, MULTICAST_PVS); - } - - gi.bprintf(PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); - - /* make sure all view stuff is valid */ - ClientEndServerFrame(ent); -} - -/* - * called when a client has finished connecting, and is ready - * to be placed into the game. This will happen every level load. - */ -void -ClientBegin(edict_t *ent) -{ - int i; - - ent->client = game.clients + (ent - g_edicts - 1); - - if (deathmatch->value) - { - ClientBeginDeathmatch(ent); - return; - } - - /* if there is already a body waiting for - us (a loadgame), just take it, otherwise - spawn one from scratch */ - if (ent->inuse == true) - { - /* the client has cleared the client side viewangles upon - connecting to the server, which is different than the - state when the game is saved, so we need to compensate - with deltaangles */ - for (i = 0; i < 3; i++) - { - ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT( - ent->client->ps.viewangles[i]); - } - } - else - { - /* a spawn point will completely reinitialize the entity - except for the persistant data that was initialized at - ClientConnect() time */ - G_InitEdict(ent); - ent->classname = "player"; - InitClientResp(ent->client); - PutClientInServer(ent); - } - - if (level.intermissiontime) - { - MoveClientToIntermission(ent); - } - else - { - /* send effect if in a multiplayer game */ - if (game.maxclients > 1) - { - gi.WriteByte(svc_muzzleflash); - gi.WriteShort(ent - g_edicts); - gi.WriteByte(MZ_LOGIN); - gi.multicast(ent->s.origin, MULTICAST_PVS); - - gi.bprintf(PRINT_HIGH, "%s entered the game\n", - ent->client->pers.netname); - } - } - - /* make sure all view stuff is valid */ - ClientEndServerFrame(ent); -} - -/* - * Called whenever the player updates a userinfo variable. - * - * The game can override any of the settings in place - * (forcing skins or names, etc) before copying it off. - */ -void -ClientUserinfoChanged(edict_t *ent, char *userinfo) -{ - char *s; - int playernum; - - /* check for malformed or illegal info strings */ - if (!Info_Validate(userinfo)) - { - strcpy(userinfo, "\\name\\badinfo\\skin\\male/grunt"); - } - - /* set name */ - s = Info_ValueForKey(userinfo, "name"); - strncpy(ent->client->pers.netname, s, sizeof(ent->client->pers.netname) - 1); - - /* set skin */ - s = Info_ValueForKey(userinfo, "skin"); - - playernum = ent - g_edicts - 1; - - /* combine name and skin into a configstring */ - if (ctf->value) - { - CTFAssignSkin(ent, s); - } - else - { - gi.configstring(CS_PLAYERSKINS + playernum, - va("%s\\%s", ent->client->pers.netname, s)); - } - - /* set player name field (used in id_state view) */ - gi.configstring(CS_GENERAL + playernum, ent->client->pers.netname); - - /* fov */ - if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) - { - ent->client->ps.fov = 90; - } - else - { - ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); - - if (ent->client->ps.fov < 1) - { - ent->client->ps.fov = 90; - } - else if (ent->client->ps.fov > 160) - { - ent->client->ps.fov = 160; - } - } - - /* handedness */ - s = Info_ValueForKey(userinfo, "hand"); - - if (strlen(s)) - { - ent->client->pers.hand = atoi(s); - } - - /* save off the userinfo in case we want to check something later */ - strncpy(ent->client->pers.userinfo, userinfo, - sizeof(ent->client->pers.userinfo) - 1); -} - -/* - * Called when a player begins connecting to the server. - * The game can refuse entrance to a client by returning false. - * If the client is allowed, the connection process will continue - * and eventually get to ClientBegin() - * Changing levels will NOT cause this to be called again, but - * loadgames will. - */ -qboolean -ClientConnect(edict_t *ent, char *userinfo) -{ - char *value; - - /* check to see if they are on the banned IP list */ - value = Info_ValueForKey(userinfo, "ip"); - - if (SV_FilterPacket(value)) - { - Info_SetValueForKey(userinfo, "rejmsg", "Banned."); - return false; - } - - /* check for a password */ - value = Info_ValueForKey(userinfo, "password"); - - if (*password->string && strcmp(password->string, "none") && - strcmp(password->string, value)) - { - Info_SetValueForKey(userinfo, "rejmsg", - "Password required or incorrect."); - return false; - } - - /* they can connect */ - ent->client = game.clients + (ent - g_edicts - 1); - - /* if there is already a body waiting for us (a loadgame), - just take it, otherwise spawn one from scratch */ - if (ent->inuse == false) - { - /* clear the respawning variables */ - ent->client->resp.ctf_team = -1; - ent->client->resp.id_state = true; - - InitClientResp(ent->client); - - if (!game.autosaved || !ent->client->pers.weapon) - { - InitClientPersistant(ent->client); - } - } - - ClientUserinfoChanged(ent, userinfo); - - if (game.maxclients > 1) - { - gi.dprintf("%s connected\n", ent->client->pers.netname); - } - - ent->client->pers.connected = true; - return true; -} - -/* - * Called when a player drops from the server. - * Will not be called between levels. - */ -void -ClientDisconnect(edict_t *ent) -{ - int playernum; - - if (!ent->client) - { - return; - } - - gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); - - CTFDeadDropFlag(ent); - CTFDeadDropTech(ent); - - /* send effect */ - gi.WriteByte(svc_muzzleflash); - gi.WriteShort(ent - g_edicts); - gi.WriteByte(MZ_LOGOUT); - gi.multicast(ent->s.origin, MULTICAST_PVS); - - gi.unlinkentity(ent); - ent->s.modelindex = 0; - ent->solid = SOLID_NOT; - ent->inuse = false; - ent->classname = "disconnected"; - ent->client->pers.connected = false; - - playernum = ent - g_edicts - 1; - gi.configstring(CS_PLAYERSKINS + playernum, ""); -} - -/* ============================================================== */ - -edict_t *pm_passent; - -/* - * pmove doesn't need to know about - * passent and contentmask - */ -trace_t -PM_trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) -{ - if (pm_passent->health > 0) - { - return gi.trace(start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); - } - else - { - return gi.trace(start, mins, maxs, end, pm_passent, MASK_DEADSOLID); - } -} - -unsigned -CheckBlock(void *b, int c) -{ - int v, i; - - v = 0; - - for (i = 0; i < c; i++) - { - v += ((byte *)b)[i]; - } - - return v; -} - -void -PrintPmove(pmove_t *pm) -{ - unsigned c1, c2; - - c1 = CheckBlock(&pm->s, sizeof(pm->s)); - c2 = CheckBlock(&pm->cmd, sizeof(pm->cmd)); - Com_Printf("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); -} - -/* - * This will be called once for each client frame, which will - * usually be a couple times for each server frame. - */ -void -ClientThink(edict_t *ent, usercmd_t *ucmd) -{ - gclient_t *client; - edict_t *other; - int i, j; - pmove_t pm; - - level.current_entity = ent; - client = ent->client; - - if (level.intermissiontime) - { - client->ps.pmove.pm_type = PM_FREEZE; - - /* can exit intermission after five seconds */ - if ((level.time > level.intermissiontime + 5.0) && - (ucmd->buttons & BUTTON_ANY)) - { - level.exitintermission = true; - } - - return; - } - - pm_passent = ent; - - if (ent->client->chase_target) - { - client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); - client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); - client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); - return; - } - - /* set up for pmove */ - memset(&pm, 0, sizeof(pm)); - - if (ent->movetype == MOVETYPE_NOCLIP) - { - client->ps.pmove.pm_type = PM_SPECTATOR; - } - else if (ent->s.modelindex != 255) - { - client->ps.pmove.pm_type = PM_GIB; - } - else if (ent->deadflag) - { - client->ps.pmove.pm_type = PM_DEAD; - } - else - { - client->ps.pmove.pm_type = PM_NORMAL; - } - - client->ps.pmove.gravity = sv_gravity->value; - pm.s = client->ps.pmove; - - for (i = 0; i < 3; i++) - { - pm.s.origin[i] = ent->s.origin[i] * 8; - /* save to an int first, in case the short overflows - * so we get defined behavior (at least with -fwrapv) */ - int tmpVel = ent->velocity[i] * 8; - pm.s.velocity[i] = tmpVel; - } - - if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) - { - pm.snapinitial = true; - } - - pm.cmd = *ucmd; - - pm.trace = PM_trace; /* adds default parms */ - pm.pointcontents = gi.pointcontents; - - /* perform a pmove */ - gi.Pmove(&pm); - - /* save results of pmove */ - client->ps.pmove = pm.s; - client->old_pmove = pm.s; - - for (i = 0; i < 3; i++) - { - ent->s.origin[i] = pm.s.origin[i] * 0.125; - ent->velocity[i] = pm.s.velocity[i] * 0.125; - } - - VectorCopy(pm.mins, ent->mins); - VectorCopy(pm.maxs, ent->maxs); - - client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); - client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); - client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); - - if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && - (pm.waterlevel == 0)) - { - gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); - PlayerNoise(ent, ent->s.origin, PNOISE_SELF); - } - - ent->viewheight = pm.viewheight; - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; - ent->groundentity = pm.groundentity; - - if (pm.groundentity) - { - ent->groundentity_linkcount = pm.groundentity->linkcount; - } - - if (ent->deadflag) - { - client->ps.viewangles[ROLL] = 40; - client->ps.viewangles[PITCH] = -15; - client->ps.viewangles[YAW] = client->killer_yaw; - } - else - { - VectorCopy(pm.viewangles, client->v_angle); - VectorCopy(pm.viewangles, client->ps.viewangles); - } - - if (client->ctf_grapple) - { - CTFGrapplePull(client->ctf_grapple); - } - - gi.linkentity(ent); - - if (ent->movetype != MOVETYPE_NOCLIP) - { - G_TouchTriggers(ent); - } - - /* touch other objects */ - for (i = 0; i < pm.numtouch; i++) - { - other = pm.touchents[i]; - - for (j = 0; j < i; j++) - { - if (pm.touchents[j] == other) - { - break; - } - } - - if (j != i) - { - continue; /* duplicated */ - } - - if (!other->touch) - { - continue; - } - - other->touch(other, ent, NULL, NULL); - } - - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - client->latched_buttons |= client->buttons & ~client->oldbuttons; - - /* save light level the player is - standing on for monster sighting AI */ - ent->light_level = ucmd->lightlevel; - - /* fire weapon from final position if needed */ - if (client->latched_buttons & BUTTON_ATTACK - && (ent->movetype != MOVETYPE_NOCLIP)) - { - if (!client->weapon_thunk) - { - client->weapon_thunk = true; - Think_Weapon(ent); - } - } - - /* regen tech */ - CTFApplyRegeneration(ent); - - for (i = 1; i <= maxclients->value; i++) - { - other = g_edicts + i; - - if (other->inuse && (other->client->chase_target == ent)) - { - UpdateChaseCam(other); - } - } - - if (client->menudirty && (client->menutime <= level.time)) - { - PMenu_Do_Update(ent); - gi.unicast(ent, true); - client->menutime = level.time; - client->menudirty = false; - } -} - -/* - * This will be called once for each server frame, - * before running any other entities in the world. - */ -void -ClientBeginServerFrame(edict_t *ent) -{ - gclient_t *client; - int buttonMask; - - if (level.intermissiontime) - { - return; - } - - client = ent->client; - - /* run weapon animations if it hasn't been done by a ucmd_t */ - if (!client->weapon_thunk - && (ent->movetype != MOVETYPE_NOCLIP)) - { - Think_Weapon(ent); - } - else - { - client->weapon_thunk = false; - } - - if (ent->deadflag) - { - /* wait for any button just going down */ - if (level.time > client->respawn_time) - { - /* in deathmatch, only wait for attack button */ - if (deathmatch->value) - { - buttonMask = BUTTON_ATTACK; - } - else - { - buttonMask = -1; - } - - if ((client->latched_buttons & buttonMask) || - (deathmatch->value && - ((int)dmflags->value & DF_FORCE_RESPAWN)) || - CTFMatchOn()) - { - respawn(ent); - client->latched_buttons = 0; - } - } - - return; - } - - /* add player trail so monsters can follow */ - if (!deathmatch->value) - { - if (!visible(ent, PlayerTrail_LastSpot())) - { - PlayerTrail_Add(ent->s.old_origin); - } - } - - client->latched_buttons = 0; -} - -/* - * This is called to clean up the pain daemons that - * the disruptor attaches to clients to damage them. - */ -void -RemoveAttackingPainDaemons(edict_t *self) -{ - edict_t *tracker; - - if (!self) - { - return; - } - - tracker = G_Find(NULL, FOFS(classname), "pain daemon"); - - while (tracker) - { - if (tracker->enemy == self) - { - G_FreeEdict(tracker); - } - - tracker = G_Find(tracker, FOFS(classname), "pain daemon"); - } - - if (self->client) - { - self->client->tracker_pain_framenum = 0; - } -} diff --git a/src/ctf/player/view.c b/src/ctf/player/view.c deleted file mode 100644 index cd1d8354..00000000 --- a/src/ctf/player/view.c +++ /dev/null @@ -1,1381 +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. - * - * ======================================================================= - * - * The "camera" through which the player looks into the game. - * - * ======================================================================= - */ - -#include "../header/local.h" -#include "../monster/misc/player.h" - -static edict_t *current_player; -static gclient_t *current_client; - -static vec3_t forward, right, up; -static float xyspeed; - -static float bobmove; -static int bobcycle; /* odd cycles are right foot going forward */ -static float bobfracsin; /* sin(bobfrac*M_PI) */ - -float -SV_CalcRoll(vec3_t angles, vec3_t velocity) -{ - float sign; - float side; - float value; - - side = DotProduct(velocity, right); - sign = side < 0 ? -1 : 1; - side = fabs(side); - - value = sv_rollangle->value; - - if (side < sv_rollspeed->value) - { - side = side * value / sv_rollspeed->value; - } - else - { - side = value; - } - - return side * sign; -} - -/* - * Handles color blends and view kicks - */ -void -P_DamageFeedback(edict_t *player) -{ - gclient_t *client; - float side; - float realcount, count, kick; - vec3_t v; - int r, l; - static vec3_t power_color = {0.0, 1.0, 0.0}; - static vec3_t acolor = {1.0, 1.0, 1.0}; - static vec3_t bcolor = {1.0, 0.0, 0.0}; - - if (!player) - { - return; - } - - /* death/gib sound is now aggregated and played here */ - if (player->sounds) - { - gi.sound (player, CHAN_VOICE, player->sounds, 1, ATTN_NORM, 0); - player->sounds = 0; - } - - client = player->client; - - /* flash the backgrounds behind the status numbers */ - client->ps.stats[STAT_FLASHES] = 0; - - if (client->damage_blood) - { - client->ps.stats[STAT_FLASHES] |= 1; - } - - if (client->damage_armor && !(player->flags & FL_GODMODE) && - (client->invincible_framenum <= level.framenum)) - { - client->ps.stats[STAT_FLASHES] |= 2; - } - - /* total points of damage shot at the player this frame */ - count = - (client->damage_blood + client->damage_armor + client->damage_parmor); - - if (count == 0) - { - return; /* didn't take any damage */ - } - - /* start a pain animation if still in the player model */ - if ((client->anim_priority < ANIM_PAIN) && (player->s.modelindex == 255)) - { - static int i; - - client->anim_priority = ANIM_PAIN; - - if (client->ps.pmove.pm_flags & PMF_DUCKED) - { - player->s.frame = FRAME_crpain1 - 1; - client->anim_end = FRAME_crpain4; - } - else - { - i = (i + 1) % 3; - - switch (i) - { - case 0: - player->s.frame = FRAME_pain101 - 1; - client->anim_end = FRAME_pain104; - break; - case 1: - player->s.frame = FRAME_pain201 - 1; - client->anim_end = FRAME_pain204; - break; - case 2: - player->s.frame = FRAME_pain301 - 1; - client->anim_end = FRAME_pain304; - break; - } - } - } - - realcount = count; - - if (count < 10) - { - count = 10; /* allways make a visible effect */ - } - - /* play an apropriate pain sound */ - if ((level.time > player->pain_debounce_time) && - !(player->flags & FL_GODMODE) && - (client->invincible_framenum <= level.framenum)) - { - r = 1 + (rand() & 1); - player->pain_debounce_time = level.time + 0.7; - - if (player->health < 25) - { - l = 25; - } - else if (player->health < 50) - { - l = 50; - } - else if (player->health < 75) - { - l = 75; - } - else - { - l = 100; - } - - gi.sound(player, CHAN_VOICE, gi.soundindex(va("*pain%i_%i.wav", - l, r)), 1, ATTN_NORM, 0); - } - - /* the total alpha of the blend is allways proportional to count */ - if (client->damage_alpha < 0) - { - client->damage_alpha = 0; - } - - client->damage_alpha += count * 0.01; - - if (client->damage_alpha < 0.2) - { - client->damage_alpha = 0.2; - } - - if (client->damage_alpha > 0.6) - { - client->damage_alpha = 0.6; /* don't go too saturated */ - } - - /* the color of the blend will vary based - on how much was absorbed by different armors */ - VectorClear(v); - - if (client->damage_parmor) - { - VectorMA(v, (float)client->damage_parmor / realcount, power_color, v); - } - - if (client->damage_armor) - { - VectorMA(v, (float)client->damage_armor / realcount, acolor, v); - } - - if (client->damage_blood) - { - VectorMA(v, (float)client->damage_blood / realcount, bcolor, v); - } - - VectorCopy(v, client->damage_blend); - - /* calculate view angle kicks */ - kick = abs(client->damage_knockback); - - if (kick && (player->health > 0)) /* kick of 0 means no view adjust at all */ - { - kick = kick * 100 / player->health; - - if (kick < count * 0.5) - { - kick = count * 0.5; - } - - if (kick > 50) - { - kick = 50; - } - - VectorSubtract(client->damage_from, player->s.origin, v); - VectorNormalize(v); - - side = DotProduct(v, right); - client->v_dmg_roll = kick * side * 0.3; - - side = -DotProduct(v, forward); - client->v_dmg_pitch = kick * side * 0.3; - - client->v_dmg_time = level.time + DAMAGE_TIME; - } - - /* clear totals */ - client->damage_blood = 0; - client->damage_armor = 0; - client->damage_parmor = 0; - client->damage_knockback = 0; -} - -/* - * Auto pitching on slopes? - * - * fall from 128: 400 = 160000 - * fall from 256: 580 = 336400 - * fall from 384: 720 = 518400 - * fall from 512: 800 = 640000 - * fall from 640: 960 = - * - * damage = deltavelocity*deltavelocity * 0.0001 - */ -void -SV_CalcViewOffset(edict_t *ent) -{ - float *angles; - float bob; - float ratio; - float delta; - vec3_t v; - - /* =================================== */ - - /* base angles */ - angles = ent->client->ps.kick_angles; - - /* if dead, fix the angle and don't add any kick */ - if (ent->deadflag) - { - VectorClear(angles); - - ent->client->ps.viewangles[ROLL] = 40; - ent->client->ps.viewangles[PITCH] = -15; - ent->client->ps.viewangles[YAW] = ent->client->killer_yaw; - } - else - { - /* add angles based on weapon kick */ - VectorCopy(ent->client->kick_angles, angles); - - /* add angles based on damage kick */ - ratio = (ent->client->v_dmg_time - level.time) / DAMAGE_TIME; - - if (ratio < 0) - { - ratio = 0; - ent->client->v_dmg_pitch = 0; - ent->client->v_dmg_roll = 0; - } - - angles[PITCH] += ratio * ent->client->v_dmg_pitch; - angles[ROLL] += ratio * ent->client->v_dmg_roll; - - /* add pitch based on fall kick */ - ratio = (ent->client->fall_time - level.time) / FALL_TIME; - - if (ratio < 0) - { - ratio = 0; - } - - angles[PITCH] += ratio * ent->client->fall_value; - - /* add angles based on velocity */ - delta = DotProduct(ent->velocity, forward); - angles[PITCH] += delta * run_pitch->value; - - delta = DotProduct(ent->velocity, right); - angles[ROLL] += delta * run_roll->value; - - /* add angles based on bob */ - delta = bobfracsin * bob_pitch->value * xyspeed; - - if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) - { - delta *= 6; /* crouching */ - } - - angles[PITCH] += delta; - delta = bobfracsin * bob_roll->value * xyspeed; - - if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) - { - delta *= 6; /* crouching */ - } - - if (bobcycle & 1) - { - delta = -delta; - } - - angles[ROLL] += delta; - } - - /* =================================== */ - - /* base origin */ - VectorClear(v); - - /* add view height */ - v[2] += ent->viewheight; - - /* add fall height */ - ratio = (ent->client->fall_time - level.time) / FALL_TIME; - - if (ratio < 0) - { - ratio = 0; - } - - v[2] -= ratio * ent->client->fall_value * 0.4; - - /* add bob height */ - bob = bobfracsin * xyspeed * bob_up->value; - - if (bob > 6) - { - bob = 6; - } - - v[2] += bob; - - /* add kick offset */ - VectorAdd(v, ent->client->kick_origin, v); - - /* absolutely bound offsets so the view - can never be outside the player box */ - if (v[0] < -14) - { - v[0] = -14; - } - else if (v[0] > 14) - { - v[0] = 14; - } - - if (v[1] < -14) - { - v[1] = -14; - } - else if (v[1] > 14) - { - v[1] = 14; - } - - if (v[2] < -22) - { - v[2] = -22; - } - else if (v[2] > 30) - { - v[2] = 30; - } - - VectorCopy(v, ent->client->ps.viewoffset); -} - -void -SV_CalcGunOffset(edict_t *ent) -{ - int i; - float delta; - - /* gun angles from bobbing */ - ent->client->ps.gunangles[ROLL] = xyspeed * bobfracsin * 0.005; - ent->client->ps.gunangles[YAW] = xyspeed * bobfracsin * 0.01; - - if (bobcycle & 1) - { - ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL]; - ent->client->ps.gunangles[YAW] = -ent->client->ps.gunangles[YAW]; - } - - ent->client->ps.gunangles[PITCH] = xyspeed * bobfracsin * 0.005; - - /* gun angles from delta movement */ - for (i = 0; i < 3; i++) - { - delta = ent->client->oldviewangles[i] - ent->client->ps.viewangles[i]; - - if (delta > 180) - { - delta -= 360; - } - - if (delta < -180) - { - delta += 360; - } - - if (delta > 45) - { - delta = 45; - } - - if (delta < -45) - { - delta = -45; - } - - if (i == YAW) - { - ent->client->ps.gunangles[ROLL] += 0.1 * delta; - } - - ent->client->ps.gunangles[i] += 0.2 * delta; - } - - /* gun height */ - VectorClear(ent->client->ps.gunoffset); - - /* gun_x / gun_y / gun_z are development tools */ - for (i = 0; i < 3; i++) - { - ent->client->ps.gunoffset[i] += forward[i] * (gun_y->value); - ent->client->ps.gunoffset[i] += right[i] * gun_x->value; - ent->client->ps.gunoffset[i] += up[i] * (-gun_z->value); - } -} - -void -SV_AddBlend(float r, float g, float b, float a, float *v_blend) -{ - float a2, a3; - - if (a <= 0) - { - return; - } - - a2 = v_blend[3] + (1 - v_blend[3]) * a; /* new total alpha */ - a3 = v_blend[3] / a2; /* fraction of color from old */ - - v_blend[0] = v_blend[0] * a3 + r * (1 - a3); - v_blend[1] = v_blend[1] * a3 + g * (1 - a3); - v_blend[2] = v_blend[2] * a3 + b * (1 - a3); - v_blend[3] = a2; -} - -void -SV_CalcBlend(edict_t *ent) -{ - int contents; - vec3_t vieworg; - int remaining; - - ent->client->ps.blend[0] = ent->client->ps.blend[1] = - ent->client->ps.blend[2] = - ent->client->ps.blend[3] = 0; - - /* add for contents */ - VectorAdd(ent->s.origin, ent->client->ps.viewoffset, vieworg); - contents = gi.pointcontents(vieworg); - - if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) - { - ent->client->ps.rdflags |= RDF_UNDERWATER; - } - else - { - ent->client->ps.rdflags &= ~RDF_UNDERWATER; - } - - if (contents & (CONTENTS_SOLID | CONTENTS_LAVA)) - { - SV_AddBlend(1.0, 0.3, 0.0, 0.6, ent->client->ps.blend); - } - else if (contents & CONTENTS_SLIME) - { - SV_AddBlend(0.0, 0.1, 0.05, 0.6, ent->client->ps.blend); - } - else if (contents & CONTENTS_WATER) - { - SV_AddBlend(0.5, 0.3, 0.2, 0.4, ent->client->ps.blend); - } - - /* add for powerups */ - if (ent->client->quad_framenum > level.framenum) - { - remaining = ent->client->quad_framenum - level.framenum; - - if (remaining == 30) /* beginning to fade */ - { - gi.sound(ent, CHAN_ITEM, gi.soundindex( - "items/damage2.wav"), 1, ATTN_NORM, 0); - } - - if ((remaining > 30) || (remaining & 4)) - { - SV_AddBlend(0, 0, 1, 0.08, ent->client->ps.blend); - } - } - else if (ent->client->invincible_framenum > level.framenum) - { - remaining = ent->client->invincible_framenum - level.framenum; - - if (remaining == 30) /* beginning to fade */ - { - gi.sound(ent, CHAN_ITEM, gi.soundindex( - "items/protect2.wav"), 1, ATTN_NORM, 0); - } - - if ((remaining > 30) || (remaining & 4)) - { - SV_AddBlend(1, 1, 0, 0.08, ent->client->ps.blend); - } - } - else if (ent->client->enviro_framenum > level.framenum) - { - remaining = ent->client->enviro_framenum - level.framenum; - - if (remaining == 30) /* beginning to fade */ - { - gi.sound(ent, CHAN_ITEM, gi.soundindex( - "items/airout.wav"), 1, ATTN_NORM, 0); - } - - if ((remaining > 30) || (remaining & 4)) - { - SV_AddBlend(0, 1, 0, 0.08, ent->client->ps.blend); - } - } - else if (ent->client->breather_framenum > level.framenum) - { - remaining = ent->client->breather_framenum - level.framenum; - - if (remaining == 30) /* beginning to fade */ - { - gi.sound(ent, CHAN_ITEM, gi.soundindex( - "items/airout.wav"), 1, ATTN_NORM, 0); - } - - if ((remaining > 30) || (remaining & 4)) - { - SV_AddBlend(0.4, 1, 0.4, 0.04, ent->client->ps.blend); - } - } - - /* add for damage */ - if (ent->client->damage_alpha > 0) - { - SV_AddBlend(ent->client->damage_blend[0], - ent->client->damage_blend[1], - ent->client->damage_blend[2], - ent->client->damage_alpha, - ent->client->ps.blend); - } - - if (ent->client->bonus_alpha > 0) - { - SV_AddBlend(0.85, 0.7, 0.3, ent->client->bonus_alpha, - ent->client->ps.blend); - } - - /* drop the damage value */ - ent->client->damage_alpha -= 0.06; - - if (ent->client->damage_alpha < 0) - { - ent->client->damage_alpha = 0; - } - - /* drop the bonus value */ - ent->client->bonus_alpha -= 0.1; - - if (ent->client->bonus_alpha < 0) - { - ent->client->bonus_alpha = 0; - } -} - -void -P_FallingDamage(edict_t *ent) -{ - float delta; - int damage; - vec3_t dir; - - if (ent->s.modelindex != 255) - { - return; /* not in the player model */ - } - - if (ent->movetype == MOVETYPE_NOCLIP) - { - return; - } - - if ((ent->client->oldvelocity[2] < 0) && - (ent->velocity[2] > ent->client->oldvelocity[2]) && (!ent->groundentity)) - { - delta = ent->client->oldvelocity[2]; - } - else - { - if (!ent->groundentity) - { - return; - } - - delta = ent->velocity[2] - ent->client->oldvelocity[2]; - } - - delta = delta * delta * 0.0001; - - /* never take damage if just release grapple or on grapple */ - if ((level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2) || - (ent->client->ctf_grapple && - (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY))) - { - return; - } - - /* never take falling damage if completely underwater */ - if (ent->waterlevel == 3) - { - return; - } - - if (ent->waterlevel == 2) - { - delta *= 0.25; - } - - if (ent->waterlevel == 1) - { - delta *= 0.5; - } - - if (delta < 1) - { - return; - } - - if (delta < 15) - { - ent->s.event = EV_FOOTSTEP; - return; - } - - ent->client->fall_value = delta * 0.5; - - if (ent->client->fall_value > 40) - { - ent->client->fall_value = 40; - } - - ent->client->fall_time = level.time + FALL_TIME; - - if (delta > 30) - { - if (ent->health > 0) - { - if (delta >= 55) - { - ent->s.event = EV_FALLFAR; - } - else - { - ent->s.event = EV_FALL; - } - } - - ent->pain_debounce_time = level.time; /* no normal pain sound */ - damage = (delta - 30) / 2; - - if (damage < 1) - { - damage = 1; - } - - VectorSet(dir, 0, 0, 1); - - if (!deathmatch->value || !((int)dmflags->value & DF_NO_FALLING)) - { - T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin, - damage, 0, 0, MOD_FALLING); - } - } - else - { - ent->s.event = EV_FALLSHORT; - return; - } -} - -void -P_WorldEffects(void) -{ - qboolean breather; - qboolean envirosuit; - int waterlevel, old_waterlevel; - - if (current_player->movetype == MOVETYPE_NOCLIP) - { - current_player->air_finished = level.time + 12; /* don't need air */ - return; - } - - waterlevel = current_player->waterlevel; - old_waterlevel = current_client->old_waterlevel; - current_client->old_waterlevel = waterlevel; - - breather = current_client->breather_framenum > level.framenum; - envirosuit = current_client->enviro_framenum > level.framenum; - - /* if just entered a water volume, play a sound */ - if (!old_waterlevel && waterlevel) - { - PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); - - if (current_player->watertype & CONTENTS_LAVA) - { - gi.sound(current_player, CHAN_BODY, - gi.soundindex("player/lava_in.wav"), 1, ATTN_NORM, 0); - } - else if (current_player->watertype & CONTENTS_SLIME) - { - gi.sound(current_player, CHAN_BODY, - gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); - } - else if (current_player->watertype & CONTENTS_WATER) - { - gi.sound(current_player, CHAN_BODY, - gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); - } - - current_player->flags |= FL_INWATER; - - /* clear damage_debounce, so the pain sound will play immediately */ - current_player->damage_debounce_time = level.time - 1; - } - - /* if just completely exited a water volume, play a sound */ - if (old_waterlevel && !waterlevel) - { - PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); - gi.sound(current_player, CHAN_BODY, gi.soundindex( - "player/watr_out.wav"), 1, ATTN_NORM, 0); - current_player->flags &= ~FL_INWATER; - } - - /* check for head just going under water */ - if ((old_waterlevel != 3) && (waterlevel == 3)) - { - gi.sound(current_player, CHAN_BODY, gi.soundindex( - "player/watr_un.wav"), 1, ATTN_NORM, 0); - } - - /* check for head just coming out of water */ - if ((old_waterlevel == 3) && (waterlevel != 3)) - { - if (current_player->air_finished < level.time) - { - /* gasp for air */ - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("player/gasp1.wav"), 1, ATTN_NORM, 0); - PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); - } - else if (current_player->air_finished < level.time + 11) - { - /* just break surface */ - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("player/gasp2.wav"), 1, ATTN_NORM, 0); - } - } - - /* check for drowning */ - if (waterlevel == 3) - { - /* breather or envirosuit give air */ - if (breather || envirosuit) - { - current_player->air_finished = level.time + 10; - - if (((int)(current_client->breather_framenum - - level.framenum) % 25) == 0) - { - if (!current_client->breather_sound) - { - gi.sound(current_player, CHAN_AUTO, - gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); - } - else - { - gi.sound(current_player, CHAN_AUTO, - gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); - } - - current_client->breather_sound ^= 1; - PlayerNoise(current_player, current_player->s.origin, - PNOISE_SELF); - } - } - - /* if out of air, start drowning */ - if (current_player->air_finished < level.time) - { - /* drown! */ - if ((current_player->client->next_drown_time < level.time) && - (current_player->health > 0)) - { - current_player->client->next_drown_time = level.time + 1; - - /* take more damage the longer underwater */ - current_player->dmg += 2; - - if (current_player->dmg > 15) - { - current_player->dmg = 15; - } - - /* play a gurp sound instead of a normal pain sound */ - if (current_player->health <= current_player->dmg) - { - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0); - } - else if (rand() & 1) - { - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0); - } - else - { - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("*gurp2.wav"), 1, ATTN_NORM, 0); - } - - current_player->pain_debounce_time = level.time; - - T_Damage(current_player, world, world, vec3_origin, - current_player->s.origin, vec3_origin, - current_player->dmg, 0, DAMAGE_NO_ARMOR, - MOD_WATER); - } - } - } - else - { - current_player->air_finished = level.time + 12; - current_player->dmg = 2; - } - - /* check for sizzle damage */ - if (waterlevel && (current_player->watertype & (CONTENTS_LAVA | CONTENTS_SLIME))) - { - if (current_player->watertype & CONTENTS_LAVA) - { - if ((current_player->health > 0) && - (current_player->pain_debounce_time <= level.time) && - (current_client->invincible_framenum < level.framenum)) - { - if (rand() & 1) - { - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("player/burn1.wav"), 1, ATTN_NORM, 0); - } - else - { - gi.sound(current_player, CHAN_VOICE, - gi.soundindex("player/burn2.wav"), 1, ATTN_NORM, 0); - } - - current_player->pain_debounce_time = level.time + 1; - } - - if (envirosuit) /* take 1/3 damage with envirosuit */ - { - T_Damage(current_player, world, world, vec3_origin, - current_player->s.origin, vec3_origin, - 1 * waterlevel, 0, 0, MOD_LAVA); - } - else - { - T_Damage(current_player, world, world, vec3_origin, - current_player->s.origin, vec3_origin, - 3 * waterlevel, 0, 0, MOD_LAVA); - } - } - - if (current_player->watertype & CONTENTS_SLIME) - { - if (!envirosuit) - { - /* no damage from slime with envirosuit */ - T_Damage(current_player, world, world, vec3_origin, - current_player->s.origin, vec3_origin, - 1 * waterlevel, 0, 0, MOD_SLIME); - } - } - } -} - -void -G_SetClientEffects(edict_t *ent) -{ - int pa_type; - int remaining; - - ent->s.effects = 0; - ent->s.renderfx = 0; - - if ((ent->health <= 0) || level.intermissiontime) - { - return; - } - - if (ent->powerarmor_time > level.time) - { - pa_type = PowerArmorType(ent); - - if (pa_type == POWER_ARMOR_SCREEN) - { - ent->s.effects |= EF_POWERSCREEN; - } - else if (pa_type == POWER_ARMOR_SHIELD) - { - ent->s.effects |= EF_COLOR_SHELL; - ent->s.renderfx |= RF_SHELL_GREEN; - } - } - - CTFEffects(ent); - - if (ent->client->quad_framenum > level.framenum) - { - remaining = ent->client->quad_framenum - level.framenum; - - if ((remaining > 30) || (remaining & 4)) - { - CTFSetPowerUpEffect(ent, EF_QUAD); - } - } - - if (ent->client->invincible_framenum > level.framenum) - { - remaining = ent->client->invincible_framenum - level.framenum; - - if ((remaining > 30) || (remaining & 4)) - { - CTFSetPowerUpEffect(ent, EF_PENT); - } - } - - /* show cheaters!!! */ - if (ent->flags & FL_GODMODE) - { - ent->s.effects |= EF_COLOR_SHELL; - ent->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE); - } -} - -void -G_SetClientEvent(edict_t *ent) -{ - if (ent->s.event) - { - return; - } - - if (ent->groundentity && (xyspeed > 225)) - { - if ((int)(current_client->bobtime + bobmove) != bobcycle) - { - ent->s.event = EV_FOOTSTEP; - } - } -} - -void -G_SetClientSound(edict_t *ent) -{ - char *weap; - - if (ent->client->resp.game_helpchanged != game.helpchanged) - { - ent->client->resp.game_helpchanged = game.helpchanged; - ent->client->resp.helpchanged = 1; - } - - /* help beep (no more than three times) */ - if (ent->client->resp.helpchanged && - (ent->client->resp.helpchanged <= 3) && !(level.framenum & 63)) - { - ent->client->resp.helpchanged++; - gi.sound(ent, CHAN_VOICE, gi.soundindex( - "misc/pc_up.wav"), 1, ATTN_STATIC, 0); - } - - if (ent->client->pers.weapon) - { - weap = ent->client->pers.weapon->classname; - } - else - { - weap = ""; - } - - if (ent->waterlevel && (ent->watertype & (CONTENTS_LAVA | CONTENTS_SLIME))) - { - ent->s.sound = snd_fry; - } - else if (strcmp(weap, "weapon_railgun") == 0) - { - ent->s.sound = gi.soundindex("weapons/rg_hum.wav"); - } - else if (strcmp(weap, "weapon_bfg") == 0) - { - ent->s.sound = gi.soundindex("weapons/bfg_hum.wav"); - } - else if (ent->client->weapon_sound) - { - ent->s.sound = ent->client->weapon_sound; - } - else - { - ent->s.sound = 0; - } -} - -void -G_SetClientFrame(edict_t *ent) -{ - gclient_t *client; - qboolean duck, run; - - if (ent->s.modelindex != 255) - { - return; /* not in the player model */ - } - - client = ent->client; - - if (client->ps.pmove.pm_flags & PMF_DUCKED) - { - duck = true; - } - else - { - duck = false; - } - - if (xyspeed) - { - run = true; - } - else - { - run = false; - } - - /* check for stand/duck and stop/go transitions */ - if ((duck != client->anim_duck) && (client->anim_priority < ANIM_DEATH)) - { - goto newanim; - } - - if ((run != client->anim_run) && (client->anim_priority == ANIM_BASIC)) - { - goto newanim; - } - - if (!ent->groundentity && (client->anim_priority <= ANIM_WAVE)) - { - goto newanim; - } - - if (client->anim_priority == ANIM_REVERSE) - { - if (ent->s.frame > client->anim_end) - { - ent->s.frame--; - return; - } - } - else if (ent->s.frame < client->anim_end) - { - /* continue an animation */ - ent->s.frame++; - return; - } - - if (client->anim_priority == ANIM_DEATH) - { - return; /* stay there */ - } - - if (client->anim_priority == ANIM_JUMP) - { - if (!ent->groundentity) - { - return; /* stay there */ - } - - ent->client->anim_priority = ANIM_WAVE; - ent->s.frame = FRAME_jump3; - ent->client->anim_end = FRAME_jump6; - return; - } - -newanim: - - /* return to either a running or standing frame */ - client->anim_priority = ANIM_BASIC; - client->anim_duck = duck; - client->anim_run = run; - - if (!ent->groundentity) - { - /* if on grapple, don't go into jump - frame, go into standing frame */ - if (client->ctf_grapple) - { - ent->s.frame = FRAME_stand01; - client->anim_end = FRAME_stand40; - } - else - { - client->anim_priority = ANIM_JUMP; - - if (ent->s.frame != FRAME_jump2) - { - ent->s.frame = FRAME_jump1; - } - - client->anim_end = FRAME_jump2; - } - } - else if (run) - { - /* running */ - if (duck) - { - ent->s.frame = FRAME_crwalk1; - client->anim_end = FRAME_crwalk6; - } - else - { - ent->s.frame = FRAME_run1; - client->anim_end = FRAME_run6; - } - } - else - { - /* standing */ - if (duck) - { - ent->s.frame = FRAME_crstnd01; - client->anim_end = FRAME_crstnd19; - } - else - { - ent->s.frame = FRAME_stand01; - client->anim_end = FRAME_stand40; - } - } -} - -/* - * Called for each player at the end of the server frame - * and right after spawning - */ -void -ClientEndServerFrame(edict_t *ent) -{ - float bobtime; - int i; - - current_player = ent; - current_client = ent->client; - - /* If the origin or velocity have changed since ClientThink(), - update the pmove values. This will happen when the client - is pushed by a bmodel or kicked by an explosion. - - If it wasn't updated here, the view position would lag a frame - behind the body position when pushed -- "sinking into plats" */ - for (i = 0; i < 3; i++) - { - current_client->ps.pmove.origin[i] = ent->s.origin[i] * 8.0; - current_client->ps.pmove.velocity[i] = ent->velocity[i] * 8.0; - } - - /* If the end of unit layout is displayed, don't give - the player any normal movement attributes */ - if (level.intermissiontime) - { - current_client->ps.blend[3] = 0; - current_client->ps.fov = 90; - G_SetStats(ent); - return; - } - - AngleVectors(ent->client->v_angle, forward, right, up); - - /* burn from lava, etc */ - P_WorldEffects(); - - /* set model angles from view angles so other things in - the world can tell which direction you are looking */ - if (ent->client->v_angle[PITCH] > 180) - { - ent->s.angles[PITCH] = (-360 + ent->client->v_angle[PITCH]) / 3; - } - else - { - ent->s.angles[PITCH] = ent->client->v_angle[PITCH] / 3; - } - - ent->s.angles[YAW] = ent->client->v_angle[YAW]; - ent->s.angles[ROLL] = 0; - ent->s.angles[ROLL] = SV_CalcRoll(ent->s.angles, ent->velocity) * 4; - - /* calculate speed and cycle to be used for - all cyclic walking effects */ - xyspeed = sqrt( - ent->velocity[0] * ent->velocity[0] + ent->velocity[1] * - ent->velocity[1]); - - if (xyspeed < 5) - { - bobmove = 0; - current_client->bobtime = 0; /* start at beginning of cycle again */ - } - else if (ent->groundentity) - { - /* so bobbing only cycles when on ground */ - if (xyspeed > 210) - { - bobmove = 0.25; - } - else if (xyspeed > 100) - { - bobmove = 0.125; - } - else - { - bobmove = 0.0625; - } - } - - bobtime = (current_client->bobtime += bobmove); - - if (current_client->ps.pmove.pm_flags & PMF_DUCKED) - { - bobtime *= 4; - } - - bobcycle = (int)bobtime; - bobfracsin = fabs(sin(bobtime * M_PI)); - - /* detect hitting the floor */ - P_FallingDamage(ent); - - /* apply all the damage taken this frame */ - P_DamageFeedback(ent); - - /* determine the view offsets */ - SV_CalcViewOffset(ent); - - /* determine the gun offsets */ - SV_CalcGunOffset(ent); - - /* determine the full screen color blend - must be after viewoffset, so eye contents can be - accurately determined */ - SV_CalcBlend(ent); - - if (!ent->client->chase_target) - { - G_SetStats(ent); - } - - /* update chasecam follower stats */ - for (i = 1; i <= maxclients->value; i++) - { - edict_t *e = g_edicts + i; - - if (!e->inuse || (e->client->chase_target != ent)) - { - continue; - } - - memcpy(e->client->ps.stats, ent->client->ps.stats, - sizeof(ent->client->ps.stats)); - e->client->ps.stats[STAT_LAYOUTS] = 1; - break; - } - - G_CheckChaseStats(ent); - G_SetClientEvent(ent); - G_SetClientEffects(ent); - G_SetClientSound(ent); - G_SetClientFrame(ent); - - VectorCopy(ent->velocity, ent->client->oldvelocity); - VectorCopy(ent->client->ps.viewangles, ent->client->oldviewangles); - - /* clear weapon kicks */ - VectorClear(ent->client->kick_origin); - VectorClear(ent->client->kick_angles); - - /* if the scoreboard is up, update it */ - if (ent->client->showscores && !(level.framenum & 31)) - { - if (ent->client->menu) - { - PMenu_Do_Update(ent); - ent->client->menudirty = false; - ent->client->menutime = level.time; - } - else - { - DeathmatchScoreboardMessage(ent, ent->enemy); - } - - gi.unicast(ent, false); - } -} diff --git a/src/game/player/client.c b/src/game/player/client.c index 52a9fbea..66e15788 100644 --- a/src/game/player/client.c +++ b/src/game/player/client.c @@ -1050,6 +1050,7 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, self->movetype = MOVETYPE_TOSS; self->s.modelindex2 = 0; /* remove linked weapon model */ + self->s.modelindex3 = 0; /* remove linked ctf flag */ self->s.angles[0] = 0; self->s.angles[2] = 0; @@ -1069,7 +1070,22 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, ClientObituary(self, inflictor, attacker); TossClientWeapon(self); - if (deathmatch->value) + /* if at start and same team, clear */ + if (ctf->value && (meansOfDeath == MOD_TELEFRAG) && + (self->client->resp.ctf_state < 2) && + (self->client->resp.ctf_team == attacker->client->resp.ctf_team)) + { + attacker->client->resp.score--; + self->client->resp.ctf_state = 0; + } + + CTFFragBonuses(self, inflictor, attacker); + TossClientWeapon(self); + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); + + if (deathmatch->value && !self->client->showscores) { Cmd_Help_f(self); /* show scores */ } @@ -1114,6 +1130,9 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, sphere->die(sphere, self, self, 0, vec3_origin); } + /* clear inventory */ + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + /* if we've been killed by the tracker, GIB! */ if ((meansOfDeath & ~MOD_FRIENDLY_FIRE) == MOD_TRACKER) { @@ -1158,7 +1177,8 @@ player_die(edict_t *self, edict_t *inflictor, edict_t *attacker, self->flags &= ~FL_NOGIB; ThrowClientHead(self, damage); - + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; self->takedamage = DAMAGE_NO; } else @@ -1235,6 +1255,10 @@ InitClientPersistant(gclient_t *client) client->pers.inventory[client->pers.selected_item] = 1; client->pers.weapon = item; + client->pers.lastweapon = item; + + item = FindItem("Grapple"); + client->pers.inventory[ITEM_INDEX(item)] = 1; client->pers.health = 100; client->pers.max_health = 100; @@ -1264,9 +1288,21 @@ InitClientResp(gclient_t *client) return; } + int ctf_team = client->resp.ctf_team; + qboolean id_state = client->resp.id_state; + memset(&client->resp, 0, sizeof(client->resp)); + + client->resp.ctf_team = ctf_team; + client->resp.id_state = id_state; + client->resp.enterframe = level.framenum; client->resp.coop_respawn = client->pers; + + if (ctf->value && (client->resp.ctf_team < CTF_TEAM1)) + { + CTFAssignTeam(client); + } } /* @@ -1676,7 +1712,14 @@ SelectSpawnPoint(edict_t *ent, vec3_t origin, vec3_t angles) if (deathmatch->value) { - spot = SelectDeathmatchSpawnPoint(); + if (ctf->value) + { + spot = SelectCTFSpawnPoint(ent); + } + else + { + spot = SelectDeathmatchSpawnPoint(); + } } else if (coop->value) { @@ -2050,12 +2093,23 @@ PutClientInServer(edict_t *ent) } else if (coop->value) { + int n; char userinfo[MAX_INFO_STRING]; resp = client->resp; memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); resp.coop_respawn.game_helpchanged = client->pers.game_helpchanged; resp.coop_respawn.helpchanged = client->pers.helpchanged; + + /* this is kind of ugly, but it's how we want to handle keys in coop */ + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + { + resp.coop_respawn.inventory[n] = client->pers.inventory[n]; + } + } + client->pers = resp.coop_respawn; ClientUserinfoChanged(ent, userinfo); @@ -2103,7 +2157,7 @@ PutClientInServer(edict_t *ent) ent->waterlevel = 0; ent->watertype = 0; ent->flags &= ~FL_NO_KNOCKBACK; - ent->svflags = 0; + ent->svflags &= ~SVF_DEADMONSTER; VectorCopy(mins, ent->mins); VectorCopy(maxs, ent->maxs); @@ -2115,6 +2169,7 @@ PutClientInServer(edict_t *ent) client->ps.pmove.origin[0] = spawn_origin[0] * 8; client->ps.pmove.origin[1] = spawn_origin[1] * 8; client->ps.pmove.origin[2] = spawn_origin[2] * 8; + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) { @@ -2146,6 +2201,7 @@ PutClientInServer(edict_t *ent) /* clear entity state values */ ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; ent->s.modelindex = 255; /* will use the skin specified model */ ent->s.modelindex2 = 255; /* custom gun model */ @@ -2171,6 +2227,11 @@ PutClientInServer(edict_t *ent) VectorCopy(ent->s.angles, client->ps.viewangles); VectorCopy(ent->s.angles, client->v_angle); + if (CTFStartClient(ent)) + { + return; + } + /* spawn a spectator */ if (client->pers.spectator) { @@ -2376,8 +2437,18 @@ ClientUserinfoChanged(edict_t *ent, char *userinfo) playernum = ent - g_edicts - 1; /* combine name and skin into a configstring */ - gi.configstring(CS_PLAYERSKINS + playernum, - va("%s\\%s", ent->client->pers.netname, s)); + if (ctf->value) + { + CTFAssignSkin(ent, s); + } + else + { + gi.configstring(CS_PLAYERSKINS + playernum, + va("%s\\%s", ent->client->pers.netname, s)); + } + + /* set player name field (used in id_state view) */ + gi.configstring(CS_GENERAL + playernum, ent->client->pers.netname); /* fov */ if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) @@ -2490,6 +2561,9 @@ ClientConnect(edict_t *ent, char *userinfo) if (ent->inuse == false) { /* clear the respawning variables */ + ent->client->resp.ctf_team = -1; + ent->client->resp.id_state = true; + InitClientResp(ent->client); if (!game.autosaved || !ent->client->pers.weapon) @@ -2531,6 +2605,12 @@ ClientDisconnect(edict_t *ent) gi.bprintf(PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + if (ctf->value) + { + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + } + /* make sure no trackers are still hurting us. */ if (ent->client->tracker_pain_framenum) { @@ -2770,6 +2850,11 @@ ClientThink(edict_t *ent, usercmd_t *ucmd) VectorCopy(pm.viewangles, client->ps.viewangles); } + if (client->ctf_grapple) + { + CTFGrapplePull(client->ctf_grapple); + } + gi.linkentity(ent); ent->gravity = 1.0; @@ -2815,7 +2900,8 @@ ClientThink(edict_t *ent, usercmd_t *ucmd) ent->light_level = ucmd->lightlevel; /* fire weapon from final position if needed */ - if (client->latched_buttons & BUTTON_ATTACK) + if (client->latched_buttons & BUTTON_ATTACK + && (ent->movetype != MOVETYPE_NOCLIP)) { if (client->resp.spectator) { @@ -2838,6 +2924,12 @@ ClientThink(edict_t *ent, usercmd_t *ucmd) } } + if (ctf->value) + { + /* regen tech */ + CTFApplyRegeneration(ent); + } + if (client->resp.spectator) { if (ucmd->upmove >= 10) @@ -2872,6 +2964,14 @@ ClientThink(edict_t *ent, usercmd_t *ucmd) UpdateChaseCam(other); } } + + if (ctf->value && client->menudirty && (client->menutime <= level.time)) + { + PMenu_Do_Update(ent); + gi.unicast(ent, true); + client->menutime = level.time; + client->menudirty = false; + } } /* @@ -2906,7 +3006,8 @@ ClientBeginServerFrame(edict_t *ent) } /* run weapon animations if it hasn't been done by a ucmd_t */ - if (!client->weapon_thunk && !client->resp.spectator) + if (!client->weapon_thunk && !client->resp.spectator + && (ent->movetype != MOVETYPE_NOCLIP)) { Think_Weapon(ent); } @@ -2931,7 +3032,9 @@ ClientBeginServerFrame(edict_t *ent) } if ((client->latched_buttons & buttonMask) || - (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN))) + (deathmatch->value && + ((int)dmflags->value & DF_FORCE_RESPAWN)) || + CTFMatchOn()) { respawn(ent); client->latched_buttons = 0; diff --git a/src/game/player/view.c b/src/game/player/view.c index 345865ea..dc23c07b 100644 --- a/src/game/player/view.c +++ b/src/game/player/view.c @@ -772,6 +772,16 @@ P_FallingDamage(edict_t *ent) delta = delta * delta * 0.0001; + /* never take damage if just release grapple or on grapple */ + if (ctf->value && ( + (level.time - ent->client->ctf_grapplereleasetime <= FRAMETIME * 2) || + (ent->client->ctf_grapple && + (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)) + )) + { + return; + } + /* never take falling damage if completely underwater */ if (ent->waterlevel == 3) { @@ -1104,13 +1114,18 @@ G_SetClientEffects(edict_t *ent) } } + if (ctf->value) + { + CTFEffects(ent); + } + if (ent->client->quad_framenum > level.framenum) { remaining = ent->client->quad_framenum - level.framenum; if ((remaining > 30) || (remaining & 4)) { - ent->s.effects |= EF_QUAD; + CTFSetPowerUpEffect(ent, EF_QUAD); } } @@ -1151,7 +1166,7 @@ G_SetClientEffects(edict_t *ent) if ((remaining > 30) || (remaining & 4)) { - ent->s.effects |= EF_PENT; + CTFSetPowerUpEffect(ent, EF_PENT); } } @@ -1364,14 +1379,24 @@ newanim: if (!ent->groundentity) { - client->anim_priority = ANIM_JUMP; - - if (ent->s.frame != FRAME_jump2) + /* if on grapple, don't go into jump + frame, go into standing frame */ + if (client->ctf_grapple) { - ent->s.frame = FRAME_jump1; + ent->s.frame = FRAME_stand01; + client->anim_end = FRAME_stand40; } + else + { + client->anim_priority = ANIM_JUMP; - client->anim_end = FRAME_jump2; + if (ent->s.frame != FRAME_jump2) + { + ent->s.frame = FRAME_jump1; + } + + client->anim_end = FRAME_jump2; + } } else if (run) { @@ -1522,11 +1547,27 @@ ClientEndServerFrame(edict_t *ent) { G_SetSpectatorStats(ent); } - else + else if (!ent->client->chase_target) { G_SetStats(ent); } + /* update chasecam follower stats */ + for (i = 1; i <= maxclients->value; i++) + { + edict_t *e = g_edicts + i; + + if (!e->inuse || (e->client->chase_target != ent)) + { + continue; + } + + memcpy(e->client->ps.stats, ent->client->ps.stats, + sizeof(ent->client->ps.stats)); + e->client->ps.stats[STAT_LAYOUTS] = 1; + break; + } + G_CheckChaseStats(ent); G_SetClientEvent(ent); G_SetClientEffects(ent); @@ -1545,7 +1586,18 @@ ClientEndServerFrame(edict_t *ent) /* if the scoreboard is up, update it */ if (ent->client->showscores) { - DeathmatchScoreboardMessage(ent, ent->enemy); + + if (ent->client->menu) + { + PMenu_Do_Update(ent); + ent->client->menudirty = false; + ent->client->menutime = level.time; + } + else + { + DeathmatchScoreboardMessage(ent, ent->enemy); + } + gi.unicast(ent, false); } diff --git a/src/game/savegame/savegame.c b/src/game/savegame/savegame.c index 4686f5f2..aefb5dbe 100644 --- a/src/game/savegame/savegame.c +++ b/src/game/savegame/savegame.c @@ -266,8 +266,8 @@ InitGame(void) /* items */ InitItems(); - game.helpmessage1[0] = 0; - game.helpmessage2[0] = 0; + Com_sprintf(game.helpmessage1, sizeof(game.helpmessage1), ""); + Com_sprintf(game.helpmessage2, sizeof(game.helpmessage2), ""); /* initialize all entities for this game */ game.maxentities = maxentities->value;