mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2024-11-27 06:33:47 +00:00
1753 lines
35 KiB
C++
1753 lines
35 KiB
C++
// Copyright (c) ZeniMax Media Inc.
|
|
// Licensed under the GNU General Public License 2.0.
|
|
#include "g_local.h"
|
|
#include "m_player.h"
|
|
|
|
void SelectNextItem(edict_t *ent, item_flags_t itflags, bool menu = true)
|
|
{
|
|
gclient_t *cl;
|
|
item_id_t i, index;
|
|
gitem_t *it;
|
|
|
|
cl = ent->client;
|
|
|
|
// ZOID
|
|
if (menu && cl->menu)
|
|
{
|
|
PMenu_Next(ent);
|
|
return;
|
|
}
|
|
else if (menu && cl->chase_target)
|
|
{
|
|
ChaseNext(ent);
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
// scan for the next valid one
|
|
for (i = static_cast<item_id_t>(IT_NULL + 1); i <= IT_TOTAL; i = static_cast<item_id_t>(i + 1))
|
|
{
|
|
index = static_cast<item_id_t>((cl->pers.selected_item + i) % IT_TOTAL);
|
|
if (!cl->pers.inventory[index])
|
|
continue;
|
|
it = &itemlist[index];
|
|
if (!it->use)
|
|
continue;
|
|
if (!(it->flags & itflags))
|
|
continue;
|
|
|
|
cl->pers.selected_item = index;
|
|
cl->pers.selected_item_time = level.time + SELECTED_ITEM_TIME;
|
|
cl->ps.stats[STAT_SELECTED_ITEM_NAME] = CS_ITEMS + index;
|
|
return;
|
|
}
|
|
|
|
cl->pers.selected_item = IT_NULL;
|
|
}
|
|
|
|
void SelectPrevItem(edict_t *ent, item_flags_t itflags)
|
|
{
|
|
gclient_t *cl;
|
|
item_id_t i, index;
|
|
gitem_t *it;
|
|
|
|
cl = ent->client;
|
|
|
|
// ZOID
|
|
if (cl->menu)
|
|
{
|
|
PMenu_Prev(ent);
|
|
return;
|
|
}
|
|
else if (cl->chase_target)
|
|
{
|
|
ChasePrev(ent);
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
// scan for the next valid one
|
|
for (i = static_cast<item_id_t>(IT_NULL + 1); i <= IT_TOTAL; i = static_cast<item_id_t>(i + 1))
|
|
{
|
|
index = static_cast<item_id_t>((cl->pers.selected_item + IT_TOTAL - i) % IT_TOTAL);
|
|
if (!cl->pers.inventory[index])
|
|
continue;
|
|
it = &itemlist[index];
|
|
if (!it->use)
|
|
continue;
|
|
if (!(it->flags & itflags))
|
|
continue;
|
|
|
|
cl->pers.selected_item = index;
|
|
cl->pers.selected_item_time = level.time + SELECTED_ITEM_TIME;
|
|
cl->ps.stats[STAT_SELECTED_ITEM_NAME] = CS_ITEMS + index;
|
|
return;
|
|
}
|
|
|
|
cl->pers.selected_item = IT_NULL;
|
|
}
|
|
|
|
void ValidateSelectedItem(edict_t *ent)
|
|
{
|
|
gclient_t *cl;
|
|
|
|
cl = ent->client;
|
|
|
|
if (cl->pers.inventory[cl->pers.selected_item])
|
|
return; // valid
|
|
|
|
SelectNextItem(ent, IF_ANY, false);
|
|
}
|
|
|
|
//=================================================================================
|
|
|
|
inline bool G_CheatCheck(edict_t *ent)
|
|
{
|
|
if (game.maxclients > 1 && !sv_cheats->integer)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_need_cheats");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void SpawnAndGiveItem(edict_t *ent, item_id_t id)
|
|
{
|
|
gitem_t *it = GetItemByIndex(id);
|
|
|
|
if (!it)
|
|
return;
|
|
|
|
edict_t *it_ent = G_Spawn();
|
|
it_ent->classname = it->classname;
|
|
SpawnItem(it_ent, it);
|
|
|
|
if (it_ent->inuse)
|
|
{
|
|
Touch_Item(it_ent, ent, null_trace, true);
|
|
if (it_ent->inuse)
|
|
G_FreeEdict(it_ent);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Give_f
|
|
|
|
Give items to a client
|
|
==================
|
|
*/
|
|
void Cmd_Give_f(edict_t *ent)
|
|
{
|
|
const char *name;
|
|
gitem_t *it;
|
|
item_id_t index;
|
|
int i;
|
|
bool give_all;
|
|
edict_t *it_ent;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
name = gi.args();
|
|
|
|
if (Q_strcasecmp(name, "all") == 0)
|
|
give_all = true;
|
|
else
|
|
give_all = false;
|
|
|
|
if (give_all || Q_strcasecmp(gi.argv(1), "health") == 0)
|
|
{
|
|
if (gi.argc() == 3)
|
|
ent->health = atoi(gi.argv(2));
|
|
else
|
|
ent->health = ent->max_health;
|
|
if (!give_all)
|
|
return;
|
|
}
|
|
|
|
if (give_all || Q_strcasecmp(name, "weapons") == 0)
|
|
{
|
|
for (i = 0; i < IT_TOTAL; i++)
|
|
{
|
|
it = itemlist + i;
|
|
if (!it->pickup)
|
|
continue;
|
|
if (!(it->flags & IF_WEAPON))
|
|
continue;
|
|
ent->client->pers.inventory[i] += 1;
|
|
}
|
|
if (!give_all)
|
|
return;
|
|
}
|
|
|
|
if (give_all || Q_strcasecmp(name, "ammo") == 0)
|
|
{
|
|
if (give_all)
|
|
SpawnAndGiveItem(ent, IT_ITEM_PACK);
|
|
|
|
for (i = 0; i < IT_TOTAL; i++)
|
|
{
|
|
it = itemlist + i;
|
|
if (!it->pickup)
|
|
continue;
|
|
if (!(it->flags & IF_AMMO))
|
|
continue;
|
|
Add_Ammo(ent, it, 1000);
|
|
}
|
|
if (!give_all)
|
|
return;
|
|
}
|
|
|
|
if (give_all || Q_strcasecmp(name, "armor") == 0)
|
|
{
|
|
ent->client->pers.inventory[IT_ARMOR_JACKET] = 0;
|
|
ent->client->pers.inventory[IT_ARMOR_COMBAT] = 0;
|
|
ent->client->pers.inventory[IT_ARMOR_BODY] = GetItemByIndex(IT_ARMOR_BODY)->armor_info->max_count;
|
|
|
|
if (!give_all)
|
|
return;
|
|
}
|
|
|
|
if (give_all)
|
|
{
|
|
SpawnAndGiveItem(ent, IT_ITEM_POWER_SHIELD);
|
|
|
|
if (!give_all)
|
|
return;
|
|
}
|
|
|
|
if (give_all)
|
|
{
|
|
for (i = 0; i < IT_TOTAL; i++)
|
|
{
|
|
it = itemlist + i;
|
|
if (!it->pickup)
|
|
continue;
|
|
// ROGUE
|
|
if (it->flags & (IF_ARMOR | IF_WEAPON | IF_AMMO | IF_NOT_GIVEABLE | IF_TECH))
|
|
continue;
|
|
else if (it->pickup == CTFPickup_Flag)
|
|
continue;
|
|
else if ((it->flags & IF_HEALTH) && !it->use)
|
|
continue;
|
|
// ROGUE
|
|
ent->client->pers.inventory[i] = (it->flags & IF_KEY) ? 8 : 1;
|
|
}
|
|
|
|
G_CheckPowerArmor(ent);
|
|
ent->client->pers.power_cubes = 0xFF;
|
|
return;
|
|
}
|
|
|
|
it = FindItem(name);
|
|
if (!it)
|
|
{
|
|
name = gi.argv(1);
|
|
it = FindItem(name);
|
|
}
|
|
if (!it)
|
|
it = FindItemByClassname(name);
|
|
|
|
if (!it)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_item");
|
|
return;
|
|
}
|
|
|
|
// ROGUE
|
|
if (it->flags & IF_NOT_GIVEABLE)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_not_giveable");
|
|
return;
|
|
}
|
|
// ROGUE
|
|
|
|
index = it->id;
|
|
|
|
if (!it->pickup)
|
|
{
|
|
ent->client->pers.inventory[index] = 1;
|
|
return;
|
|
}
|
|
|
|
if (it->flags & IF_AMMO)
|
|
{
|
|
if (gi.argc() == 3)
|
|
ent->client->pers.inventory[index] = atoi(gi.argv(2));
|
|
else
|
|
ent->client->pers.inventory[index] += it->quantity;
|
|
}
|
|
else
|
|
{
|
|
it_ent = G_Spawn();
|
|
it_ent->classname = it->classname;
|
|
SpawnItem(it_ent, it);
|
|
// PMM - since some items don't actually spawn when you say to ..
|
|
if (!it_ent->inuse)
|
|
return;
|
|
// pmm
|
|
Touch_Item(it_ent, ent, null_trace, true);
|
|
if (it_ent->inuse)
|
|
G_FreeEdict(it_ent);
|
|
}
|
|
}
|
|
|
|
void Cmd_SetPOI_f(edict_t *self)
|
|
{
|
|
if (!G_CheatCheck(self))
|
|
return;
|
|
|
|
level.current_poi = self->s.origin;
|
|
level.valid_poi = true;
|
|
}
|
|
|
|
void Cmd_CheckPOI_f(edict_t *self)
|
|
{
|
|
if (!G_CheatCheck(self))
|
|
return;
|
|
|
|
if (!level.valid_poi)
|
|
return;
|
|
|
|
char visible_pvs = gi.inPVS(self->s.origin, level.current_poi, false) ? 'y' : 'n';
|
|
char visible_pvs_portals = gi.inPVS(self->s.origin, level.current_poi, true) ? 'y' : 'n';
|
|
char visible_phs = gi.inPHS(self->s.origin, level.current_poi, false) ? 'y' : 'n';
|
|
char visible_phs_portals = gi.inPHS(self->s.origin, level.current_poi, true) ? 'y' : 'n';
|
|
|
|
gi.Com_PrintFmt("pvs {} + portals {}, phs {} + portals {}\n", visible_pvs, visible_pvs_portals, visible_phs, visible_phs_portals);
|
|
}
|
|
|
|
// [Paril-KEX]
|
|
static void Cmd_Target_f(edict_t *ent)
|
|
{
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
ent->target = gi.argv(1);
|
|
G_UseTargets(ent, ent);
|
|
ent->target = nullptr;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_God_f
|
|
|
|
Sets client to godmode
|
|
|
|
argv(0) god
|
|
==================
|
|
*/
|
|
void Cmd_God_f(edict_t *ent)
|
|
{
|
|
const char *msg;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
ent->flags ^= FL_GODMODE;
|
|
if (!(ent->flags & FL_GODMODE))
|
|
msg = "godmode OFF\n";
|
|
else
|
|
msg = "godmode ON\n";
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH, msg);
|
|
}
|
|
void ED_ParseField(const char *key, const char *value, edict_t *ent);
|
|
|
|
/*
|
|
==================
|
|
Cmd_Immortal_f
|
|
|
|
Sets client to immortal - take damage but never go below 1 hp
|
|
|
|
argv(0) immortal
|
|
==================
|
|
*/
|
|
void Cmd_Immortal_f(edict_t *ent)
|
|
{
|
|
const char *msg;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
ent->flags ^= FL_IMMORTAL;
|
|
if (!(ent->flags & FL_IMMORTAL))
|
|
msg = "immortal OFF\n";
|
|
else
|
|
msg = "immortal ON\n";
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH, msg);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Spawn_f
|
|
|
|
Spawn class name
|
|
|
|
argv(0) spawn
|
|
argv(1) <classname>
|
|
argv(2+n) "key"...
|
|
argv(3+n) "value"...
|
|
=================
|
|
*/
|
|
void Cmd_Spawn_f(edict_t *ent)
|
|
{
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
solid_t backup = ent->solid;
|
|
ent->solid = SOLID_NOT;
|
|
gi.linkentity(ent);
|
|
|
|
edict_t* other = G_Spawn();
|
|
other->classname = gi.argv(1);
|
|
|
|
other->s.origin = ent->s.origin + (AngleVectors(ent->s.angles).forward * 24.f);
|
|
other->s.angles[1] = ent->s.angles[1];
|
|
|
|
st = {};
|
|
|
|
if (gi.argc() > 3)
|
|
{
|
|
for (int i = 2; i < gi.argc(); i += 2)
|
|
ED_ParseField(gi.argv(i), gi.argv(i + 1), other);
|
|
}
|
|
|
|
ED_CallSpawn(other);
|
|
|
|
if (other->inuse)
|
|
{
|
|
vec3_t forward, end;
|
|
AngleVectors(ent->client->v_angle, forward, nullptr, nullptr);
|
|
end = ent->s.origin;
|
|
end[2] += ent->viewheight;
|
|
end += (forward * 8192);
|
|
|
|
trace_t tr = gi.traceline(ent->s.origin + vec3_t{0.f, 0.f, (float) ent->viewheight}, end, other, MASK_SHOT | CONTENTS_MONSTERCLIP);
|
|
other->s.origin = tr.endpos;
|
|
|
|
for (int32_t i = 0; i < 3; i++)
|
|
{
|
|
if (tr.plane.normal[i] > 0)
|
|
other->s.origin[i] -= other->mins[i] * tr.plane.normal[i];
|
|
else
|
|
other->s.origin[i] += other->maxs[i] * -tr.plane.normal[i];
|
|
}
|
|
|
|
while (gi.trace(other->s.origin, other->mins, other->maxs, other->s.origin, other,
|
|
MASK_SHOT | CONTENTS_MONSTERCLIP)
|
|
.startsolid)
|
|
{
|
|
float dx = other->mins[0] - other->maxs[0];
|
|
float dy = other->mins[1] - other->maxs[1];
|
|
other->s.origin += forward * -sqrtf(dx * dx + dy * dy);
|
|
|
|
if ((other->s.origin - ent->s.origin).dot(forward) < 0)
|
|
{
|
|
gi.Client_Print(ent, PRINT_HIGH, "Couldn't find a suitable spawn location\n");
|
|
G_FreeEdict(other);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (other->inuse)
|
|
gi.linkentity(other);
|
|
|
|
if ((other->svflags & SVF_MONSTER) && other->think)
|
|
other->think(other);
|
|
}
|
|
|
|
ent->solid = backup;
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Spawn_f
|
|
|
|
Telepo'
|
|
|
|
argv(0) teleport
|
|
argv(1) x
|
|
argv(2) y
|
|
argv(3) z
|
|
=================
|
|
*/
|
|
void Cmd_Teleport_f(edict_t *ent)
|
|
{
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
if (gi.argc() < 4)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "Not enough args; teleport x y z\n");
|
|
return;
|
|
}
|
|
|
|
ent->s.origin[0] = (float) atof(gi.argv(1));
|
|
ent->s.origin[1] = (float) atof(gi.argv(2));
|
|
ent->s.origin[2] = (float) atof(gi.argv(3));
|
|
|
|
if (gi.argc() >= 4)
|
|
{
|
|
float pitch = (float) atof(gi.argv(4));
|
|
float yaw = (float) atof(gi.argv(5));
|
|
float roll = (float) atof(gi.argv(6));
|
|
vec3_t ang { pitch, yaw, roll };
|
|
|
|
ent->client->ps.pmove.delta_angles = ( ang - ent->client->resp.cmd_angles );
|
|
ent->client->ps.viewangles = {};
|
|
ent->client->v_angle = {};
|
|
}
|
|
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Notarget_f
|
|
|
|
Sets client to notarget
|
|
|
|
argv(0) notarget
|
|
==================
|
|
*/
|
|
void Cmd_Notarget_f(edict_t *ent)
|
|
{
|
|
const char *msg;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
ent->flags ^= FL_NOTARGET;
|
|
if (!(ent->flags & FL_NOTARGET))
|
|
msg = "notarget OFF\n";
|
|
else
|
|
msg = "notarget ON\n";
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH, msg);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Novisible_f
|
|
|
|
Sets client to "super notarget"
|
|
|
|
argv(0) notarget
|
|
==================
|
|
*/
|
|
void Cmd_Novisible_f(edict_t *ent)
|
|
{
|
|
const char *msg;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
ent->flags ^= FL_NOVISIBLE;
|
|
if (!(ent->flags & FL_NOVISIBLE))
|
|
msg = "novisible OFF\n";
|
|
else
|
|
msg = "novisible ON\n";
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH, msg);
|
|
}
|
|
|
|
void Cmd_AlertAll_f(edict_t *ent)
|
|
{
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
for (size_t i = 0; i < globals.num_edicts; i++)
|
|
{
|
|
edict_t *t = &g_edicts[i];
|
|
|
|
if (!t->inuse || t->health <= 0 || !(t->svflags & SVF_MONSTER))
|
|
continue;
|
|
|
|
t->enemy = ent;
|
|
FoundTarget(t);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Noclip_f
|
|
|
|
argv(0) noclip
|
|
==================
|
|
*/
|
|
void Cmd_Noclip_f(edict_t *ent)
|
|
{
|
|
const char *msg;
|
|
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
|
|
if (ent->movetype == MOVETYPE_NOCLIP)
|
|
{
|
|
ent->movetype = MOVETYPE_WALK;
|
|
msg = "noclip OFF\n";
|
|
}
|
|
else
|
|
{
|
|
ent->movetype = MOVETYPE_NOCLIP;
|
|
msg = "noclip ON\n";
|
|
}
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH, msg);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Use_f
|
|
|
|
Use an inventory item
|
|
==================
|
|
*/
|
|
void Cmd_Use_f(edict_t *ent)
|
|
{
|
|
item_id_t index;
|
|
gitem_t *it;
|
|
const char *s;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
|
|
s = gi.args();
|
|
|
|
const char *cmd = gi.argv(0);
|
|
if (!Q_strcasecmp(cmd, "use_index") || !Q_strcasecmp(cmd, "use_index_only"))
|
|
{
|
|
it = GetItemByIndex((item_id_t) atoi(s));
|
|
}
|
|
else
|
|
{
|
|
it = FindItem(s);
|
|
}
|
|
|
|
if (!it)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_unknown_item_name", s);
|
|
return;
|
|
}
|
|
if (!it->use)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_usable");
|
|
return;
|
|
}
|
|
index = it->id;
|
|
|
|
// Paril: Use_Weapon handles weapon availability
|
|
if (!(it->flags & IF_WEAPON) && !ent->client->pers.inventory[index])
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", it->pickup_name);
|
|
return;
|
|
}
|
|
|
|
// allow weapon chains for use
|
|
ent->client->no_weapon_chains = !!strcmp(gi.argv(0), "use") && !!strcmp(gi.argv(0), "use_index");
|
|
|
|
it->use(ent, it);
|
|
|
|
ValidateSelectedItem(ent);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Cmd_Drop_f
|
|
|
|
Drop an inventory item
|
|
==================
|
|
*/
|
|
void Cmd_Drop_f(edict_t *ent)
|
|
{
|
|
item_id_t index;
|
|
gitem_t *it;
|
|
const char *s;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
|
|
// ZOID--special case for tech powerups
|
|
if (Q_strcasecmp(gi.args(), "tech") == 0)
|
|
{
|
|
it = CTFWhat_Tech(ent);
|
|
|
|
if (it)
|
|
{
|
|
it->drop(ent, it);
|
|
ValidateSelectedItem(ent);
|
|
}
|
|
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
s = gi.args();
|
|
|
|
const char *cmd = gi.argv(0);
|
|
|
|
if (!Q_strcasecmp(cmd, "drop_index"))
|
|
{
|
|
it = GetItemByIndex((item_id_t) atoi(s));
|
|
}
|
|
else
|
|
{
|
|
it = FindItem(s);
|
|
}
|
|
|
|
if (!it)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "Unknown item : {}\n", s);
|
|
return;
|
|
}
|
|
if (!it->drop)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_droppable");
|
|
return;
|
|
}
|
|
index = it->id;
|
|
if (!ent->client->pers.inventory[index])
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_out_of_item", it->pickup_name);
|
|
return;
|
|
}
|
|
|
|
it->drop(ent, it);
|
|
|
|
ValidateSelectedItem(ent);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Inven_f
|
|
=================
|
|
*/
|
|
void Cmd_Inven_f(edict_t *ent)
|
|
{
|
|
int i;
|
|
gclient_t *cl;
|
|
|
|
cl = ent->client;
|
|
|
|
cl->showscores = false;
|
|
cl->showhelp = false;
|
|
|
|
globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
|
|
|
|
// ZOID
|
|
if (ent->client->menu)
|
|
{
|
|
PMenu_Close(ent);
|
|
ent->client->update_chase = true;
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
if (cl->showinventory)
|
|
{
|
|
cl->showinventory = false;
|
|
return;
|
|
}
|
|
|
|
// ZOID
|
|
if (G_TeamplayEnabled() && cl->resp.ctf_team == CTF_NOTEAM)
|
|
{
|
|
CTFOpenJoinMenu(ent);
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
cl->showinventory = true;
|
|
|
|
gi.WriteByte(svc_inventory);
|
|
for (i = 0; i < IT_TOTAL; i++)
|
|
gi.WriteShort(cl->pers.inventory[i]);
|
|
for (; i < MAX_ITEMS; i++)
|
|
gi.WriteShort(0);
|
|
gi.unicast(ent, true);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_InvUse_f
|
|
=================
|
|
*/
|
|
void Cmd_InvUse_f(edict_t *ent)
|
|
{
|
|
gitem_t *it;
|
|
|
|
// ZOID
|
|
if (ent->client->menu)
|
|
{
|
|
PMenu_Select(ent);
|
|
return;
|
|
}
|
|
// ZOID
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
|
|
ValidateSelectedItem(ent);
|
|
|
|
if (ent->client->pers.selected_item == IT_NULL)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_item_to_use");
|
|
return;
|
|
}
|
|
|
|
it = &itemlist[ent->client->pers.selected_item];
|
|
if (!it->use)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_usable");
|
|
return;
|
|
}
|
|
|
|
// don't allow weapon chains for invuse
|
|
ent->client->no_weapon_chains = true;
|
|
it->use(ent, it);
|
|
|
|
ValidateSelectedItem(ent);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_WeapPrev_f
|
|
=================
|
|
*/
|
|
void Cmd_WeapPrev_f(edict_t *ent)
|
|
{
|
|
gclient_t *cl;
|
|
item_id_t i, index;
|
|
gitem_t *it;
|
|
item_id_t selected_weapon;
|
|
|
|
cl = ent->client;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
if (!cl->pers.weapon)
|
|
return;
|
|
|
|
// don't allow weapon chains for weapprev
|
|
cl->no_weapon_chains = true;
|
|
|
|
selected_weapon = cl->pers.weapon->id;
|
|
|
|
// scan for the next valid one
|
|
for (i = static_cast<item_id_t>(IT_NULL + 1); i <= IT_TOTAL; i = static_cast<item_id_t>(i + 1))
|
|
{
|
|
// PMM - prevent scrolling through ALL weapons
|
|
index = static_cast<item_id_t>((selected_weapon + IT_TOTAL - i) % IT_TOTAL);
|
|
if (!cl->pers.inventory[index])
|
|
continue;
|
|
it = &itemlist[index];
|
|
if (!it->use)
|
|
continue;
|
|
if (!(it->flags & IF_WEAPON))
|
|
continue;
|
|
it->use(ent, it);
|
|
// ROGUE
|
|
if (cl->newweapon == it)
|
|
return; // successful
|
|
// ROGUE
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_WeapNext_f
|
|
=================
|
|
*/
|
|
void Cmd_WeapNext_f(edict_t *ent)
|
|
{
|
|
gclient_t *cl;
|
|
item_id_t i, index;
|
|
gitem_t *it;
|
|
item_id_t selected_weapon;
|
|
|
|
cl = ent->client;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
if (!cl->pers.weapon)
|
|
return;
|
|
|
|
// don't allow weapon chains for weapnext
|
|
cl->no_weapon_chains = true;
|
|
|
|
selected_weapon = cl->pers.weapon->id;
|
|
|
|
// scan for the next valid one
|
|
for (i = static_cast<item_id_t>(IT_NULL + 1); i <= IT_TOTAL; i = static_cast<item_id_t>(i + 1))
|
|
{
|
|
// PMM - prevent scrolling through ALL weapons
|
|
index = static_cast<item_id_t>((selected_weapon + i) % IT_TOTAL);
|
|
if (!cl->pers.inventory[index])
|
|
continue;
|
|
it = &itemlist[index];
|
|
if (!it->use)
|
|
continue;
|
|
if (!(it->flags & IF_WEAPON))
|
|
continue;
|
|
it->use(ent, it);
|
|
// PMM - prevent scrolling through ALL weapons
|
|
|
|
// ROGUE
|
|
if (cl->newweapon == it)
|
|
return;
|
|
// ROGUE
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_WeapLast_f
|
|
=================
|
|
*/
|
|
void Cmd_WeapLast_f(edict_t *ent)
|
|
{
|
|
gclient_t *cl;
|
|
int index;
|
|
gitem_t *it;
|
|
|
|
cl = ent->client;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
if (!cl->pers.weapon || !cl->pers.lastweapon)
|
|
return;
|
|
|
|
// don't allow weapon chains for weaplast
|
|
cl->no_weapon_chains = true;
|
|
|
|
index = cl->pers.lastweapon->id;
|
|
if (!cl->pers.inventory[index])
|
|
return;
|
|
it = &itemlist[index];
|
|
if (!it->use)
|
|
return;
|
|
if (!(it->flags & IF_WEAPON))
|
|
return;
|
|
it->use(ent, it);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_InvDrop_f
|
|
=================
|
|
*/
|
|
void Cmd_InvDrop_f(edict_t *ent)
|
|
{
|
|
gitem_t *it;
|
|
|
|
if (ent->health <= 0 || ent->deadflag)
|
|
return;
|
|
|
|
ValidateSelectedItem(ent);
|
|
|
|
if (ent->client->pers.selected_item == IT_NULL)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_item_to_drop");
|
|
return;
|
|
}
|
|
|
|
it = &itemlist[ent->client->pers.selected_item];
|
|
if (!it->drop)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_item_not_droppable");
|
|
return;
|
|
}
|
|
it->drop(ent, it);
|
|
|
|
ValidateSelectedItem(ent);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Kill_f
|
|
=================
|
|
*/
|
|
void Cmd_Kill_f(edict_t *ent)
|
|
{
|
|
// ZOID
|
|
if (ent->client->resp.spectator)
|
|
return;
|
|
// ZOID
|
|
|
|
if ((level.time - ent->client->respawn_time) < 5_sec)
|
|
return;
|
|
|
|
ent->flags &= ~FL_GODMODE;
|
|
ent->health = 0;
|
|
|
|
// ROGUE
|
|
// make sure no trackers are still hurting us.
|
|
if (ent->client->tracker_pain_time)
|
|
RemoveAttackingPainDaemons(ent);
|
|
|
|
if (ent->client->owned_sphere)
|
|
{
|
|
G_FreeEdict(ent->client->owned_sphere);
|
|
ent->client->owned_sphere = nullptr;
|
|
}
|
|
// ROGUE
|
|
|
|
// [Paril-KEX] don't allow kill to take points away in TDM
|
|
player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, !!teamplay->integer });
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Kill_AI_f
|
|
=================
|
|
*/
|
|
void Cmd_Kill_AI_f( edict_t * ent ) {
|
|
if ( !sv_cheats->integer ) {
|
|
gi.LocClient_Print( ent, PRINT_HIGH, "Kill_AI: Cheats Must Be Enabled!\n" );
|
|
return;
|
|
}
|
|
|
|
// except the one we're looking at...
|
|
edict_t *looked_at = nullptr;
|
|
|
|
vec3_t start = ent->s.origin + vec3_t{0.f, 0.f, (float) ent->viewheight};
|
|
vec3_t end = start + ent->client->v_forward * 1024.f;
|
|
|
|
looked_at = gi.traceline(start, end, ent, MASK_SHOT).ent;
|
|
|
|
const int numEdicts = globals.num_edicts;
|
|
for ( int edictIdx = 1; edictIdx < numEdicts; ++edictIdx )
|
|
{
|
|
edict_t * edict = &g_edicts[ edictIdx ];
|
|
if ( !edict->inuse || edict == looked_at ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ( edict->svflags & SVF_MONSTER ) == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
G_FreeEdict( edict );
|
|
}
|
|
|
|
gi.LocClient_Print( ent, PRINT_HIGH, "Kill_AI: All AI Are Dead...\n" );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Where_f
|
|
=================
|
|
*/
|
|
void Cmd_Where_f( edict_t * ent ) {
|
|
if ( ent == nullptr || ent->client == nullptr ) {
|
|
return;
|
|
}
|
|
|
|
const vec3_t & origin = ent->s.origin;
|
|
|
|
std::string location;
|
|
fmt::format_to( std::back_inserter( location ), FMT_STRING( "{:.1f} {:.1f} {:.1f} {:.1f} {:.1f} {:.1f}\n" ), origin[ 0 ], origin[ 1 ], origin[ 2 ], ent->client->ps.viewangles[0], ent->client->ps.viewangles[1], ent->client->ps.viewangles[2] );
|
|
gi.LocClient_Print( ent, PRINT_HIGH, "Location: {}\n", location.c_str() );
|
|
gi.SendToClipBoard( location.c_str() );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Clear_AI_Enemy_f
|
|
=================
|
|
*/
|
|
void Cmd_Clear_AI_Enemy_f( edict_t * ent ) {
|
|
if ( !sv_cheats->integer ) {
|
|
gi.LocClient_Print( ent, PRINT_HIGH, "Cmd_Clear_AI_Enemy: Cheats Must Be Enabled!\n" );
|
|
return;
|
|
}
|
|
|
|
const int numEdicts = globals.num_edicts;
|
|
for ( int edictIdx = 1; edictIdx < numEdicts; ++edictIdx ) {
|
|
edict_t * edict = &g_edicts[ edictIdx ];
|
|
if ( !edict->inuse ) {
|
|
continue;
|
|
}
|
|
|
|
if ( ( edict->svflags & SVF_MONSTER ) == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
edict->monsterinfo.aiflags |= AI_FORGET_ENEMY;
|
|
}
|
|
|
|
gi.LocClient_Print( ent, PRINT_HIGH, "Cmd_Clear_AI_Enemy: Clear All AI Enemies...\n" );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_PutAway_f
|
|
=================
|
|
*/
|
|
void Cmd_PutAway_f(edict_t *ent)
|
|
{
|
|
ent->client->showscores = false;
|
|
ent->client->showhelp = false;
|
|
ent->client->showinventory = false;
|
|
|
|
globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
|
|
|
|
// ZOID
|
|
if (ent->client->menu)
|
|
PMenu_Close(ent);
|
|
ent->client->update_chase = true;
|
|
// ZOID
|
|
}
|
|
|
|
int PlayerSort(const void *a, const void *b)
|
|
{
|
|
int anum, bnum;
|
|
|
|
anum = *(const int *) a;
|
|
bnum = *(const int *) b;
|
|
|
|
anum = game.clients[anum].ps.stats[STAT_FRAGS];
|
|
bnum = game.clients[bnum].ps.stats[STAT_FRAGS];
|
|
|
|
if (anum < bnum)
|
|
return -1;
|
|
if (anum > bnum)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
constexpr size_t MAX_IDEAL_PACKET_SIZE = 1024;
|
|
|
|
/*
|
|
=================
|
|
Cmd_Players_f
|
|
=================
|
|
*/
|
|
void Cmd_Players_f(edict_t *ent)
|
|
{
|
|
size_t i;
|
|
size_t count;
|
|
static std::string small, large;
|
|
int index[MAX_CLIENTS];
|
|
|
|
small.clear();
|
|
large.clear();
|
|
|
|
count = 0;
|
|
for (i = 0; i < game.maxclients; i++)
|
|
if (game.clients[i].pers.connected)
|
|
{
|
|
index[count] = i;
|
|
count++;
|
|
}
|
|
|
|
// sort by frags
|
|
qsort(index, count, sizeof(index[0]), PlayerSort);
|
|
|
|
// print information
|
|
large[0] = 0;
|
|
|
|
if (count)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
fmt::format_to(std::back_inserter(small), FMT_STRING("{:3} {}\n"), game.clients[index[i]].ps.stats[STAT_FRAGS],
|
|
game.clients[index[i]].pers.netname);
|
|
|
|
if (small.length() + large.length() > MAX_IDEAL_PACKET_SIZE - 50)
|
|
{ // can't print all of them in one packet
|
|
large += "...\n";
|
|
break;
|
|
}
|
|
|
|
large += small;
|
|
small.clear();
|
|
}
|
|
|
|
// remove the last newline
|
|
large.pop_back();
|
|
}
|
|
|
|
gi.LocClient_Print(ent, PRINT_HIGH | PRINT_NO_NOTIFY, "$g_players", large.c_str(), count);
|
|
}
|
|
|
|
bool CheckFlood(edict_t *ent)
|
|
{
|
|
int i;
|
|
gclient_t *cl;
|
|
|
|
if (flood_msgs->integer)
|
|
{
|
|
cl = ent->client;
|
|
|
|
if (level.time < cl->flood_locktill)
|
|
{
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "$g_flood_cant_talk",
|
|
(cl->flood_locktill - level.time).seconds<int32_t>());
|
|
return true;
|
|
}
|
|
i = cl->flood_whenhead - flood_msgs->integer + 1;
|
|
if (i < 0)
|
|
i = (sizeof(cl->flood_when) / sizeof(cl->flood_when[0])) + i;
|
|
if (i >= q_countof(cl->flood_when))
|
|
i = 0;
|
|
if (cl->flood_when[i] && level.time - cl->flood_when[i] < gtime_t::from_sec(flood_persecond->value))
|
|
{
|
|
cl->flood_locktill = level.time + gtime_t::from_sec(flood_waitdelay->value);
|
|
gi.LocClient_Print(ent, PRINT_CHAT, "$g_flood_cant_talk",
|
|
flood_waitdelay->integer);
|
|
return true;
|
|
}
|
|
cl->flood_whenhead = (cl->flood_whenhead + 1) % (sizeof(cl->flood_when) / sizeof(cl->flood_when[0]));
|
|
cl->flood_when[cl->flood_whenhead] = level.time;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Cmd_Wave_f
|
|
=================
|
|
*/
|
|
void Cmd_Wave_f(edict_t *ent)
|
|
{
|
|
int i;
|
|
|
|
i = atoi(gi.argv(1));
|
|
|
|
// no dead or noclip waving
|
|
if (ent->deadflag || ent->movetype == MOVETYPE_NOCLIP)
|
|
return;
|
|
|
|
// can't wave when ducked
|
|
bool do_animate = ent->client->anim_priority <= ANIM_WAVE && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED);
|
|
|
|
if (do_animate)
|
|
ent->client->anim_priority = ANIM_WAVE;
|
|
|
|
const char *other_notify_msg = nullptr, *other_notify_none_msg = nullptr;
|
|
|
|
vec3_t start, dir;
|
|
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, 0 }, start, dir);
|
|
|
|
// see who we're aiming at
|
|
edict_t *aiming_at = nullptr;
|
|
float best_dist = -9999;
|
|
|
|
for (auto player : active_players())
|
|
{
|
|
if (player == ent)
|
|
continue;
|
|
|
|
vec3_t cdir = player->s.origin - start;
|
|
float dist = cdir.normalize();
|
|
|
|
float dot = ent->client->v_forward.dot(cdir);
|
|
|
|
if (dot < 0.97)
|
|
continue;
|
|
else if (dist < best_dist)
|
|
continue;
|
|
|
|
best_dist = dist;
|
|
aiming_at = player;
|
|
}
|
|
|
|
switch (i)
|
|
{
|
|
case GESTURE_FLIP_OFF:
|
|
other_notify_msg = "$g_flipoff_other";
|
|
other_notify_none_msg = "$g_flipoff_none";
|
|
if (do_animate)
|
|
{
|
|
ent->s.frame = FRAME_flip01 - 1;
|
|
ent->client->anim_end = FRAME_flip12;
|
|
}
|
|
break;
|
|
case GESTURE_SALUTE:
|
|
other_notify_msg = "$g_salute_other";
|
|
other_notify_none_msg = "$g_salute_none";
|
|
if (do_animate)
|
|
{
|
|
ent->s.frame = FRAME_salute01 - 1;
|
|
ent->client->anim_end = FRAME_salute11;
|
|
}
|
|
break;
|
|
case GESTURE_TAUNT:
|
|
other_notify_msg = "$g_taunt_other";
|
|
other_notify_none_msg = "$g_taunt_none";
|
|
if (do_animate)
|
|
{
|
|
ent->s.frame = FRAME_taunt01 - 1;
|
|
ent->client->anim_end = FRAME_taunt17;
|
|
}
|
|
break;
|
|
case GESTURE_WAVE:
|
|
other_notify_msg = "$g_wave_other";
|
|
other_notify_none_msg = "$g_wave_none";
|
|
if (do_animate)
|
|
{
|
|
ent->s.frame = FRAME_wave01 - 1;
|
|
ent->client->anim_end = FRAME_wave11;
|
|
}
|
|
break;
|
|
case GESTURE_POINT:
|
|
default:
|
|
other_notify_msg = "$g_point_other";
|
|
other_notify_none_msg = "$g_point_none";
|
|
if (do_animate)
|
|
{
|
|
ent->s.frame = FRAME_point01 - 1;
|
|
ent->client->anim_end = FRAME_point12;
|
|
}
|
|
break;
|
|
}
|
|
|
|
bool has_a_target = false;
|
|
|
|
if (i == GESTURE_POINT)
|
|
{
|
|
for (auto player : active_players())
|
|
{
|
|
if (player == ent)
|
|
continue;
|
|
else if (!OnSameTeam(ent, player))
|
|
continue;
|
|
|
|
has_a_target = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == GESTURE_POINT && has_a_target)
|
|
{
|
|
// don't do this stuff if we're flooding
|
|
if (CheckFlood(ent))
|
|
return;
|
|
|
|
trace_t tr = gi.traceline(start, start + (ent->client->v_forward * 2048), ent, MASK_SHOT & ~CONTENTS_WINDOW);
|
|
other_notify_msg = "$g_point_other_ping";
|
|
|
|
uint32_t key = GetUnicastKey();
|
|
|
|
if (tr.fraction != 1.0f)
|
|
{
|
|
// send to all teammates
|
|
for (auto player : active_players())
|
|
{
|
|
if (player != ent && !OnSameTeam(ent, player))
|
|
continue;
|
|
|
|
gi.WriteByte(svc_poi);
|
|
gi.WriteShort(POI_PING + (ent->s.number - 1));
|
|
gi.WriteShort(5000);
|
|
gi.WritePosition(tr.endpos);
|
|
gi.WriteShort(level.pic_ping);
|
|
gi.WriteByte(208);
|
|
gi.WriteByte(POI_FLAG_NONE);
|
|
gi.unicast(player, false);
|
|
|
|
gi.local_sound(player, CHAN_AUTO, gi.soundindex("misc/help_marker.wav"), 1.0f, ATTN_NONE, 0.0f, key);
|
|
gi.LocClient_Print(player, PRINT_HIGH, other_notify_msg, ent->client->pers.netname);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CheckFlood(ent))
|
|
return;
|
|
|
|
edict_t* targ = nullptr;
|
|
while ((targ = findradius(targ, ent->s.origin, 1024)) != nullptr)
|
|
{
|
|
if (ent == targ) continue;
|
|
if (!targ->client) continue;
|
|
if (!gi.inPVS(ent->s.origin, targ->s.origin, false)) continue;
|
|
|
|
if (aiming_at && other_notify_msg)
|
|
gi.LocClient_Print(targ, PRINT_TTS, other_notify_msg, ent->client->pers.netname, aiming_at->client->pers.netname);
|
|
else if (other_notify_none_msg)
|
|
gi.LocClient_Print(targ, PRINT_TTS, other_notify_none_msg, ent->client->pers.netname);
|
|
}
|
|
|
|
if (aiming_at && other_notify_msg)
|
|
gi.LocClient_Print(ent, PRINT_TTS, other_notify_msg, ent->client->pers.netname, aiming_at->client->pers.netname);
|
|
else if (other_notify_none_msg)
|
|
gi.LocClient_Print(ent, PRINT_TTS, other_notify_none_msg, ent->client->pers.netname);
|
|
}
|
|
|
|
ent->client->anim_time = 0_ms;
|
|
}
|
|
|
|
#ifndef KEX_Q2_GAME
|
|
/*
|
|
==================
|
|
Cmd_Say_f
|
|
|
|
NB: only used for non-Playfab stuff
|
|
==================
|
|
*/
|
|
void Cmd_Say_f(edict_t *ent, bool arg0)
|
|
{
|
|
edict_t *other;
|
|
const char *p_in;
|
|
static std::string text;
|
|
|
|
if (gi.argc() < 2 && !arg0)
|
|
return;
|
|
else if (CheckFlood(ent))
|
|
return;
|
|
|
|
text.clear();
|
|
fmt::format_to(std::back_inserter(text), FMT_STRING("{}: "), ent->client->pers.netname);
|
|
|
|
if (arg0)
|
|
{
|
|
text += gi.argv(0);
|
|
text += " ";
|
|
text += gi.args();
|
|
}
|
|
else
|
|
{
|
|
p_in = gi.args();
|
|
size_t in_len = strlen(p_in);
|
|
|
|
if (p_in[0] == '\"' && p_in[in_len - 1] == '\"')
|
|
text += std::string_view(p_in + 1, in_len - 2);
|
|
else
|
|
text += p_in;
|
|
}
|
|
|
|
// don't let text be too long for malicious reasons
|
|
if (text.length() > 150)
|
|
text.resize(150);
|
|
|
|
if (text.back() != '\n')
|
|
text.push_back('\n');
|
|
|
|
if (sv_dedicated->integer)
|
|
gi.Client_Print(nullptr, PRINT_CHAT, text.c_str());
|
|
|
|
for (uint32_t j = 1; j <= game.maxclients; j++)
|
|
{
|
|
other = &g_edicts[j];
|
|
if (!other->inuse)
|
|
continue;
|
|
if (!other->client)
|
|
continue;
|
|
gi.Client_Print(other, PRINT_CHAT, text.c_str());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void Cmd_PlayerList_f(edict_t *ent)
|
|
{
|
|
uint32_t i;
|
|
static std::string str, text;
|
|
edict_t *e2;
|
|
|
|
str.clear();
|
|
text.clear();
|
|
|
|
// connect time, ping, score, name
|
|
for (i = 0, e2 = g_edicts + 1; i < game.maxclients; i++, e2++)
|
|
{
|
|
if (!e2->inuse)
|
|
continue;
|
|
|
|
fmt::format_to(std::back_inserter(str), FMT_STRING("{:02}:{:02} {:4} {:3} {}{}\n"), (level.time - e2->client->resp.entertime).milliseconds() / 60000,
|
|
((level.time - e2->client->resp.entertime).milliseconds() % 60000) / 1000, e2->client->ping,
|
|
e2->client->resp.score, e2->client->pers.netname, e2->client->resp.spectator ? " (spectator)" : "");
|
|
|
|
if (text.length() + str.length() > MAX_IDEAL_PACKET_SIZE - 50)
|
|
{
|
|
text += "...\n";
|
|
break;
|
|
}
|
|
|
|
text += str;
|
|
}
|
|
|
|
if (text.length())
|
|
gi.Client_Print(ent, PRINT_HIGH, text.c_str());
|
|
}
|
|
|
|
void Cmd_Switchteam_f(edict_t* ent)
|
|
{
|
|
if (!G_TeamplayEnabled())
|
|
return;
|
|
|
|
// [Paril-KEX] in force-join, just do a regular team join.
|
|
if (g_teamplay_force_join->integer)
|
|
{
|
|
// check if we should even switch teams
|
|
edict_t *player;
|
|
uint32_t team1count = 0, team2count = 0;
|
|
ctfteam_t best_team;
|
|
|
|
for (uint32_t i = 1; i <= game.maxclients; i++)
|
|
{
|
|
player = &g_edicts[i];
|
|
|
|
// NB: we are counting ourselves in this one, unlike
|
|
// the other assign team func
|
|
if (!player->inuse)
|
|
continue;
|
|
|
|
switch (player->client->resp.ctf_team)
|
|
{
|
|
case CTF_TEAM1:
|
|
team1count++;
|
|
break;
|
|
case CTF_TEAM2:
|
|
team2count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (team1count < team2count)
|
|
best_team = CTF_TEAM1;
|
|
else
|
|
best_team = CTF_TEAM2;
|
|
|
|
if (ent->client->resp.ctf_team != best_team)
|
|
{
|
|
////
|
|
ent->svflags = SVF_NONE;
|
|
ent->flags &= ~FL_GODMODE;
|
|
ent->client->resp.ctf_team = best_team;
|
|
ent->client->resp.ctf_state = 0;
|
|
char value[MAX_INFO_VALUE] = { 0 };
|
|
gi.Info_ValueForKey(ent->client->pers.userinfo, "skin", value, sizeof(value));
|
|
CTFAssignSkin(ent, value);
|
|
|
|
// if anybody has a menu open, update it immediately
|
|
CTFDirtyTeamMenu();
|
|
|
|
if (ent->solid == SOLID_NOT)
|
|
{
|
|
// spectator
|
|
PutClientInServer(ent);
|
|
|
|
G_PostRespawn(ent);
|
|
|
|
gi.LocBroadcast_Print(PRINT_HIGH, "$g_joined_team",
|
|
ent->client->pers.netname, CTFTeamName(best_team));
|
|
return;
|
|
}
|
|
|
|
ent->health = 0;
|
|
player_die(ent, ent, ent, 100000, vec3_origin, { MOD_SUICIDE, true });
|
|
|
|
// don't even bother waiting for death frames
|
|
ent->deadflag = true;
|
|
respawn(ent);
|
|
|
|
ent->client->resp.score = 0;
|
|
|
|
gi.LocBroadcast_Print(PRINT_HIGH, "$g_changed_team",
|
|
ent->client->pers.netname, CTFTeamName(best_team));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (ent->client->resp.ctf_team != CTF_NOTEAM)
|
|
CTFObserver(ent);
|
|
|
|
if (!ent->client->menu)
|
|
CTFOpenJoinMenu(ent);
|
|
}
|
|
|
|
static void Cmd_ListMonsters_f(edict_t *ent)
|
|
{
|
|
if (!G_CheatCheck(ent))
|
|
return;
|
|
else if (!g_debug_monster_kills->integer)
|
|
return;
|
|
|
|
for (size_t i = 0; i < level.total_monsters; i++)
|
|
{
|
|
edict_t *e = level.monsters_registered[i];
|
|
|
|
if (!e || !e->inuse)
|
|
continue;
|
|
else if (!(e->svflags & SVF_MONSTER) || (e->monsterinfo.aiflags & AI_DO_NOT_COUNT))
|
|
continue;
|
|
else if (e->deadflag)
|
|
continue;
|
|
|
|
gi.Com_PrintFmt("{}\n", *e);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
ClientCommand
|
|
=================
|
|
*/
|
|
void ClientCommand(edict_t *ent)
|
|
{
|
|
const char *cmd;
|
|
|
|
if (!ent->client)
|
|
return; // not fully in game yet
|
|
|
|
cmd = gi.argv(0);
|
|
|
|
if (Q_strcasecmp(cmd, "players") == 0)
|
|
{
|
|
Cmd_Players_f(ent);
|
|
return;
|
|
}
|
|
// [Paril-KEX] these have to go through the lobby system
|
|
#ifndef KEX_Q2_GAME
|
|
if (Q_strcasecmp(cmd, "say") == 0)
|
|
{
|
|
Cmd_Say_f(ent, false);
|
|
return;
|
|
}
|
|
if (Q_strcasecmp(cmd, "say_team") == 0 || Q_strcasecmp(cmd, "steam") == 0)
|
|
{
|
|
if (G_TeamplayEnabled())
|
|
CTFSay_Team(ent, gi.args());
|
|
else
|
|
Cmd_Say_f(ent, false);
|
|
return;
|
|
}
|
|
#endif
|
|
if (Q_strcasecmp(cmd, "score") == 0)
|
|
{
|
|
Cmd_Score_f(ent);
|
|
return;
|
|
}
|
|
if (Q_strcasecmp(cmd, "help") == 0)
|
|
{
|
|
Cmd_Help_f(ent);
|
|
return;
|
|
}
|
|
if (Q_strcasecmp(cmd, "listmonsters") == 0)
|
|
{
|
|
Cmd_ListMonsters_f(ent);
|
|
return;
|
|
}
|
|
|
|
if (level.intermissiontime)
|
|
return;
|
|
|
|
if ( Q_strcasecmp( cmd, "target" ) == 0 )
|
|
Cmd_Target_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "use" ) == 0 || Q_strcasecmp( cmd, "use_only" ) == 0 ||
|
|
Q_strcasecmp( cmd, "use_index" ) == 0 || Q_strcasecmp( cmd, "use_index_only" ) == 0 )
|
|
Cmd_Use_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "drop" ) == 0 ||
|
|
Q_strcasecmp( cmd, "drop_index" ) == 0 )
|
|
Cmd_Drop_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "give" ) == 0 )
|
|
Cmd_Give_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "god" ) == 0 )
|
|
Cmd_God_f( ent );
|
|
else if (Q_strcasecmp(cmd, "immortal") == 0)
|
|
Cmd_Immortal_f(ent);
|
|
else if ( Q_strcasecmp( cmd, "setpoi" ) == 0 )
|
|
Cmd_SetPOI_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "checkpoi" ) == 0 )
|
|
Cmd_CheckPOI_f( ent );
|
|
// Paril: cheats to help with dev
|
|
else if ( Q_strcasecmp( cmd, "spawn" ) == 0 )
|
|
Cmd_Spawn_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "teleport" ) == 0 )
|
|
Cmd_Teleport_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "notarget" ) == 0 )
|
|
Cmd_Notarget_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "novisible" ) == 0 )
|
|
Cmd_Novisible_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "alertall" ) == 0 )
|
|
Cmd_AlertAll_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "noclip" ) == 0 )
|
|
Cmd_Noclip_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "inven" ) == 0 )
|
|
Cmd_Inven_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "invnext" ) == 0 )
|
|
SelectNextItem( ent, IF_ANY );
|
|
else if ( Q_strcasecmp( cmd, "invprev" ) == 0 )
|
|
SelectPrevItem( ent, IF_ANY );
|
|
else if ( Q_strcasecmp( cmd, "invnextw" ) == 0 )
|
|
SelectNextItem( ent, IF_WEAPON );
|
|
else if ( Q_strcasecmp( cmd, "invprevw" ) == 0 )
|
|
SelectPrevItem( ent, IF_WEAPON );
|
|
else if ( Q_strcasecmp( cmd, "invnextp" ) == 0 )
|
|
SelectNextItem( ent, IF_POWERUP );
|
|
else if ( Q_strcasecmp( cmd, "invprevp" ) == 0 )
|
|
SelectPrevItem( ent, IF_POWERUP );
|
|
else if ( Q_strcasecmp( cmd, "invuse" ) == 0 )
|
|
Cmd_InvUse_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "invdrop" ) == 0 )
|
|
Cmd_InvDrop_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "weapprev" ) == 0 )
|
|
Cmd_WeapPrev_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "weapnext" ) == 0 )
|
|
Cmd_WeapNext_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "weaplast" ) == 0 || Q_strcasecmp( cmd, "lastweap" ) == 0 )
|
|
Cmd_WeapLast_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "kill" ) == 0 )
|
|
Cmd_Kill_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "kill_ai" ) == 0 )
|
|
Cmd_Kill_AI_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "where" ) == 0 )
|
|
Cmd_Where_f( ent );
|
|
else if ( Q_strcasecmp( cmd, "clear_ai_enemy" ) == 0 )
|
|
Cmd_Clear_AI_Enemy_f( ent );
|
|
else if (Q_strcasecmp(cmd, "putaway") == 0)
|
|
Cmd_PutAway_f(ent);
|
|
else if (Q_strcasecmp(cmd, "wave") == 0)
|
|
Cmd_Wave_f(ent);
|
|
else if (Q_strcasecmp(cmd, "playerlist") == 0)
|
|
Cmd_PlayerList_f(ent);
|
|
// ZOID
|
|
else if (Q_strcasecmp(cmd, "team") == 0)
|
|
CTFTeam_f(ent);
|
|
else if (Q_strcasecmp(cmd, "id") == 0)
|
|
CTFID_f(ent);
|
|
else if (Q_strcasecmp(cmd, "yes") == 0)
|
|
CTFVoteYes(ent);
|
|
else if (Q_strcasecmp(cmd, "no") == 0)
|
|
CTFVoteNo(ent);
|
|
else if (Q_strcasecmp(cmd, "ready") == 0)
|
|
CTFReady(ent);
|
|
else if (Q_strcasecmp(cmd, "notready") == 0)
|
|
CTFNotReady(ent);
|
|
else if (Q_strcasecmp(cmd, "ghost") == 0)
|
|
CTFGhost(ent);
|
|
else if (Q_strcasecmp(cmd, "admin") == 0)
|
|
CTFAdmin(ent);
|
|
else if (Q_strcasecmp(cmd, "stats") == 0)
|
|
CTFStats(ent);
|
|
else if (Q_strcasecmp(cmd, "warp") == 0)
|
|
CTFWarp(ent);
|
|
else if (Q_strcasecmp(cmd, "boot") == 0)
|
|
CTFBoot(ent);
|
|
else if (Q_strcasecmp(cmd, "playerlist") == 0)
|
|
CTFPlayerList(ent);
|
|
else if (Q_strcasecmp(cmd, "observer") == 0)
|
|
CTFObserver(ent);
|
|
// ZOID
|
|
else if (Q_strcasecmp(cmd, "switchteam") == 0)
|
|
Cmd_Switchteam_f(ent);
|
|
#ifndef KEX_Q2_GAME
|
|
else // anything that doesn't match a command will be a chat
|
|
Cmd_Say_f(ent, true);
|
|
#else
|
|
// anything that doesn't match a command will inform them
|
|
else
|
|
gi.LocClient_Print(ent, PRINT_HIGH, "invalid game command \"{}\"\n", gi.argv(0));
|
|
#endif
|
|
}
|