fteqw/engine/client/zqtp.c
Spoike 545b457f02 Trying to improve portability.
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@969 fc73d0e0-1445-4013-8a0c-d673dee63da5
2005-04-19 21:09:29 +00:00

2702 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 & (QWEF_FLAG1|QWEF_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_Mapname (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("mapname", Macro_Mapname, 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