mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-10 23:32:09 +00:00
876eaa467c
The player global branch variable is now updated with useful information and the location.get builtin is available to return the current location of the player based on loc files. Fixed a bug with zooming out in zoom.gib.
495 lines
10 KiB
C
495 lines
10 KiB
C
/*
|
|
teamplay.c
|
|
|
|
Teamplay enhancements ("proxy features")
|
|
|
|
Copyright (C) 2000 Anton Gavrilov (tonik@quake.ru)
|
|
|
|
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:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
static const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include "QF/cbuf.h"
|
|
#include "QF/console.h"
|
|
#include "QF/cmd.h"
|
|
#include "QF/cvar.h"
|
|
#include "QF/locs.h"
|
|
#include "QF/model.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/teamplay.h"
|
|
#include "QF/va.h"
|
|
#include "QF/skin.h"
|
|
#include "QF/gib_builtin.h"
|
|
|
|
#include "bothdefs.h"
|
|
#include "cl_input.h"
|
|
#include "client.h"
|
|
#include "compat.h"
|
|
|
|
static qboolean died = false, recorded_location = false;
|
|
static vec3_t death_location, last_recorded_location;
|
|
|
|
cvar_t *cl_deadbodyfilter;
|
|
cvar_t *cl_gibfilter;
|
|
cvar_t *cl_parsesay;
|
|
cvar_t *cl_nofake;
|
|
cvar_t *cl_freply;
|
|
|
|
|
|
void
|
|
Team_BestWeaponImpulse (void)
|
|
{
|
|
int best, i, imp, items;
|
|
|
|
items = cl.stats[STAT_ITEMS];
|
|
best = 0;
|
|
|
|
for (i = Cmd_Argc () - 1; i > 0; i--) {
|
|
imp = atoi (Cmd_Argv (i));
|
|
if (imp < 1 || imp > 8)
|
|
continue;
|
|
|
|
switch (imp) {
|
|
case 1:
|
|
if (items & IT_AXE)
|
|
best = 1;
|
|
break;
|
|
case 2:
|
|
if (items & IT_SHOTGUN && cl.stats[STAT_SHELLS] >= 1)
|
|
best = 2;
|
|
break;
|
|
case 3:
|
|
if (items & IT_SUPER_SHOTGUN && cl.stats[STAT_SHELLS] >= 2)
|
|
best = 3;
|
|
break;
|
|
case 4:
|
|
if (items & IT_NAILGUN && cl.stats[STAT_NAILS] >= 1)
|
|
best = 4;
|
|
break;
|
|
case 5:
|
|
if (items & IT_SUPER_NAILGUN && cl.stats[STAT_NAILS] >= 2)
|
|
best = 5;
|
|
break;
|
|
case 6:
|
|
if (items & IT_GRENADE_LAUNCHER && cl.stats[STAT_ROCKETS] >= 1)
|
|
best = 6;
|
|
break;
|
|
case 7:
|
|
if (items & IT_ROCKET_LAUNCHER && cl.stats[STAT_ROCKETS] >= 1)
|
|
best = 7;
|
|
break;
|
|
case 8:
|
|
if (items & IT_LIGHTNING && cl.stats[STAT_CELLS] >= 1)
|
|
best = 8;
|
|
}
|
|
}
|
|
|
|
if (best)
|
|
in_impulse = best;
|
|
}
|
|
|
|
|
|
const char *
|
|
Team_ParseSay (const char *s)
|
|
{
|
|
char chr, t2[128], t3[128];
|
|
const char *t1;
|
|
static char buf[1024];
|
|
int i, bracket;
|
|
static location_t *location = NULL;
|
|
|
|
if (!cl_parsesay->int_val)
|
|
return s;
|
|
|
|
i = 0;
|
|
|
|
while (*s && (i <= sizeof (buf))) {
|
|
if ((*s == '%') && (s[1] != '\0')) {
|
|
t1 = NULL;
|
|
memset (t2, '\0', sizeof (t2));
|
|
memset (t3, '\0', sizeof (t3));
|
|
|
|
if ((s[1] == '[') && (s[3] == ']')) {
|
|
bracket = 1;
|
|
chr = s[2];
|
|
s += 4;
|
|
} else {
|
|
bracket = 0;
|
|
chr = s[1];
|
|
s += 2;
|
|
}
|
|
switch (chr) {
|
|
case '%':
|
|
t2[0] = '%';
|
|
t2[1] = 0;
|
|
t1 = t2;
|
|
break;
|
|
case 's':
|
|
bracket = 0;
|
|
t1 = skin->string;
|
|
break;
|
|
case 'd':
|
|
bracket = 0;
|
|
if (died) {
|
|
location = locs_find (death_location);
|
|
if (location) {
|
|
recorded_location = true;
|
|
VectorCopy (death_location, last_recorded_location);
|
|
t1 = location->name;
|
|
break;
|
|
}
|
|
}
|
|
goto location;
|
|
case 'r':
|
|
bracket = 0;
|
|
if (recorded_location) {
|
|
location = locs_find (last_recorded_location);
|
|
if (location) {
|
|
t1 = location->name;
|
|
break;
|
|
}
|
|
}
|
|
goto location;
|
|
case 'l':
|
|
location:
|
|
bracket = 0;
|
|
location = locs_find (cl.simorg);
|
|
if (location) {
|
|
recorded_location = true;
|
|
VectorCopy (cl.simorg, last_recorded_location);
|
|
t1 = location->name;
|
|
} else
|
|
snprintf (t2, sizeof (t2), "Unknown!\n");
|
|
break;
|
|
case 'a':
|
|
if (bracket) {
|
|
if (cl.stats[STAT_ARMOR] > 50)
|
|
bracket = 0;
|
|
|
|
if (cl.stats[STAT_ITEMS] & IT_ARMOR3)
|
|
t3[0] = 'R' | 0x80;
|
|
else if (cl.stats[STAT_ITEMS] & IT_ARMOR2)
|
|
t3[0] = 'Y' | 0x80;
|
|
else if (cl.stats[STAT_ITEMS] & IT_ARMOR1)
|
|
t3[0] = 'G' | 0x80;
|
|
else {
|
|
t2[0] = 'N' | 0x80;
|
|
t2[1] = 'O' | 0x80;
|
|
t2[2] = 'N' | 0x80;
|
|
t2[3] = 'E' | 0x80;
|
|
t2[4] = '!' | 0x80;
|
|
}
|
|
|
|
snprintf (t2, sizeof (t2), "%sa:%i", t3,
|
|
cl.stats[STAT_ARMOR]);
|
|
} else
|
|
snprintf (t2, sizeof (t2), "%i", cl.stats[STAT_ARMOR]);
|
|
break;
|
|
case 'A':
|
|
bracket = 0;
|
|
if (cl.stats[STAT_ITEMS] & IT_ARMOR3)
|
|
t2[0] = 'R' | 0x80;
|
|
else if (cl.stats[STAT_ITEMS] & IT_ARMOR2)
|
|
t2[0] = 'Y' | 0x80;
|
|
else if (cl.stats[STAT_ITEMS] & IT_ARMOR1)
|
|
t2[0] = 'G' | 0x80;
|
|
else {
|
|
t2[0] = 'N' | 0x80;
|
|
t2[1] = 'O' | 0x80;
|
|
t2[2] = 'N' | 0x80;
|
|
t2[3] = 'E' | 0x80;
|
|
t2[4] = '!' | 0x80;
|
|
}
|
|
break;
|
|
case 'h':
|
|
if (bracket) {
|
|
if (cl.stats[STAT_HEALTH] > 50)
|
|
bracket = 0;
|
|
snprintf (t2, sizeof (t2), "h:%i",
|
|
cl.stats[STAT_HEALTH]);
|
|
} else
|
|
snprintf (t2, sizeof (t2), "%i", cl.stats[STAT_HEALTH]);
|
|
break;
|
|
default:
|
|
bracket = 0;
|
|
}
|
|
|
|
if (!t1) {
|
|
if (!t2[0]) {
|
|
t2[0] = '%';
|
|
t2[1] = chr;
|
|
}
|
|
|
|
t1 = t2;
|
|
}
|
|
|
|
if (bracket)
|
|
buf[i++] = 0x90; // '['
|
|
|
|
if (t1) {
|
|
int len;
|
|
|
|
len = strlen (t1);
|
|
if (i + len >= sizeof (buf))
|
|
continue; // No more space in buffer, icky.
|
|
strncpy (buf + i, t1, len);
|
|
i += len;
|
|
}
|
|
|
|
if (bracket)
|
|
buf[i++] = 0x91; // ']'
|
|
|
|
continue;
|
|
}
|
|
buf[i++] = *s++;
|
|
}
|
|
buf[i] = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
Team_Dead (void)
|
|
{
|
|
died = true;
|
|
VectorCopy (cl.simorg, death_location);
|
|
}
|
|
|
|
void
|
|
Team_NewMap (void)
|
|
{
|
|
char *mapname, *t1, *t2;
|
|
|
|
died = false;
|
|
recorded_location = false;
|
|
mapname = strdup (cl.worldmodel->name);
|
|
t2 = malloc (sizeof (cl.worldmodel->name));
|
|
if (!mapname || !t2)
|
|
Sys_Error ("Can't duplicate mapname!");
|
|
map_to_loc (mapname,t2);
|
|
t1 = strrchr (t2, '/');
|
|
if (!t1)
|
|
Sys_Error ("Can't find /!");
|
|
t1++; // skip over /
|
|
locs_reset ();
|
|
locs_load (t1);
|
|
free (mapname);
|
|
free (t2);
|
|
}
|
|
|
|
void
|
|
Team_Init_Cvars (void)
|
|
{
|
|
cl_deadbodyfilter = Cvar_Get ("cl_deadbodyfilter", "0", CVAR_NONE, NULL,
|
|
"Hide dead player models");
|
|
cl_gibfilter = Cvar_Get ("cl_gibfilter", "0", CVAR_NONE, NULL,
|
|
"Hide gibs");
|
|
cl_parsesay = Cvar_Get ("cl_parsesay", "0", CVAR_NONE, NULL,
|
|
"Use .loc files to find your present location "
|
|
"when you put %l in messages");
|
|
cl_nofake = Cvar_Get ("cl_nofake", "0", CVAR_NONE, NULL,
|
|
"Unhide fake messages");
|
|
cl_freply = Cvar_Get ("cl_freply", "0", CVAR_NONE, NULL,
|
|
"Delay between replies to f_*. 0 disables. Minimum suggested setting is 20");
|
|
}
|
|
|
|
/*
|
|
locs_loc
|
|
|
|
Location marker manipulation
|
|
*/
|
|
void
|
|
locs_loc (void)
|
|
{
|
|
char locfile[MAX_OSPATH];
|
|
char *mapname;
|
|
const char *desc = NULL;
|
|
|
|
// FIXME: need to check to ensure you are actually in the game and alive.
|
|
if (Cmd_Argc () == 1) {
|
|
Con_Printf ("loc <add|delete|rename|move|save|zsave> [<description>] "
|
|
":Modifies location data, add|rename take <description> "
|
|
"parameter\n");
|
|
return;
|
|
}
|
|
if (Cmd_Argc () >= 3)
|
|
desc = Cmd_Args (2);
|
|
mapname = malloc (sizeof (cl.worldmodel->name));
|
|
if (!mapname)
|
|
Sys_Error ("Can't duplicate mapname!");
|
|
map_to_loc (cl.worldmodel->name,mapname);
|
|
snprintf (locfile, sizeof (locfile), "%s/%s", com_gamedir, mapname);
|
|
free(mapname);
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"save") == 0) {
|
|
if (Cmd_Argc () == 2) {
|
|
locs_save (locfile, false);
|
|
} else
|
|
Con_Printf ("loc save :saves locs from memory into a .loc file\n");
|
|
}
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"zsave") == 0) {
|
|
if (Cmd_Argc () == 2) {
|
|
locs_save (locfile, true);
|
|
} else
|
|
Con_Printf ("loc save :saves locs from memory into a .loc file\n");
|
|
}
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"add") == 0) {
|
|
if (Cmd_Argc () >= 3)
|
|
locs_mark (cl.simorg,desc);
|
|
else
|
|
Con_Printf ("loc add <description> :marks the current location "
|
|
"with the description and records the information "
|
|
"into a loc file.\n");
|
|
}
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"rename") == 0) {
|
|
if (Cmd_Argc () >= 3)
|
|
locs_edit (cl.simorg,desc);
|
|
else
|
|
Con_Printf ("loc rename <description> :changes the description of "
|
|
"the nearest location marker\n");
|
|
}
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"delete") == 0) {
|
|
if (Cmd_Argc () == 2)
|
|
locs_del (cl.simorg);
|
|
else
|
|
Con_Printf ("loc delete :removes nearest location marker\n");
|
|
}
|
|
|
|
if (strcasecmp (Cmd_Argv(1),"move") == 0) {
|
|
if (Cmd_Argc () == 2)
|
|
locs_edit (cl.simorg,NULL);
|
|
else
|
|
Con_Printf ("loc move :moves the nearest location marker to your "
|
|
"current location\n");
|
|
}
|
|
}
|
|
|
|
void
|
|
Locs_Location_Get (void)
|
|
{
|
|
location_t *location;
|
|
if (GIB_Argc() != 1)
|
|
Cbuf_Error (
|
|
"syntax",
|
|
"location.get: invalid syntax\n"
|
|
"usage: location.get"
|
|
);
|
|
else {
|
|
location = locs_find (cl.simorg);
|
|
GIB_Return (location ? location->name : "unknown");
|
|
}
|
|
}
|
|
|
|
void
|
|
Locs_Init (void)
|
|
{
|
|
Cmd_AddCommand ("loc", locs_loc, "Location marker editing commands: 'loc "
|
|
"help' for more");
|
|
GIB_Builtin_Add ("location.get", Locs_Location_Get, GIB_BUILTIN_NORMAL);
|
|
}
|
|
|
|
char *
|
|
Team_F_Version (char *args)
|
|
{
|
|
return va("say %s %s", PROGRAM, VERSION);
|
|
}
|
|
|
|
char *
|
|
Team_F_Skins (char *args)
|
|
{
|
|
int totalfb, l;
|
|
|
|
while(isspace((byte) *args))
|
|
args++;
|
|
for (l = 0;args[l] && !isspace((byte) args[l]);l++);
|
|
|
|
if (l == 0) {
|
|
totalfb = Skin_FbPercent (0);
|
|
return va("say Average percent fullbright for all loaded skins is %d.%d%%", totalfb / 10, totalfb % 10);
|
|
}
|
|
|
|
totalfb = Skin_FbPercent (args);
|
|
|
|
if (totalfb >= 0)
|
|
return va("say \"Skin %s is %d.%d%% fullbright\"",
|
|
args, totalfb / 10, totalfb % 10);
|
|
else
|
|
return ("say \"Skin not currently loaded.\"");
|
|
}
|
|
|
|
freply_t f_replies[] = {
|
|
{"f_version", Team_F_Version, 0},
|
|
{"f_skins", Team_F_Skins, 0},
|
|
{0, 0}
|
|
};
|
|
|
|
void
|
|
Team_ParseChat (const char *string)
|
|
{
|
|
char *s;
|
|
int i;
|
|
|
|
if (!cl_freply->value)
|
|
return;
|
|
|
|
s = strchr(string, ':');
|
|
if (!(s = strchr(string, ':')))
|
|
return;
|
|
s++;
|
|
while (isspace((byte) *s))
|
|
s++;
|
|
|
|
for (i = 0; f_replies[i].name; i++) {
|
|
if (!strncmp(f_replies[i].name, s, strlen(f_replies[i].name)) && cl_freply->value) {
|
|
while (*s && !isspace((byte) *s))
|
|
s++;
|
|
Cbuf_AddText(cl_cbuf, f_replies[i].func(s));
|
|
f_replies[i].lasttime = realtime;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Team_ResetTimers (void)
|
|
{
|
|
int i;
|
|
for (i = 0; f_replies[i].name; i++)
|
|
f_replies[i].lasttime = realtime - cl_freply->value;
|
|
return;
|
|
}
|