/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "g_local.h"
/*
* ======================================================================
*
* INTERMISSION
*
* ======================================================================
*/
void
MoveClientToIntermission(edict_t * ent)
{
if (deathmatch->value || coop->value)
ent->client->showscores = true;
VectorCopy(level.intermission_origin, ent->s.origin);
ent->client->ps.pmove.origin[0] = level.intermission_origin[0] * 8;
ent->client->ps.pmove.origin[1] = level.intermission_origin[1] * 8;
ent->client->ps.pmove.origin[2] = level.intermission_origin[2] * 8;
VectorCopy(level.intermission_angle, ent->client->ps.viewangles);
ent->client->ps.pmove.pm_type = PM_FREEZE;
ent->client->ps.gunindex = 0;
ent->client->ps.blend[3] = 0;
ent->client->ps.rdflags &= ~RDF_UNDERWATER;
/* clean up powerup info */
ent->client->quad_framenum = 0;
ent->client->invincible_framenum = 0;
ent->client->breather_framenum = 0;
ent->client->enviro_framenum = 0;
ent->client->grenade_blew_up = false;
ent->client->grenade_time = 0;
ent->viewheight = 0;
ent->s.modelindex = 0;
ent->s.modelindex2 = 0;
ent->s.modelindex3 = 0;
ent->s.modelindex = 0;
ent->s.effects = 0;
ent->s.sound = 0;
ent->solid = SOLID_NOT;
/* add the layout */
if (deathmatch->value || coop->value) {
DeathmatchScoreboardMessage(ent, NULL);
gi.unicast(ent, true);
}
}
void
BeginIntermission(edict_t * targ)
{
int i , n;
edict_t *ent, *client;
if (level.intermissiontime)
return; /* already activated */
game.autosaved = false;
/* respawn any dead clients */
for (i = 0; i < maxclients->value; i++) {
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
if (client->health <= 0)
respawn(client);
}
level.intermissiontime = level.time;
level.changemap = targ->map;
if (strstr(level.changemap, "*")) {
if (coop->value) {
for (i = 0; i < maxclients->value; i++) {
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
/* strip players of all keys between units */
for (n = 0; n < MAX_ITEMS; n++) {
if (itemlist[n].flags & IT_KEY)
client->client->pers.inventory[n] = 0;
}
}
}
} else {
if (!deathmatch->value) {
level.exitintermission = 1; /* go immediately to the
* next level */
return;
}
}
level.exitintermission = 0;
/* find an intermission spot */
ent = G_Find(NULL, FOFS(classname), "info_player_intermission");
if (!ent) { /* the map creator forgot to put in an
* intermission point... */
ent = G_Find(NULL, FOFS(classname), "info_player_start");
if (!ent)
ent = G_Find(NULL, FOFS(classname), "info_player_deathmatch");
} else { /* chose one of four spots */
i = rand() & 3;
while (i--) {
ent = G_Find(ent, FOFS(classname), "info_player_intermission");
if (!ent) /* wrap around the list */
ent = G_Find(ent, FOFS(classname), "info_player_intermission");
}
}
VectorCopy(ent->s.origin, level.intermission_origin);
VectorCopy(ent->s.angles, level.intermission_angle);
/* move all clients to the intermission point */
for (i = 0; i < maxclients->value; i++) {
client = g_edicts + 1 + i;
if (!client->inuse)
continue;
MoveClientToIntermission(client);
}
}
#ifdef GAME_MOD
/* Internal functions for compute the cooplayout data */
/* ================== */
//
/* ================== */
int
entArmor(edict_t * ent)
{
gitem_t *item;
int index , iCells, iArmor;
int power_armor_type;
power_armor_type = PowerArmorType(ent);
if (power_armor_type) {
iCells = ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))];
if (iCells == 0) { /* ran out of cells for power armor */
ent->flags &= ~FL_POWER_ARMOR;
power_armor_type = 0;;
}
}
index = ArmorIndex(ent);
if (power_armor_type && (!index || (level.framenum & 8)))
iArmor = iCells;
else if (index) {
item = GetItemByIndex(index);
iArmor = ent->client->pers.inventory[index];
} else
iArmor = 0;
return iArmor;
}
/* CoopScoreboardMessage (cooperative layout) */
/* ================== */
/* Display a layout for cooperative play */
/* ================== */
#define RANGE_1_METER 39 /* 1 meter in map unit */
void
CoopScoreboardMessage(edict_t * ent)
{
gclient_t *cl;
edict_t *cl_ent;
char string [1400];
int health , armor;
int i , j, x, y;
int dist , dir_h, dir_v;
vec3_t v;
float angle;
char szArrow [8];
string[0] = '\0';
x = 160;
y = 8;
for (i = 0; i < game.maxclients; i++) {
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse || game.clients[i].resp.spectator)
continue;
cl = &game.clients[i];
health = max(cl_ent->health, 0);
health = (min(health, 100) / 5) * 5; /* bh0..5..10.. ..100 */
armor = entArmor(cl_ent);
armor = max(armor, 0);
armor = (min(armor, 100) / 5) * 5; /* ba0..5..10.. ..100 */
j = strlen(string);
if (cl_ent == ent) { /* The player himself */
Com_sprintf(string + j, sizeof(string) - j,
"xr -%i yt %i picn /players/%s_i.pcx " /* face */
"xr -%i yt %i string2 \"%s\" " /* name */
"xr -%i yt %i picn bh%i " /* health */
"yt %i picn ba%i " /* armor */
"xr -%i yt %i string2 \"%s\" ", /* ping */
x, y, Info_ValueForKey(cl_ent->client->pers.userinfo, "skin"),
x - 34, y, cl_ent->client->pers.netname,
x - 35, y + 8, health,
y + 13, armor,
x - 82, y + 20, va("(%dms)", min(cl->ping, 999)));
} else {
VectorSubtract(cl_ent->s.origin, ent->s.origin, v);
dist = (int)(VectorLength(v) / RANGE_1_METER);
/* direction_V : up, down or same height */
if (v[2] > 3 * RANGE_1_METER)
dir_v = 1;
else if (v[2] < -3 * RANGE_1_METER)
dir_v = 2;
else
dir_v = 0;
/* direction_H : left, right, front or back */
if (fabs(v[0]) < RANGE_1_METER && fabs(v[1]) < RANGE_1_METER)
dir_h = 0; /* Too near, no direction */
else {
angle = vectoyaw(v) - ent->s.angles[YAW];
if (angle < 0)
angle += 360;
else if (angle >= 360)
angle -= 360;
if (angle < 45)
dir_h = 1; /* front */
else if (angle < 135)
dir_h = 2; /* left */
else if (angle < 225)
dir_h = 3; /* back */
else if (angle < 315)
dir_h = 4; /* right */
else
dir_h = 1; /* front */
}
Com_sprintf(szArrow, sizeof(szArrow), "v%ih%i", dir_v, dir_h);
Com_sprintf(string + j, sizeof(string) - j,
"xr -%i yt %i picn /players/%s_i.pcx " /* face */
"xr -%i yt %i string \"%s\" " /* name */
"xr -%i yt %i picn bh%i " /* health */
"yt %i picn ba%i " /* armor */
"yt %i picn %s " /* directional arrow */
"xr -%i string \"%im (%dms)\" ", /* distance + ping */
x, y, Info_ValueForKey(cl_ent->client->pers.userinfo, "skin"),
x - 34, y, cl_ent->client->pers.netname,
x - 35, y + 8, health,
y + 13, armor,
y + 20, szArrow,
x - 62, dist, min(cl->ping, 999));
}
x += 160;
}
if (string[0]) {
gi.WriteByte(svc_layout);
gi.WriteString(string);
}
}
/* */
/*
* ================== DeathmatchScoreboardMessage
*
* ==================
*/
void
NewDeathmatchScoreboardMessage(edict_t * ent, edict_t * killer)
{
char entry [1024];
char string [1400];
int stringlength = 0;
int i , j, k;
int sorted [MAX_CLIENTS];
int sortedscores[MAX_CLIENTS];
int score , total;
int picnum;
int x , y;
gclient_t *cl;
edict_t *cl_ent;
char *tag;
#ifdef WITH_ACEBOT
/* ACEBOT_ADD */
if (ent->is_bot)
return;
/* ACEBOT_END */
#endif
/* sort the clients by score */
total = 0;
for (i = 0; i < game.maxclients; i++) {
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse || game.clients[i].resp.spectator)
continue;
score = game.clients[i].resp.score;
for (j = 0; j < total; j++) {
if (score > sortedscores[j])
break;
}
for (k = total; k > j; k--) {
sorted[k] = sorted[k - 1];
sortedscores[k] = sortedscores[k - 1];
}
sorted[j] = i;
sortedscores[j] = score;
total++;
}
Com_sprintf(entry, sizeof(entry),
"xv 32 yv 16 string2 \"Player\" "
"xv 168 yv 16 string2 \"Frags\" "
"xv 216 yv 16 string2 \"Ping\" "
"xv 256 yv 16 string2 \"Time\" "
"xv 32 yv 24 string2 \"--------------------------------\" ");
j = strlen(entry);
if (stringlength + j < 1024) {
strcpy(string + stringlength, entry);
stringlength += j;
}
if (total > 25)
total = 25;
for (i = 0; i < total; i++) {
cl = &game.clients[sorted[i]];
cl_ent = g_edicts + 1 + sorted[i];
picnum = gi.imageindex("i_fixme");
x = 32;
y = 32 + 8 * i;
/* add a dogtag */
if (cl_ent == ent)
tag = ">";
else if (cl_ent == killer)
tag = "X>";
else
tag = NULL;
if (tag) {
Com_sprintf(entry, sizeof(entry),
"xv 8 yv %i string \"%s\" ",
y, tag);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy(string + stringlength, entry);
stringlength += j;
}
Com_sprintf(entry, sizeof(entry),
"xv 32 yv %i string2 \"%s\" "
"xv 152 yv %i string \"%3i %3i %3i\" ",
y, cl->pers.netname,
y, cl->resp.score,
cl->ping,
(level.framenum - cl->resp.enterframe) / 600);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy(string + stringlength, entry);
stringlength += j;
}
gi.WriteByte(svc_layout);
gi.WriteString(string);
}
#endif
void
DeathmatchScoreboardMessage(edict_t * ent, edict_t * killer)
{
char entry [1024];
char string [1400];
int stringlength;
int i , j, k;
int sorted [MAX_CLIENTS];
int sortedscores[MAX_CLIENTS];
int score , total;
int picnum;
int x , y;
gclient_t *cl;
edict_t *cl_ent;
char *tag;
#ifdef WITH_ACEBOT
/* ACEBOT_ADD */
if (ent->is_bot)
return;
/* ACEBOT_END */
#endif
#ifdef GAME_MOD
if (opt_dm_scoreboard->value) {
if (coop->value)
CoopScoreboardMessage(ent);
else
NewDeathmatchScoreboardMessage(ent, ent->enemy);
}
else {
total = 0;
for (i = 0; i < game.maxclients; i++) {
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse || game.clients[i].resp.spectator)
continue;
score = game.clients[i].resp.score;
for (j = 0; j < total; j++) {
if (score > sortedscores[j])
break;
}
for (k = total; k > j; k--) {
sorted[k] = sorted[k - 1];
sortedscores[k] = sortedscores[k - 1];
}
sorted[j] = i;
sortedscores[j] = score;
total++;
}
#else
total = 0;
for (i = 0; i < game.maxclients; i++) {
cl_ent = g_edicts + 1 + i;
if (!cl_ent->inuse || game.clients[i].resp.spectator)
continue;
score = game.clients[i].resp.score;
for (j = 0; j < total; j++) {
if (score > sortedscores[j])
break;
}
for (k = total; k > j; k--) {
sorted[k] = sorted[k - 1];
sortedscores[k] = sortedscores[k - 1];
}
sorted[j] = i;
sortedscores[j] = score;
total++;
}
#endif
/* print level name and exit rules */
string[0] = 0;
stringlength = strlen(string);
/* add the clients in sorted order */
if (total > 12)
total = 12;
for (i = 0; i < total; i++) {
cl = &game.clients[sorted[i]];
cl_ent = g_edicts + 1 + sorted[i];
picnum = gi.imageindex("i_fixme");
x = (i >= 6) ? 160 : 0;
y = 32 + 32 * (i % 6);
/* add a dogtag */
if (cl_ent == ent)
tag = "tag1";
else if (cl_ent == killer)
tag = "tag2";
else
tag = NULL;
if (tag) {
Com_sprintf(entry, sizeof(entry),
"xv %i yv %i picn %s ", x + 32, y, tag);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy(string + stringlength, entry);
stringlength += j;
}
/* send the layout */
Com_sprintf(entry, sizeof(entry),
"client %i %i %i %i %i %i ",
x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe) / 600);
j = strlen(entry);
if (stringlength + j > 1024)
break;
strcpy(string + stringlength, entry);
stringlength += j;
}
gi.WriteByte(svc_layout);
gi.WriteString(string);
#ifdef GAME_MOD
}
#endif
}
/*
* ================== DeathmatchScoreboard
*
* Draw instead of help message. Note that it isn't that hard to overflow the
* 1400 byte message limit! ==================
*/
void
DeathmatchScoreboard(edict_t * ent)
{
#ifdef WITH_ACEBOT
/* ACEBOT_ADD */
if (ent->is_bot)
return;
/* ACEBOT_END */
#endif
DeathmatchScoreboardMessage(ent, ent->enemy);
gi.unicast(ent, true);
}
/*
* ================== Cmd_Score_f
*
* Display the scoreboard ==================
*/
void
Cmd_Score_f(edict_t * ent)
{
ent->client->showinventory = false;
ent->client->showhelp = false;
if (!deathmatch->value && !coop->value)
return;
if (ent->client->showscores) {
ent->client->showscores = false;
return;
}
ent->client->showscores = true;
DeathmatchScoreboard(ent);
}
/*
* ================== HelpComputer
*
* Draw help computer. ==================
*/
void
HelpComputer(edict_t * ent)
{
char string [1024];
char *sk;
#ifdef WITH_ACEBOT
/* ACEBOT_ADD */
if (ent->is_bot)
return;
/* ACEBOT_END */
#endif
if (skill->value == 0)
sk = "easy";
else if (skill->value == 1)
sk = "medium";
else if (skill->value == 2)
sk = "hard";
else
sk = "hard+";
/* send the layout */
Com_sprintf(string, sizeof(string),
"xv 32 yv 8 picn help " /* background */
"xv 202 yv 12 string2 \"%s\" " /* skill */
"xv 0 yv 24 cstring2 \"%s\" " /* level name */
"xv 0 yv 54 cstring2 \"%s\" " /* help 1 */
"xv 0 yv 110 cstring2 \"%s\" " /* help 2 */
"xv 50 yv 164 string2 \" kills goals secrets\" "
"xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
sk,
level.level_name,
game.helpmessage1,
game.helpmessage2,
level.killed_monsters, level.total_monsters,
level.found_goals, level.total_goals,
level.found_secrets, level.total_secrets);
gi.WriteByte(svc_layout);
gi.WriteString(string);
gi.unicast(ent, true);
}
/*
* ================== Cmd_Help_f
*
* Display the current help message ==================
*/
void
Cmd_Help_f(edict_t * ent)
{
/* this is for backwards compatability */
if (deathmatch->value) {
Cmd_Score_f(ent);
return;
}
ent->client->showinventory = false;
ent->client->showscores = false;
if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged)) {
ent->client->showhelp = false;
return;
}
ent->client->showhelp = true;
ent->client->pers.helpchanged = 0;
HelpComputer(ent);
}
#ifdef GAME_MOD
void
HelpComputerMin(edict_t * ent)
{
char string [1024];
#ifdef WITH_ACEBOT
if (ent->is_bot)
return;
#endif
/* send the layout */
Com_sprintf(string, sizeof(string),
"xv 0 (yv) cstring2 \"%s\" "
"xv 1 (yv) string2 \"%3i/%3i",
game.helpmessage2, level.killed_monsters, level.total_monsters);
gi.WriteByte(svc_layout);
gi.WriteString(string);
gi.unicast(ent, true);
}
void
Cmd_HelpMin_f(edict_t * ent)
{
/* this is for backwards compatability */
if (deathmatch->value) {
Cmd_Score_f(ent);
return;
}
ent->client->showinventory = false;
ent->client->showscores = false;
if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged)) {
ent->client->showhelp = false;
return;
}
ent->client->showhelp = true;
ent->client->pers.helpchanged = 0;
HelpComputerMin(ent);
}
#endif
/* ======================================================================= */
/*
* =============== G_SetStats ===============
*/
void
G_SetStats(edict_t * ent)
{
gitem_t *item;
int index , cells;
int power_armor_type;
/* health */
ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
ent->client->ps.stats[STAT_HEALTH] = ent->health;
/* ammo */
if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */ ) {
ent->client->ps.stats[STAT_AMMO_ICON] = 0;
ent->client->ps.stats[STAT_AMMO] = 0;
} else {
item = &itemlist[ent->client->ammo_index];
ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon);
ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
}
cells = 0;
/* armor */
power_armor_type = PowerArmorType(ent);
if (power_armor_type) {
cells = ent->client->pers.inventory[ITEM_INDEX(FindItem("cells"))];
if (cells == 0) { /* ran out of cells for power armor */
ent->flags &= ~FL_POWER_ARMOR;
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
power_armor_type = 0;;
}
}
index = ArmorIndex(ent);
if (power_armor_type && (!index || (level.framenum & 8))) { /* flash between power
* armor and other armor
* icon */
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex("i_powershield");
ent->client->ps.stats[STAT_ARMOR] = cells;
} else if (index) {
item = GetItemByIndex(index);
ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex(item->icon);
ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
} else {
ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
ent->client->ps.stats[STAT_ARMOR] = 0;
}
/* pickup message */
if (level.time > ent->client->pickup_msg_time) {
ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
}
/* timers */
if (ent->client->quad_framenum > level.framenum) {
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_quad");
ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum) / 10;
} else if (ent->client->invincible_framenum > level.framenum) {
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_invulnerability");
ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum) / 10;
} else if (ent->client->enviro_framenum > level.framenum) {
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_envirosuit");
ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum) / 10;
} else if (ent->client->breather_framenum > level.framenum) {
ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_rebreather");
ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum) / 10;
} else {
ent->client->ps.stats[STAT_TIMER_ICON] = 0;
ent->client->ps.stats[STAT_TIMER] = 0;
}
/* selected item */
if (ent->client->pers.selected_item == -1)
ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
else
ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex(itemlist[ent->client->pers.selected_item].icon);
ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
/* layouts */
ent->client->ps.stats[STAT_LAYOUTS] = 0;
if (deathmatch->value) {
if (ent->client->pers.health <= 0 || level.intermissiontime
|| ent->client->showscores)
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
if (ent->client->showinventory && ent->client->pers.health > 0)
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
} else {
if (ent->client->showscores || ent->client->showhelp)
ent->client->ps.stats[STAT_LAYOUTS] |= 1;
if (ent->client->showinventory && ent->client->pers.health > 0)
ent->client->ps.stats[STAT_LAYOUTS] |= 2;
}
/* frags */
ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
/* help icon / current weapon if not shown */
if (ent->client->pers.helpchanged && (level.framenum & 8))
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex("i_help");
else if ((ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
&& ent->client->pers.weapon)
ent->client->ps.stats[STAT_HELPICON] = gi.imageindex(ent->client->pers.weapon->icon);
else
ent->client->ps.stats[STAT_HELPICON] = 0;
ent->client->ps.stats[STAT_SPECTATOR] = 0;
}
/*
* =============== G_CheckChaseStats ===============
*/
void
G_CheckChaseStats(edict_t * ent)
{
int i;
gclient_t *cl;
for (i = 1; i <= maxclients->value; i++) {
cl = g_edicts[i].client;
if (!g_edicts[i].inuse || cl->chase_target != ent)
continue;
memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
G_SetSpectatorStats(g_edicts + i);
}
}
/*
* =============== G_SetSpectatorStats ===============
*/
void
G_SetSpectatorStats(edict_t * ent)
{
gclient_t *cl = ent->client;
if (!cl->chase_target)
G_SetStats(ent);
cl->ps.stats[STAT_SPECTATOR] = 1;
/* layouts are independant in spectator */
cl->ps.stats[STAT_LAYOUTS] = 0;
if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
cl->ps.stats[STAT_LAYOUTS] |= 1;
if (cl->showinventory && cl->pers.health > 0)
cl->ps.stats[STAT_LAYOUTS] |= 2;
if (cl->chase_target && cl->chase_target->inuse)
cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
(cl->chase_target - g_edicts) - 1;
else
cl->ps.stats[STAT_CHASE] = 0;
}