/* * 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; }