d7bb31a516
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@925 fc73d0e0-1445-4013-8a0c-d673dee63da5
2693 lines
62 KiB
C
2693 lines
62 KiB
C
/*
|
|
teamplay.c
|
|
|
|
Teamplay enhancements
|
|
|
|
Copyright (C) 2000-2001 Anton Gavrilov
|
|
|
|
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
|
|
*/
|
|
|
|
//Hacked by spike.
|
|
//things to fix:
|
|
//TP_SearchForMsgTriggers: should only allow safe commands. work out what the meaning of safe commands is.
|
|
|
|
|
|
#include "quakedef.h"
|
|
#if 1 //def ZQUAKETEAMPLAY
|
|
|
|
//#include "version.h"
|
|
#include "sound.h"
|
|
//#include "pmove.h"
|
|
#include <time.h>
|
|
#include <ctype.h>
|
|
|
|
typedef qboolean qbool;
|
|
|
|
#define SP 0
|
|
|
|
#define Com_Printf Con_Printf
|
|
|
|
#define strlcpy Q_strncpyz
|
|
#define Q_stricmp stricmp
|
|
#define Q_strnicmp strnicmp
|
|
|
|
/*#define isalpha(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z'))
|
|
#define isdigit(x) ((x) >= '0' && (x) <= '9')
|
|
#define isxdigit(x) (isdigit(x) || ((x) >= 'a' && (x) <= 'f'))
|
|
*/
|
|
#define Q_rint(f) ((int)((f)+0.5))
|
|
|
|
#ifndef HAVE_STRLCAT
|
|
static size_t strlcat (char *dst, const char *src, size_t size)
|
|
{
|
|
int dstlen = strlen(dst);
|
|
int srclen = strlen(src);
|
|
int len = dstlen + srclen;
|
|
|
|
if (len < size)
|
|
{
|
|
// it'll fit
|
|
memcpy (dst + dstlen, src, srclen + 1);
|
|
return len;
|
|
}
|
|
|
|
if (dstlen >= size - 1)
|
|
return srclen + size;
|
|
|
|
if (size == 0)
|
|
return srclen;
|
|
|
|
memcpy (dst + dstlen, src, size - 1 - dstlen);
|
|
dst[size - 1] = 0;
|
|
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
static void VARGS Q_snprintfz (char *dest, size_t size, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
#ifdef _WIN32
|
|
_vsnprintf (dest, size, fmt, argptr);
|
|
#else
|
|
vsnprintf (dest, size, fmt, argptr);
|
|
#endif
|
|
va_end (argptr);
|
|
|
|
dest[size-1] = 0;
|
|
}
|
|
|
|
//a list of all the cvars
|
|
//this is down to the fact that I keep defining them but forgetting to register. :/
|
|
#define TP_CVARS \
|
|
TP_CVAR(cl_fakename, ""); \
|
|
TP_CVAR(cl_parseSay, "1"); \
|
|
TP_CVAR(cl_parseFunChars, "1"); \
|
|
TP_CVAR(cl_triggers, "1"); \
|
|
TP_CVAR(tp_forceTriggers, "0"); \
|
|
TP_CVAR(tp_loadlocs, "1"); \
|
|
TP_CVAR(cl_teamskin, ""); \
|
|
TP_CVAR(cl_enemyskin, ""); \
|
|
TP_CVAR(tp_soundtrigger, "~"); \
|
|
\
|
|
TP_CVAR(tp_name_none, ""); \
|
|
TP_CVAR(tp_name_axe, "axe"); \
|
|
TP_CVAR(tp_name_sg, "sg"); \
|
|
TP_CVAR(tp_name_ssg, "ssg"); \
|
|
TP_CVAR(tp_name_ng, "ng"); \
|
|
TP_CVAR(tp_name_sng, "sng"); \
|
|
TP_CVAR(tp_name_gl, "gl"); \
|
|
TP_CVAR(tp_name_rl, "rl"); \
|
|
TP_CVAR(tp_name_lg, "lg"); \
|
|
TP_CVAR(tp_name_ra, "ra"); \
|
|
TP_CVAR(tp_name_ya, "ya"); \
|
|
TP_CVAR(tp_name_ga, "ga"); \
|
|
TP_CVAR(tp_name_quad, "quad"); \
|
|
TP_CVAR(tp_name_pent, "pent"); \
|
|
TP_CVAR(tp_name_ring, "ring"); \
|
|
TP_CVAR(tp_name_suit, "suit"); \
|
|
TP_CVAR(tp_name_shells, "shells"); \
|
|
TP_CVAR(tp_name_nails, "nails"); \
|
|
TP_CVAR(tp_name_rockets, "rockets"); \
|
|
TP_CVAR(tp_name_cells, "cells"); \
|
|
TP_CVAR(tp_name_mh, "mega"); \
|
|
TP_CVAR(tp_name_health, "health"); \
|
|
TP_CVAR(tp_name_backpack, "pack"); \
|
|
TP_CVAR(tp_name_flag, "flag"); \
|
|
TP_CVAR(tp_name_nothing, "nothing"); \
|
|
TP_CVAR(tp_name_someplace, "someplace"); \
|
|
TP_CVAR(tp_name_at, "at"); \
|
|
TP_CVAR(tp_need_ra, "50"); \
|
|
TP_CVAR(tp_need_ya, "50"); \
|
|
TP_CVAR(tp_need_ga, "50"); \
|
|
TP_CVAR(tp_need_health, "50"); \
|
|
TP_CVAR(tp_need_weapon, "35687"); \
|
|
TP_CVAR(tp_need_rl, "1"); \
|
|
TP_CVAR(tp_need_rockets, "5"); \
|
|
TP_CVAR(tp_need_cells, "20"); \
|
|
TP_CVAR(tp_need_nails, "40"); \
|
|
TP_CVAR(tp_need_shells, "10"); \
|
|
TP_CVAR(tp_name_disp, "dispenser"); \
|
|
TP_CVAR(tp_name_sentry, "sentry gun"); \
|
|
TP_CVAR(tp_name_rune_1, "resistance rune"); \
|
|
TP_CVAR(tp_name_rune_2, "strength rune"); \
|
|
TP_CVAR(tp_name_rune_3, "haste rune"); \
|
|
TP_CVAR(tp_name_rune_4, "regeneration rune")
|
|
|
|
//create the globals for all the TP cvars.
|
|
#define TP_CVAR(name,def) cvar_t name = {#name, def}
|
|
TP_CVARS;
|
|
#undef TP_CVAR
|
|
|
|
|
|
extern cvar_t host_mapname;
|
|
|
|
static void TP_FindModelNumbers (void);
|
|
static void TP_FindPoint (void);
|
|
static char *TP_LocationName (vec3_t location);
|
|
|
|
|
|
#define MAX_LOC_NAME 48
|
|
|
|
// this structure is cleared after entering a new map
|
|
typedef struct tvars_s {
|
|
int health;
|
|
int items;
|
|
int olditems;
|
|
int stat_framecounts[MAX_CL_STATS];
|
|
int activeweapon;
|
|
float respawntrigger_time;
|
|
float deathtrigger_time;
|
|
float f_version_reply_time;
|
|
char lastdeathloc[MAX_LOC_NAME];
|
|
char tookname[32];
|
|
char tookloc[MAX_LOC_NAME];
|
|
float tooktime;
|
|
|
|
int pointframe; // cls.framecount for which point* vars are valid
|
|
char pointname[32];
|
|
vec3_t pointorg;
|
|
char pointloc[MAX_LOC_NAME];
|
|
int droppedweapon;
|
|
} tvars_t;
|
|
|
|
tvars_t vars;
|
|
|
|
//===========================================================================
|
|
// TRIGGERS
|
|
//===========================================================================
|
|
|
|
static void TP_ExecTrigger (char *s)
|
|
{
|
|
char *astr;
|
|
|
|
if (!cl_triggers.value || cls.demoplayback)
|
|
return;
|
|
|
|
astr = Cmd_AliasExist(s, RESTRICT_LOCAL);
|
|
if (astr)
|
|
{
|
|
char *p;
|
|
qbool quote = false;
|
|
|
|
for (p=astr ; *p ; p++)
|
|
{
|
|
if (*p == '"')
|
|
quote = !quote;
|
|
if (!quote && *p == ';')
|
|
{
|
|
// more than one command, add it to the command buffer
|
|
Cbuf_AddText (astr, RESTRICT_LOCAL);
|
|
Cbuf_AddText ("\n", RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
}
|
|
// a single line, so execute it right away
|
|
Cmd_ExecuteString (astr, RESTRICT_LOCAL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==========================================================================
|
|
MACRO FUNCTIONS
|
|
==========================================================================
|
|
*/
|
|
|
|
#define MAX_MACRO_VALUE 256
|
|
static char macro_buf[MAX_MACRO_VALUE] = "";
|
|
|
|
// buffer-size-safe helper functions
|
|
//static void MacroBuf_strcat (char *str) {
|
|
// strlcat (macro_buf, str, sizeof(macro_buf));
|
|
//}
|
|
static void MacroBuf_strcat_with_separator (char *str) {
|
|
if (macro_buf[0])
|
|
strlcat (macro_buf, "/", sizeof(macro_buf));
|
|
strlcat (macro_buf, str, sizeof(macro_buf));
|
|
}
|
|
|
|
|
|
|
|
static char *Macro_Quote (void)
|
|
{
|
|
return "\"";
|
|
}
|
|
|
|
static char *Macro_Latency (void)
|
|
{
|
|
sprintf(macro_buf, "%i", Q_rint(cls.latency*1000));
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Health (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_HEALTH]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Armor (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_ARMOR]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Shells (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_SHELLS]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Nails (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_NAILS]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Rockets (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_ROCKETS]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Cells (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_CELLS]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Ammo (void)
|
|
{
|
|
sprintf(macro_buf, "%i", cl.stats[SP][STAT_AMMO]);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Weapon_NumToString (int wnum)
|
|
{
|
|
switch (wnum)
|
|
{
|
|
case IT_AXE: return tp_name_axe.string;
|
|
case IT_SHOTGUN: return tp_name_sg.string;
|
|
case IT_SUPER_SHOTGUN: return tp_name_ssg.string;
|
|
case IT_NAILGUN: return tp_name_ng.string;
|
|
case IT_SUPER_NAILGUN: return tp_name_sng.string;
|
|
case IT_GRENADE_LAUNCHER: return tp_name_gl.string;
|
|
case IT_ROCKET_LAUNCHER: return tp_name_rl.string;
|
|
case IT_LIGHTNING: return tp_name_lg.string;
|
|
default: return tp_name_none.string;
|
|
}
|
|
}
|
|
|
|
static char *Macro_Weapon (void)
|
|
{
|
|
return Weapon_NumToString(cl.stats[SP][STAT_ACTIVEWEAPON]);
|
|
}
|
|
|
|
static char *Macro_DroppedWeapon (void)
|
|
{
|
|
return Weapon_NumToString(vars.droppedweapon);
|
|
}
|
|
|
|
static char *Macro_Weapons (void) {
|
|
macro_buf[0] = 0;
|
|
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_LIGHTNING)
|
|
strcpy(macro_buf, tp_name_lg.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_ROCKET_LAUNCHER)
|
|
MacroBuf_strcat_with_separator (tp_name_rl.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_GRENADE_LAUNCHER)
|
|
MacroBuf_strcat_with_separator (tp_name_gl.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_NAILGUN)
|
|
MacroBuf_strcat_with_separator (tp_name_sng.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_NAILGUN)
|
|
MacroBuf_strcat_with_separator (tp_name_ng.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_SHOTGUN)
|
|
MacroBuf_strcat_with_separator (tp_name_ssg.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_SHOTGUN)
|
|
MacroBuf_strcat_with_separator (tp_name_sg.string);
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_AXE)
|
|
MacroBuf_strcat_with_separator (tp_name_axe.string);
|
|
// if (!macro_buf[0])
|
|
// strlcpy(macro_buf, tp_name_none.string, sizeof(macro_buf));
|
|
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_WeaponAndAmmo (void)
|
|
{
|
|
char buf[sizeof(macro_buf)];
|
|
Q_snprintfz (buf, sizeof(buf), "%s:%s", Macro_Weapon(), Macro_Ammo());
|
|
strcpy (macro_buf, buf);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_WeaponNum (void)
|
|
{
|
|
switch (cl.stats[SP][STAT_ACTIVEWEAPON])
|
|
{
|
|
case IT_AXE: return "1";
|
|
case IT_SHOTGUN: return "2";
|
|
case IT_SUPER_SHOTGUN: return "3";
|
|
case IT_NAILGUN: return "4";
|
|
case IT_SUPER_NAILGUN: return "5";
|
|
case IT_GRENADE_LAUNCHER: return "6";
|
|
case IT_ROCKET_LAUNCHER: return "7";
|
|
case IT_LIGHTNING: return "8";
|
|
default:
|
|
return "0";
|
|
}
|
|
}
|
|
|
|
static int _Macro_BestWeapon (void)
|
|
{
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_ROCKET_LAUNCHER)
|
|
return IT_ROCKET_LAUNCHER;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_LIGHTNING)
|
|
return IT_LIGHTNING;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_GRENADE_LAUNCHER)
|
|
return IT_GRENADE_LAUNCHER;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_NAILGUN)
|
|
return IT_SUPER_NAILGUN;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_NAILGUN)
|
|
return IT_NAILGUN;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_SHOTGUN)
|
|
return IT_SUPER_SHOTGUN;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_SHOTGUN)
|
|
return IT_SHOTGUN;
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_AXE)
|
|
return IT_AXE;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static char *Macro_BestWeapon (void)
|
|
{
|
|
return Weapon_NumToString(_Macro_BestWeapon());
|
|
}
|
|
|
|
static char *Macro_BestAmmo (void)
|
|
{
|
|
switch (_Macro_BestWeapon())
|
|
{
|
|
case IT_SHOTGUN: case IT_SUPER_SHOTGUN:
|
|
sprintf(macro_buf, "%i", cl.stats[0][STAT_SHELLS]);
|
|
return macro_buf;
|
|
|
|
case IT_NAILGUN: case IT_SUPER_NAILGUN:
|
|
sprintf(macro_buf, "%i", cl.stats[0][STAT_NAILS]);
|
|
return macro_buf;
|
|
|
|
case IT_GRENADE_LAUNCHER: case IT_ROCKET_LAUNCHER:
|
|
sprintf(macro_buf, "%i", cl.stats[0][STAT_ROCKETS]);
|
|
return macro_buf;
|
|
|
|
case IT_LIGHTNING:
|
|
sprintf(macro_buf, "%i", cl.stats[0][STAT_CELLS]);
|
|
return macro_buf;
|
|
|
|
default:
|
|
return "0";
|
|
}
|
|
}
|
|
|
|
// needed for %b parsing
|
|
static char *Macro_BestWeaponAndAmmo (void)
|
|
{
|
|
char buf[MAX_MACRO_VALUE];
|
|
sprintf (buf, "%s:%s", Macro_BestWeapon(), Macro_BestAmmo());
|
|
strcpy (macro_buf, buf);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_ArmorType (void)
|
|
{
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_ARMOR1)
|
|
return "g";
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_ARMOR2)
|
|
return "y";
|
|
else if (cl.stats[SP][STAT_ITEMS] & IT_ARMOR3)
|
|
return "r";
|
|
else
|
|
return ""; // no armor at all
|
|
}
|
|
|
|
static char *Macro_Powerups (void)
|
|
{
|
|
int effects;
|
|
|
|
macro_buf[0] = 0;
|
|
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_QUAD)
|
|
strcpy(macro_buf, "quad");
|
|
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_INVULNERABILITY) {
|
|
if (macro_buf[0])
|
|
strcat(macro_buf, "/");
|
|
strcat(macro_buf, "pent");
|
|
}
|
|
|
|
if (cl.stats[SP][STAT_ITEMS] & IT_INVISIBILITY) {
|
|
if (macro_buf[0])
|
|
strcat(macro_buf, "/");
|
|
strcat(macro_buf, "ring");
|
|
}
|
|
|
|
effects = cl.frames[cl.parsecount&UPDATE_MASK].playerstate[cl.playernum[SP]].effects;
|
|
if ( (effects & (EF_FLAG1|EF_FLAG2)) || // CTF
|
|
(cl.teamfortress && cl.stats[SP][STAT_ITEMS] & (IT_KEY1|IT_KEY2)) ) // TF
|
|
{
|
|
if (macro_buf[0])
|
|
strcat(macro_buf, "/");
|
|
strcat(macro_buf, "flag");
|
|
}
|
|
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Location (void)
|
|
{
|
|
return TP_LocationName (cl.simorg[SP]);
|
|
}
|
|
|
|
static char *Macro_LastDeath (void)
|
|
{
|
|
if (vars.deathtrigger_time)
|
|
return vars.lastdeathloc;
|
|
else
|
|
return tp_name_someplace.string;
|
|
}
|
|
|
|
|
|
static char *Macro_Location2 (void)
|
|
{
|
|
if (vars.deathtrigger_time && realtime - vars.deathtrigger_time <= 5)
|
|
return vars.lastdeathloc;
|
|
return Macro_Location();
|
|
}
|
|
|
|
static char *Macro_Time (void)
|
|
{
|
|
time_t t;
|
|
struct tm *ptm;
|
|
|
|
time (&t);
|
|
ptm = localtime (&t);
|
|
if (!ptm)
|
|
return "#bad date#";
|
|
strftime (macro_buf, sizeof(macro_buf)-1, "%H:%M", ptm);
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_Date (void)
|
|
{
|
|
time_t t;
|
|
struct tm *ptm;
|
|
|
|
time (&t);
|
|
ptm = localtime (&t);
|
|
if (!ptm)
|
|
return "#bad date#";
|
|
strftime (macro_buf, sizeof(macro_buf)-1, "%d.%m.%Y", ptm);
|
|
return macro_buf;
|
|
}
|
|
|
|
// returns the last item picked up
|
|
static char *Macro_Took (void)
|
|
{
|
|
if (!vars.tooktime || realtime > vars.tooktime + 20)
|
|
strlcpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf));
|
|
else
|
|
strcpy (macro_buf, vars.tookname);
|
|
return macro_buf;
|
|
}
|
|
|
|
// returns location of the last item picked up
|
|
static char *Macro_TookLoc (void)
|
|
{
|
|
if (!vars.tooktime || realtime > vars.tooktime + 20)
|
|
strlcpy (macro_buf, tp_name_someplace.string, sizeof(macro_buf));
|
|
else
|
|
strcpy (macro_buf, vars.tookloc);
|
|
return macro_buf;
|
|
}
|
|
|
|
|
|
// %i macro - last item picked up in "name at location" style
|
|
static char *Macro_TookAtLoc (void)
|
|
{
|
|
if (!vars.tooktime || realtime > vars.tooktime + 20)
|
|
strncpy (macro_buf, tp_name_nothing.string, sizeof(macro_buf)-1);
|
|
else
|
|
{
|
|
strlcpy (macro_buf, va("%s %s %s", vars.tookname,
|
|
tp_name_at.string, vars.tookloc), sizeof(macro_buf));
|
|
}
|
|
return macro_buf;
|
|
}
|
|
|
|
// pointing calculations are CPU expensive, so the results are cached
|
|
// in vars.pointname & vars.pointloc
|
|
static char *Macro_PointName (void)
|
|
{
|
|
if (cls.framecount != vars.pointframe)
|
|
TP_FindPoint ();
|
|
return vars.pointname;
|
|
}
|
|
|
|
static char *Macro_PointLocation (void)
|
|
{
|
|
if (cls.framecount != vars.pointframe)
|
|
TP_FindPoint ();
|
|
if (vars.pointloc[0])
|
|
return vars.pointloc;
|
|
else {
|
|
strlcpy (macro_buf, tp_name_someplace.string, sizeof(macro_buf));
|
|
return macro_buf;
|
|
}
|
|
}
|
|
|
|
static char *Macro_PointNameAtLocation (void)
|
|
{
|
|
if (cls.framecount != vars.pointframe)
|
|
TP_FindPoint ();
|
|
if (vars.pointloc[0])
|
|
return va ("%s %s %s", vars.pointname, tp_name_at.string, vars.pointloc);
|
|
else
|
|
return vars.pointname;
|
|
}
|
|
|
|
static char *Macro_Need (void)
|
|
{
|
|
int i, weapon;
|
|
char *needammo = NULL;
|
|
|
|
macro_buf[0] = 0;
|
|
|
|
// check armor
|
|
if ( ((cl.stats[SP][STAT_ITEMS] & IT_ARMOR1) && cl.stats[SP][STAT_ARMOR] < tp_need_ga.value)
|
|
|| ((cl.stats[SP][STAT_ITEMS] & IT_ARMOR2) && cl.stats[SP][STAT_ARMOR] < tp_need_ya.value)
|
|
|| ((cl.stats[SP][STAT_ITEMS] & IT_ARMOR3) && cl.stats[SP][STAT_ARMOR] < tp_need_ra.value)
|
|
|| (!(cl.stats[SP][STAT_ITEMS] & (IT_ARMOR1|IT_ARMOR2|IT_ARMOR3))
|
|
&& (tp_need_ga.value || tp_need_ya.value || tp_need_ra.value)))
|
|
strcpy (macro_buf, "armor");
|
|
|
|
// check health
|
|
if (tp_need_health.value && cl.stats[SP][STAT_HEALTH] < tp_need_health.value) {
|
|
MacroBuf_strcat_with_separator ("health");
|
|
}
|
|
|
|
if (cl.teamfortress)
|
|
{
|
|
// in TF, we have all weapons from the start,
|
|
// and ammo is checked differently
|
|
if (cl.stats[SP][STAT_ROCKETS] < tp_need_rockets.value)
|
|
MacroBuf_strcat_with_separator ("rockets");
|
|
if (cl.stats[SP][STAT_SHELLS] < tp_need_shells.value)
|
|
MacroBuf_strcat_with_separator ("shells");
|
|
if (cl.stats[SP][STAT_NAILS] < tp_need_nails.value)
|
|
MacroBuf_strcat_with_separator ("nails");
|
|
if (cl.stats[SP][STAT_CELLS] < tp_need_cells.value)
|
|
MacroBuf_strcat_with_separator ("cells");
|
|
goto done;
|
|
}
|
|
|
|
// check weapon
|
|
weapon = 0;
|
|
for (i=strlen(tp_need_weapon.string)-1 ; i>=0 ; i--) {
|
|
switch (tp_need_weapon.string[i]) {
|
|
case '2': if (cl.stats[SP][STAT_ITEMS] & IT_SHOTGUN) weapon = 2; break;
|
|
case '3': if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_SHOTGUN) weapon = 3; break;
|
|
case '4': if (cl.stats[SP][STAT_ITEMS] & IT_NAILGUN) weapon = 4; break;
|
|
case '5': if (cl.stats[SP][STAT_ITEMS] & IT_SUPER_NAILGUN) weapon = 5; break;
|
|
case '6': if (cl.stats[SP][STAT_ITEMS] & IT_GRENADE_LAUNCHER) weapon = 6; break;
|
|
case '7': if (cl.stats[SP][STAT_ITEMS] & IT_ROCKET_LAUNCHER) weapon = 7; break;
|
|
case '8': if (cl.stats[SP][STAT_ITEMS] & IT_LIGHTNING) weapon = 8; break;
|
|
}
|
|
if (weapon)
|
|
break;
|
|
}
|
|
|
|
if (!weapon) {
|
|
MacroBuf_strcat_with_separator ("weapon");
|
|
} else {
|
|
if (tp_need_rl.value && !(cl.stats[SP][STAT_ITEMS] & IT_ROCKET_LAUNCHER)) {
|
|
MacroBuf_strcat_with_separator ("rl");
|
|
}
|
|
|
|
switch (weapon) {
|
|
case 2: case 3: if (cl.stats[SP][STAT_SHELLS] < tp_need_shells.value) needammo = "shells"; break;
|
|
case 4: case 5: if (cl.stats[SP][STAT_NAILS] < tp_need_nails.value) needammo = "nails"; break;
|
|
case 6: case 7: if (cl.stats[SP][STAT_ROCKETS] < tp_need_rockets.value) needammo = "rockets"; break;
|
|
case 8: if (cl.stats[SP][STAT_CELLS] < tp_need_cells.value) needammo = "cells"; break;
|
|
}
|
|
if (needammo) {
|
|
MacroBuf_strcat_with_separator (needammo);
|
|
}
|
|
}
|
|
|
|
done:
|
|
if (!macro_buf[0])
|
|
strcpy (macro_buf, "nothing");
|
|
|
|
return macro_buf;
|
|
}
|
|
|
|
static char *Macro_TF_Skin (void)
|
|
{
|
|
char *myskin;
|
|
|
|
myskin = Info_ValueForKey(cl.players[cl.playernum[SP]].userinfo, "skin");
|
|
if (!cl.teamfortress)
|
|
strcpy(macro_buf, myskin);
|
|
else
|
|
{
|
|
if (!Q_stricmp(myskin, "tf_demo"))
|
|
strcpy(macro_buf, "demoman");
|
|
else if (!Q_stricmp(myskin, "tf_eng"))
|
|
strcpy (macro_buf, "engineer");
|
|
else if (!Q_stricmp(myskin, "tf_hwguy"))
|
|
strcpy(macro_buf, "hwguy");
|
|
else if (!Q_stricmp(myskin, "tf_medic"))
|
|
strcpy(macro_buf, "medic");
|
|
else if (!Q_stricmp(myskin, "tf_pyro"))
|
|
strcpy(macro_buf, "pyro");
|
|
else if (!Q_stricmp(myskin, "tf_scout"))
|
|
strcpy(macro_buf, "scout");
|
|
else if (!Q_stricmp(myskin, "tf_snipe"))
|
|
strcpy(macro_buf, "sniper");
|
|
else if (!Q_stricmp(myskin, "tf_sold"))
|
|
strcpy(macro_buf, "soldier");
|
|
else if (!Q_stricmp(myskin, "tf_spy"))
|
|
strcpy(macro_buf, "spy");
|
|
else
|
|
strcpy(macro_buf, myskin);
|
|
}
|
|
return macro_buf;
|
|
}
|
|
|
|
|
|
|
|
//Spike: added these:
|
|
static char *Macro_ConnectionType (void)
|
|
{
|
|
if (!cls.state)
|
|
return "disconnected";
|
|
if (cl.spectator)
|
|
return "spectator";
|
|
return "connected";
|
|
}
|
|
|
|
static char *Macro_demoplayback (void)
|
|
{
|
|
switch (cls.demoplayback)
|
|
{
|
|
case DPB_NONE:
|
|
return "0";
|
|
case DPB_QUAKEWORLD:
|
|
return "qwdplayback";
|
|
case DPB_MVD:
|
|
return "mvdplayback";
|
|
#ifdef NQPROT
|
|
case DPB_NETQUAKE:
|
|
return "demplayback";
|
|
#endif
|
|
#ifdef Q2CLIENT
|
|
case DPB_QUAKE2:
|
|
return "dm2playback";
|
|
#endif
|
|
//gcc will warn if we add annother playback and forget here, otherwise I'd use a default.
|
|
}
|
|
return "1"; //unknown.
|
|
}
|
|
|
|
static char *Macro_Match_Name (void)
|
|
{
|
|
int i;
|
|
i = TP_CountPlayers();
|
|
if (cl.teamplay && i >= 3)
|
|
{ // Teamplay
|
|
return va ("%s %s vs %s - %s",
|
|
TP_PlayerName(),
|
|
TP_PlayerTeam(),
|
|
TP_EnemyTeam(),
|
|
TP_MapName());
|
|
}
|
|
else
|
|
{
|
|
if (i == 2)
|
|
{ // Duel
|
|
return va ("%s vs %s - %s",
|
|
TP_PlayerName(),
|
|
TP_EnemyName(),
|
|
TP_MapName());
|
|
}
|
|
else if (i > 2)
|
|
{ // FFA
|
|
return va ("%s ffa - %s",
|
|
TP_PlayerName(),
|
|
TP_MapName());
|
|
}
|
|
else
|
|
{ // one player
|
|
return va ("%s - %s",
|
|
TP_PlayerName(),
|
|
TP_MapName());
|
|
}
|
|
}
|
|
}
|
|
|
|
//$matchtype
|
|
//duel,2on2,4on4,ffa,etc...
|
|
static char *Macro_Match_Type (void)
|
|
{
|
|
int i;
|
|
i = TP_CountPlayers();
|
|
if (cl.teamplay && i >= 3)
|
|
{
|
|
if (i >= 6)
|
|
return "4on4";
|
|
return "2on2";
|
|
}
|
|
if (i == 2)
|
|
return "duel";
|
|
if (i == 1)
|
|
return "single";
|
|
if (i == 0)
|
|
return "empty";
|
|
return "ffa";
|
|
}
|
|
|
|
static char *Macro_Version (void)
|
|
{
|
|
/* you probably don't need date, but it's included as this is likly to be used by
|
|
q2 servers checking for cheats. */
|
|
return va("%.2f "DISTRIBUTION" ["__DATE__"] (%i)", VERSION, build_number());
|
|
}
|
|
|
|
/*
|
|
$droploc
|
|
Tells location of the dropped flag.
|
|
Note: This will tell only if you have dropped the flag (CTF/TF).
|
|
|
|
$droptime
|
|
Tells how many seconds gone of dropped flag.
|
|
|
|
$ledpoint
|
|
This reports the type of the pointed object as a LED according to the
|
|
following rules:
|
|
If teammate then green.
|
|
If enemy then red.
|
|
If powerup then yellow.
|
|
if item then blue.
|
|
|
|
$ledstatus
|
|
This checks your current status (health, armor and best weapon) and
|
|
reports a LED according to the following rules:
|
|
if all of the above is ok then green.
|
|
if there is one thing low then yellow.
|
|
if there are two or over things low then red.
|
|
|
|
$matchname
|
|
you can use to get the name of the match
|
|
manually (echo $matchname).
|
|
Example: a matchname might be
|
|
"[clan]quaker - [4on4_myclan_vs_someclan] - [dm3]" or whatever.
|
|
|
|
$matchstatus
|
|
("disconnected", "standby" or "normal"). This can be
|
|
used for detecting prewar/prematch on ktpro/oztf servers.
|
|
|
|
$mp3info
|
|
Evaluates to "author - title".
|
|
Examples:
|
|
if you bind space "say listening to $mp3info"
|
|
then hitting space will say something like
|
|
"listening to disturbed - rise".
|
|
bind x "if disturbed isin $mp3info then say dde music is cool"
|
|
|
|
$triggermatch
|
|
$triggermatch is the last chat message that exec'd a msg_trigger.
|
|
|
|
*/
|
|
//Spike: added end.
|
|
|
|
static void TP_InitMacros(void)
|
|
{
|
|
Cmd_AddMacro("version", Macro_Version, false);
|
|
Cmd_AddMacro("qt", Macro_Quote, false);
|
|
Cmd_AddMacro("latency", Macro_Latency, false);
|
|
Cmd_AddMacro("health", Macro_Health, true);
|
|
Cmd_AddMacro("armortype", Macro_ArmorType, true);
|
|
Cmd_AddMacro("armor", Macro_Armor, true);
|
|
Cmd_AddMacro("shells", Macro_Shells, true);
|
|
Cmd_AddMacro("nails", Macro_Nails, true);
|
|
Cmd_AddMacro("rockets", Macro_Rockets, true);
|
|
Cmd_AddMacro("cells", Macro_Cells, true);
|
|
Cmd_AddMacro("weaponnum", Macro_WeaponNum, true);
|
|
Cmd_AddMacro("weapons", Macro_Weapons, true);
|
|
Cmd_AddMacro("weapon", Macro_Weapon, true);
|
|
Cmd_AddMacro("ammo", Macro_Ammo, true);
|
|
Cmd_AddMacro("bestweapon", Macro_BestWeapon, true);
|
|
Cmd_AddMacro("bestammo", Macro_BestAmmo, true);
|
|
Cmd_AddMacro("powerups", Macro_Powerups, true);
|
|
Cmd_AddMacro("location", Macro_Location, false);
|
|
Cmd_AddMacro("deathloc", Macro_LastDeath, true);
|
|
Cmd_AddMacro("time", Macro_Time, true);
|
|
Cmd_AddMacro("date", Macro_Date, false);
|
|
Cmd_AddMacro("tookatloc", Macro_TookAtLoc, true);
|
|
Cmd_AddMacro("tookloc", Macro_TookLoc, true);
|
|
Cmd_AddMacro("took", Macro_Took, true);
|
|
Cmd_AddMacro("tf_skin", Macro_TF_Skin, true);
|
|
|
|
Cmd_AddMacro("droppedweapon", Macro_DroppedWeapon, true);
|
|
|
|
//ones added by Spike, for fuhquake compatability
|
|
Cmd_AddMacro("connectiontype", Macro_ConnectionType, false);
|
|
Cmd_AddMacro("demoplayback", Macro_demoplayback, false);
|
|
Cmd_AddMacro("need", Macro_Need, true);
|
|
Cmd_AddMacro("point", Macro_PointName, true);
|
|
Cmd_AddMacro("pointatloc", Macro_PointNameAtLocation, true);
|
|
Cmd_AddMacro("pointloc", Macro_PointLocation, true);
|
|
Cmd_AddMacro("matchname", Macro_Match_Name, false);
|
|
Cmd_AddMacro("matchtype", Macro_Match_Type, false);
|
|
|
|
// Cmd_AddMacro("droploc", Macro_LastDrop, true);
|
|
// Cmd_AddMacro("droptime", Macro_LastDropTime, true);
|
|
// Cmd_AddMacro("ledpoint", Macro_Point_LED, true);
|
|
// Cmd_AddMacro("ledstatus", Macro_MyStatus_LED, true);
|
|
// Cmd_AddMacro("matchstatus", Macro_Match_Status, false);
|
|
// Cmd_AddMacro("mp3info", , false);
|
|
// Cmd_AddMacro("triggermatch", Macro_LastTrigger_Match, false);
|
|
}
|
|
|
|
#define MAX_MACRO_STRING 1024
|
|
|
|
/*
|
|
=============
|
|
TP_ParseMacroString
|
|
|
|
Parses %a-like expressions
|
|
=============
|
|
*/
|
|
static char *TP_ParseMacroString (char *s)
|
|
{
|
|
static char buf[MAX_MACRO_STRING];
|
|
int i = 0;
|
|
char *macro_string;
|
|
|
|
if (!cl_parseSay.value)
|
|
return s;
|
|
|
|
while (*s && i < MAX_MACRO_STRING-1)
|
|
{
|
|
// check %[P], etc
|
|
if (*s == '%' && s[1]=='[' && s[2] && s[3]==']')
|
|
{
|
|
static char mbuf[MAX_MACRO_VALUE];
|
|
switch (s[2]) {
|
|
case 'a':
|
|
macro_string = Macro_ArmorType();
|
|
if (!macro_string[0])
|
|
macro_string = "a";
|
|
if (cl.stats[SP][STAT_ARMOR] < 30)
|
|
sprintf (mbuf, "\x10%s:%i\x11", macro_string, cl.stats[SP][STAT_ARMOR]);
|
|
else
|
|
sprintf (mbuf, "%s:%i", macro_string, cl.stats[SP][STAT_ARMOR]);
|
|
macro_string = mbuf;
|
|
break;
|
|
|
|
case 'h':
|
|
if (cl.stats[SP][STAT_HEALTH] >= 50)
|
|
sprintf (macro_buf, "%i", cl.stats[SP][STAT_HEALTH]);
|
|
else
|
|
sprintf (macro_buf, "\x10%i\x11", cl.stats[SP][STAT_HEALTH]);
|
|
macro_string = macro_buf;
|
|
break;
|
|
|
|
case 'p':
|
|
case 'P':
|
|
macro_string = Macro_Powerups();
|
|
if (macro_string[0])
|
|
sprintf (mbuf, "\x10%s\x11", macro_string);
|
|
else
|
|
mbuf[0] = 0;
|
|
macro_string = mbuf;
|
|
break;
|
|
|
|
// todo: %[w], %[b]
|
|
|
|
default:
|
|
buf[i++] = *s++;
|
|
continue;
|
|
}
|
|
if (i + strlen(macro_string) >= MAX_MACRO_STRING-1)
|
|
Sys_Error("TP_ParseMacroString: macro string length > MAX_MACRO_STRING)");
|
|
strcpy (&buf[i], macro_string);
|
|
i += strlen(macro_string);
|
|
s += 4; // skip %[<char>]
|
|
continue;
|
|
}
|
|
|
|
// check %a, etc
|
|
if (*s == '%')
|
|
{
|
|
switch (s[1])
|
|
{
|
|
case 'a': macro_string = Macro_Armor(); break;
|
|
case 'A': macro_string = Macro_ArmorType(); break;
|
|
case 'b': macro_string = Macro_BestWeaponAndAmmo(); break;
|
|
case 'c': macro_string = Macro_Cells(); break;
|
|
case 'd': macro_string = Macro_LastDeath(); break;
|
|
case 'h': macro_string = Macro_Health(); break;
|
|
case 'i': macro_string = Macro_TookAtLoc(); break;
|
|
case 'l': macro_string = Macro_Location(); break;
|
|
case 'L': macro_string = Macro_Location2(); break;
|
|
case 'P':
|
|
case 'p': macro_string = Macro_Powerups(); break;
|
|
case 'r': macro_string = Macro_Rockets(); break;
|
|
case 'u': macro_string = Macro_Need(); break;
|
|
case 'w': macro_string = Macro_WeaponAndAmmo(); break;
|
|
case 'x': macro_string = Macro_PointName(); break;
|
|
case 'y': macro_string = Macro_PointLocation(); break;
|
|
case 't': macro_string = Macro_PointNameAtLocation(); break;
|
|
case 'S': macro_string = Macro_TF_Skin(); break;
|
|
default:
|
|
buf[i++] = *s++;
|
|
continue;
|
|
}
|
|
if (i + strlen(macro_string) >= MAX_MACRO_STRING-1)
|
|
Sys_Error("TP_ParseMacroString: macro string length > MAX_MACRO_STRING)");
|
|
strcpy (&buf[i], macro_string);
|
|
i += strlen(macro_string);
|
|
s += 2; // skip % and letter
|
|
continue;
|
|
}
|
|
|
|
buf[i++] = *s++;
|
|
}
|
|
buf[i] = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
TP_ParseFunChars
|
|
|
|
Doesn't check for overflows, so strlen(s) should be < MAX_MACRO_STRING
|
|
==============
|
|
*/
|
|
static char *TP_ParseFunChars (char *s, qbool chat)
|
|
{
|
|
static char buf[MAX_MACRO_STRING];
|
|
char *out = buf;
|
|
int c;
|
|
|
|
if (!cl_parseFunChars.value)
|
|
return s;
|
|
|
|
while (*s) {
|
|
if (*s == '$' && s[1] == 'x') {
|
|
int i;
|
|
// check for $x10, $x8a, etc
|
|
c = tolower((int)(unsigned char)s[2]);
|
|
if ( isdigit(c) )
|
|
i = (c - (int)'0') << 4;
|
|
else if ( isxdigit(c) )
|
|
i = (c - (int)'a' + 10) << 4;
|
|
else goto skip;
|
|
c = tolower((int)(unsigned char)s[3]);
|
|
if ( isdigit(c) )
|
|
i += (c - (int)'0');
|
|
else if ( isxdigit(c) )
|
|
i += (c - (int)'a' + 10);
|
|
else goto skip;
|
|
if (!i)
|
|
i = (int)' ';
|
|
*out++ = (char)i;
|
|
s += 4;
|
|
continue;
|
|
}
|
|
if (*s == '$' && s[1]) {
|
|
c = 0;
|
|
switch (s[1]) {
|
|
case '\\': c = 0x0D; break;
|
|
case ':': c = 0x0A; break;
|
|
case '[': c = 0x10; break;
|
|
case ']': c = 0x11; break;
|
|
case 'G': c = 0x86; break;
|
|
case 'R': c = 0x87; break;
|
|
case 'Y': c = 0x88; break;
|
|
case 'B': c = 0x89; break;
|
|
case '(': c = 0x80; break;
|
|
case '=': c = 0x81; break;
|
|
case ')': c = 0x82; break;
|
|
case 'a': c = 0x83; break;
|
|
case '<': c = 0x1d; break;
|
|
case '-': c = 0x1e; break;
|
|
case '>': c = 0x1f; break;
|
|
case ',': c = 0x1c; break;
|
|
case '.': c = 0x9c; break;
|
|
case 'b': c = 0x8b; break;
|
|
case 'c':
|
|
case 'd': c = 0x8d; break;
|
|
case '$': c = '$'; break;
|
|
case '^': c = '^'; break;
|
|
}
|
|
if ( isdigit((int)(unsigned char)s[1]) )
|
|
c = s[1] - (int)'0' + 0x12;
|
|
if (c) {
|
|
*out++ = (char)c;
|
|
s += 2;
|
|
continue;
|
|
}
|
|
}
|
|
if (!chat && *s == '^' && s[1] && s[1] != ' ') {
|
|
*out++ = s[1] | 128;
|
|
s += 2;
|
|
continue;
|
|
}
|
|
skip:
|
|
*out++ = *s++;
|
|
}
|
|
*out = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
PROXY .LOC FILES
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
typedef struct locdata_s {
|
|
vec3_t coord;
|
|
char name[MAX_LOC_NAME];
|
|
} locdata_t;
|
|
|
|
#define MAX_LOC_ENTRIES 4096
|
|
|
|
locdata_t locdata[MAX_LOC_ENTRIES]; // FIXME: allocate dynamically?
|
|
int loc_numentries;
|
|
|
|
|
|
static void TP_LoadLocFile (char *filename, qbool quiet)
|
|
{
|
|
char fullpath[MAX_QPATH];
|
|
char *buf, *p;
|
|
char line[1024];
|
|
int i, argc;
|
|
int errorcount = 0;
|
|
locdata_t *loc;
|
|
|
|
if (!*filename)
|
|
return;
|
|
|
|
Q_snprintfz (fullpath, sizeof(fullpath) - 4, "locs/%s", filename);
|
|
COM_DefaultExtension (fullpath, ".loc");
|
|
|
|
buf = (char *) COM_LoadTempFile (fullpath);
|
|
if (!buf) {
|
|
if (!quiet)
|
|
Com_Printf ("Could not load %s\n", fullpath);
|
|
return;
|
|
}
|
|
|
|
loc_numentries = 0;
|
|
|
|
// parse the file
|
|
// we rely on the fact that FS_Load*File always appends a 0 at the end
|
|
p = buf;
|
|
while (1) {
|
|
if (!*p)
|
|
break; // end of file
|
|
|
|
// get a line out
|
|
for (i = 0; i < sizeof(line)-1; ) {
|
|
char c = *p++;
|
|
if (!c || c == 10)
|
|
break;
|
|
if (c != 13)
|
|
line[i++] = c;
|
|
}
|
|
line[i] = 0;
|
|
|
|
Cmd_TokenizeString (line, true, false);
|
|
|
|
argc = Cmd_Argc();
|
|
if (!argc)
|
|
continue;
|
|
|
|
if (argc < 4) {
|
|
errorcount++;
|
|
continue;
|
|
}
|
|
|
|
if (atoi(Cmd_Argv(0)) == 0 && Cmd_Argv(0)[0] != '0') {
|
|
// first token is not a number
|
|
errorcount++;
|
|
continue;
|
|
}
|
|
|
|
if (loc_numentries >= MAX_LOC_ENTRIES)
|
|
continue;
|
|
|
|
loc = &locdata[loc_numentries];
|
|
loc_numentries++;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
loc->coord[i] = atoi(Cmd_Argv(i)) / 8.0;
|
|
|
|
loc->name[0] = 0;
|
|
loc->name[sizeof(loc->name)-1] = 0; // can't rely on strncat
|
|
for (i = 3; i < argc; i++) {
|
|
if (i != 3)
|
|
strncat (loc->name, " ", sizeof(loc->name)-1);
|
|
strncat (loc->name, Cmd_Argv(i), sizeof(loc->name)-1);
|
|
}
|
|
}
|
|
|
|
if (!quiet)
|
|
Com_Printf ("Loaded %s (%i points)\n", fullpath, loc_numentries);
|
|
}
|
|
|
|
static void TP_LoadLocFile_f (void)
|
|
{
|
|
if (Cmd_Argc() != 2)
|
|
{
|
|
Com_Printf ("loadloc <filename> : load a loc file\n");
|
|
return;
|
|
}
|
|
|
|
TP_LoadLocFile (Cmd_Argv(1), false);
|
|
}
|
|
|
|
static char *TP_LocationName (vec3_t location)
|
|
{
|
|
int i, minnum;
|
|
float dist, mindist;
|
|
vec3_t vec;
|
|
static qbool recursive;
|
|
static char buf[1024];
|
|
|
|
if (!loc_numentries || (cls.state != ca_active))
|
|
return tp_name_someplace.string;
|
|
|
|
if (recursive)
|
|
return "";
|
|
|
|
minnum = 0;
|
|
mindist = 9999999;
|
|
|
|
for (i = 0; i < loc_numentries; i++) {
|
|
VectorSubtract (location, locdata[i].coord, vec);
|
|
dist = VectorLength (vec);
|
|
if (dist < mindist) {
|
|
minnum = i;
|
|
mindist = dist;
|
|
}
|
|
}
|
|
|
|
recursive = true;
|
|
Cmd_ExpandString (locdata[minnum].name, buf, sizeof(buf), Cmd_ExecLevel, false);
|
|
recursive = false;
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
MESSAGE TRIGGERS
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
// FIXME, we don't provide a way to remove triggers
|
|
// allocated heap memory is not freed when the engine shuts down
|
|
|
|
typedef struct msg_trigger_s {
|
|
char name[32];
|
|
char string[64];
|
|
int level;
|
|
struct msg_trigger_s *next;
|
|
} msg_trigger_t;
|
|
|
|
static msg_trigger_t *msg_triggers;
|
|
|
|
static msg_trigger_t *TP_FindTrigger (char *name)
|
|
{
|
|
msg_trigger_t *t;
|
|
|
|
for (t=msg_triggers; t; t=t->next)
|
|
if (!strcmp(t->name, name))
|
|
return t;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void TP_MsgTrigger_f (void)
|
|
{
|
|
int c;
|
|
char *name;
|
|
msg_trigger_t *trig;
|
|
|
|
c = Cmd_Argc();
|
|
|
|
if (c > 5) {
|
|
Com_Printf ("msg_trigger <trigger name> \"string\" [-l <level>]\n");
|
|
return;
|
|
}
|
|
|
|
if (c == 1) {
|
|
if (!msg_triggers)
|
|
Com_Printf ("no triggers defined\n");
|
|
else
|
|
for (trig=msg_triggers; trig; trig=trig->next)
|
|
Com_Printf ("%s : \"%s\"\n", trig->name, trig->string);
|
|
return;
|
|
}
|
|
|
|
name = Cmd_Argv(1);
|
|
if (strlen(name) > 31) {
|
|
Com_Printf ("trigger name too long\n");
|
|
return;
|
|
}
|
|
|
|
if (c == 2) {
|
|
trig = TP_FindTrigger (name);
|
|
if (trig)
|
|
Com_Printf ("%s: \"%s\"\n", trig->name, trig->string);
|
|
else
|
|
Com_Printf ("trigger \"%s\" not found\n", name);
|
|
return;
|
|
}
|
|
|
|
if (c >= 3) {
|
|
if (strlen(Cmd_Argv(2)) > 63) {
|
|
Com_Printf ("trigger string too long\n");
|
|
return;
|
|
}
|
|
|
|
trig = TP_FindTrigger (name);
|
|
|
|
if (!trig) {
|
|
// allocate new trigger
|
|
trig = Z_Malloc (sizeof(msg_trigger_t));
|
|
trig->next = msg_triggers;
|
|
msg_triggers = trig;
|
|
strcpy (trig->name, name); // safe (length checked earlier)
|
|
trig->level = PRINT_HIGH;
|
|
}
|
|
|
|
strcpy (trig->string, Cmd_Argv(2)); // safe (length checked earlier)
|
|
if (c == 5 && !Q_stricmp (Cmd_Argv(3), "-l")) {
|
|
if (!strcmp(Cmd_Argv(4), "t"))
|
|
trig->level = 4;
|
|
else {
|
|
trig->level = Q_atoi (Cmd_Argv(4));
|
|
if ((unsigned)trig->level > PRINT_CHAT)
|
|
trig->level = PRINT_HIGH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TP_SearchForMsgTriggers (char *s, int level)
|
|
{
|
|
msg_trigger_t *t;
|
|
char *string;
|
|
|
|
if (cls.demoplayback)
|
|
return;
|
|
|
|
for (t=msg_triggers; t; t=t->next)
|
|
if ((t->level == level || (t->level == 3 && level == 4))
|
|
&& t->string[0] && strstr(s, t->string))
|
|
{
|
|
if (level == PRINT_CHAT && (
|
|
strstr (s, "f_version") || strstr (s, "f_system") ||
|
|
strstr (s, "f_speed") || strstr (s, "f_modified")))
|
|
continue; // don't let llamas fake proxy replies
|
|
|
|
string = Cmd_AliasExist (t->name, RESTRICT_LOCAL);
|
|
if (string)
|
|
{
|
|
Cbuf_AddText (string, RESTRICT_LOCAL);
|
|
// Cbuf_ExecuteLevel (RESTRICT_LOCAL);
|
|
}
|
|
else
|
|
Com_Printf ("trigger \"%s\" has no matching alias\n", t->name);
|
|
}
|
|
}
|
|
|
|
/*
|
|
void TP_CheckVersionRequest (char *s)
|
|
{
|
|
char buf[11];
|
|
int i;
|
|
|
|
if (cl.spectator)
|
|
return;
|
|
|
|
if (vars.f_version_reply_time
|
|
&& realtime - vars.f_version_reply_time < 20)
|
|
return; // don't reply again if 20 seconds haven't passed
|
|
|
|
while (1)
|
|
{
|
|
switch (*s++)
|
|
{
|
|
case 0:
|
|
case '\n':
|
|
return;
|
|
case ':':
|
|
case (char)(':'|128): // hmm.... why is this here?
|
|
goto ok;
|
|
}
|
|
}
|
|
return;
|
|
|
|
ok:
|
|
for (i = 0; i < 11 && s[i]; i++)
|
|
buf[i] = s[i] &~ 128; // strip high bit
|
|
|
|
if (!strncmp(buf, " f_version\n", 11) || !strncmp(buf, " z_version\n", 11))
|
|
{
|
|
Cbuf_AddText (va("say ZQuake version %s "
|
|
QW_PLATFORM ":" QW_RENDERER "\n", VersionString()));
|
|
vars.f_version_reply_time = realtime;
|
|
}
|
|
}*/
|
|
|
|
|
|
int TP_CountPlayers (void)
|
|
{
|
|
int i, count;
|
|
|
|
count = 0;
|
|
for (i = 0; i < MAX_CLIENTS ; i++) {
|
|
if (cl.players[i].name[0] && !cl.players[i].spectator)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
char *TP_EnemyTeam (void)
|
|
{
|
|
int i;
|
|
char myteam[MAX_INFO_KEY];
|
|
static char enemyteam[MAX_INFO_KEY];
|
|
|
|
strcpy (myteam, Info_ValueForKey(cls.userinfo, "team"));
|
|
|
|
for (i = 0; i < MAX_CLIENTS ; i++) {
|
|
if (cl.players[i].name[0] && !cl.players[i].spectator)
|
|
{
|
|
strcpy (enemyteam, Info_ValueForKey(cl.players[i].userinfo, "team"));
|
|
if (strcmp(myteam, enemyteam) != 0)
|
|
return enemyteam;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
char *TP_PlayerName (void)
|
|
{
|
|
static char myname[MAX_INFO_KEY];
|
|
strcpy (myname, Info_ValueForKey(cl.players[cl.playernum[SP]].userinfo, "name"));
|
|
return myname;
|
|
}
|
|
|
|
char *TP_PlayerTeam (void)
|
|
{
|
|
static char myteam[MAX_INFO_KEY];
|
|
strcpy (myteam, Info_ValueForKey(cl.players[cl.playernum[SP]].userinfo, "team"));
|
|
return myteam;
|
|
}
|
|
|
|
char *TP_EnemyName (void)
|
|
{
|
|
int i;
|
|
char *myname;
|
|
static char enemyname[MAX_INFO_KEY];
|
|
|
|
myname = TP_PlayerName ();
|
|
|
|
for (i = 0; i < MAX_CLIENTS ; i++) {
|
|
if (cl.players[i].name[0] && !cl.players[i].spectator)
|
|
{
|
|
strcpy (enemyname, Info_ValueForKey(cl.players[i].userinfo, "name"));
|
|
if (!strcmp(enemyname, myname))
|
|
return enemyname;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
char *TP_MapName (void)
|
|
{
|
|
return host_mapname.string;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
TEAMCOLOR & ENEMYCOLOR
|
|
=============================================================================
|
|
*/
|
|
|
|
int cl_teamtopcolor = -1;
|
|
int cl_teambottomcolor = -1;
|
|
int cl_enemytopcolor = -1;
|
|
int cl_enemybottomcolor = -1;
|
|
|
|
static void TP_TeamColor_f (void)
|
|
{
|
|
int top, bottom;
|
|
int i;
|
|
|
|
if (Cmd_Argc() == 1)
|
|
{
|
|
if (cl_teamtopcolor < 0)
|
|
Com_Printf ("\"teamcolor\" is \"off\"\n");
|
|
else
|
|
Com_Printf ("\"teamcolor\" is \"%i %i\"\n",
|
|
cl_teamtopcolor,
|
|
cl_teambottomcolor);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(Cmd_Argv(1), "off"))
|
|
{
|
|
cl_teamtopcolor = -1;
|
|
cl_teambottomcolor = -1;
|
|
if (qrenderer) //make sure we have the renderer initialised...
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
CL_NewTranslation(i);
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc() == 2)
|
|
top = bottom = atoi(Cmd_Argv(1));
|
|
else {
|
|
top = atoi(Cmd_Argv(1));
|
|
bottom = atoi(Cmd_Argv(2));
|
|
}
|
|
|
|
top &= 15;
|
|
if (top > 13)
|
|
top = 13;
|
|
bottom &= 15;
|
|
if (bottom > 13)
|
|
bottom = 13;
|
|
|
|
// if (top != cl_teamtopcolor || bottom != cl_teambottomcolor)
|
|
{
|
|
cl_teamtopcolor = top;
|
|
cl_teambottomcolor = bottom;
|
|
|
|
if (qrenderer) //make sure we have the renderer initialised...
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
CL_NewTranslation(i);
|
|
}
|
|
}
|
|
|
|
static void TP_EnemyColor_f (void)
|
|
{
|
|
int top, bottom;
|
|
int i;
|
|
|
|
if (Cmd_Argc() == 1)
|
|
{
|
|
if (cl_enemytopcolor < 0)
|
|
Com_Printf ("\"enemycolor\" is \"off\"\n");
|
|
else
|
|
Com_Printf ("\"enemycolor\" is \"%i %i\"\n",
|
|
cl_enemytopcolor,
|
|
cl_enemybottomcolor);
|
|
return;
|
|
}
|
|
|
|
if (!strcmp(Cmd_Argv(1), "off"))
|
|
{
|
|
cl_enemytopcolor = -1;
|
|
cl_enemybottomcolor = -1;
|
|
if (qrenderer) //make sure we have the renderer initialised...
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
CL_NewTranslation(i);
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc() == 2)
|
|
top = bottom = atoi(Cmd_Argv(1));
|
|
else {
|
|
top = atoi(Cmd_Argv(1));
|
|
bottom = atoi(Cmd_Argv(2));
|
|
}
|
|
|
|
top &= 15;
|
|
if (top > 13)
|
|
top = 13;
|
|
bottom &= 15;
|
|
if (bottom > 13)
|
|
bottom = 13;
|
|
|
|
// if (top != cl_enemytopcolor || bottom != cl_enemybottomcolor)
|
|
{
|
|
cl_enemytopcolor = top;
|
|
cl_enemybottomcolor = bottom;
|
|
|
|
if (qrenderer) //make sure we have the renderer initialised...
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
CL_NewTranslation(i);
|
|
}
|
|
}
|
|
|
|
//===================================================================
|
|
|
|
void TP_NewMap (void)
|
|
{
|
|
static char last_map[MAX_QPATH];
|
|
char locname[MAX_OSPATH];
|
|
|
|
memset (&vars, 0, sizeof(vars));
|
|
TP_FindModelNumbers ();
|
|
|
|
// FIXME, just try to load the loc file no matter what?
|
|
if (strcmp(host_mapname.string, last_map))
|
|
{ // map name has changed
|
|
loc_numentries = 0; // clear loc file
|
|
if (tp_loadlocs.value && cl.deathmatch && !cls.demoplayback) {
|
|
Q_snprintfz (locname, sizeof(locname), "%s.loc", host_mapname.string);
|
|
TP_LoadLocFile (locname, true);
|
|
}
|
|
strlcpy (last_map, host_mapname.string, sizeof(last_map));
|
|
}
|
|
|
|
TP_ExecTrigger ("f_newmap");
|
|
}
|
|
|
|
/*
|
|
======================
|
|
TP_CategorizeMessage
|
|
|
|
returns a combination of these values:
|
|
0 -- unknown (probably generated by the server)
|
|
1 -- normal
|
|
2 -- team message
|
|
4 -- spectator
|
|
Note that sometimes we can't be sure who really sent the message,
|
|
e.g. when there's a player "unnamed" in your team and "(unnamed)"
|
|
in the enemy team. The result will be 3 (1+2)
|
|
|
|
Never returns 2 if we are a spectator.
|
|
======================
|
|
*/
|
|
int TP_CategorizeMessage (char *s, int *offset)
|
|
{
|
|
int i, msglen, len;
|
|
int flags;
|
|
player_info_t *player;
|
|
char *name;
|
|
|
|
flags = 0;
|
|
msglen = strlen(s);
|
|
if (!msglen)
|
|
return 0;
|
|
|
|
*offset = 0;
|
|
|
|
for (i=0, player=cl.players ; i < MAX_CLIENTS ; i++, player++)
|
|
{
|
|
if (!player->name[0])
|
|
continue;
|
|
name = Info_ValueForKey (player->userinfo, "name");
|
|
len = strlen(name);
|
|
// check messagemode1
|
|
if (len+2 <= msglen && s[len] == ':' && s[len+1] == ' ' &&
|
|
!strncmp(name, s, len))
|
|
{
|
|
if (player->spectator)
|
|
flags |= 4;
|
|
else
|
|
flags |= 1;
|
|
*offset = len + 2;
|
|
}
|
|
// check messagemode2
|
|
else if (s[0] == '(' && !cl.spectator && len+4 <= msglen &&
|
|
!strncmp(s+len+1, "): ", 3) &&
|
|
!strncmp(name, s+1, len))
|
|
{
|
|
// no team messages in teamplay 0, except for our own
|
|
if (i == cl.playernum[SP] || ( cl.teamplay &&
|
|
!strcmp(cl.players[cl.playernum[SP]].team, player->team)) )
|
|
flags |= 2;
|
|
*offset = len + 4;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
//===================================================================
|
|
// Pickup triggers
|
|
//
|
|
|
|
// symbolic names used in tp_took, tp_pickup, tp_point commands
|
|
static char *pknames[] = {"quad", "pent", "ring", "suit", "ra", "ya", "ga",
|
|
"mh", "health", "lg", "rl", "gl", "sng", "ng", "ssg", "pack",
|
|
"cells", "rockets", "nails", "shells", "flag", "pointed",
|
|
"sentry", "disp", "runes"};
|
|
|
|
#define it_quad (1<<0)
|
|
#define it_pent (1<<1)
|
|
#define it_ring (1<<2)
|
|
#define it_suit (1<<3)
|
|
#define it_ra (1<<4)
|
|
#define it_ya (1<<5)
|
|
#define it_ga (1<<6)
|
|
#define it_mh (1<<7)
|
|
#define it_health (1<<8)
|
|
#define it_lg (1<<9)
|
|
#define it_rl (1<<10)
|
|
#define it_gl (1<<11)
|
|
#define it_sng (1<<12)
|
|
#define it_ng (1<<13)
|
|
#define it_ssg (1<<14)
|
|
#define it_pack (1<<15)
|
|
#define it_cells (1<<16)
|
|
#define it_rockets (1<<17)
|
|
#define it_nails (1<<18)
|
|
#define it_shells (1<<19)
|
|
#define it_flag (1<<20)
|
|
#define it_pointed (1<<21) // only valid for tp_took
|
|
#define it_sentry (1 << 22)
|
|
#define it_disp (1 << 23)
|
|
#define it_runes (1 << 24)
|
|
#define NUM_ITEMFLAGS 25
|
|
|
|
#define it_powerups (it_quad|it_pent|it_ring)
|
|
#define it_weapons (it_lg|it_rl|it_gl|it_sng|it_ng|it_ssg)
|
|
#define it_armor (it_ra|it_ya|it_ga)
|
|
#define it_ammo (it_cells|it_rockets|it_nails|it_shells)
|
|
|
|
#define default_pkflags (it_powerups|it_suit|it_armor|it_weapons|it_mh| \
|
|
it_rockets|it_pack|it_flag)
|
|
|
|
#define default_tookflags (it_powerups|it_ra|it_ya|it_lg|it_rl|it_mh|it_flag|it_pointed)
|
|
|
|
#define default_pointflags (it_powerups|it_suit|it_armor|it_mh| \
|
|
it_lg|it_rl|it_gl|it_sng|it_rockets|it_pack|it_flag)
|
|
|
|
static int pkflags = default_pkflags;
|
|
static int tookflags = default_tookflags;
|
|
static int pointflags = default_pointflags;
|
|
|
|
|
|
static void FlagCommand (int *flags, int defaultflags)
|
|
{
|
|
int i, j, c;
|
|
char *p;
|
|
char str[255] = "";
|
|
qbool removeflag = false;
|
|
int flag;
|
|
|
|
c = Cmd_Argc ();
|
|
if (c == 1)
|
|
{
|
|
if (!*flags)
|
|
strcpy (str, "nothing");
|
|
for (i=0 ; i<NUM_ITEMFLAGS ; i++)
|
|
if (*flags & (1<<i))
|
|
{
|
|
if (*str)
|
|
strcat (str, " ");
|
|
strcat (str, pknames[i]);
|
|
}
|
|
Com_Printf ("%s\n", str);
|
|
return;
|
|
}
|
|
|
|
if (*Cmd_Argv(1) != '+' && *Cmd_Argv(1) != '-')
|
|
*flags = 0;
|
|
|
|
for (i=1 ; i<c ; i++)
|
|
{
|
|
p = Cmd_Argv (i);
|
|
if (*p == '+') {
|
|
removeflag = false;
|
|
p++;
|
|
} else if (*p == '-') {
|
|
removeflag = true;
|
|
p++;
|
|
}
|
|
|
|
flag = 0;
|
|
for (j=0 ; j<NUM_ITEMFLAGS ; j++) {
|
|
if ((1<<j) == it_pointed && defaultflags != default_tookflags /* FIXME FIXME */)
|
|
continue;
|
|
if (!Q_strnicmp (p, pknames[j], 3)) {
|
|
flag = 1<<j;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!flag) {
|
|
if (!Q_stricmp (p, "armor"))
|
|
flag = it_ra|it_ya|it_ga;
|
|
else if (!Q_stricmp (p, "weapons"))
|
|
flag = it_lg|it_rl|it_gl|it_sng|it_ng|it_ssg;
|
|
else if (!Q_stricmp (p, "powerups"))
|
|
flag = it_quad|it_pent|it_ring;
|
|
else if (!Q_stricmp (p, "ammo"))
|
|
flag = it_cells|it_rockets|it_nails|it_shells;
|
|
else if (!Q_stricmp (p, "default"))
|
|
flag = defaultflags;
|
|
else if (!Q_stricmp (p, "all")){
|
|
flag = (1<<NUM_ITEMFLAGS)-1;
|
|
if (defaultflags != default_tookflags /* FIXME FIXME */)
|
|
flag &= ~it_pointed;
|
|
}
|
|
}
|
|
|
|
if (removeflag)
|
|
*flags &= ~flag;
|
|
else
|
|
*flags |= flag;
|
|
}
|
|
}
|
|
|
|
static void TP_Took_f (void)
|
|
{
|
|
FlagCommand (&tookflags, default_tookflags);
|
|
}
|
|
|
|
void TP_Pickup_f (void)
|
|
{
|
|
FlagCommand (&pkflags, default_pkflags);
|
|
}
|
|
|
|
static void TP_Point_f (void)
|
|
{
|
|
FlagCommand (&pointflags, default_pointflags);
|
|
}
|
|
|
|
|
|
/*
|
|
// FIXME: maybe use sound indexes so we don't have to make strcmp's
|
|
// every time?
|
|
|
|
#define S_LOCK4 1 // weapons/lock4.wav
|
|
#define S_PKUP 2 // weapons/pkup.wav
|
|
#define S_HEALTH25 3 // items/health1.wav
|
|
#define S_HEALTH15 4 // items/r_item1.wav
|
|
#define S_MHEALTH 5 // items/r_item2.wav
|
|
#define S_DAMAGE 6 // items/damage.wav
|
|
#define S_EYES 7 // items/inv1.wav
|
|
#define S_PENT 8 // items/protect.wav
|
|
#define S_ARMOR 9 // items/armor1.wav
|
|
|
|
static char *tp_soundnames[] =
|
|
{
|
|
"weapons/lock4.wav",
|
|
"weapons/pkup.wav",
|
|
"items/health1.wav",
|
|
"items/r_item1.wav",
|
|
"items/r_item2.wav",
|
|
"items/damage.wav",
|
|
"items/inv1.wav",
|
|
"items/protect.wav"
|
|
"items/armor1.wav"
|
|
};
|
|
|
|
#define TP_NUMSOUNDS (sizeof(tp_soundnames)/sizeof(tp_soundnames[0]))
|
|
|
|
int sound_numbers[MAX_SOUNDS];
|
|
|
|
void TP_FindSoundNumbers (void)
|
|
{
|
|
int i, j;
|
|
char *s;
|
|
for (i=0 ; i<MAX_SOUNDS ; i++)
|
|
{
|
|
s = &cl.sound_name[i];
|
|
for (j=0 ; j<TP_NUMSOUNDS ; j++)
|
|
...
|
|
}
|
|
}
|
|
*/
|
|
|
|
typedef struct {
|
|
int itemflag;
|
|
cvar_t *cvar;
|
|
char *modelname;
|
|
vec3_t offset; // offset of model graphics center
|
|
float radius; // model graphics radius
|
|
int flags; // TODO: "NOPICKUP" (disp), "TEAMENEMY" (flag, disp)
|
|
} item_t;
|
|
|
|
static item_t tp_items[] = {
|
|
{ it_quad, &tp_name_quad, "progs/quaddama.mdl",
|
|
{0, 0, 24}, 25,
|
|
},
|
|
{ it_pent, &tp_name_pent, "progs/invulner.mdl",
|
|
{0, 0, 22}, 25,
|
|
},
|
|
{ it_ring, &tp_name_ring, "progs/invisibl.mdl",
|
|
{0, 0, 16}, 12,
|
|
},
|
|
{ it_suit, &tp_name_suit, "progs/suit.mdl",
|
|
{0, 0, 24}, 20,
|
|
},
|
|
{ it_lg, &tp_name_lg, "progs/g_light.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_rl, &tp_name_rl, "progs/g_rock2.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_gl, &tp_name_gl, "progs/g_rock.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_sng, &tp_name_sng, "progs/g_nail2.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_ng, &tp_name_ng, "progs/g_nail.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_ssg, &tp_name_ssg, "progs/g_shot.mdl",
|
|
{0, 0, 30}, 20,
|
|
},
|
|
{ it_cells, &tp_name_cells, "maps/b_batt0.bsp",
|
|
{16, 16, 24}, 18,
|
|
},
|
|
{ it_cells, &tp_name_cells, "maps/b_batt1.bsp",
|
|
{16, 16, 24}, 18,
|
|
},
|
|
{ it_rockets, &tp_name_rockets,"maps/b_rock0.bsp",
|
|
{8, 8, 20}, 18,
|
|
},
|
|
{ it_rockets, &tp_name_rockets,"maps/b_rock1.bsp",
|
|
{16, 8, 20}, 18,
|
|
},
|
|
{ it_nails, &tp_name_nails, "maps/b_nail0.bsp",
|
|
{16, 16, 10}, 18,
|
|
},
|
|
{ it_nails, &tp_name_nails, "maps/b_nail1.bsp",
|
|
{16, 16, 10}, 18,
|
|
},
|
|
{ it_shells, &tp_name_shells,"maps/b_shell0.bsp",
|
|
{16, 16, 10}, 18,
|
|
},
|
|
{ it_shells, &tp_name_shells,"maps/b_shell1.bsp",
|
|
{16, 16, 10}, 18,
|
|
},
|
|
{ it_health, &tp_name_health,"maps/b_bh10.bsp",
|
|
{16, 16, 8}, 18,
|
|
},
|
|
{ it_health, &tp_name_health,"maps/b_bh25.bsp",
|
|
{16, 16, 8}, 18,
|
|
},
|
|
{ it_mh, &tp_name_mh, "maps/b_bh100.bsp",
|
|
{16, 16, 14}, 20,
|
|
},
|
|
{ it_pack, &tp_name_backpack, "progs/backpack.mdl",
|
|
{0, 0, 18}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/tf_flag.mdl",
|
|
{0, 0, 14}, 25,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/tf_stan.mdl",
|
|
{0, 0, 45}, 40,
|
|
},
|
|
{ it_ra|it_ya|it_ga, NULL, "progs/armor.mdl",
|
|
{0, 0, 24}, 22,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/w_g_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/w_s_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/m_g_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/m_s_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/b_s_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/b_g_key.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_flag, &tp_name_flag, "progs/flag.mdl",
|
|
{0, 0, 14}, 25,
|
|
},
|
|
{ it_runes, &tp_name_rune_1, "progs/end1.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_runes, &tp_name_rune_2, "progs/end2.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_runes, &tp_name_rune_3, "progs/end3.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_runes, &tp_name_rune_4, "progs/end4.mdl",
|
|
{0, 0, 20}, 18,
|
|
},
|
|
{ it_sentry, &tp_name_sentry, "progs/turrgun.mdl",
|
|
{0, 0, 23}, 25,
|
|
},
|
|
{ it_disp, &tp_name_disp, "progs/disp.mdl",
|
|
{0, 0, 24}, 25,
|
|
}
|
|
};
|
|
|
|
#define NUMITEMS (sizeof(tp_items) / sizeof(tp_items[0]))
|
|
|
|
static item_t *model2item[MAX_MODELS];
|
|
|
|
static void TP_FindModelNumbers (void)
|
|
{
|
|
int i, j;
|
|
char *s;
|
|
item_t *item;
|
|
|
|
for (i=0 ; i<MAX_MODELS ; i++) {
|
|
model2item[i] = NULL;
|
|
s = cl.model_name[i];
|
|
if (!s)
|
|
continue;
|
|
for (j=0, item=tp_items ; j<NUMITEMS ; j++, item++)
|
|
if (!strcmp(s, item->modelname))
|
|
model2item[i] = item;
|
|
}
|
|
}
|
|
|
|
|
|
// on success, result is non-zero
|
|
// on failure, result is zero
|
|
// for armors, returns skinnum+1 on success
|
|
static int FindNearestItem (int flags, item_t **pitem)
|
|
{
|
|
frame_t *frame;
|
|
packet_entities_t *pak;
|
|
entity_state_t *ent;
|
|
int i = 0, bestidx = 0, bestskin = 0;
|
|
float bestdist = 0.0, dist = 0.0;
|
|
vec3_t org, v;
|
|
item_t *item;
|
|
|
|
VectorCopy (cl.frames[cl.validsequence&UPDATE_MASK]
|
|
.playerstate[cl.playernum[SP]].origin, org);
|
|
|
|
// look in previous frame
|
|
frame = &cl.frames[cl.oldvalidsequence&UPDATE_MASK];
|
|
pak = &frame->packet_entities;
|
|
bestdist = 100.0f;
|
|
bestidx = 0;
|
|
*pitem = NULL;
|
|
for (i=0,ent=pak->entities ; i<pak->num_entities ; i++,ent++)
|
|
{
|
|
item = model2item[ent->modelindex];
|
|
if (!item)
|
|
continue;
|
|
if ( ! (item->itemflag & flags) )
|
|
continue;
|
|
|
|
VectorCopy(ent->origin, v);
|
|
VectorSubtract (v, org, v);
|
|
VectorAdd (v, item->offset, v);
|
|
dist = VectorLength (v);
|
|
// Com_Printf ("%s %f\n", item->modelname, dist);
|
|
|
|
if (dist <= bestdist) {
|
|
bestdist = dist;
|
|
bestidx = ent->modelindex;
|
|
bestskin = ent->skinnum;
|
|
*pitem = item;
|
|
}
|
|
}
|
|
|
|
if (bestidx && (*pitem)->itemflag == it_armor)
|
|
return bestskin + 1; // 1=green, 2=yellow, 3=red
|
|
|
|
return bestidx;
|
|
}
|
|
|
|
|
|
static int CountTeammates (void)
|
|
{
|
|
int i, count;
|
|
player_info_t *player;
|
|
char *myteam;
|
|
|
|
if (tp_forceTriggers.value)
|
|
return 1;
|
|
|
|
if (!cl.teamplay)
|
|
return 0;
|
|
|
|
count = 0;
|
|
myteam = cl.players[cl.playernum[SP]].team;
|
|
for (i=0, player=cl.players; i < MAX_CLIENTS ; i++, player++) {
|
|
if (player->name[0] && !player->spectator && (i != cl.playernum[SP])
|
|
&& !strcmp(player->team, myteam))
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void ExecTookTrigger (char *s, int flag, vec3_t org)
|
|
{
|
|
qbool report;
|
|
|
|
// decide whether this pickup should be reported
|
|
if ( !((pkflags|tookflags) & flag) )
|
|
return;
|
|
|
|
vars.tooktime = realtime;
|
|
strncpy (vars.tookname, s, sizeof(vars.tookname)-1);
|
|
strncpy (vars.tookloc, TP_LocationName (org), sizeof(vars.tookloc)-1);
|
|
|
|
if (flag & it_weapons) {
|
|
if (cl.deathmatch == 2 || cl.deathmatch == 3)
|
|
return;
|
|
}
|
|
|
|
report = (tookflags & flag) ? true : false;
|
|
|
|
if (!report) {
|
|
if ((tookflags & it_pointed) && !strcmp(vars.pointname, s)) {
|
|
vec3_t dist;
|
|
VectorSubtract (org, vars.pointorg, dist);
|
|
//Com_DPrintf ("dist: %f\n", VectorLength(dist));
|
|
if (VectorLength(dist) < 80) { // tune this!
|
|
// ok, this looks like the item we have pointed at
|
|
report = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (report && CountTeammates()) {
|
|
TP_ExecTrigger ("f_took");
|
|
}
|
|
}
|
|
|
|
|
|
void TP_CheckPickupSound (char *s, vec3_t org)
|
|
{
|
|
if (cl.spectator)
|
|
return;
|
|
|
|
if (!strcmp(s, "items/damage.wav"))
|
|
ExecTookTrigger (tp_name_quad.string, it_quad, org);
|
|
else if (!strcmp(s, "items/protect.wav"))
|
|
ExecTookTrigger (tp_name_pent.string, it_pent, org);
|
|
else if (!strcmp(s, "items/inv1.wav"))
|
|
ExecTookTrigger (tp_name_ring.string, it_ring, org);
|
|
else if (!strcmp(s, "items/suit.wav"))
|
|
ExecTookTrigger (tp_name_suit.string, it_suit, org);
|
|
else if (!strcmp(s, "items/health1.wav") ||
|
|
!strcmp(s, "items/r_item1.wav"))
|
|
ExecTookTrigger (tp_name_health.string, it_health, org);
|
|
else if (!strcmp(s, "items/r_item2.wav"))
|
|
ExecTookTrigger (tp_name_mh.string, it_mh, org);
|
|
else
|
|
goto more;
|
|
return;
|
|
|
|
more:
|
|
if (!cl.validsequence || !cl.oldvalidsequence)
|
|
return;
|
|
|
|
// weapons
|
|
if (!strcmp(s, "weapons/pkup.wav"))
|
|
{
|
|
item_t *item;
|
|
if (FindNearestItem (it_weapons, &item)) {
|
|
ExecTookTrigger (item->cvar->string, item->itemflag, org);
|
|
}
|
|
else {
|
|
// we don't know what entity caused the sound, try to guess...
|
|
if (vars.stat_framecounts[STAT_ITEMS] == cls.framecount) {
|
|
if (vars.items & ~vars.olditems & IT_LIGHTNING)
|
|
ExecTookTrigger (tp_name_lg.string, it_lg, cl.simorg[SP]);
|
|
else if (vars.items & ~vars.olditems & IT_ROCKET_LAUNCHER)
|
|
ExecTookTrigger (tp_name_rl.string, it_rl, cl.simorg[SP]);
|
|
else if (vars.items & ~vars.olditems & IT_GRENADE_LAUNCHER)
|
|
ExecTookTrigger (tp_name_gl.string, it_gl, cl.simorg[SP]);
|
|
else if (vars.items & ~vars.olditems & IT_SUPER_NAILGUN)
|
|
ExecTookTrigger (tp_name_sng.string, it_sng, cl.simorg[SP]);
|
|
else if (vars.items & ~vars.olditems & IT_NAILGUN)
|
|
ExecTookTrigger (tp_name_ng.string, it_ng, cl.simorg[SP]);
|
|
else if (vars.items & ~vars.olditems & IT_SUPER_SHOTGUN)
|
|
ExecTookTrigger (tp_name_ssg.string, it_ssg, cl.simorg[SP]);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// armor
|
|
if (!strcmp(s, "items/armor1.wav")) {
|
|
item_t *item;
|
|
qbool armor_updated;
|
|
int armortype;
|
|
|
|
armor_updated = (vars.stat_framecounts[STAT_ARMOR] == cls.framecount);
|
|
armortype = FindNearestItem (it_armor, &item);
|
|
if (armortype == 1 || (!armortype && armor_updated && cl.stats[SP][STAT_ARMOR] == 100))
|
|
ExecTookTrigger (tp_name_ga.string, it_ga, org);
|
|
else if (armortype == 2 || (!armortype && armor_updated && cl.stats[SP][STAT_ARMOR] == 150))
|
|
ExecTookTrigger (tp_name_ya.string, it_ya, org);
|
|
else if (armortype == 3 || (!armortype && armor_updated && cl.stats[SP][STAT_ARMOR] == 200))
|
|
ExecTookTrigger (tp_name_ra.string, it_ra, org);
|
|
return;
|
|
}
|
|
|
|
// backpack or ammo
|
|
if (!strcmp (s, "weapons/lock4.wav"))
|
|
{
|
|
item_t *item;
|
|
if (!FindNearestItem (it_ammo|it_pack|it_runes, &item))
|
|
return;
|
|
ExecTookTrigger (item->cvar->string, item->itemflag, org);
|
|
}
|
|
}
|
|
|
|
|
|
static void TP_FindPoint (void)
|
|
{
|
|
packet_entities_t *pak;
|
|
entity_state_t *ent;
|
|
int i;
|
|
vec3_t forward, right, up;
|
|
float best = 0.0;
|
|
entity_state_t *bestent = NULL;
|
|
vec3_t ang;
|
|
vec3_t vieworg, entorg;
|
|
item_t *item = NULL, *bestitem = NULL;
|
|
|
|
ang[0] = cl.viewangles[SP][0];
|
|
ang[1] = cl.viewangles[SP][1];
|
|
ang[2] = 0;
|
|
AngleVectors (ang, forward, right, up);
|
|
VectorCopy (cl.simorg[SP], vieworg);
|
|
vieworg[2] += 22; // adjust for view height
|
|
|
|
if (!cl.validsequence)
|
|
goto nothing;
|
|
|
|
best = -1;
|
|
|
|
pak = &cl.frames[cl.validsequence&UPDATE_MASK].packet_entities;
|
|
for (i=0,ent=pak->entities ; i<pak->num_entities ; i++,ent++)
|
|
{
|
|
vec3_t v, v2, v3;
|
|
float dist, miss, rank;
|
|
|
|
item = model2item[ent->modelindex];
|
|
if (!item)
|
|
continue;
|
|
if (! (item->itemflag & pointflags) )
|
|
continue;
|
|
// special check for armors
|
|
if (item->itemflag == (it_ra|it_ya|it_ga)) {
|
|
switch (ent->skinnum) {
|
|
case 0: if (!(pointflags & it_ga)) continue; break;
|
|
case 1: if (!(pointflags & it_ya)) continue; break;
|
|
default: if (!(pointflags & it_ra)) continue;
|
|
}
|
|
}
|
|
|
|
VectorCopy(ent->origin, entorg);
|
|
VectorAdd (entorg, item->offset, entorg);
|
|
VectorSubtract (entorg, vieworg, v);
|
|
|
|
dist = DotProduct (v, forward);
|
|
if (dist < 10)
|
|
continue;
|
|
VectorScale (forward, dist, v2);
|
|
VectorSubtract (v2, v, v3);
|
|
miss = VectorLength (v3);
|
|
if (miss > 300)
|
|
continue;
|
|
if (miss > dist*1.7)
|
|
continue; // over 60 degrees off
|
|
if (dist < 3000.0/8.0)
|
|
rank = miss * (dist*8.0*0.0002f + 0.3f);
|
|
else
|
|
rank = miss;
|
|
|
|
if (rank < best || best < 0) {
|
|
// check if we can actually see the object
|
|
vec3_t end;
|
|
trace_t trace;
|
|
float radius;
|
|
|
|
radius = item->radius;
|
|
if (ent->effects & (EF_BLUE|EF_RED|EF_DIMLIGHT|EF_BRIGHTLIGHT))
|
|
radius = 200;
|
|
|
|
if (dist <= radius)
|
|
goto ok;
|
|
|
|
// FIXME: is it ok to use PM_TraceLine here?
|
|
// physent list might not have been built yet...
|
|
|
|
VectorSubtract (vieworg, entorg, v);
|
|
VectorNormalize (v);
|
|
VectorMA (entorg, radius, v, end);
|
|
trace = PM_PlayerTrace (vieworg, end);
|
|
if (trace.fraction == 1)
|
|
goto ok;
|
|
|
|
VectorMA (entorg, radius, right, end);
|
|
VectorSubtract (vieworg, end, v);
|
|
VectorNormalize (v);
|
|
VectorMA (end, radius, v, end);
|
|
trace = PM_PlayerTrace (vieworg, end);
|
|
if (trace.fraction == 1)
|
|
goto ok;
|
|
|
|
VectorMA (entorg, -radius, right, end);
|
|
VectorSubtract (vieworg, end, v);
|
|
VectorNormalize (v);
|
|
VectorMA (end, radius, v, end);
|
|
trace = PM_PlayerTrace (vieworg, end);
|
|
if (trace.fraction == 1)
|
|
goto ok;
|
|
|
|
VectorMA (entorg, radius, up, end);
|
|
VectorSubtract (vieworg, end, v);
|
|
VectorNormalize (v);
|
|
VectorMA (end, radius, v, end);
|
|
trace = PM_PlayerTrace (vieworg, end);
|
|
if (trace.fraction == 1)
|
|
goto ok;
|
|
|
|
// use half the radius, otherwise it's possible to see
|
|
// through floor in some places
|
|
VectorMA (entorg, -radius/2, up, end);
|
|
VectorSubtract (vieworg, end, v);
|
|
VectorNormalize (v);
|
|
VectorMA (end, radius, v, end);
|
|
trace = PM_PlayerTrace (vieworg, end);
|
|
if (trace.fraction == 1)
|
|
goto ok;
|
|
|
|
continue; // not visible
|
|
ok:
|
|
best = rank;
|
|
bestent = ent;
|
|
bestitem = item;
|
|
}
|
|
}
|
|
|
|
if (best >= 0) {
|
|
char *p;
|
|
if (!bestitem->cvar) {
|
|
// armors are special
|
|
switch (bestent->skinnum) {
|
|
case 0: p = tp_name_ga.string; break;
|
|
case 1: p = tp_name_ya.string; break;
|
|
default: p = tp_name_ra.string;
|
|
}
|
|
} else
|
|
p = bestitem->cvar->string;
|
|
|
|
strlcpy (vars.pointname, p, sizeof(vars.pointname));
|
|
VectorCopy (bestent->origin, entorg);
|
|
strlcpy (vars.pointloc, TP_LocationName (entorg), sizeof(vars.pointloc));
|
|
VectorCopy (entorg, vars.pointorg);
|
|
}
|
|
else {
|
|
nothing:
|
|
strlcpy (vars.pointname, tp_name_nothing.string, sizeof(vars.pointname));
|
|
vars.pointloc[0] = 0;
|
|
}
|
|
|
|
vars.pointframe = cls.framecount;
|
|
}
|
|
|
|
|
|
void TP_StatChanged (int stat, int value)
|
|
{
|
|
int i;
|
|
|
|
if (stat == STAT_HEALTH)
|
|
{
|
|
if (value > 0)
|
|
{
|
|
if (vars.health <= 0)
|
|
{
|
|
// we just respawned
|
|
vars.respawntrigger_time = realtime;
|
|
|
|
if (!cl.spectator && CountTeammates())
|
|
TP_ExecTrigger ("f_respawn");
|
|
}
|
|
vars.health = value;
|
|
return;
|
|
}
|
|
if (vars.health > 0)
|
|
{ // We have just died
|
|
|
|
vars.droppedweapon = cl.stats[SP][STAT_ACTIVEWEAPON];
|
|
|
|
vars.deathtrigger_time = realtime;
|
|
strcpy (vars.lastdeathloc, Macro_Location());
|
|
|
|
if (!cl.spectator && CountTeammates())
|
|
{
|
|
if (cl.teamfortress && (cl.stats[SP][STAT_ITEMS] & (IT_KEY1|IT_KEY2))
|
|
&& Cmd_AliasExist("f_flagdeath", RESTRICT_LOCAL))
|
|
TP_ExecTrigger ("f_flagdeath");
|
|
else
|
|
TP_ExecTrigger ("f_death");
|
|
}
|
|
}
|
|
vars.health = value;
|
|
}
|
|
else if (stat == STAT_ITEMS)
|
|
{
|
|
i = value &~ vars.items;
|
|
|
|
if (i & (IT_KEY1|IT_KEY2)) {
|
|
if (cl.teamfortress && !cl.spectator)
|
|
ExecTookTrigger (tp_name_flag.string, it_flag,
|
|
cl.frames[cl.validsequence&UPDATE_MASK].playerstate[cl.playernum[SP]].origin);
|
|
}
|
|
|
|
vars.olditems = vars.items;
|
|
vars.items = value;
|
|
}
|
|
else if (stat == STAT_ACTIVEWEAPON)
|
|
{
|
|
if (cl.stats[SP][STAT_ACTIVEWEAPON] != vars.activeweapon)
|
|
TP_ExecTrigger ("f_weaponchange");
|
|
vars.activeweapon = cl.stats[SP][STAT_ACTIVEWEAPON];
|
|
}
|
|
|
|
vars.stat_framecounts[stat] = cls.framecount;
|
|
}
|
|
|
|
|
|
/*
|
|
======================
|
|
TP_CheckSoundTrigger
|
|
|
|
Find and execute sound triggers.
|
|
A sound trigger must be terminated by either a CR or LF.
|
|
Returns true if a sound was found and played
|
|
======================
|
|
*/
|
|
qbool TP_CheckSoundTrigger (char *str)
|
|
{
|
|
int i, j;
|
|
int start, length;
|
|
char soundname[MAX_OSPATH];
|
|
FILE *f;
|
|
|
|
if (!tp_soundtrigger.string[0])
|
|
return false;
|
|
|
|
for (i=strlen(str)-1 ; i ; i--)
|
|
{
|
|
if (str[i] != 0x0A && str[i] != 0x0D)
|
|
continue;
|
|
|
|
for (j = i-1 ; j >= 0 ; j--)
|
|
{
|
|
// quick check for chars that cannot be used
|
|
// as sound triggers but might be part of a file name
|
|
if ( isalpha((int)(unsigned char)str[j]) ||
|
|
isdigit((int)(unsigned char)str[j]) )
|
|
continue; // file name or chat
|
|
|
|
if (strchr(tp_soundtrigger.string, str[j]))
|
|
{
|
|
// this might be a sound trigger
|
|
|
|
start = j + 1;
|
|
length = i - start;
|
|
|
|
if (!length)
|
|
break;
|
|
if (length >= MAX_QPATH)
|
|
break;
|
|
|
|
strlcpy (soundname, str + start, length + 1);
|
|
if (strstr(soundname, ".."))
|
|
break; // no thank you
|
|
|
|
// clean up the message
|
|
strcpy (str + j, str + i);
|
|
|
|
if (!snd_initialized)
|
|
return false;
|
|
|
|
COM_DefaultExtension (soundname, ".wav");
|
|
|
|
// make sure we have it on disk (FIXME)
|
|
COM_FOpenFile (va("sound/%s", soundname), &f);
|
|
if (!f)
|
|
return false;
|
|
fclose (f);
|
|
|
|
// now play the sound
|
|
S_LocalSound (soundname);
|
|
return true;
|
|
}
|
|
|
|
if (str[j] <= ' ' || strchr("\"&'*,:;<>?\\|\x7f", str[j]))
|
|
break; // we don't allow these in a file name
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#define MAX_FILTER_LENGTH 4
|
|
static char filter_strings[8][MAX_FILTER_LENGTH+1];
|
|
static int num_filters = 0;
|
|
|
|
/*
|
|
======================
|
|
TP_FilterMessage
|
|
|
|
returns false if the message shouldn't be printed
|
|
matching filters are stripped from the message
|
|
======================
|
|
*/
|
|
qbool TP_FilterMessage (char *s)
|
|
{
|
|
int i, j, len, maxlen;
|
|
|
|
if (!num_filters)
|
|
return true;
|
|
|
|
len = strlen (s);
|
|
if (len < 2 || s[len-1] != '\n' || s[len-2] == '#')
|
|
return true;
|
|
|
|
maxlen = MAX_FILTER_LENGTH + 1;
|
|
for (i=len-2 ; i >= 0 && maxlen > 0 ; i--, maxlen--) {
|
|
if (s[i] == ' ')
|
|
return true;
|
|
if (s[i] == '#')
|
|
break;
|
|
}
|
|
if (i < 0 || !maxlen)
|
|
return true; // no filter at all
|
|
|
|
s[len-1] = 0; // so that strcmp works properly
|
|
|
|
for (j=0 ; j<num_filters ; j++)
|
|
if (!strcmp(s + i + 1, filter_strings[j]))
|
|
{
|
|
// strip the filter from message
|
|
if (i && s[i-1] == ' ')
|
|
{ // there's a space just before the filter, remove it
|
|
// so that soundtriggers like ^blah #att work
|
|
s[i-1] = '\n';
|
|
s[i] = 0;
|
|
} else {
|
|
s[i] = '\n';
|
|
s[i+1] = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
s[len-1] = '\n';
|
|
return false; // this message is not for us, don't print it
|
|
}
|
|
|
|
static void TP_MsgFilter_f (void)
|
|
{
|
|
int c, i;
|
|
char *s;
|
|
|
|
c = Cmd_Argc ();
|
|
if (c == 1) {
|
|
if (!num_filters) {
|
|
Com_Printf ("No filters defined\n");
|
|
return;
|
|
}
|
|
for (i=0 ; i<num_filters ; i++)
|
|
Com_Printf ("%s#%s", i ? " " : "", filter_strings[i]);
|
|
Com_Printf ("\n");
|
|
return;
|
|
}
|
|
|
|
if (c == 2 && (Cmd_Argv(1)[0] == 0 || !strcmp(Cmd_Argv(1), "clear"))) {
|
|
num_filters = 0;
|
|
return;
|
|
}
|
|
|
|
num_filters = 0;
|
|
for (i=1 ; i < c ; i++) {
|
|
s = Cmd_Argv(i);
|
|
if (*s != '#') {
|
|
Com_Printf ("A filter must start with \"#\"\n");
|
|
return;
|
|
}
|
|
if (strchr(s+1, ' ')) {
|
|
Com_Printf ("A filter may not contain spaces\n");
|
|
return;
|
|
}
|
|
strlcpy (filter_strings[num_filters], s+1, sizeof(filter_strings[0]));
|
|
num_filters++;
|
|
if (num_filters >= 8)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TP_CheckVars(void)
|
|
{
|
|
if (cl_enemyskin.modified || cl_teamskin.modified)
|
|
{
|
|
Skin_FlushPlayers();
|
|
cl_enemyskin.modified = false;
|
|
cl_teamskin.modified = false;
|
|
}
|
|
}
|
|
|
|
void TP_Init (void)
|
|
{
|
|
#define TEAMPLAYVARS "Teamplay Variables"
|
|
|
|
//register all the TeamPlay cvars.
|
|
#define TP_CVAR(name,def) Cvar_Register (&name, TEAMPLAYVARS);
|
|
TP_CVARS;
|
|
#undef TP_CVAR
|
|
|
|
Cmd_AddCommand ("loadloc", TP_LoadLocFile_f);
|
|
Cmd_AddCommand ("filter", TP_MsgFilter_f);
|
|
Cmd_AddCommand ("msg_trigger", TP_MsgTrigger_f);
|
|
Cmd_AddCommand ("teamcolor", TP_TeamColor_f);
|
|
Cmd_AddCommand ("enemycolor", TP_EnemyColor_f);
|
|
Cmd_AddCommand ("tp_took", TP_Took_f);
|
|
Cmd_AddCommand ("tp_pickup", TP_Pickup_f);
|
|
Cmd_AddCommand ("tp_point", TP_Point_f);
|
|
|
|
TP_InitMacros();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void CL_Say (qboolean team, char *extra)
|
|
{
|
|
extern cvar_t cl_fakename;
|
|
char text[1024], sendtext[1024], *s;
|
|
|
|
if (Cmd_Argc() < 2)
|
|
{
|
|
if (team)
|
|
Con_Printf ("%s <text>: send a team message\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
|
|
if (cls.state == ca_disconnected)
|
|
{
|
|
Con_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0));
|
|
return;
|
|
}
|
|
|
|
s = TP_ParseMacroString (Cmd_Args());
|
|
Q_strncpyz (text, TP_ParseFunChars (s, true), sizeof(text));
|
|
|
|
sendtext[0] = 0;
|
|
if (team && !cl.spectator && cl_fakename.string[0] &&
|
|
!strchr(s, '\x0d') /* explicit $\ in message overrides cl_fakename */)
|
|
{
|
|
char buf[1024];
|
|
Cmd_ExpandString (cl_fakename.string, buf, sizeof(buf), Cmd_ExecLevel, true);
|
|
strcpy (buf, TP_ParseMacroString (buf));
|
|
Q_snprintfz (sendtext, sizeof(sendtext), "\x0d%s: ", TP_ParseFunChars(buf, true));
|
|
}
|
|
|
|
strlcat (sendtext, text, sizeof(sendtext));
|
|
#ifdef Q3CLIENT
|
|
if (cls.q2server==2)
|
|
{
|
|
CLQ3_SendClientCommand("%s %s%s", team ? "say_team " : "say ", extra?extra:"", sendtext);
|
|
return;
|
|
}
|
|
#endif
|
|
CL_SendClientCommand(true, "%s \"%s%s\"", team ? "say_team " : "say ", extra?extra:"", sendtext);
|
|
}
|
|
|
|
|
|
void CL_Say_f (void)
|
|
{
|
|
CL_Say (false, NULL);
|
|
}
|
|
|
|
void CL_SayMe_f (void)
|
|
{
|
|
CL_Say (false, "/me ");
|
|
}
|
|
|
|
void CL_SayTeam_f (void)
|
|
{
|
|
CL_Say (true, NULL);
|
|
}
|
|
#endif
|
|
|
|
|