From 6bf20c78f5b69d40bcc4931df93d29198435ab67 Mon Sep 17 00:00:00 2001 From: zakk Date: Fri, 26 Aug 2005 17:39:27 +0000 Subject: newlines fixed git-svn-id: svn://svn.icculus.org/quake3/trunk@6 edf5b092-35ff-0310-97b2-ce42778d08ea --- code/cgame/Conscript | 276 +-- code/cgame/cg_consolecmds.c | 1156 +++++----- code/cgame/cg_draw.c | 5318 +++++++++++++++++++++---------------------- code/cgame/cg_drawtools.c | 1648 +++++++------- code/cgame/cg_effects.c | 1436 ++++++------ code/cgame/cg_ents.c | 2074 ++++++++--------- code/cgame/cg_event.c | 2410 ++++++++++---------- code/cgame/cg_info.c | 594 ++--- code/cgame/cg_local.h | 3338 +++++++++++++-------------- code/cgame/cg_localents.c | 1772 +++++++------- code/cgame/cg_main.c | 3994 ++++++++++++++++---------------- code/cgame/cg_marks.c | 4548 ++++++++++++++++++------------------ code/cgame/cg_newdraw.c | 3704 +++++++++++++++--------------- code/cgame/cg_particles.c | 4036 ++++++++++++++++---------------- code/cgame/cg_players.c | 5238 +++++++++++++++++++++--------------------- code/cgame/cg_playerstate.c | 1052 ++++----- code/cgame/cg_predict.c | 1256 +++++----- code/cgame/cg_public.h | 476 ++-- code/cgame/cg_scoreboard.c | 1068 ++++----- code/cgame/cg_servercmds.c | 2196 +++++++++--------- code/cgame/cg_snapshot.c | 806 +++---- code/cgame/cg_syscalls.asm | 212 +- code/cgame/cg_syscalls.c | 890 ++++---- code/cgame/cg_view.c | 1752 +++++++------- code/cgame/cg_weapons.c | 4554 ++++++++++++++++++------------------ code/cgame/cgame.bat | 126 +- code/cgame/cgame.def | 6 +- code/cgame/cgame.plg | 248 +- code/cgame/cgame.q3asm | 52 +- code/cgame/cgame.sh | 72 +- code/cgame/cgame.vcproj | 2442 ++++++++++---------- code/cgame/cgame_ta.bat | 130 +- code/cgame/cgame_ta.q3asm | 56 +- code/cgame/cgame_ta.sh | 76 +- code/cgame/tr_types.h | 458 ++-- 35 files changed, 29735 insertions(+), 29735 deletions(-) (limited to 'code/cgame') diff --git a/code/cgame/Conscript b/code/cgame/Conscript index 887a2a1..0cac8ff 100755 --- a/code/cgame/Conscript +++ b/code/cgame/Conscript @@ -1,138 +1,138 @@ -# cgame building -# builds the cgame for vanilla Q3 and TA - -# there are slight differences between Q3 and TA build: -# -DMISSIONPACK -# TA has cg_newdraw.c and ../ui/ui_shared.c -# the config is passed in the imported variable TARGET_DIR - -# qvm building against native: -# only native has cg_syscalls.c -# only qvm has ../game/bg_lib.c -# qvm uses a custom cg_syscalls.asm with equ stubs - -Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); - -$env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => $CC, - CXX => $CXX, - LINK => $LINK, - ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, - CFLAGS => $BASE_CFLAGS . '-fPIC', - LDFLAGS => '-shared -ldl -lm' -); - -# for TA, use -DMISSIONPACK -%ta_env_hash = $env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; -$ta_env = new cons(%ta_env_hash); - -# qvm building -# we heavily customize the cons environment -$vm_env = new cons( - # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" - # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix - CPPPATH => '#cgame:#game:#q3_ui', - CC => 'q3lcc', - CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', - SUFOBJ => '.asm', - LINK => 'q3asm', - CFLAGS => '-DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g', - # need to know where to find the compiler tools - ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, -); - -# TA qvm building -%vm_ta_env_hash = $vm_env->copy( - CPPPATH => '#cgame:#game:#ui' - ); -$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; -$vm_ta_env = new cons(%vm_ta_env_hash); - -# the file with vmMain function MUST be the first one of the list -@FILES = qw( - cg_main.c - ../game/bg_misc.c - ../game/bg_pmove.c - ../game/bg_slidemove.c - ../game/q_math.c - ../game/q_shared.c - cg_consolecmds.c - cg_draw.c - cg_drawtools.c - cg_effects.c - cg_ents.c - cg_event.c - cg_info.c - cg_localents.c - cg_marks.c - cg_players.c - cg_playerstate.c - cg_predict.c - cg_scoreboard.c - cg_servercmds.c - cg_snapshot.c - cg_view.c - cg_weapons.c - ); -$FILESREF = \@FILES; - -# only in .so -# (VM uses a custom .asm with equ stubs) -@SO_FILES = qw( - cg_syscalls.c - ); -$SO_FILESREF = \@SO_FILES; - -# only for VM -@VM_FILES = qw( - ../game/bg_lib.c - cg_syscalls.asm - ); -$VM_FILESREF = \@VM_FILES; - -# common additionals for TA -@TA_FILES = qw( - cg_newdraw.c - ../ui/ui_shared.c - ); -$TA_FILESREF = \@TA_FILES; - -# FIXME CPU string -if ($TARGET_DIR eq 'Q3') -{ - if ($NO_SO eq 0) - { - Program $env 'cgamei386.so', @$FILESREF, @$SO_FILESREF; - Install $env $INSTALL_DIR, 'cgamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; - Program $vm_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF; - Install $vm_env $INSTALL_DIR . '/vm', 'cgame.qvm'; - } -} -else -{ - if ($NO_SO eq 0) - { - Program $ta_env 'cgamei386.so', - @$FILESREF, @$SO_FILESREF, @$TA_FILESREF; - Install $ta_env $INSTALL_DIR, 'cgamei386.so'; - } - if ($NO_VM eq 0) - { - Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; - Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; - Program $vm_ta_env 'cgame.qvm', - @$FILESREF, @$VM_FILESREF, @$TA_FILESREF; - Install $vm_ta_env $INSTALL_DIR . '/vm', 'cgame.qvm'; - } -} +# cgame building +# builds the cgame for vanilla Q3 and TA + +# there are slight differences between Q3 and TA build: +# -DMISSIONPACK +# TA has cg_newdraw.c and ../ui/ui_shared.c +# the config is passed in the imported variable TARGET_DIR + +# qvm building against native: +# only native has cg_syscalls.c +# only qvm has ../game/bg_lib.c +# qvm uses a custom cg_syscalls.asm with equ stubs + +Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); + +$env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => $CC, + CXX => $CXX, + LINK => $LINK, + ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, + CFLAGS => $BASE_CFLAGS . '-fPIC', + LDFLAGS => '-shared -ldl -lm' +); + +# for TA, use -DMISSIONPACK +%ta_env_hash = $env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; +$ta_env = new cons(%ta_env_hash); + +# qvm building +# we heavily customize the cons environment +$vm_env = new cons( + # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" + # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix + CPPPATH => '#cgame:#game:#q3_ui', + CC => 'q3lcc', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', + SUFOBJ => '.asm', + LINK => 'q3asm', + CFLAGS => '-DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g', + # need to know where to find the compiler tools + ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, +); + +# TA qvm building +%vm_ta_env_hash = $vm_env->copy( + CPPPATH => '#cgame:#game:#ui' + ); +$vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; +$vm_ta_env = new cons(%vm_ta_env_hash); + +# the file with vmMain function MUST be the first one of the list +@FILES = qw( + cg_main.c + ../game/bg_misc.c + ../game/bg_pmove.c + ../game/bg_slidemove.c + ../game/q_math.c + ../game/q_shared.c + cg_consolecmds.c + cg_draw.c + cg_drawtools.c + cg_effects.c + cg_ents.c + cg_event.c + cg_info.c + cg_localents.c + cg_marks.c + cg_players.c + cg_playerstate.c + cg_predict.c + cg_scoreboard.c + cg_servercmds.c + cg_snapshot.c + cg_view.c + cg_weapons.c + ); +$FILESREF = \@FILES; + +# only in .so +# (VM uses a custom .asm with equ stubs) +@SO_FILES = qw( + cg_syscalls.c + ); +$SO_FILESREF = \@SO_FILES; + +# only for VM +@VM_FILES = qw( + ../game/bg_lib.c + cg_syscalls.asm + ); +$VM_FILESREF = \@VM_FILES; + +# common additionals for TA +@TA_FILES = qw( + cg_newdraw.c + ../ui/ui_shared.c + ); +$TA_FILESREF = \@TA_FILES; + +# FIXME CPU string +if ($TARGET_DIR eq 'Q3') +{ + if ($NO_SO eq 0) + { + Program $env 'cgamei386.so', @$FILESREF, @$SO_FILESREF; + Install $env $INSTALL_DIR, 'cgamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; + Program $vm_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF; + Install $vm_env $INSTALL_DIR . '/vm', 'cgame.qvm'; + } +} +else +{ + if ($NO_SO eq 0) + { + Program $ta_env 'cgamei386.so', + @$FILESREF, @$SO_FILESREF, @$TA_FILESREF; + Install $ta_env $INSTALL_DIR, 'cgamei386.so'; + } + if ($NO_VM eq 0) + { + Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; + Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; + Program $vm_ta_env 'cgame.qvm', + @$FILESREF, @$VM_FILESREF, @$TA_FILESREF; + Install $vm_ta_env $INSTALL_DIR . '/vm', 'cgame.qvm'; + } +} diff --git a/code/cgame/cg_consolecmds.c b/code/cgame/cg_consolecmds.c index 6f13a22..584ebd4 100755 --- a/code/cgame/cg_consolecmds.c +++ b/code/cgame/cg_consolecmds.c @@ -1,578 +1,578 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_consolecmds.c -- text commands typed in at the local console, or -// executed by a key binding - -#include "cg_local.h" -#include "../ui/ui_shared.h" -#ifdef MISSIONPACK -extern menuDef_t *menuScoreboard; -#endif - - - -void CG_TargetCommand_f( void ) { - int targetNum; - char test[4]; - - targetNum = CG_CrosshairPlayer(); - if (!targetNum ) { - return; - } - - trap_Argv( 1, test, 4 ); - trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); -} - - - -/* -================= -CG_SizeUp_f - -Keybinding command -================= -*/ -static void CG_SizeUp_f (void) { - trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); -} - - -/* -================= -CG_SizeDown_f - -Keybinding command -================= -*/ -static void CG_SizeDown_f (void) { - trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); -} - - -/* -============= -CG_Viewpos_f - -Debugging command to print the current position -============= -*/ -static void CG_Viewpos_f (void) { - CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], - (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], - (int)cg.refdefViewAngles[YAW]); -} - - -static void CG_ScoresDown_f( void ) { - -#ifdef MISSIONPACK - CG_BuildSpectatorString(); -#endif - if ( cg.scoresRequestTime + 2000 < cg.time ) { - // the scores are more than two seconds out of data, - // so request new ones - cg.scoresRequestTime = cg.time; - trap_SendClientCommand( "score" ); - - // leave the current scores up if they were already - // displayed, but if this is the first hit, clear them out - if ( !cg.showScores ) { - cg.showScores = qtrue; - cg.numScores = 0; - } - } else { - // show the cached contents even if they just pressed if it - // is within two seconds - cg.showScores = qtrue; - } -} - -static void CG_ScoresUp_f( void ) { - if ( cg.showScores ) { - cg.showScores = qfalse; - cg.scoreFadeTime = cg.time; - } -} - -#ifdef MISSIONPACK -extern menuDef_t *menuScoreboard; -void Menu_Reset(); // FIXME: add to right include file - -static void CG_LoadHud_f( void) { - char buff[1024]; - const char *hudSet; - memset(buff, 0, sizeof(buff)); - - String_Init(); - Menu_Reset(); - - trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); - hudSet = buff; - if (hudSet[0] == '\0') { - hudSet = "ui/hud.txt"; - } - - CG_LoadMenus(hudSet); - menuScoreboard = NULL; -} - - -static void CG_scrollScoresDown_f( void) { - if (menuScoreboard && cg.scoreBoardShowing) { - Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); - Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); - Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); - } -} - - -static void CG_scrollScoresUp_f( void) { - if (menuScoreboard && cg.scoreBoardShowing) { - Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); - Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); - Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); - } -} - - -static void CG_spWin_f( void) { - trap_Cvar_Set("cg_cameraOrbit", "2"); - trap_Cvar_Set("cg_cameraOrbitDelay", "35"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - CG_AddBufferedSound(cgs.media.winnerSound); - //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); - CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); -} - -static void CG_spLose_f( void) { - trap_Cvar_Set("cg_cameraOrbit", "2"); - trap_Cvar_Set("cg_cameraOrbitDelay", "35"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - CG_AddBufferedSound(cgs.media.loserSound); - //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); - CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); -} - -#endif - -static void CG_TellTarget_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_TellAttacker_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_LastAttacker(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "tell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_VoiceTellTarget_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -static void CG_VoiceTellAttacker_f( void ) { - int clientNum; - char command[128]; - char message[128]; - - clientNum = CG_LastAttacker(); - if ( clientNum == -1 ) { - return; - } - - trap_Args( message, 128 ); - Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); - trap_SendClientCommand( command ); -} - -#ifdef MISSIONPACK -static void CG_NextTeamMember_f( void ) { - CG_SelectNextPlayer(); -} - -static void CG_PrevTeamMember_f( void ) { - CG_SelectPrevPlayer(); -} - -// ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last -// -static void CG_NextOrder_f( void ) { - clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; - if (ci) { - if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { - return; - } - } - if (cgs.currentOrder < TEAMTASK_CAMP) { - cgs.currentOrder++; - - if (cgs.currentOrder == TEAMTASK_RETRIEVE) { - if (!CG_OtherTeamHasFlag()) { - cgs.currentOrder++; - } - } - - if (cgs.currentOrder == TEAMTASK_ESCORT) { - if (!CG_YourTeamHasFlag()) { - cgs.currentOrder++; - } - } - - } else { - cgs.currentOrder = TEAMTASK_OFFENSE; - } - cgs.orderPending = qtrue; - cgs.orderTime = cg.time + 3000; -} - - -static void CG_ConfirmOrder_f (void ) { - trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); - trap_SendConsoleCommand("+button5; wait; -button5"); - if (cg.time < cgs.acceptOrderTime) { - trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); - cgs.acceptOrderTime = 0; - } -} - -static void CG_DenyOrder_f (void ) { - trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); - trap_SendConsoleCommand("+button6; wait; -button6"); - if (cg.time < cgs.acceptOrderTime) { - cgs.acceptOrderTime = 0; - } -} - -static void CG_TaskOffense_f (void ) { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); - } else { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); - } - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); -} - -static void CG_TaskDefense_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); -} - -static void CG_TaskPatrol_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); -} - -static void CG_TaskCamp_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); -} - -static void CG_TaskFollow_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); -} - -static void CG_TaskRetrieve_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); -} - -static void CG_TaskEscort_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); - trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); -} - -static void CG_TaskOwnFlag_f (void ) { - trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); -} - -static void CG_TauntKillInsult_f (void ) { - trap_SendConsoleCommand("cmd vsay kill_insult\n"); -} - -static void CG_TauntPraise_f (void ) { - trap_SendConsoleCommand("cmd vsay praise\n"); -} - -static void CG_TauntTaunt_f (void ) { - trap_SendConsoleCommand("cmd vtaunt\n"); -} - -static void CG_TauntDeathInsult_f (void ) { - trap_SendConsoleCommand("cmd vsay death_insult\n"); -} - -static void CG_TauntGauntlet_f (void ) { - trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); -} - -static void CG_TaskSuicide_f (void ) { - int clientNum; - char command[128]; - - clientNum = CG_CrosshairPlayer(); - if ( clientNum == -1 ) { - return; - } - - Com_sprintf( command, 128, "tell %i suicide", clientNum ); - trap_SendClientCommand( command ); -} - - - -/* -================== -CG_TeamMenu_f -================== -*/ -/* -static void CG_TeamMenu_f( void ) { - if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { - CG_EventHandling(CGAME_EVENT_NONE); - trap_Key_SetCatcher(0); - } else { - CG_EventHandling(CGAME_EVENT_TEAMMENU); - //trap_Key_SetCatcher(KEYCATCH_CGAME); - } -} -*/ - -/* -================== -CG_EditHud_f -================== -*/ -/* -static void CG_EditHud_f( void ) { - //cls.keyCatchers ^= KEYCATCH_CGAME; - //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); -} -*/ - -#endif - -/* -================== -CG_StartOrbit_f -================== -*/ - -static void CG_StartOrbit_f( void ) { - char var[MAX_TOKEN_CHARS]; - - trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); - if ( !atoi(var) ) { - return; - } - if (cg_cameraOrbit.value != 0) { - trap_Cvar_Set ("cg_cameraOrbit", "0"); - trap_Cvar_Set("cg_thirdPerson", "0"); - } else { - trap_Cvar_Set("cg_cameraOrbit", "5"); - trap_Cvar_Set("cg_thirdPerson", "1"); - trap_Cvar_Set("cg_thirdPersonAngle", "0"); - trap_Cvar_Set("cg_thirdPersonRange", "100"); - } -} - -/* -static void CG_Camera_f( void ) { - char name[1024]; - trap_Argv( 1, name, sizeof(name)); - if (trap_loadCamera(name)) { - cg.cameraMode = qtrue; - trap_startCamera(cg.time); - } else { - CG_Printf ("Unable to load camera %s\n",name); - } -} -*/ - - -typedef struct { - char *cmd; - void (*function)(void); -} consoleCommand_t; - -static consoleCommand_t commands[] = { - { "testgun", CG_TestGun_f }, - { "testmodel", CG_TestModel_f }, - { "nextframe", CG_TestModelNextFrame_f }, - { "prevframe", CG_TestModelPrevFrame_f }, - { "nextskin", CG_TestModelNextSkin_f }, - { "prevskin", CG_TestModelPrevSkin_f }, - { "viewpos", CG_Viewpos_f }, - { "+scores", CG_ScoresDown_f }, - { "-scores", CG_ScoresUp_f }, - { "+zoom", CG_ZoomDown_f }, - { "-zoom", CG_ZoomUp_f }, - { "sizeup", CG_SizeUp_f }, - { "sizedown", CG_SizeDown_f }, - { "weapnext", CG_NextWeapon_f }, - { "weapprev", CG_PrevWeapon_f }, - { "weapon", CG_Weapon_f }, - { "tell_target", CG_TellTarget_f }, - { "tell_attacker", CG_TellAttacker_f }, - { "vtell_target", CG_VoiceTellTarget_f }, - { "vtell_attacker", CG_VoiceTellAttacker_f }, - { "tcmd", CG_TargetCommand_f }, -#ifdef MISSIONPACK - { "loadhud", CG_LoadHud_f }, - { "nextTeamMember", CG_NextTeamMember_f }, - { "prevTeamMember", CG_PrevTeamMember_f }, - { "nextOrder", CG_NextOrder_f }, - { "confirmOrder", CG_ConfirmOrder_f }, - { "denyOrder", CG_DenyOrder_f }, - { "taskOffense", CG_TaskOffense_f }, - { "taskDefense", CG_TaskDefense_f }, - { "taskPatrol", CG_TaskPatrol_f }, - { "taskCamp", CG_TaskCamp_f }, - { "taskFollow", CG_TaskFollow_f }, - { "taskRetrieve", CG_TaskRetrieve_f }, - { "taskEscort", CG_TaskEscort_f }, - { "taskSuicide", CG_TaskSuicide_f }, - { "taskOwnFlag", CG_TaskOwnFlag_f }, - { "tauntKillInsult", CG_TauntKillInsult_f }, - { "tauntPraise", CG_TauntPraise_f }, - { "tauntTaunt", CG_TauntTaunt_f }, - { "tauntDeathInsult", CG_TauntDeathInsult_f }, - { "tauntGauntlet", CG_TauntGauntlet_f }, - { "spWin", CG_spWin_f }, - { "spLose", CG_spLose_f }, - { "scoresDown", CG_scrollScoresDown_f }, - { "scoresUp", CG_scrollScoresUp_f }, -#endif - { "startOrbit", CG_StartOrbit_f }, - //{ "camera", CG_Camera_f }, - { "loaddeferred", CG_LoadDeferredPlayers } -}; - - -/* -================= -CG_ConsoleCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() -================= -*/ -qboolean CG_ConsoleCommand( void ) { - const char *cmd; - int i; - - cmd = CG_Argv(0); - - for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { - if ( !Q_stricmp( cmd, commands[i].cmd ) ) { - commands[i].function(); - return qtrue; - } - } - - return qfalse; -} - - -/* -================= -CG_InitConsoleCommands - -Let the client system know about all of our commands -so it can perform tab completion -================= -*/ -void CG_InitConsoleCommands( void ) { - int i; - - for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { - trap_AddCommand( commands[i].cmd ); - } - - // - // the game server will interpret these commands, which will be automatically - // forwarded to the server after they are not recognized locally - // - trap_AddCommand ("kill"); - trap_AddCommand ("say"); - trap_AddCommand ("say_team"); - trap_AddCommand ("tell"); - trap_AddCommand ("vsay"); - trap_AddCommand ("vsay_team"); - trap_AddCommand ("vtell"); - trap_AddCommand ("vtaunt"); - trap_AddCommand ("vosay"); - trap_AddCommand ("vosay_team"); - trap_AddCommand ("votell"); - trap_AddCommand ("give"); - trap_AddCommand ("god"); - trap_AddCommand ("notarget"); - trap_AddCommand ("noclip"); - trap_AddCommand ("team"); - trap_AddCommand ("follow"); - trap_AddCommand ("levelshot"); - trap_AddCommand ("addbot"); - trap_AddCommand ("setviewpos"); - trap_AddCommand ("callvote"); - trap_AddCommand ("vote"); - trap_AddCommand ("callteamvote"); - trap_AddCommand ("teamvote"); - trap_AddCommand ("stats"); - trap_AddCommand ("teamtask"); - trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" +#include "../ui/ui_shared.h" +#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +#endif + + + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if (!targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f (void) { + CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdefViewAngles[YAW]); +} + + +static void CG_ScoresDown_f( void ) { + +#ifdef MISSIONPACK + CG_BuildSpectatorString(); +#endif + if ( cg.scoresRequestTime + 2000 < cg.time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) { + cg.showScores = qtrue; + cg.numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + if ( cg.showScores ) { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + +#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +static void CG_LoadHud_f( void) { + char buff[1024]; + const char *hudSet; + memset(buff, 0, sizeof(buff)); + + String_Init(); + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); + menuScoreboard = NULL; +} + + +static void CG_scrollScoresDown_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); + } +} + + +static void CG_scrollScoresUp_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); + } +} + + +static void CG_spWin_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.winnerSound); + //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); +} + +static void CG_spLose_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.loserSound); + //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); +} + +#endif + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +#ifdef MISSIONPACK +static void CG_NextTeamMember_f( void ) { + CG_SelectNextPlayer(); +} + +static void CG_PrevTeamMember_f( void ) { + CG_SelectPrevPlayer(); +} + +// ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last +// +static void CG_NextOrder_f( void ) { + clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; + if (ci) { + if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { + return; + } + } + if (cgs.currentOrder < TEAMTASK_CAMP) { + cgs.currentOrder++; + + if (cgs.currentOrder == TEAMTASK_RETRIEVE) { + if (!CG_OtherTeamHasFlag()) { + cgs.currentOrder++; + } + } + + if (cgs.currentOrder == TEAMTASK_ESCORT) { + if (!CG_YourTeamHasFlag()) { + cgs.currentOrder++; + } + } + + } else { + cgs.currentOrder = TEAMTASK_OFFENSE; + } + cgs.orderPending = qtrue; + cgs.orderTime = cg.time + 3000; +} + + +static void CG_ConfirmOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); + trap_SendConsoleCommand("+button5; wait; -button5"); + if (cg.time < cgs.acceptOrderTime) { + trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); + cgs.acceptOrderTime = 0; + } +} + +static void CG_DenyOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); + trap_SendConsoleCommand("+button6; wait; -button6"); + if (cg.time < cgs.acceptOrderTime) { + cgs.acceptOrderTime = 0; + } +} + +static void CG_TaskOffense_f (void ) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); + } else { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); + } + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); +} + +static void CG_TaskDefense_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); +} + +static void CG_TaskPatrol_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); +} + +static void CG_TaskCamp_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); +} + +static void CG_TaskFollow_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); +} + +static void CG_TaskRetrieve_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); +} + +static void CG_TaskEscort_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); +} + +static void CG_TaskOwnFlag_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); +} + +static void CG_TauntKillInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_insult\n"); +} + +static void CG_TauntPraise_f (void ) { + trap_SendConsoleCommand("cmd vsay praise\n"); +} + +static void CG_TauntTaunt_f (void ) { + trap_SendConsoleCommand("cmd vtaunt\n"); +} + +static void CG_TauntDeathInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay death_insult\n"); +} + +static void CG_TauntGauntlet_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); +} + +static void CG_TaskSuicide_f (void ) { + int clientNum; + char command[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + Com_sprintf( command, 128, "tell %i suicide", clientNum ); + trap_SendClientCommand( command ); +} + + + +/* +================== +CG_TeamMenu_f +================== +*/ +/* +static void CG_TeamMenu_f( void ) { + if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + } else { + CG_EventHandling(CGAME_EVENT_TEAMMENU); + //trap_Key_SetCatcher(KEYCATCH_CGAME); + } +} +*/ + +/* +================== +CG_EditHud_f +================== +*/ +/* +static void CG_EditHud_f( void ) { + //cls.keyCatchers ^= KEYCATCH_CGAME; + //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); +} +*/ + +#endif + +/* +================== +CG_StartOrbit_f +================== +*/ + +static void CG_StartOrbit_f( void ) { + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); + if ( !atoi(var) ) { + return; + } + if (cg_cameraOrbit.value != 0) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + } else { + trap_Cvar_Set("cg_cameraOrbit", "5"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + } +} + +/* +static void CG_Camera_f( void ) { + char name[1024]; + trap_Argv( 1, name, sizeof(name)); + if (trap_loadCamera(name)) { + cg.cameraMode = qtrue; + trap_startCamera(cg.time); + } else { + CG_Printf ("Unable to load camera %s\n",name); + } +} +*/ + + +typedef struct { + char *cmd; + void (*function)(void); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "+zoom", CG_ZoomDown_f }, + { "-zoom", CG_ZoomUp_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "vtell_target", CG_VoiceTellTarget_f }, + { "vtell_attacker", CG_VoiceTellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, +#ifdef MISSIONPACK + { "loadhud", CG_LoadHud_f }, + { "nextTeamMember", CG_NextTeamMember_f }, + { "prevTeamMember", CG_PrevTeamMember_f }, + { "nextOrder", CG_NextOrder_f }, + { "confirmOrder", CG_ConfirmOrder_f }, + { "denyOrder", CG_DenyOrder_f }, + { "taskOffense", CG_TaskOffense_f }, + { "taskDefense", CG_TaskDefense_f }, + { "taskPatrol", CG_TaskPatrol_f }, + { "taskCamp", CG_TaskCamp_f }, + { "taskFollow", CG_TaskFollow_f }, + { "taskRetrieve", CG_TaskRetrieve_f }, + { "taskEscort", CG_TaskEscort_f }, + { "taskSuicide", CG_TaskSuicide_f }, + { "taskOwnFlag", CG_TaskOwnFlag_f }, + { "tauntKillInsult", CG_TauntKillInsult_f }, + { "tauntPraise", CG_TauntPraise_f }, + { "tauntTaunt", CG_TauntTaunt_f }, + { "tauntDeathInsult", CG_TauntDeathInsult_f }, + { "tauntGauntlet", CG_TauntGauntlet_f }, + { "spWin", CG_spWin_f }, + { "spLose", CG_spLose_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, +#endif + { "startOrbit", CG_StartOrbit_f }, + //{ "camera", CG_Camera_f }, + { "loaddeferred", CG_LoadDeferredPlayers } +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) { + const char *cmd; + int i; + + cmd = CG_Argv(0); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) { + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + trap_AddCommand ("vsay"); + trap_AddCommand ("vsay_team"); + trap_AddCommand ("vtell"); + trap_AddCommand ("vtaunt"); + trap_AddCommand ("vosay"); + trap_AddCommand ("vosay_team"); + trap_AddCommand ("votell"); + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); + trap_AddCommand ("addbot"); + trap_AddCommand ("setviewpos"); + trap_AddCommand ("callvote"); + trap_AddCommand ("vote"); + trap_AddCommand ("callteamvote"); + trap_AddCommand ("teamvote"); + trap_AddCommand ("stats"); + trap_AddCommand ("teamtask"); + trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c index e29c10f..5575aaf 100755 --- a/code/cgame/cg_draw.c +++ b/code/cgame/cg_draw.c @@ -1,2659 +1,2659 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_draw.c -- draw all of the graphical elements during -// active (after loading) gameplay - -#include "cg_local.h" - -#ifdef MISSIONPACK -#include "../ui/ui_shared.h" - -// used for scoreboard -extern displayContextDef_t cgDC; -menuDef_t *menuScoreboard = NULL; -#else -int drawTeamOverlayModificationCount = -1; -#endif - -int sortedTeamPlayers[TEAM_MAXOVERLAY]; -int numSortedTeamPlayers; - -char systemChat[256]; -char teamChat1[256]; -char teamChat2[256]; - -#ifdef MISSIONPACK - -int CG_Text_Width(const char *text, float scale, int limit) { - int count,len; - float out; - glyphInfo_t *glyph; - float useScale; -// FIXME: see ui_main.c, same problem -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - out = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - out += glyph->xSkip; - s++; - count++; - } - } - } - return out * useScale; -} - -int CG_Text_Height(const char *text, float scale, int limit) { - int len, count; - float max; - glyphInfo_t *glyph; - float useScale; -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - max = 0; - if (text) { - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - if ( Q_IsColorString(s) ) { - s += 2; - continue; - } else { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - if (max < glyph->height) { - max = glyph->height; - } - s++; - count++; - } - } - } - return max * useScale; -} - -void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { - float w, h; - w = width * scale; - h = height * scale; - CG_AdjustFrom640( &x, &y, &w, &h ); - trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); -} - -void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - if (text) { -// TTimo: FIXME -// const unsigned char *s = text; - const char *s = text; - trap_R_SetColor( color ); - memcpy(&newColor[0], &color[0], sizeof(vec4_t)); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; - //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { - int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; - colorBlack[3] = newColor[3]; - trap_R_SetColor( colorBlack ); - CG_Text_PaintChar(x + ofs, y - yadj + ofs, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - colorBlack[3] = 1.0; - trap_R_SetColor( newColor ); - } - CG_Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); - x += (glyph->xSkip * useScale) + adjust; - s++; - count++; - } - } - trap_R_SetColor( NULL ); - } -} - - -#endif - -/* -============== -CG_DrawField - -Draws large numbers for status bar and powerups -============== -*/ -#ifndef MISSIONPACK -static void CG_DrawField (int x, int y, int width, int value) { - char num[16], *ptr; - int l; - int frame; - - if ( width < 1 ) { - return; - } - - // draw number string - if ( width > 5 ) { - width = 5; - } - - switch ( width ) { - case 1: - value = value > 9 ? 9 : value; - value = value < 0 ? 0 : value; - break; - case 2: - value = value > 99 ? 99 : value; - value = value < -9 ? -9 : value; - break; - case 3: - value = value > 999 ? 999 : value; - value = value < -99 ? -99 : value; - break; - case 4: - value = value > 9999 ? 9999 : value; - value = value < -999 ? -999 : value; - break; - } - - Com_sprintf (num, sizeof(num), "%i", value); - l = strlen(num); - if (l > width) - l = width; - x += 2 + CHAR_WIDTH*(width - l); - - ptr = num; - while (*ptr && l) - { - if (*ptr == '-') - frame = STAT_MINUS; - else - frame = *ptr -'0'; - - CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); - x += CHAR_WIDTH; - ptr++; - l--; - } -} -#endif // MISSIONPACK - -/* -================ -CG_Draw3DModel - -================ -*/ -void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { - refdef_t refdef; - refEntity_t ent; - - if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { - return; - } - - CG_AdjustFrom640( &x, &y, &w, &h ); - - memset( &refdef, 0, sizeof( refdef ) ); - - memset( &ent, 0, sizeof( ent ) ); - AnglesToAxis( angles, ent.axis ); - VectorCopy( origin, ent.origin ); - ent.hModel = model; - ent.customSkin = skin; - ent.renderfx = RF_NOSHADOW; // no stencil shadows - - refdef.rdflags = RDF_NOWORLDMODEL; - - AxisClear( refdef.viewaxis ); - - refdef.fov_x = 30; - refdef.fov_y = 30; - - refdef.x = x; - refdef.y = y; - refdef.width = w; - refdef.height = h; - - refdef.time = cg.time; - - trap_R_ClearScene(); - trap_R_AddRefEntityToScene( &ent ); - trap_R_RenderScene( &refdef ); -} - -/* -================ -CG_DrawHead - -Used for both the status bar and the scoreboard -================ -*/ -void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { - clipHandle_t cm; - clientInfo_t *ci; - float len; - vec3_t origin; - vec3_t mins, maxs; - - ci = &cgs.clientinfo[ clientNum ]; - - if ( cg_draw3dIcons.integer ) { - cm = ci->headModel; - if ( !cm ) { - return; - } - - // offset the origin y and z to center the head - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the head nearly fills the box - // assume heads are taller than wide - len = 0.7 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - // allow per-model tweaking - VectorAdd( origin, ci->headOffset, origin ); - - CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); - } else if ( cg_drawIcons.integer ) { - CG_DrawPic( x, y, w, h, ci->modelIcon ); - } - - // if they are deferred, draw a cross out - if ( ci->deferred ) { - CG_DrawPic( x, y, w, h, cgs.media.deferShader ); - } -} - -/* -================ -CG_DrawFlagModel - -Used for both the status bar and the scoreboard -================ -*/ -void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { - qhandle_t cm; - float len; - vec3_t origin, angles; - vec3_t mins, maxs; - qhandle_t handle; - - if ( !force2D && cg_draw3dIcons.integer ) { - - VectorClear( angles ); - - cm = cgs.media.redFlagModel; - - // offset the origin y and z to center the flag - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the flag nearly fills the box - // assume heads are taller than wide - len = 0.5 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - angles[YAW] = 60 * sin( cg.time / 2000.0 );; - - if( team == TEAM_RED ) { - handle = cgs.media.redFlagModel; - } else if( team == TEAM_BLUE ) { - handle = cgs.media.blueFlagModel; - } else if( team == TEAM_FREE ) { - handle = cgs.media.neutralFlagModel; - } else { - return; - } - CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); - } else if ( cg_drawIcons.integer ) { - gitem_t *item; - - if( team == TEAM_RED ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - } else if( team == TEAM_BLUE ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - } else if( team == TEAM_FREE ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - } else { - return; - } - if (item) { - CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); - } - } -} - -/* -================ -CG_DrawStatusBarHead - -================ -*/ -#ifndef MISSIONPACK - -static void CG_DrawStatusBarHead( float x ) { - vec3_t angles; - float size, stretch; - float frac; - - VectorClear( angles ); - - if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { - frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; - size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); - - stretch = size - ICON_SIZE * 1.25; - // kick in the direction of damage - x -= stretch * 0.5 + cg.damageX * stretch * 0.5; - - cg.headStartYaw = 180 + cg.damageX * 45; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - - cg.headStartTime = cg.time; - cg.headEndTime = cg.time + 100 + random() * 2000; - } else { - if ( cg.time >= cg.headEndTime ) { - // select a new head angle - cg.headStartYaw = cg.headEndYaw; - cg.headStartPitch = cg.headEndPitch; - cg.headStartTime = cg.headEndTime; - cg.headEndTime = cg.time + 100 + random() * 2000; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - } - - size = ICON_SIZE * 1.25; - } - - // if the server was frozen for a while we may have a bad head start time - if ( cg.headStartTime > cg.time ) { - cg.headStartTime = cg.time; - } - - frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); - frac = frac * frac * ( 3 - 2 * frac ); - angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; - angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; - - CG_DrawHead( x, 480 - size, size, size, - cg.snap->ps.clientNum, angles ); -} -#endif // MISSIONPACK - -/* -================ -CG_DrawStatusBarFlag - -================ -*/ -#ifndef MISSIONPACK -static void CG_DrawStatusBarFlag( float x, int team ) { - CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); -} -#endif // MISSIONPACK - -/* -================ -CG_DrawTeamBackground - -================ -*/ -void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) -{ - vec4_t hcolor; - - hcolor[3] = alpha; - if ( team == TEAM_RED ) { - hcolor[0] = 1; - hcolor[1] = 0; - hcolor[2] = 0; - } else if ( team == TEAM_BLUE ) { - hcolor[0] = 0; - hcolor[1] = 0; - hcolor[2] = 1; - } else { - return; - } - trap_R_SetColor( hcolor ); - CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); -} - -/* -================ -CG_DrawStatusBar - -================ -*/ -#ifndef MISSIONPACK -static void CG_DrawStatusBar( void ) { - int color; - centity_t *cent; - playerState_t *ps; - int value; - vec4_t hcolor; - vec3_t angles; - vec3_t origin; -#ifdef MISSIONPACK - qhandle_t handle; -#endif - static float colors[4][4] = { -// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; - { 1.0f, 0.69f, 0.0f, 1.0f }, // normal - { 1.0f, 0.2f, 0.2f, 1.0f }, // low health - { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing - { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - // draw the team background - CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - VectorClear( angles ); - - // draw any 3D icons first, so the changes back to 2D are minimized - if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { - origin[0] = 70; - origin[1] = 0; - origin[2] = 0; - angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); - CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, - cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); - } - - CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); - - if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); - } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); - } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { - CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); - } - - if ( ps->stats[ STAT_ARMOR ] ) { - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, - cgs.media.armorModel, 0, origin, angles ); - } -#ifdef MISSIONPACK - if( cgs.gametype == GT_HARVESTER ) { - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeModel; - } else { - handle = cgs.media.blueCubeModel; - } - CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); - } -#endif - // - // ammo - // - if ( cent->currentState.weapon ) { - value = ps->ammo[cent->currentState.weapon]; - if ( value > -1 ) { - if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING - && cg.predictedPlayerState.weaponTime > 100 ) { - // draw as dark grey when reloading - color = 2; // dark grey - } else { - if ( value >= 0 ) { - color = 0; // green - } else { - color = 1; // red - } - } - trap_R_SetColor( colors[color] ); - - CG_DrawField (0, 432, 3, value); - trap_R_SetColor( NULL ); - - // if we didn't draw a 3D icon, draw a 2D icon for ammo - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - qhandle_t icon; - - icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; - if ( icon ) { - CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); - } - } - } - } - - // - // health - // - value = ps->stats[STAT_HEALTH]; - if ( value > 100 ) { - trap_R_SetColor( colors[3] ); // white - } else if (value > 25) { - trap_R_SetColor( colors[0] ); // green - } else if (value > 0) { - color = (cg.time >> 8) & 1; // flash - trap_R_SetColor( colors[color] ); - } else { - trap_R_SetColor( colors[1] ); // red - } - - // stretch the health up when taking damage - CG_DrawField ( 185, 432, 3, value); - CG_ColorForHealth( hcolor ); - trap_R_SetColor( hcolor ); - - - // - // armor - // - value = ps->stats[STAT_ARMOR]; - if (value > 0 ) { - trap_R_SetColor( colors[0] ); - CG_DrawField (370, 432, 3, value); - trap_R_SetColor( NULL ); - // if we didn't draw a 3D icon, draw a 2D icon for armor - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); - } - - } -#ifdef MISSIONPACK - // - // cubes - // - if( cgs.gametype == GT_HARVESTER ) { - value = ps->generic1; - if( value > 99 ) { - value = 99; - } - trap_R_SetColor( colors[0] ); - CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); - trap_R_SetColor( NULL ); - // if we didn't draw a 3D icon, draw a 2D icon for armor - if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeIcon; - } else { - handle = cgs.media.blueCubeIcon; - } - CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); - } - } -#endif -} -#endif - -/* -=========================================================================================== - - UPPER RIGHT CORNER - -=========================================================================================== -*/ - -/* -================ -CG_DrawAttacker - -================ -*/ -static float CG_DrawAttacker( float y ) { - int t; - float size; - vec3_t angles; - const char *info; - const char *name; - int clientNum; - - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return y; - } - - if ( !cg.attackerTime ) { - return y; - } - - clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { - return y; - } - - t = cg.time - cg.attackerTime; - if ( t > ATTACKER_HEAD_TIME ) { - cg.attackerTime = 0; - return y; - } - - size = ICON_SIZE * 1.25; - - angles[PITCH] = 0; - angles[YAW] = 180; - angles[ROLL] = 0; - CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); - - info = CG_ConfigString( CS_PLAYERS + clientNum ); - name = Info_ValueForKey( info, "n" ); - y += size; - CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); - - return y + BIGCHAR_HEIGHT + 2; -} - -/* -================== -CG_DrawSnapshot -================== -*/ -static float CG_DrawSnapshot( float y ) { - char *s; - int w; - - s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, - cg.latestSnapshotNum, cgs.serverCommandSequence ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - - return y + BIGCHAR_HEIGHT + 4; -} - -/* -================== -CG_DrawFPS -================== -*/ -#define FPS_FRAMES 4 -static float CG_DrawFPS( float y ) { - char *s; - int w; - static int previousTimes[FPS_FRAMES]; - static int index; - int i, total; - int fps; - static int previous; - int t, frameTime; - - // don't use serverTime, because that will be drifting to - // correct for internet lag changes, timescales, timedemos, etc - t = trap_Milliseconds(); - frameTime = t - previous; - previous = t; - - previousTimes[index % FPS_FRAMES] = frameTime; - index++; - if ( index > FPS_FRAMES ) { - // average multiple frames together to smooth changes out a bit - total = 0; - for ( i = 0 ; i < FPS_FRAMES ; i++ ) { - total += previousTimes[i]; - } - if ( !total ) { - total = 1; - } - fps = 1000 * FPS_FRAMES / total; - - s = va( "%ifps", fps ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - } - - return y + BIGCHAR_HEIGHT + 4; -} - -/* -================= -CG_DrawTimer -================= -*/ -static float CG_DrawTimer( float y ) { - char *s; - int w; - int mins, seconds, tens; - int msec; - - msec = cg.time - cgs.levelStartTime; - - seconds = msec / 1000; - mins = seconds / 60; - seconds -= mins * 60; - tens = seconds / 10; - seconds -= tens * 10; - - s = va( "%i:%i%i", mins, tens, seconds ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - - CG_DrawBigString( 635 - w, y + 2, s, 1.0F); - - return y + BIGCHAR_HEIGHT + 4; -} - - -/* -================= -CG_DrawTeamOverlay -================= -*/ - -static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { - int x, w, h, xx; - int i, j, len; - const char *p; - vec4_t hcolor; - int pwidth, lwidth; - int plyrs; - char st[16]; - clientInfo_t *ci; - gitem_t *item; - int ret_y, count; - - if ( !cg_drawTeamOverlay.integer ) { - return y; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { - return y; // Not on any team - } - - plyrs = 0; - - // max player name width - pwidth = 0; - count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - plyrs++; - len = CG_DrawStrlen(ci->name); - if (len > pwidth) - pwidth = len; - } - } - - if (!plyrs) - return y; - - if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) - pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; - - // max location name width - lwidth = 0; - for (i = 1; i < MAX_LOCATIONS; i++) { - p = CG_ConfigString(CS_LOCATIONS + i); - if (p && *p) { - len = CG_DrawStrlen(p); - if (len > lwidth) - lwidth = len; - } - } - - if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) - lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; - - w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; - - if ( right ) - x = 640 - w; - else - x = 0; - - h = plyrs * TINYCHAR_HEIGHT; - - if ( upper ) { - ret_y = y + h; - } else { - y -= h; - ret_y = y; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - hcolor[0] = 1.0f; - hcolor[1] = 0.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) - hcolor[0] = 0.0f; - hcolor[1] = 0.0f; - hcolor[2] = 1.0f; - hcolor[3] = 0.33f; - } - trap_R_SetColor( hcolor ); - CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); - - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - - hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; - - xx = x + TINYCHAR_WIDTH; - - CG_DrawStringExt( xx, y, - ci->name, hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); - - if (lwidth) { - p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) - p = "unknown"; - len = CG_DrawStrlen(p); - if (len > lwidth) - len = lwidth; - -// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + -// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); - xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; - CG_DrawStringExt( xx, y, - p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - TEAM_OVERLAY_MAXLOCATION_WIDTH); - } - - CG_GetColorForHealth( ci->health, ci->armor, hcolor ); - - Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); - - xx = x + TINYCHAR_WIDTH * 3 + - TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; - - CG_DrawStringExt( xx, y, - st, hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); - - // draw weapon icon - xx += TINYCHAR_WIDTH * 3; - - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - cgs.media.deferShader ); - } - - // Draw powerup icons - if (right) { - xx = x; - } else { - xx = x + w - TINYCHAR_WIDTH; - } - for (j = 0; j <= PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - - item = BG_FindItemForPowerup( j ); - - if (item) { - CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, - trap_R_RegisterShader( item->icon ) ); - if (right) { - xx -= TINYCHAR_WIDTH; - } else { - xx += TINYCHAR_WIDTH; - } - } - } - } - - y += TINYCHAR_HEIGHT; - } - } - - return ret_y; -//#endif -} - - -/* -===================== -CG_DrawUpperRight - -===================== -*/ -static void CG_DrawUpperRight( void ) { - float y; - - y = 0; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { - y = CG_DrawTeamOverlay( y, qtrue, qtrue ); - } - if ( cg_drawSnapshot.integer ) { - y = CG_DrawSnapshot( y ); - } - if ( cg_drawFPS.integer ) { - y = CG_DrawFPS( y ); - } - if ( cg_drawTimer.integer ) { - y = CG_DrawTimer( y ); - } - if ( cg_drawAttacker.integer ) { - y = CG_DrawAttacker( y ); - } - -} - -/* -=========================================================================================== - - LOWER RIGHT CORNER - -=========================================================================================== -*/ - -/* -================= -CG_DrawScores - -Draw the small two score display -================= -*/ -#ifndef MISSIONPACK -static float CG_DrawScores( float y ) { - const char *s; - int s1, s2, score; - int x, w; - int v; - vec4_t color; - float y1; - gitem_t *item; - - s1 = cgs.scores1; - s2 = cgs.scores2; - - y -= BIGCHAR_HEIGHT + 8; - - y1 = y; - - // draw from the right side to left - if ( cgs.gametype >= GT_TEAM ) { - x = 640; - color[0] = 0.0f; - color[1] = 0.0f; - color[2] = 1.0f; - color[3] = 0.33f; - s = va( "%2i", s2 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - - if ( cgs.gametype == GT_CTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); - } - } - } - color[0] = 1.0f; - color[1] = 0.0f; - color[2] = 0.0f; - color[3] = 0.33f; - s = va( "%2i", s1 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - - if ( cgs.gametype == GT_CTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_REDFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); - } - } - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF ) { - // Display flag status - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - - if (item) { - y1 = y - BIGCHAR_HEIGHT - 8; - if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { - CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); - } - } - } -#endif - if ( cgs.gametype >= GT_CTF ) { - v = cgs.capturelimit; - } else { - v = cgs.fraglimit; - } - if ( v ) { - s = va( "%2i", v ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - } else { - qboolean spectator; - - x = 640; - score = cg.snap->ps.persistant[PERS_SCORE]; - spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); - - // always show your score in the second box if not in first place - if ( s1 != score ) { - s2 = score; - } - if ( s2 != SCORE_NOT_PRESENT ) { - s = va( "%2i", s2 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - if ( !spectator && score == s2 && score != s1 ) { - color[0] = 1.0f; - color[1] = 0.0f; - color[2] = 0.0f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } else { - color[0] = 0.5f; - color[1] = 0.5f; - color[2] = 0.5f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - // first place - if ( s1 != SCORE_NOT_PRESENT ) { - s = va( "%2i", s1 ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - if ( !spectator && score == s1 ) { - color[0] = 0.0f; - color[1] = 0.0f; - color[2] = 1.0f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); - } else { - color[0] = 0.5f; - color[1] = 0.5f; - color[2] = 0.5f; - color[3] = 0.33f; - CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); - } - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - if ( cgs.fraglimit ) { - s = va( "%2i", cgs.fraglimit ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; - x -= w; - CG_DrawBigString( x + 4, y, s, 1.0F); - } - - } - - return y1 - 8; -} -#endif // MISSIONPACK - -/* -================ -CG_DrawPowerups -================ -*/ -#ifndef MISSIONPACK -static float CG_DrawPowerups( float y ) { - int sorted[MAX_POWERUPS]; - int sortedTime[MAX_POWERUPS]; - int i, j, k; - int active; - playerState_t *ps; - int t; - gitem_t *item; - int x; - int color; - float size; - float f; - static float colors[2][4] = { - { 0.2f, 1.0f, 0.2f, 1.0f } , - { 1.0f, 0.2f, 0.2f, 1.0f } - }; - - ps = &cg.snap->ps; - - if ( ps->stats[STAT_HEALTH] <= 0 ) { - return y; - } - - // sort the list by time remaining - active = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( !ps->powerups[ i ] ) { - continue; - } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) - // This is true of the CTF flags - if ( t < 0 || t > 999000) { - continue; - } - - // insert into the list - for ( j = 0 ; j < active ; j++ ) { - if ( sortedTime[j] >= t ) { - for ( k = active - 1 ; k >= j ; k-- ) { - sorted[k+1] = sorted[k]; - sortedTime[k+1] = sortedTime[k]; - } - break; - } - } - sorted[j] = i; - sortedTime[j] = t; - active++; - } - - // draw the icons and timers - x = 640 - ICON_SIZE - CHAR_WIDTH * 2; - for ( i = 0 ; i < active ; i++ ) { - item = BG_FindItemForPowerup( sorted[i] ); - - if (item) { - - color = 1; - - y -= ICON_SIZE; - - trap_R_SetColor( colors[color] ); - CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); - - t = ps->powerups[ sorted[i] ]; - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - trap_R_SetColor( NULL ); - } else { - vec4_t modulate; - - f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; - f -= (int)f; - modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; - trap_R_SetColor( modulate ); - } - - if ( cg.powerupActive == sorted[i] && - cg.time - cg.powerupTime < PULSE_TIME ) { - f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); - size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); - } else { - size = ICON_SIZE; - } - - CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, - size, size, trap_R_RegisterShader( item->icon ) ); - } - } - trap_R_SetColor( NULL ); - - return y; -} -#endif // MISSIONPACK - -/* -===================== -CG_DrawLowerRight - -===================== -*/ -#ifndef MISSIONPACK -static void CG_DrawLowerRight( void ) { - float y; - - y = 480 - ICON_SIZE; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { - y = CG_DrawTeamOverlay( y, qtrue, qfalse ); - } - - y = CG_DrawScores( y ); - y = CG_DrawPowerups( y ); -} -#endif // MISSIONPACK - -/* -=================== -CG_DrawPickupItem -=================== -*/ -#ifndef MISSIONPACK -static int CG_DrawPickupItem( int y ) { - int value; - float *fadeColor; - - if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { - return y; - } - - y -= ICON_SIZE; - - value = cg.itemPickup; - if ( value ) { - fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); - if ( fadeColor ) { - CG_RegisterItemVisuals( value ); - trap_R_SetColor( fadeColor ); - CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); - trap_R_SetColor( NULL ); - } - } - - return y; -} -#endif // MISSIONPACK - -/* -===================== -CG_DrawLowerLeft - -===================== -*/ -#ifndef MISSIONPACK -static void CG_DrawLowerLeft( void ) { - float y; - - y = 480 - ICON_SIZE; - - if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { - y = CG_DrawTeamOverlay( y, qfalse, qfalse ); - } - - - y = CG_DrawPickupItem( y ); -} -#endif // MISSIONPACK - - -//=========================================================================================== - -/* -================= -CG_DrawTeamInfo -================= -*/ -#ifndef MISSIONPACK -static void CG_DrawTeamInfo( void ) { - int w, h; - int i, len; - vec4_t hcolor; - int chatHeight; - -#define CHATLOC_Y 420 // bottom end -#define CHATLOC_X 0 - - if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) - chatHeight = cg_teamChatHeight.integer; - else - chatHeight = TEAMCHAT_HEIGHT; - if (chatHeight <= 0) - return; // disabled - - if (cgs.teamLastChatPos != cgs.teamChatPos) { - if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { - cgs.teamLastChatPos++; - } - - h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; - - w = 0; - - for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { - len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); - if (len > w) - w = len; - } - w *= TINYCHAR_WIDTH; - w += TINYCHAR_WIDTH * 2; - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { - hcolor[0] = 1.0f; - hcolor[1] = 0.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - hcolor[0] = 0.0f; - hcolor[1] = 0.0f; - hcolor[2] = 1.0f; - hcolor[3] = 0.33f; - } else { - hcolor[0] = 0.0f; - hcolor[1] = 1.0f; - hcolor[2] = 0.0f; - hcolor[3] = 0.33f; - } - - trap_R_SetColor( hcolor ); - CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); - trap_R_SetColor( NULL ); - - hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; - hcolor[3] = 1.0f; - - for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { - CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, - CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, - cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, - TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); - } - } -} -#endif // MISSIONPACK - -/* -=================== -CG_DrawHoldableItem -=================== -*/ -#ifndef MISSIONPACK -static void CG_DrawHoldableItem( void ) { - int value; - - value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - } - -} -#endif // MISSIONPACK - -#ifdef MISSIONPACK -/* -=================== -CG_DrawPersistantPowerup -=================== -*/ -#if 0 // sos001208 - DEAD -static void CG_DrawPersistantPowerup( void ) { - int value; - - value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); - } -} -#endif -#endif // MISSIONPACK - - -/* -=================== -CG_DrawReward -=================== -*/ -static void CG_DrawReward( void ) { - float *color; - int i, count; - float x, y; - char buf[32]; - - if ( !cg_drawRewards.integer ) { - return; - } - - color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); - if ( !color ) { - if (cg.rewardStack > 0) { - for(i = 0; i < cg.rewardStack; i++) { - cg.rewardSound[i] = cg.rewardSound[i+1]; - cg.rewardShader[i] = cg.rewardShader[i+1]; - cg.rewardCount[i] = cg.rewardCount[i+1]; - } - cg.rewardTime = cg.time; - cg.rewardStack--; - color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); - trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); - } else { - return; - } - } - - trap_R_SetColor( color ); - - /* - count = cg.rewardCount[0]/10; // number of big rewards to draw - - if (count) { - y = 4; - x = 320 - count * ICON_SIZE; - for ( i = 0 ; i < count ; i++ ) { - CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); - x += (ICON_SIZE*2); - } - } - - count = cg.rewardCount[0] - count*10; // number of small rewards to draw - */ - - if ( cg.rewardCount[0] >= 10 ) { - y = 56; - x = 320 - ICON_SIZE/2; - CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); - Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); - x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; - CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, - SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); - } - else { - - count = cg.rewardCount[0]; - - y = 56; - x = 320 - count * ICON_SIZE/2; - for ( i = 0 ; i < count ; i++ ) { - CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); - x += ICON_SIZE; - } - } - trap_R_SetColor( NULL ); -} - - -/* -=============================================================================== - -LAGOMETER - -=============================================================================== -*/ - -#define LAG_SAMPLES 128 - - -typedef struct { - int frameSamples[LAG_SAMPLES]; - int frameCount; - int snapshotFlags[LAG_SAMPLES]; - int snapshotSamples[LAG_SAMPLES]; - int snapshotCount; -} lagometer_t; - -lagometer_t lagometer; - -/* -============== -CG_AddLagometerFrameInfo - -Adds the current interpolate / extrapolate bar for this frame -============== -*/ -void CG_AddLagometerFrameInfo( void ) { - int offset; - - offset = cg.time - cg.latestSnapshotTime; - lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; - lagometer.frameCount++; -} - -/* -============== -CG_AddLagometerSnapshotInfo - -Each time a snapshot is received, log its ping time and -the number of snapshots that were dropped before it. - -Pass NULL for a dropped packet. -============== -*/ -void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { - // dropped packet - if ( !snap ) { - lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; - lagometer.snapshotCount++; - return; - } - - // add this snapshot's info - lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; - lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; - lagometer.snapshotCount++; -} - -/* -============== -CG_DrawDisconnect - -Should we draw something differnet for long lag vs no packets? -============== -*/ -static void CG_DrawDisconnect( void ) { - float x, y; - int cmdNum; - usercmd_t cmd; - const char *s; - int w; // bk010215 - FIXME char message[1024]; - - // draw the phone jack if we are completely past our buffers - cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; - trap_GetUserCmd( cmdNum, &cmd ); - if ( cmd.serverTime <= cg.snap->ps.commandTime - || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME - return; - } - - // also add text in center of screen - s = "Connection Interrupted"; // bk 010215 - FIXME - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString( 320 - w/2, 100, s, 1.0F); - - // blink the icon - if ( ( cg.time >> 9 ) & 1 ) { - return; - } - - x = 640 - 48; - y = 480 - 48; - - CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); -} - - -#define MAX_LAGOMETER_PING 900 -#define MAX_LAGOMETER_RANGE 300 - -/* -============== -CG_DrawLagometer -============== -*/ -static void CG_DrawLagometer( void ) { - int a, x, y, i; - float v; - float ax, ay, aw, ah, mid, range; - int color; - float vscale; - - if ( !cg_lagometer.integer || cgs.localServer ) { - CG_DrawDisconnect(); - return; - } - - // - // draw the graph - // -#ifdef MISSIONPACK - x = 640 - 48; - y = 480 - 144; -#else - x = 640 - 48; - y = 480 - 48; -#endif - - trap_R_SetColor( NULL ); - CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); - - ax = x; - ay = y; - aw = 48; - ah = 48; - CG_AdjustFrom640( &ax, &ay, &aw, &ah ); - - color = -1; - range = ah / 3; - mid = ay + range; - - vscale = range / MAX_LAGOMETER_RANGE; - - // draw the frame interpoalte / extrapolate graph - for ( a = 0 ; a < aw ; a++ ) { - i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); - v = lagometer.frameSamples[i]; - v *= vscale; - if ( v > 0 ) { - if ( color != 1 ) { - color = 1; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); - } - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } else if ( v < 0 ) { - if ( color != 2 ) { - color = 2; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); - } - v = -v; - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } - } - - // draw the snapshot latency / drop graph - range = ah / 2; - vscale = range / MAX_LAGOMETER_PING; - - for ( a = 0 ; a < aw ; a++ ) { - i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); - v = lagometer.snapshotSamples[i]; - if ( v > 0 ) { - if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { - if ( color != 5 ) { - color = 5; // YELLOW for rate delay - trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); - } - } else { - if ( color != 3 ) { - color = 3; - trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); - } - } - v = v * vscale; - if ( v > range ) { - v = range; - } - trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); - } else if ( v < 0 ) { - if ( color != 4 ) { - color = 4; // RED for dropped snapshots - trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); - } - trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); - } - } - - trap_R_SetColor( NULL ); - - if ( cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_DrawBigString( ax, ay, "snc", 1.0 ); - } - - CG_DrawDisconnect(); -} - - - -/* -=============================================================================== - -CENTER PRINTING - -=============================================================================== -*/ - - -/* -============== -CG_CenterPrint - -Called for important messages that should stay in the center of the screen -for a few moments -============== -*/ -void CG_CenterPrint( const char *str, int y, int charWidth ) { - char *s; - - Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); - - cg.centerPrintTime = cg.time; - cg.centerPrintY = y; - cg.centerPrintCharWidth = charWidth; - - // count the number of lines for centering - cg.centerPrintLines = 1; - s = cg.centerPrint; - while( *s ) { - if (*s == '\n') - cg.centerPrintLines++; - s++; - } -} - - -/* -=================== -CG_DrawCenterString -=================== -*/ -static void CG_DrawCenterString( void ) { - char *start; - int l; - int x, y, w; -#ifdef MISSIONPACK // bk010221 - unused else - int h; -#endif - float *color; - - if ( !cg.centerPrintTime ) { - return; - } - - color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); - if ( !color ) { - return; - } - - trap_R_SetColor( color ); - - start = cg.centerPrint; - - y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; - - while ( 1 ) { - char linebuffer[1024]; - - for ( l = 0; l < 50; l++ ) { - if ( !start[l] || start[l] == '\n' ) { - break; - } - linebuffer[l] = start[l]; - } - linebuffer[l] = 0; - -#ifdef MISSIONPACK - w = CG_Text_Width(linebuffer, 0.5, 0); - h = CG_Text_Height(linebuffer, 0.5, 0); - x = (SCREEN_WIDTH - w) / 2; - CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); - y += h + 6; -#else - w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); - - x = ( SCREEN_WIDTH - w ) / 2; - - CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, - cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); - - y += cg.centerPrintCharWidth * 1.5; -#endif - while ( *start && ( *start != '\n' ) ) { - start++; - } - if ( !*start ) { - break; - } - start++; - } - - trap_R_SetColor( NULL ); -} - - - -/* -================================================================================ - -CROSSHAIR - -================================================================================ -*/ - - -/* -================= -CG_DrawCrosshair -================= -*/ -static void CG_DrawCrosshair(void) { - float w, h; - qhandle_t hShader; - float f; - float x, y; - int ca; - - if ( !cg_drawCrosshair.integer ) { - return; - } - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { - return; - } - - if ( cg.renderingThirdPerson ) { - return; - } - - // set color based on health - if ( cg_crosshairHealth.integer ) { - vec4_t hcolor; - - CG_ColorForHealth( hcolor ); - trap_R_SetColor( hcolor ); - } else { - trap_R_SetColor( NULL ); - } - - w = h = cg_crosshairSize.value; - - // pulse the size of the crosshair when picking up items - f = cg.time - cg.itemPickupBlendTime; - if ( f > 0 && f < ITEM_BLOB_TIME ) { - f /= ITEM_BLOB_TIME; - w *= ( 1 + f ); - h *= ( 1 + f ); - } - - x = cg_crosshairX.integer; - y = cg_crosshairY.integer; - CG_AdjustFrom640( &x, &y, &w, &h ); - - ca = cg_drawCrosshair.integer; - if (ca < 0) { - ca = 0; - } - hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; - - trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), - y + cg.refdef.y + 0.5 * (cg.refdef.height - h), - w, h, 0, 0, 1, 1, hShader ); -} - - - -/* -================= -CG_ScanForCrosshairEntity -================= -*/ -static void CG_ScanForCrosshairEntity( void ) { - trace_t trace; - vec3_t start, end; - int content; - - VectorCopy( cg.refdef.vieworg, start ); - VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); - - CG_Trace( &trace, start, vec3_origin, vec3_origin, end, - cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); - if ( trace.entityNum >= MAX_CLIENTS ) { - return; - } - - // if the player is in fog, don't show it - content = trap_CM_PointContents( trace.endpos, 0 ); - if ( content & CONTENTS_FOG ) { - return; - } - - // if the player is invisible, don't show it - if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { - return; - } - - // update the fade timer - cg.crosshairClientNum = trace.entityNum; - cg.crosshairClientTime = cg.time; -} - - -/* -===================== -CG_DrawCrosshairNames -===================== -*/ -static void CG_DrawCrosshairNames( void ) { - float *color; - char *name; - float w; - - if ( !cg_drawCrosshair.integer ) { - return; - } - if ( !cg_drawCrosshairNames.integer ) { - return; - } - if ( cg.renderingThirdPerson ) { - return; - } - - // scan the known entities to see if the crosshair is sighted on one - CG_ScanForCrosshairEntity(); - - // draw the name of the player being looked at - color = CG_FadeColor( cg.crosshairClientTime, 1000 ); - if ( !color ) { - trap_R_SetColor( NULL ); - return; - } - - name = cgs.clientinfo[ cg.crosshairClientNum ].name; -#ifdef MISSIONPACK - color[3] *= 0.5f; - w = CG_Text_Width(name, 0.3f, 0); - CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); -#else - w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; - CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); -#endif - trap_R_SetColor( NULL ); -} - - -//============================================================================== - -/* -================= -CG_DrawSpectator -================= -*/ -static void CG_DrawSpectator(void) { - CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); - if ( cgs.gametype == GT_TOURNAMENT ) { - CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); - } - else if ( cgs.gametype >= GT_TEAM ) { - CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); - } -} - -/* -================= -CG_DrawVote -================= -*/ -static void CG_DrawVote(void) { - char *s; - int sec; - - if ( !cgs.voteTime ) { - return; - } - - // play a talk beep whenever it is modified - if ( cgs.voteModified ) { - cgs.voteModified = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - - sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; - if ( sec < 0 ) { - sec = 0; - } -#ifdef MISSIONPACK - s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); - CG_DrawSmallString( 0, 58, s, 1.0F ); - s = "or press ESC then click Vote"; - CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); -#else - s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); - CG_DrawSmallString( 0, 58, s, 1.0F ); -#endif -} - -/* -================= -CG_DrawTeamVote -================= -*/ -static void CG_DrawTeamVote(void) { - char *s; - int sec, cs_offset; - - if ( cgs.clientinfo->team == TEAM_RED ) - cs_offset = 0; - else if ( cgs.clientinfo->team == TEAM_BLUE ) - cs_offset = 1; - else - return; - - if ( !cgs.teamVoteTime[cs_offset] ) { - return; - } - - // play a talk beep whenever it is modified - if ( cgs.teamVoteModified[cs_offset] ) { - cgs.teamVoteModified[cs_offset] = qfalse; - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - } - - sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; - if ( sec < 0 ) { - sec = 0; - } - s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], - cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); - CG_DrawSmallString( 0, 90, s, 1.0F ); -} - - -static qboolean CG_DrawScoreboard() { -#ifdef MISSIONPACK - static qboolean firstTime = qtrue; - float fade, *fadeColor; - - if (menuScoreboard) { - menuScoreboard->window.flags &= ~WINDOW_FORCED; - } - if (cg_paused.integer) { - cg.deferredPlayerLoading = 0; - firstTime = qtrue; - return qfalse; - } - - // should never happen in Team Arena - if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - cg.deferredPlayerLoading = 0; - firstTime = qtrue; - return qfalse; - } - - // don't draw scoreboard during death while warmup up - if ( cg.warmup && !cg.showScores ) { - return qfalse; - } - - if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - fade = 1.0; - fadeColor = colorWhite; - } else { - fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); - if ( !fadeColor ) { - // next time scoreboard comes up, don't print killer - cg.deferredPlayerLoading = 0; - cg.killerName[0] = 0; - firstTime = qtrue; - return qfalse; - } - fade = *fadeColor; - } - - - if (menuScoreboard == NULL) { - if ( cgs.gametype >= GT_TEAM ) { - menuScoreboard = Menus_FindByName("teamscore_menu"); - } else { - menuScoreboard = Menus_FindByName("score_menu"); - } - } - - if (menuScoreboard) { - if (firstTime) { - CG_SetScoreSelection(menuScoreboard); - firstTime = qfalse; - } - Menu_Paint(menuScoreboard, qtrue); - } - - // load any models that have been deferred - if ( ++cg.deferredPlayerLoading > 10 ) { - CG_LoadDeferredPlayers(); - } - - return qtrue; -#else - return CG_DrawOldScoreboard(); -#endif -} - -/* -================= -CG_DrawIntermission -================= -*/ -static void CG_DrawIntermission( void ) { -// int key; -#ifdef MISSIONPACK - //if (cg_singlePlayer.integer) { - // CG_DrawCenterString(); - // return; - //} -#else - if ( cgs.gametype == GT_SINGLE_PLAYER ) { - CG_DrawCenterString(); - return; - } -#endif - cg.scoreFadeTime = cg.time; - cg.scoreBoardShowing = CG_DrawScoreboard(); -} - -/* -================= -CG_DrawFollow -================= -*/ -static qboolean CG_DrawFollow( void ) { - float x; - vec4_t color; - const char *name; - - if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { - return qfalse; - } - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - - CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); - - name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; - - x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); - - CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - - return qtrue; -} - - - -/* -================= -CG_DrawAmmoWarning -================= -*/ -static void CG_DrawAmmoWarning( void ) { - const char *s; - int w; - - if ( cg_drawAmmoWarning.integer == 0 ) { - return; - } - - if ( !cg.lowAmmoWarning ) { - return; - } - - if ( cg.lowAmmoWarning == 2 ) { - s = "OUT OF AMMO"; - } else { - s = "LOW AMMO WARNING"; - } - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString(320 - w / 2, 64, s, 1.0F); -} - - -#ifdef MISSIONPACK -/* -================= -CG_DrawProxWarning -================= -*/ -static void CG_DrawProxWarning( void ) { - char s [32]; - int w; - static int proxTime; - static int proxCounter; - static int proxTick; - - if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { - proxTime = 0; - return; - } - - if (proxTime == 0) { - proxTime = cg.time + 5000; - proxCounter = 5; - proxTick = 0; - } - - if (cg.time > proxTime) { - proxTick = proxCounter--; - proxTime = cg.time + 1000; - } - - if (proxTick != 0) { - Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); - } else { - Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); - } - - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); -} -#endif - - -/* -================= -CG_DrawWarmup -================= -*/ -static void CG_DrawWarmup( void ) { - int w; - int sec; - int i; - float scale; - clientInfo_t *ci1, *ci2; - int cw; - const char *s; - - sec = cg.warmup; - if ( !sec ) { - return; - } - - if ( sec < 0 ) { - s = "Waiting for players"; - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - CG_DrawBigString(320 - w / 2, 24, s, 1.0F); - cg.warmupCount = 0; - return; - } - - if (cgs.gametype == GT_TOURNAMENT) { - // find the two active players - ci1 = NULL; - ci2 = NULL; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { - if ( !ci1 ) { - ci1 = &cgs.clientinfo[i]; - } else { - ci2 = &cgs.clientinfo[i]; - } - } - } - - if ( ci1 && ci2 ) { - s = va( "%s vs %s", ci1->name, ci2->name ); -#ifdef MISSIONPACK - w = CG_Text_Width(s, 0.6f, 0); - CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - if ( w > 640 / GIANT_WIDTH ) { - cw = 640 / w; - } else { - cw = GIANT_WIDTH; - } - CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); -#endif - } - } else { - if ( cgs.gametype == GT_FFA ) { - s = "Free For All"; - } else if ( cgs.gametype == GT_TEAM ) { - s = "Team Deathmatch"; - } else if ( cgs.gametype == GT_CTF ) { - s = "Capture the Flag"; -#ifdef MISSIONPACK - } else if ( cgs.gametype == GT_1FCTF ) { - s = "One Flag CTF"; - } else if ( cgs.gametype == GT_OBELISK ) { - s = "Overload"; - } else if ( cgs.gametype == GT_HARVESTER ) { - s = "Harvester"; -#endif - } else { - s = ""; - } -#ifdef MISSIONPACK - w = CG_Text_Width(s, 0.6f, 0); - CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - if ( w > 640 / GIANT_WIDTH ) { - cw = 640 / w; - } else { - cw = GIANT_WIDTH; - } - CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); -#endif - } - - sec = ( sec - cg.time ) / 1000; - if ( sec < 0 ) { - cg.warmup = 0; - sec = 0; - } - s = va( "Starts in: %i", sec + 1 ); - if ( sec != cg.warmupCount ) { - cg.warmupCount = sec; - switch ( sec ) { - case 0: - trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); - break; - case 1: - trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); - break; - case 2: - trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); - break; - default: - break; - } - } - scale = 0.45f; - switch ( cg.warmupCount ) { - case 0: - cw = 28; - scale = 0.54f; - break; - case 1: - cw = 24; - scale = 0.51f; - break; - case 2: - cw = 20; - scale = 0.48f; - break; - default: - cw = 16; - scale = 0.45f; - break; - } - -#ifdef MISSIONPACK - w = CG_Text_Width(s, scale, 0); - CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); -#else - w = CG_DrawStrlen( s ); - CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, - qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); -#endif -} - -//================================================================================== -#ifdef MISSIONPACK -/* -================= -CG_DrawTimedMenus -================= -*/ -void CG_DrawTimedMenus() { - if (cg.voiceTime) { - int t = cg.time - cg.voiceTime; - if ( t > 2500 ) { - Menus_CloseByName("voiceMenu"); - trap_Cvar_Set("cl_conXOffset", "0"); - cg.voiceTime = 0; - } - } -} -#endif -/* -================= -CG_Draw2D -================= -*/ -static void CG_Draw2D( void ) { -#ifdef MISSIONPACK - if (cgs.orderPending && cg.time > cgs.orderTime) { - CG_CheckOrderPending(); - } -#endif - // if we are taking a levelshot for the menu, don't draw anything - if ( cg.levelShot ) { - return; - } - - if ( cg_draw2D.integer == 0 ) { - return; - } - - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - CG_DrawIntermission(); - return; - } - -/* - if (cg.cameraMode) { - return; - } -*/ - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - CG_DrawSpectator(); - CG_DrawCrosshair(); - CG_DrawCrosshairNames(); - } else { - // don't draw any status if dead or the scoreboard is being explicitly shown - if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { - -#ifdef MISSIONPACK - if ( cg_drawStatus.integer ) { - Menu_PaintAll(); - CG_DrawTimedMenus(); - } -#else - CG_DrawStatusBar(); -#endif - - CG_DrawAmmoWarning(); - -#ifdef MISSIONPACK - CG_DrawProxWarning(); -#endif - CG_DrawCrosshair(); - CG_DrawCrosshairNames(); - CG_DrawWeaponSelect(); - -#ifndef MISSIONPACK - CG_DrawHoldableItem(); -#else - //CG_DrawPersistantPowerup(); -#endif - CG_DrawReward(); - } - - if ( cgs.gametype >= GT_TEAM ) { -#ifndef MISSIONPACK - CG_DrawTeamInfo(); -#endif - } - } - - CG_DrawVote(); - CG_DrawTeamVote(); - - CG_DrawLagometer(); - -#ifdef MISSIONPACK - if (!cg_paused.integer) { - CG_DrawUpperRight(); - } -#else - CG_DrawUpperRight(); -#endif - -#ifndef MISSIONPACK - CG_DrawLowerRight(); - CG_DrawLowerLeft(); -#endif - - if ( !CG_DrawFollow() ) { - CG_DrawWarmup(); - } - - // don't draw center string if scoreboard is up - cg.scoreBoardShowing = CG_DrawScoreboard(); - if ( !cg.scoreBoardShowing) { - CG_DrawCenterString(); - } -} - - -static void CG_DrawTourneyScoreboard() { -#ifdef MISSIONPACK -#else - CG_DrawOldTourneyScoreboard(); -#endif -} - -/* -===================== -CG_DrawActive - -Perform all drawing needed to completely fill the screen -===================== -*/ -void CG_DrawActive( stereoFrame_t stereoView ) { - float separation; - vec3_t baseOrg; - - // optionally draw the info screen instead - if ( !cg.snap ) { - CG_DrawInformation(); - return; - } - - // optionally draw the tournement scoreboard instead - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && - ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { - CG_DrawTourneyScoreboard(); - return; - } - - switch ( stereoView ) { - case STEREO_CENTER: - separation = 0; - break; - case STEREO_LEFT: - separation = -cg_stereoSeparation.value / 2; - break; - case STEREO_RIGHT: - separation = cg_stereoSeparation.value / 2; - break; - default: - separation = 0; - CG_Error( "CG_DrawActive: Undefined stereoView" ); - } - - - // clear around the rendered view if sized down - CG_TileClear(); - - // offset vieworg appropriately if we're doing stereo separation - VectorCopy( cg.refdef.vieworg, baseOrg ); - if ( separation != 0 ) { - VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); - } - - // draw 3D view - trap_R_RenderScene( &cg.refdef ); - - // restore original viewpoint if running stereo - if ( separation != 0 ) { - VectorCopy( baseOrg, cg.refdef.vieworg ); - } - - // draw status bar and other floating elements - CG_Draw2D(); -} - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" + +#ifdef MISSIONPACK +#include "../ui/ui_shared.h" + +// used for scoreboard +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; +#else +int drawTeamOverlayModificationCount = -1; +#endif + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; + +#ifdef MISSIONPACK + +int CG_Text_Width(const char *text, float scale, int limit) { + int count,len; + float out; + glyphInfo_t *glyph; + float useScale; +// FIXME: see ui_main.c, same problem +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + out = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + out += glyph->xSkip; + s++; + count++; + } + } + } + return out * useScale; +} + +int CG_Text_Height(const char *text, float scale, int limit) { + int len, count; + float max; + glyphInfo_t *glyph; + float useScale; +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + max = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if (max < glyph->height) { + max = glyph->height; + } + s++; + count++; + } + } + } + return max * useScale; +} + +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { + float w, h; + w = width * scale; + h = height * scale; + CG_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + trap_R_SetColor( color ); + memcpy(&newColor[0], &color[0], sizeof(vec4_t)); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; + //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + CG_Text_PaintChar(x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + colorBlack[3] = 1.0; + trap_R_SetColor( newColor ); + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); + x += (glyph->xSkip * useScale) + adjust; + s++; + count++; + } + } + trap_R_SetColor( NULL ); + } +} + + +#endif + +/* +============== +CG_DrawField + +Draws large numbers for status bar and powerups +============== +*/ +#ifndef MISSIONPACK +static void CG_DrawField (int x, int y, int width, int value) { + char num[16], *ptr; + int l; + int frame; + + if ( width < 1 ) { + return; + } + + // draw number string + if ( width > 5 ) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + x += 2 + CHAR_WIDTH*(width - l); + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); + x += CHAR_WIDTH; + ptr++; + l--; + } +} +#endif // MISSIONPACK + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( x, y, w, h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + } +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + qhandle_t handle; + + if ( !force2D && cg_draw3dIcons.integer ) { + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg.time / 2000.0 );; + + if( team == TEAM_RED ) { + handle = cgs.media.redFlagModel; + } else if( team == TEAM_BLUE ) { + handle = cgs.media.blueFlagModel; + } else if( team == TEAM_FREE ) { + handle = cgs.media.neutralFlagModel; + } else { + return; + } + CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); + } else if ( cg_drawIcons.integer ) { + gitem_t *item; + + if( team == TEAM_RED ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + } else if( team == TEAM_BLUE ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + } else if( team == TEAM_FREE ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + } else { + return; + } + if (item) { + CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); + } + } +} + +/* +================ +CG_DrawStatusBarHead + +================ +*/ +#ifndef MISSIONPACK + +static void CG_DrawStatusBarHead( float x ) { + vec3_t angles; + float size, stretch; + float frac; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - ICON_SIZE * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = ICON_SIZE * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, 480 - size, size, size, + cg.snap->ps.clientNum, angles ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawStatusBarFlag + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBarFlag( float x, int team ) { + CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) +{ + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + } else { + return; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawStatusBar + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBar( void ) { + int color; + centity_t *cent; + playerState_t *ps; + int value; + vec4_t hcolor; + vec3_t angles; + vec3_t origin; +#ifdef MISSIONPACK + qhandle_t handle; +#endif + static float colors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + { 1.0f, 0.69f, 0.0f, 1.0f }, // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + // draw the team background + CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + VectorClear( angles ); + + // draw any 3D icons first, so the changes back to 2D are minimized + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + + CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); + + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); + } + + if ( ps->stats[ STAT_ARMOR ] ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cgs.media.armorModel, 0, origin, angles ); + } +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); + } +#endif + // + // ammo + // + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING + && cg.predictedPlayerState.weaponTime > 100 ) { + // draw as dark grey when reloading + color = 2; // dark grey + } else { + if ( value >= 0 ) { + color = 0; // green + } else { + color = 1; // red + } + } + trap_R_SetColor( colors[color] ); + + CG_DrawField (0, 432, 3, value); + trap_R_SetColor( NULL ); + + // if we didn't draw a 3D icon, draw a 2D icon for ammo + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + qhandle_t icon; + + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); + } + } + } + } + + // + // health + // + value = ps->stats[STAT_HEALTH]; + if ( value > 100 ) { + trap_R_SetColor( colors[3] ); // white + } else if (value > 25) { + trap_R_SetColor( colors[0] ); // green + } else if (value > 0) { + color = (cg.time >> 8) & 1; // flash + trap_R_SetColor( colors[color] ); + } else { + trap_R_SetColor( colors[1] ); // red + } + + // stretch the health up when taking damage + CG_DrawField ( 185, 432, 3, value); + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + + + // + // armor + // + value = ps->stats[STAT_ARMOR]; + if (value > 0 ) { + trap_R_SetColor( colors[0] ); + CG_DrawField (370, 432, 3, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); + } + + } +#ifdef MISSIONPACK + // + // cubes + // + if( cgs.gametype == GT_HARVESTER ) { + value = ps->generic1; + if( value > 99 ) { + value = 99; + } + trap_R_SetColor( colors[0] ); + CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); + } + } +#endif +} +#endif + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawAttacker + +================ +*/ +static float CG_DrawAttacker( float y ) { + int t; + float size; + vec3_t angles; + const char *info; + const char *name; + int clientNum; + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + if ( !cg.attackerTime ) { + return y; + } + + clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { + return y; + } + + t = cg.time - cg.attackerTime; + if ( t > ATTACKER_HEAD_TIME ) { + cg.attackerTime = 0; + return y; + } + + size = ICON_SIZE * 1.25; + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + name = Info_ValueForKey( info, "n" ); + y += size; + CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + + s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + } + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%i:%i%i", mins, tens, seconds ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { + int x, w, h, xx; + int i, j, len; + const char *p; + vec4_t hcolor; + int pwidth, lwidth; + int plyrs; + char st[16]; + clientInfo_t *ci; + gitem_t *item; + int ret_y, count; + + if ( !cg_drawTeamOverlay.integer ) { + return y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { + return y; // Not on any team + } + + plyrs = 0; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + plyrs++; + len = CG_DrawStrlen(ci->name); + if (len > pwidth) + pwidth = len; + } + } + + if (!plyrs) + return y; + + if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) + pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_DrawStrlen(p); + if (len > lwidth) + lwidth = len; + } + } + + if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) + lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; + + w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; + + if ( right ) + x = 640 - w; + else + x = 0; + + h = plyrs * TINYCHAR_HEIGHT; + + if ( upper ) { + ret_y = y + h; + } else { + y -= h; + ret_y = y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; + + xx = x + TINYCHAR_WIDTH; + + CG_DrawStringExt( xx, y, + ci->name, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); + + if (lwidth) { + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) + p = "unknown"; + len = CG_DrawStrlen(p); + if (len > lwidth) + len = lwidth; + +// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + +// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); + xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; + CG_DrawStringExt( xx, y, + p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + TEAM_OVERLAY_MAXLOCATION_WIDTH); + } + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + + Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + + xx = x + TINYCHAR_WIDTH * 3 + + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + + CG_DrawStringExt( xx, y, + st, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + + // draw weapon icon + xx += TINYCHAR_WIDTH * 3; + + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cgs.media.deferShader ); + } + + // Draw powerup icons + if (right) { + xx = x; + } else { + xx = x + w - TINYCHAR_WIDTH; + } + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + trap_R_RegisterShader( item->icon ) ); + if (right) { + xx -= TINYCHAR_WIDTH; + } else { + xx += TINYCHAR_WIDTH; + } + } + } + } + + y += TINYCHAR_HEIGHT; + } + } + + return ret_y; +//#endif +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + + y = 0; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { + y = CG_DrawTeamOverlay( y, qtrue, qtrue ); + } + if ( cg_drawSnapshot.integer ) { + y = CG_DrawSnapshot( y ); + } + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } + if ( cg_drawAttacker.integer ) { + y = CG_DrawAttacker( y ); + } + +} + +/* +=========================================================================================== + + LOWER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================= +CG_DrawScores + +Draw the small two score display +================= +*/ +#ifndef MISSIONPACK +static float CG_DrawScores( float y ) { + const char *s; + int s1, s2, score; + int x, w; + int v; + vec4_t color; + float y1; + gitem_t *item; + + s1 = cgs.scores1; + s2 = cgs.scores2; + + y -= BIGCHAR_HEIGHT + 8; + + y1 = y; + + // draw from the right side to left + if ( cgs.gametype >= GT_TEAM ) { + x = 640; + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); + } + } + } + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_REDFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); + } + } + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); + } + } + } +#endif + if ( cgs.gametype >= GT_CTF ) { + v = cgs.capturelimit; + } else { + v = cgs.fraglimit; + } + if ( v ) { + s = va( "%2i", v ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } else { + qboolean spectator; + + x = 640; + score = cg.snap->ps.persistant[PERS_SCORE]; + spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); + + // always show your score in the second box if not in first place + if ( s1 != score ) { + s2 = score; + } + if ( s2 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s2 && score != s1 ) { + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + // first place + if ( s1 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s1 ) { + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + if ( cgs.fraglimit ) { + s = va( "%2i", cgs.fraglimit ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } + + return y1 - 8; +} +#endif // MISSIONPACK + +/* +================ +CG_DrawPowerups +================ +*/ +#ifndef MISSIONPACK +static float CG_DrawPowerups( float y ) { + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + int x; + int color; + float size; + float f; + static float colors[2][4] = { + { 0.2f, 1.0f, 0.2f, 1.0f } , + { 1.0f, 0.2f, 0.2f, 1.0f } + }; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return y; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t < 0 || t > 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + x = 640 - ICON_SIZE - CHAR_WIDTH * 2; + for ( i = 0 ; i < active ; i++ ) { + item = BG_FindItemForPowerup( sorted[i] ); + + if (item) { + + color = 1; + + y -= ICON_SIZE; + + trap_R_SetColor( colors[color] ); + CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); + + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + if ( cg.powerupActive == sorted[i] && + cg.time - cg.powerupTime < PULSE_TIME ) { + f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); + size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); + } else { + size = ICON_SIZE; + } + + CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, + size, size, trap_R_RegisterShader( item->icon ) ); + } + } + trap_R_SetColor( NULL ); + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerRight + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerRight( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { + y = CG_DrawTeamOverlay( y, qtrue, qfalse ); + } + + y = CG_DrawScores( y ); + y = CG_DrawPowerups( y ); +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawPickupItem +=================== +*/ +#ifndef MISSIONPACK +static int CG_DrawPickupItem( int y ) { + int value; + float *fadeColor; + + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + y -= ICON_SIZE; + + value = cg.itemPickup; + if ( value ) { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); + trap_R_SetColor( NULL ); + } + } + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerLeft + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerLeft( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { + y = CG_DrawTeamOverlay( y, qfalse, qfalse ); + } + + + y = CG_DrawPickupItem( y ); +} +#endif // MISSIONPACK + + +//=========================================================================================== + +/* +================= +CG_DrawTeamInfo +================= +*/ +#ifndef MISSIONPACK +static void CG_DrawTeamInfo( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + +#define CHATLOC_Y 420 // bottom end +#define CHATLOC_X 0 + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_teamChatHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + if (chatHeight <= 0) + return; // disabled + + if (cgs.teamLastChatPos != cgs.teamChatPos) { + if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { + cgs.teamLastChatPos++; + } + + h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; + + w = 0; + + for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { + len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); + if (len > w) + w = len; + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } else { + hcolor[0] = 0.0f; + hcolor[1] = 1.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } + + trap_R_SetColor( hcolor ); + CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; + hcolor[3] = 1.0f; + + for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { + CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, + CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, + cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + } + } +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawHoldableItem +=================== +*/ +#ifndef MISSIONPACK +static void CG_DrawHoldableItem( void ) { + int value; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } + +} +#endif // MISSIONPACK + +#ifdef MISSIONPACK +/* +=================== +CG_DrawPersistantPowerup +=================== +*/ +#if 0 // sos001208 - DEAD +static void CG_DrawPersistantPowerup( void ) { + int value; + + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } +} +#endif +#endif // MISSIONPACK + + +/* +=================== +CG_DrawReward +=================== +*/ +static void CG_DrawReward( void ) { + float *color; + int i, count; + float x, y; + char buf[32]; + + if ( !cg_drawRewards.integer ) { + return; + } + + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + if ( !color ) { + if (cg.rewardStack > 0) { + for(i = 0; i < cg.rewardStack; i++) { + cg.rewardSound[i] = cg.rewardSound[i+1]; + cg.rewardShader[i] = cg.rewardShader[i+1]; + cg.rewardCount[i] = cg.rewardCount[i+1]; + } + cg.rewardTime = cg.time; + cg.rewardStack--; + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); + } else { + return; + } + } + + trap_R_SetColor( color ); + + /* + count = cg.rewardCount[0]/10; // number of big rewards to draw + + if (count) { + y = 4; + x = 320 - count * ICON_SIZE; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); + x += (ICON_SIZE*2); + } + } + + count = cg.rewardCount[0] - count*10; // number of small rewards to draw + */ + + if ( cg.rewardCount[0] >= 10 ) { + y = 56; + x = 320 - ICON_SIZE/2; + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); + x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; + CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, + SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); + } + else { + + count = cg.rewardCount[0]; + + y = 56; + x = 320 - count * ICON_SIZE/2; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + x += ICON_SIZE; + } + } + trap_R_SetColor( NULL ); +} + + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) { + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { + // dropped packet + if ( !snap ) { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; // bk010215 - FIXME char message[1024]; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg.snap->ps.commandTime + || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME + return; + } + + // also add text in center of screen + s = "Connection Interrupted"; // bk 010215 - FIXME + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w/2, 100, s, 1.0F); + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) { + return; + } + + x = 640 - 48; + y = 480 - 48; + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); +} + + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) { + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + if ( !cg_lagometer.integer || cgs.localServer ) { + CG_DrawDisconnect(); + return; + } + + // + // draw the graph + // +#ifdef MISSIONPACK + x = 640 - 48; + y = 480 - 144; +#else + x = 640 - 48; + y = 480 - 48; +#endif + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_DrawBigString( ax, ay, "snc", 1.0 ); + } + + CG_DrawDisconnect(); +} + + + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) { + char *s; + + Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) { + if (*s == '\n') + cg.centerPrintLines++; + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; +#ifdef MISSIONPACK // bk010221 - unused else + int h; +#endif + float *color; + + if ( !cg.centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < 50; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + +#ifdef MISSIONPACK + w = CG_Text_Width(linebuffer, 0.5, 0); + h = CG_Text_Height(linebuffer, 0.5, 0); + x = (SCREEN_WIDTH - w) / 2; + CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); + y += h + 6; +#else + w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); + + x = ( SCREEN_WIDTH - w ) / 2; + + CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, + cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); + + y += cg.centerPrintCharWidth * 1.5; +#endif + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair(void) { + float w, h; + qhandle_t hShader; + float f; + float x, y; + int ca; + + if ( !cg_drawCrosshair.integer ) { + return; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { + return; + } + + if ( cg.renderingThirdPerson ) { + return; + } + + // set color based on health + if ( cg_crosshairHealth.integer ) { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } else { + trap_R_SetColor( NULL ); + } + + w = h = cg_crosshairSize.value; + + // pulse the size of the crosshair when picking up items + f = cg.time - cg.itemPickupBlendTime; + if ( f > 0 && f < ITEM_BLOB_TIME ) { + f /= ITEM_BLOB_TIME; + w *= ( 1 + f ); + h *= ( 1 + f ); + } + + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + CG_AdjustFrom640( &x, &y, &w, &h ); + + ca = cg_drawCrosshair.integer; + if (ca < 0) { + ca = 0; + } + hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; + + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), + y + cg.refdef.y + 0.5 * (cg.refdef.height - h), + w, h, 0, 0, 1, 1, hShader ); +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.entityNum >= MAX_CLIENTS ) { + return; + } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { + return; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + char *name; + float w; + + if ( !cg_drawCrosshair.integer ) { + return; + } + if ( !cg_drawCrosshairNames.integer ) { + return; + } + if ( cg.renderingThirdPerson ) { + return; + } + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + name = cgs.clientinfo[ cg.crosshairClientNum ].name; +#ifdef MISSIONPACK + color[3] *= 0.5f; + w = CG_Text_Width(name, 0.3f, 0); + CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); +#else + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); +#endif + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) { + CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); + if ( cgs.gametype == GT_TOURNAMENT ) { + CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); + } + else if ( cgs.gametype >= GT_TEAM ) { + CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote(void) { + char *s; + int sec; + + if ( !cgs.voteTime ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.voteModified ) { + cgs.voteModified = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } +#ifdef MISSIONPACK + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); + CG_DrawSmallString( 0, 58, s, 1.0F ); + s = "or press ESC then click Vote"; + CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); +#else + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); + CG_DrawSmallString( 0, 58, s, 1.0F ); +#endif +} + +/* +================= +CG_DrawTeamVote +================= +*/ +static void CG_DrawTeamVote(void) { + char *s; + int sec, cs_offset; + + if ( cgs.clientinfo->team == TEAM_RED ) + cs_offset = 0; + else if ( cgs.clientinfo->team == TEAM_BLUE ) + cs_offset = 1; + else + return; + + if ( !cgs.teamVoteTime[cs_offset] ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.teamVoteModified[cs_offset] ) { + cgs.teamVoteModified[cs_offset] = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + CG_DrawSmallString( 0, 90, s, 1.0F ); +} + + +static qboolean CG_DrawScoreboard() { +#ifdef MISSIONPACK + static qboolean firstTime = qtrue; + float fade, *fadeColor; + + if (menuScoreboard) { + menuScoreboard->window.flags &= ~WINDOW_FORCED; + } + if (cg_paused.integer) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // should never happen in Team Arena + if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + firstTime = qtrue; + return qfalse; + } + fade = *fadeColor; + } + + + if (menuScoreboard == NULL) { + if ( cgs.gametype >= GT_TEAM ) { + menuScoreboard = Menus_FindByName("teamscore_menu"); + } else { + menuScoreboard = Menus_FindByName("score_menu"); + } + } + + if (menuScoreboard) { + if (firstTime) { + CG_SetScoreSelection(menuScoreboard); + firstTime = qfalse; + } + Menu_Paint(menuScoreboard, qtrue); + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +#else + return CG_DrawOldScoreboard(); +#endif +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { +// int key; +#ifdef MISSIONPACK + //if (cg_singlePlayer.integer) { + // CG_DrawCenterString(); + // return; + //} +#else + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + CG_DrawCenterString(); + return; + } +#endif + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) { + float x; + vec4_t color; + const char *name; + + if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + return qfalse; + } + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + + CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); + + name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); + + CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + return qtrue; +} + + + +/* +================= +CG_DrawAmmoWarning +================= +*/ +static void CG_DrawAmmoWarning( void ) { + const char *s; + int w; + + if ( cg_drawAmmoWarning.integer == 0 ) { + return; + } + + if ( !cg.lowAmmoWarning ) { + return; + } + + if ( cg.lowAmmoWarning == 2 ) { + s = "OUT OF AMMO"; + } else { + s = "LOW AMMO WARNING"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 64, s, 1.0F); +} + + +#ifdef MISSIONPACK +/* +================= +CG_DrawProxWarning +================= +*/ +static void CG_DrawProxWarning( void ) { + char s [32]; + int w; + static int proxTime; + static int proxCounter; + static int proxTick; + + if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { + proxTime = 0; + return; + } + + if (proxTime == 0) { + proxTime = cg.time + 5000; + proxCounter = 5; + proxTick = 0; + } + + if (cg.time > proxTime) { + proxTick = proxCounter--; + proxTime = cg.time + 1000; + } + + if (proxTick != 0) { + Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); + } else { + Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); +} +#endif + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + float scale; + clientInfo_t *ci1, *ci2; + int cw; + const char *s; + + sec = cg.warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { + s = "Waiting for players"; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 24, s, 1.0F); + cg.warmupCount = 0; + return; + } + + if (cgs.gametype == GT_TOURNAMENT) { + // find the two active players + ci1 = NULL; + ci2 = NULL; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { + if ( !ci1 ) { + ci1 = &cgs.clientinfo[i]; + } else { + ci2 = &cgs.clientinfo[i]; + } + } + } + + if ( ci1 && ci2 ) { + s = va( "%s vs %s", ci1->name, ci2->name ); +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); +#endif + } + } else { + if ( cgs.gametype == GT_FFA ) { + s = "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + s = "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + s = "Capture the Flag"; +#ifdef MISSIONPACK + } else if ( cgs.gametype == GT_1FCTF ) { + s = "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + s = "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + s = "Harvester"; +#endif + } else { + s = ""; + } +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); +#endif + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + cg.warmup = 0; + sec = 0; + } + s = va( "Starts in: %i", sec + 1 ); + if ( sec != cg.warmupCount ) { + cg.warmupCount = sec; + switch ( sec ) { + case 0: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + scale = 0.45f; + switch ( cg.warmupCount ) { + case 0: + cw = 28; + scale = 0.54f; + break; + case 1: + cw = 24; + scale = 0.51f; + break; + case 2: + cw = 20; + scale = 0.48f; + break; + default: + cw = 16; + scale = 0.45f; + break; + } + +#ifdef MISSIONPACK + w = CG_Text_Width(s, scale, 0); + CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); +#endif +} + +//================================================================================== +#ifdef MISSIONPACK +/* +================= +CG_DrawTimedMenus +================= +*/ +void CG_DrawTimedMenus() { + if (cg.voiceTime) { + int t = cg.time - cg.voiceTime; + if ( t > 2500 ) { + Menus_CloseByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "0"); + cg.voiceTime = 0; + } + } +} +#endif +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) { +#ifdef MISSIONPACK + if (cgs.orderPending && cg.time > cgs.orderTime) { + CG_CheckOrderPending(); + } +#endif + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + return; + } + +/* + if (cg.cameraMode) { + return; + } +*/ + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } else { + // don't draw any status if dead or the scoreboard is being explicitly shown + if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { + +#ifdef MISSIONPACK + if ( cg_drawStatus.integer ) { + Menu_PaintAll(); + CG_DrawTimedMenus(); + } +#else + CG_DrawStatusBar(); +#endif + + CG_DrawAmmoWarning(); + +#ifdef MISSIONPACK + CG_DrawProxWarning(); +#endif + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + CG_DrawWeaponSelect(); + +#ifndef MISSIONPACK + CG_DrawHoldableItem(); +#else + //CG_DrawPersistantPowerup(); +#endif + CG_DrawReward(); + } + + if ( cgs.gametype >= GT_TEAM ) { +#ifndef MISSIONPACK + CG_DrawTeamInfo(); +#endif + } + } + + CG_DrawVote(); + CG_DrawTeamVote(); + + CG_DrawLagometer(); + +#ifdef MISSIONPACK + if (!cg_paused.integer) { + CG_DrawUpperRight(); + } +#else + CG_DrawUpperRight(); +#endif + +#ifndef MISSIONPACK + CG_DrawLowerRight(); + CG_DrawLowerLeft(); +#endif + + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg.scoreBoardShowing) { + CG_DrawCenterString(); + } +} + + +static void CG_DrawTourneyScoreboard() { +#ifdef MISSIONPACK +#else + CG_DrawOldTourneyScoreboard(); +#endif +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg.snap ) { + CG_DrawInformation(); + return; + } + + // optionally draw the tournement scoreboard instead + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && + ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + // draw 3D view + trap_R_RenderScene( &cg.refdef ); + + // restore original viewpoint if running stereo + if ( separation != 0 ) { + VectorCopy( baseOrg, cg.refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + + + diff --git a/code/cgame/cg_drawtools.c b/code/cgame/cg_drawtools.c index 9c27606..2ec45ed 100755 --- a/code/cgame/cg_drawtools.c +++ b/code/cgame/cg_drawtools.c @@ -1,824 +1,824 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc -#include "cg_local.h" - -/* -================ -CG_AdjustFrom640 - -Adjusted for resolution and screen aspect ratio -================ -*/ -void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { -#if 0 - // adjust for wide screens - if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { - *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); - } -#endif - // scale for screen sizes - *x *= cgs.screenXScale; - *y *= cgs.screenYScale; - *w *= cgs.screenXScale; - *h *= cgs.screenYScale; -} - -/* -================ -CG_FillRect - -Coordinates are 640*480 virtual values -================= -*/ -void CG_FillRect( float x, float y, float width, float height, const float *color ) { - trap_R_SetColor( color ); - - CG_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); - - trap_R_SetColor( NULL ); -} - -/* -================ -CG_DrawSides - -Coords are virtual 640x480 -================ -*/ -void CG_DrawSides(float x, float y, float w, float h, float size) { - CG_AdjustFrom640( &x, &y, &w, &h ); - size *= cgs.screenXScale; - trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); -} - -void CG_DrawTopBottom(float x, float y, float w, float h, float size) { - CG_AdjustFrom640( &x, &y, &w, &h ); - size *= cgs.screenYScale; - trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); - trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); -} -/* -================ -UI_DrawRect - -Coordinates are 640*480 virtual values -================= -*/ -void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { - trap_R_SetColor( color ); - - CG_DrawTopBottom(x, y, width, height, size); - CG_DrawSides(x, y, width, height, size); - - trap_R_SetColor( NULL ); -} - - - -/* -================ -CG_DrawPic - -Coordinates are 640*480 virtual values -================= -*/ -void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { - CG_AdjustFrom640( &x, &y, &width, &height ); - trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); -} - - - -/* -=============== -CG_DrawChar - -Coordinates and size in 640*480 virtual screen size -=============== -*/ -void CG_DrawChar( int x, int y, int width, int height, int ch ) { - int row, col; - float frow, fcol; - float size; - float ax, ay, aw, ah; - - ch &= 255; - - if ( ch == ' ' ) { - return; - } - - ax = x; - ay = y; - aw = width; - ah = height; - CG_AdjustFrom640( &ax, &ay, &aw, &ah ); - - row = ch>>4; - col = ch&15; - - frow = row*0.0625; - fcol = col*0.0625; - size = 0.0625; - - trap_R_DrawStretchPic( ax, ay, aw, ah, - fcol, frow, - fcol + size, frow + size, - cgs.media.charsetShader ); -} - - -/* -================== -CG_DrawStringExt - -Draws a multi-colored string with a drop shadow, optionally forcing -to a fixed color. - -Coordinates are at 640 by 480 virtual resolution -================== -*/ -void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, - qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { - vec4_t color; - const char *s; - int xx; - int cnt; - - if (maxChars <= 0) - maxChars = 32767; // do them all! - - // draw the drop shadow - if (shadow) { - color[0] = color[1] = color[2] = 0; - color[3] = setColor[3]; - trap_R_SetColor( color ); - s = string; - xx = x; - cnt = 0; - while ( *s && cnt < maxChars) { - if ( Q_IsColorString( s ) ) { - s += 2; - continue; - } - CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); - cnt++; - xx += charWidth; - s++; - } - } - - // draw the colored text - s = string; - xx = x; - cnt = 0; - trap_R_SetColor( setColor ); - while ( *s && cnt < maxChars) { - if ( Q_IsColorString( s ) ) { - if ( !forceColor ) { - memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); - color[3] = setColor[3]; - trap_R_SetColor( color ); - } - s += 2; - continue; - } - CG_DrawChar( xx, y, charWidth, charHeight, *s ); - xx += charWidth; - cnt++; - s++; - } - trap_R_SetColor( NULL ); -} - -void CG_DrawBigString( int x, int y, const char *s, float alpha ) { - float color[4]; - - color[0] = color[1] = color[2] = 1.0; - color[3] = alpha; - CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); -} - -void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { - CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); -} - -void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { - float color[4]; - - color[0] = color[1] = color[2] = 1.0; - color[3] = alpha; - CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); -} - -void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { - CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); -} - -/* -================= -CG_DrawStrlen - -Returns character count, skiping color escape codes -================= -*/ -int CG_DrawStrlen( const char *str ) { - const char *s = str; - int count = 0; - - while ( *s ) { - if ( Q_IsColorString( s ) ) { - s += 2; - } else { - count++; - s++; - } - } - - return count; -} - -/* -============= -CG_TileClearBox - -This repeats a 64*64 tile graphic to fill the screen around a sized down -refresh window. -============= -*/ -static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { - float s1, t1, s2, t2; - - s1 = x/64.0; - t1 = y/64.0; - s2 = (x+w)/64.0; - t2 = (y+h)/64.0; - trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); -} - - - -/* -============== -CG_TileClear - -Clear around a sized down screen -============== -*/ -void CG_TileClear( void ) { - int top, bottom, left, right; - int w, h; - - w = cgs.glconfig.vidWidth; - h = cgs.glconfig.vidHeight; - - if ( cg.refdef.x == 0 && cg.refdef.y == 0 && - cg.refdef.width == w && cg.refdef.height == h ) { - return; // full screen rendering - } - - top = cg.refdef.y; - bottom = top + cg.refdef.height-1; - left = cg.refdef.x; - right = left + cg.refdef.width-1; - - // clear above view screen - CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); - - // clear below view screen - CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); - - // clear left of view screen - CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); - - // clear right of view screen - CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); -} - - - -/* -================ -CG_FadeColor -================ -*/ -float *CG_FadeColor( int startMsec, int totalMsec ) { - static vec4_t color; - int t; - - if ( startMsec == 0 ) { - return NULL; - } - - t = cg.time - startMsec; - - if ( t >= totalMsec ) { - return NULL; - } - - // fade out - if ( totalMsec - t < FADE_TIME ) { - color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; - } else { - color[3] = 1.0; - } - color[0] = color[1] = color[2] = 1; - - return color; -} - - -/* -================ -CG_TeamColor -================ -*/ -float *CG_TeamColor( int team ) { - static vec4_t red = {1, 0.2f, 0.2f, 1}; - static vec4_t blue = {0.2f, 0.2f, 1, 1}; - static vec4_t other = {1, 1, 1, 1}; - static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; - - switch ( team ) { - case TEAM_RED: - return red; - case TEAM_BLUE: - return blue; - case TEAM_SPECTATOR: - return spectator; - default: - return other; - } -} - - - -/* -================= -CG_GetColorForHealth -================= -*/ -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { - int count; - int max; - - // calculate the total points of damage that can - // be sustained at the current health / armor level - if ( health <= 0 ) { - VectorClear( hcolor ); // black - hcolor[3] = 1; - return; - } - count = armor; - max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); - if ( max < count ) { - count = max; - } - health += count; - - // set the color based on health - hcolor[0] = 1.0; - hcolor[3] = 1.0; - if ( health >= 100 ) { - hcolor[2] = 1.0; - } else if ( health < 66 ) { - hcolor[2] = 0; - } else { - hcolor[2] = ( health - 66 ) / 33.0; - } - - if ( health > 60 ) { - hcolor[1] = 1.0; - } else if ( health < 30 ) { - hcolor[1] = 0; - } else { - hcolor[1] = ( health - 30 ) / 30.0; - } -} - -/* -================= -CG_ColorForHealth -================= -*/ -void CG_ColorForHealth( vec4_t hcolor ) { - - CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], - cg.snap->ps.stats[STAT_ARMOR], hcolor ); -} - - - - -// bk001205 - code below duplicated in q3_ui/ui-atoms.c -// bk001205 - FIXME: does this belong in ui_shared.c? -// bk001205 - FIXME: HARD_LINKED flags not visible here -#ifndef Q3_STATIC // bk001205 - q_shared defines not visible here -/* -================= -UI_DrawProportionalString2 -================= -*/ -static int propMap[128][3] = { -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, - -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, -{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, - -{0, 0, PROP_SPACE_WIDTH}, // SPACE -{11, 122, 7}, // ! -{154, 181, 14}, // " -{55, 122, 17}, // # -{79, 122, 18}, // $ -{101, 122, 23}, // % -{153, 122, 18}, // & -{9, 93, 7}, // ' -{207, 122, 8}, // ( -{230, 122, 9}, // ) -{177, 122, 18}, // * -{30, 152, 18}, // + -{85, 181, 7}, // , -{34, 93, 11}, // - -{110, 181, 6}, // . -{130, 152, 14}, // / - -{22, 64, 17}, // 0 -{41, 64, 12}, // 1 -{58, 64, 17}, // 2 -{78, 64, 18}, // 3 -{98, 64, 19}, // 4 -{120, 64, 18}, // 5 -{141, 64, 18}, // 6 -{204, 64, 16}, // 7 -{162, 64, 17}, // 8 -{182, 64, 18}, // 9 -{59, 181, 7}, // : -{35,181, 7}, // ; -{203, 152, 14}, // < -{56, 93, 14}, // = -{228, 152, 14}, // > -{177, 181, 18}, // ? - -{28, 122, 22}, // @ -{5, 4, 18}, // A -{27, 4, 18}, // B -{48, 4, 18}, // C -{69, 4, 17}, // D -{90, 4, 13}, // E -{106, 4, 13}, // F -{121, 4, 18}, // G -{143, 4, 17}, // H -{164, 4, 8}, // I -{175, 4, 16}, // J -{195, 4, 18}, // K -{216, 4, 12}, // L -{230, 4, 23}, // M -{6, 34, 18}, // N -{27, 34, 18}, // O - -{48, 34, 18}, // P -{68, 34, 18}, // Q -{90, 34, 17}, // R -{110, 34, 18}, // S -{130, 34, 14}, // T -{146, 34, 18}, // U -{166, 34, 19}, // V -{185, 34, 29}, // W -{215, 34, 18}, // X -{234, 34, 18}, // Y -{5, 64, 14}, // Z -{60, 152, 7}, // [ -{106, 151, 13}, // '\' -{83, 152, 7}, // ] -{128, 122, 17}, // ^ -{4, 152, 21}, // _ - -{134, 181, 5}, // ' -{5, 4, 18}, // A -{27, 4, 18}, // B -{48, 4, 18}, // C -{69, 4, 17}, // D -{90, 4, 13}, // E -{106, 4, 13}, // F -{121, 4, 18}, // G -{143, 4, 17}, // H -{164, 4, 8}, // I -{175, 4, 16}, // J -{195, 4, 18}, // K -{216, 4, 12}, // L -{230, 4, 23}, // M -{6, 34, 18}, // N -{27, 34, 18}, // O - -{48, 34, 18}, // P -{68, 34, 18}, // Q -{90, 34, 17}, // R -{110, 34, 18}, // S -{130, 34, 14}, // T -{146, 34, 18}, // U -{166, 34, 19}, // V -{185, 34, 29}, // W -{215, 34, 18}, // X -{234, 34, 18}, // Y -{5, 64, 14}, // Z -{153, 152, 13}, // { -{11, 181, 5}, // | -{180, 152, 13}, // } -{79, 93, 17}, // ~ -{0, 0, -1} // DEL -}; - -static int propMapB[26][3] = { -{11, 12, 33}, -{49, 12, 31}, -{85, 12, 31}, -{120, 12, 30}, -{156, 12, 21}, -{183, 12, 21}, -{207, 12, 32}, - -{13, 55, 30}, -{49, 55, 13}, -{66, 55, 29}, -{101, 55, 31}, -{135, 55, 21}, -{158, 55, 40}, -{204, 55, 32}, - -{12, 97, 31}, -{48, 97, 31}, -{82, 97, 30}, -{118, 97, 30}, -{153, 97, 30}, -{185, 97, 25}, -{213, 97, 30}, - -{11, 139, 32}, -{42, 139, 51}, -{93, 139, 32}, -{126, 139, 31}, -{158, 139, 25}, -}; - -#define PROPB_GAP_WIDTH 4 -#define PROPB_SPACE_WIDTH 12 -#define PROPB_HEIGHT 36 - -/* -================= -UI_DrawBannerString -================= -*/ -static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) -{ - const char* s; - unsigned char ch; // bk001204 : array subscript - float ax; - float ay; - float aw; - float ah; - float frow; - float fcol; - float fwidth; - float fheight; - - // draw the colored text - trap_R_SetColor( color ); - - ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; - - s = str; - while ( *s ) - { - ch = *s & 127; - if ( ch == ' ' ) { - ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; - } - else if ( ch >= 'A' && ch <= 'Z' ) { - ch -= 'A'; - fcol = (float)propMapB[ch][0] / 256.0f; - frow = (float)propMapB[ch][1] / 256.0f; - fwidth = (float)propMapB[ch][2] / 256.0f; - fheight = (float)PROPB_HEIGHT / 256.0f; - aw = (float)propMapB[ch][2] * cgs.screenXScale; - ah = (float)PROPB_HEIGHT * cgs.screenXScale; - trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); - ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); - } - s++; - } - - trap_R_SetColor( NULL ); -} - -void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { - const char * s; - int ch; - int width; - vec4_t drawcolor; - - // find the width of the drawn text - s = str; - width = 0; - while ( *s ) { - ch = *s; - if ( ch == ' ' ) { - width += PROPB_SPACE_WIDTH; - } - else if ( ch >= 'A' && ch <= 'Z' ) { - width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; - } - s++; - } - width -= PROPB_GAP_WIDTH; - - switch( style & UI_FORMATMASK ) { - case UI_CENTER: - x -= width / 2; - break; - - case UI_RIGHT: - x -= width; - break; - - case UI_LEFT: - default: - break; - } - - if ( style & UI_DROPSHADOW ) { - drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; - drawcolor[3] = color[3]; - UI_DrawBannerString2( x+2, y+2, str, drawcolor ); - } - - UI_DrawBannerString2( x, y, str, color ); -} - - -int UI_ProportionalStringWidth( const char* str ) { - const char * s; - int ch; - int charWidth; - int width; - - s = str; - width = 0; - while ( *s ) { - ch = *s & 127; - charWidth = propMap[ch][2]; - if ( charWidth != -1 ) { - width += charWidth; - width += PROP_GAP_WIDTH; - } - s++; - } - - width -= PROP_GAP_WIDTH; - return width; -} - -static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) -{ - const char* s; - unsigned char ch; // bk001204 - unsigned - float ax; - float ay; - float aw; - float ah; - float frow; - float fcol; - float fwidth; - float fheight; - - // draw the colored text - trap_R_SetColor( color ); - - ax = x * cgs.screenXScale + cgs.screenXBias; - ay = y * cgs.screenXScale; - - s = str; - while ( *s ) - { - ch = *s & 127; - if ( ch == ' ' ) { - aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; - } else if ( propMap[ch][2] != -1 ) { - fcol = (float)propMap[ch][0] / 256.0f; - frow = (float)propMap[ch][1] / 256.0f; - fwidth = (float)propMap[ch][2] / 256.0f; - fheight = (float)PROP_HEIGHT / 256.0f; - aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; - ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; - trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); - } else { - aw = 0; - } - - ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); - s++; - } - - trap_R_SetColor( NULL ); -} - -/* -================= -UI_ProportionalSizeScale -================= -*/ -float UI_ProportionalSizeScale( int style ) { - if( style & UI_SMALLFONT ) { - return 0.75; - } - - return 1.00; -} - - -/* -================= -UI_DrawProportionalString -================= -*/ -void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { - vec4_t drawcolor; - int width; - float sizeScale; - - sizeScale = UI_ProportionalSizeScale( style ); - - switch( style & UI_FORMATMASK ) { - case UI_CENTER: - width = UI_ProportionalStringWidth( str ) * sizeScale; - x -= width / 2; - break; - - case UI_RIGHT: - width = UI_ProportionalStringWidth( str ) * sizeScale; - x -= width; - break; - - case UI_LEFT: - default: - break; - } - - if ( style & UI_DROPSHADOW ) { - drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); - } - - if ( style & UI_INVERSE ) { - drawcolor[0] = color[0] * 0.8; - drawcolor[1] = color[1] * 0.8; - drawcolor[2] = color[2] * 0.8; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); - return; - } - - if ( style & UI_PULSE ) { - drawcolor[0] = color[0] * 0.8; - drawcolor[1] = color[1] * 0.8; - drawcolor[2] = color[2] * 0.8; - drawcolor[3] = color[3]; - UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); - - drawcolor[0] = color[0]; - drawcolor[1] = color[1]; - drawcolor[2] = color[2]; - drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); - UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); - return; - } - - UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); -} -#endif // Q3STATIC +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +=============== +CG_DrawChar + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cgs.media.charsetShader ); +} + + +/* +================== +CG_DrawStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if (maxChars <= 0) + maxChars = 32767; // do them all! + + // draw the drop shadow + if (shadow) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +void CG_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x/64.0; + t1 = y/64.0; + s2 = (x+w)/64.0; + t2 = (y+h)/64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height-1; + left = cg.refdef.x; + right = left + cg.refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2f, 0.2f, 1}; + static vec4_t blue = {0.2f, 0.2f, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) { + + CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], + cg.snap->ps.stats[STAT_ARMOR], hcolor ); +} + + + + +// bk001205 - code below duplicated in q3_ui/ui-atoms.c +// bk001205 - FIXME: does this belong in ui_shared.c? +// bk001205 - FIXME: HARD_LINKED flags not visible here +#ifndef Q3_STATIC // bk001205 - q_shared defines not visible here +/* +================= +UI_DrawProportionalString2 +================= +*/ +static int propMap[128][3] = { +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, PROP_SPACE_WIDTH}, // SPACE +{11, 122, 7}, // ! +{154, 181, 14}, // " +{55, 122, 17}, // # +{79, 122, 18}, // $ +{101, 122, 23}, // % +{153, 122, 18}, // & +{9, 93, 7}, // ' +{207, 122, 8}, // ( +{230, 122, 9}, // ) +{177, 122, 18}, // * +{30, 152, 18}, // + +{85, 181, 7}, // , +{34, 93, 11}, // - +{110, 181, 6}, // . +{130, 152, 14}, // / + +{22, 64, 17}, // 0 +{41, 64, 12}, // 1 +{58, 64, 17}, // 2 +{78, 64, 18}, // 3 +{98, 64, 19}, // 4 +{120, 64, 18}, // 5 +{141, 64, 18}, // 6 +{204, 64, 16}, // 7 +{162, 64, 17}, // 8 +{182, 64, 18}, // 9 +{59, 181, 7}, // : +{35,181, 7}, // ; +{203, 152, 14}, // < +{56, 93, 14}, // = +{228, 152, 14}, // > +{177, 181, 18}, // ? + +{28, 122, 22}, // @ +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{60, 152, 7}, // [ +{106, 151, 13}, // '\' +{83, 152, 7}, // ] +{128, 122, 17}, // ^ +{4, 152, 21}, // _ + +{134, 181, 5}, // ' +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{153, 152, 13}, // { +{11, 181, 5}, // | +{180, 152, 13}, // } +{79, 93, 17}, // ~ +{0, 0, -1} // DEL +}; + +static int propMapB[26][3] = { +{11, 12, 33}, +{49, 12, 31}, +{85, 12, 31}, +{120, 12, 30}, +{156, 12, 21}, +{183, 12, 21}, +{207, 12, 32}, + +{13, 55, 30}, +{49, 55, 13}, +{66, 55, 29}, +{101, 55, 31}, +{135, 55, 21}, +{158, 55, 40}, +{204, 55, 32}, + +{12, 97, 31}, +{48, 97, 31}, +{82, 97, 30}, +{118, 97, 30}, +{153, 97, 30}, +{185, 97, 25}, +{213, 97, 30}, + +{11, 139, 32}, +{42, 139, 51}, +{93, 139, 32}, +{126, 139, 31}, +{158, 139, 25}, +}; + +#define PROPB_GAP_WIDTH 4 +#define PROPB_SPACE_WIDTH 12 +#define PROPB_HEIGHT 36 + +/* +================= +UI_DrawBannerString +================= +*/ +static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) +{ + const char* s; + unsigned char ch; // bk001204 : array subscript + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + ch -= 'A'; + fcol = (float)propMapB[ch][0] / 256.0f; + frow = (float)propMapB[ch][1] / 256.0f; + fwidth = (float)propMapB[ch][2] / 256.0f; + fheight = (float)PROPB_HEIGHT / 256.0f; + aw = (float)propMapB[ch][2] * cgs.screenXScale; + ah = (float)PROPB_HEIGHT * cgs.screenXScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); + ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); + } + s++; + } + + trap_R_SetColor( NULL ); +} + +void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { + const char * s; + int ch; + int width; + vec4_t drawcolor; + + // find the width of the drawn text + s = str; + width = 0; + while ( *s ) { + ch = *s; + if ( ch == ' ' ) { + width += PROPB_SPACE_WIDTH; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; + } + s++; + } + width -= PROPB_GAP_WIDTH; + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + x -= width / 2; + break; + + case UI_RIGHT: + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawBannerString2( x+2, y+2, str, drawcolor ); + } + + UI_DrawBannerString2( x, y, str, color ); +} + + +int UI_ProportionalStringWidth( const char* str ) { + const char * s; + int ch; + int charWidth; + int width; + + s = str; + width = 0; + while ( *s ) { + ch = *s & 127; + charWidth = propMap[ch][2]; + if ( charWidth != -1 ) { + width += charWidth; + width += PROP_GAP_WIDTH; + } + s++; + } + + width -= PROP_GAP_WIDTH; + return width; +} + +static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) +{ + const char* s; + unsigned char ch; // bk001204 - unsigned + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; + } else if ( propMap[ch][2] != -1 ) { + fcol = (float)propMap[ch][0] / 256.0f; + frow = (float)propMap[ch][1] / 256.0f; + fwidth = (float)propMap[ch][2] / 256.0f; + fheight = (float)PROP_HEIGHT / 256.0f; + aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); + } else { + aw = 0; + } + + ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); + s++; + } + + trap_R_SetColor( NULL ); +} + +/* +================= +UI_ProportionalSizeScale +================= +*/ +float UI_ProportionalSizeScale( int style ) { + if( style & UI_SMALLFONT ) { + return 0.75; + } + + return 1.00; +} + + +/* +================= +UI_DrawProportionalString +================= +*/ +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { + vec4_t drawcolor; + int width; + float sizeScale; + + sizeScale = UI_ProportionalSizeScale( style ); + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width / 2; + break; + + case UI_RIGHT: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); + } + + if ( style & UI_INVERSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); + return; + } + + if ( style & UI_PULSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); + + drawcolor[0] = color[0]; + drawcolor[1] = color[1]; + drawcolor[2] = color[2]; + drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); + return; + } + + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); +} +#endif // Q3STATIC diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c index 1ea447d..a29b856 100755 --- a/code/cgame/cg_effects.c +++ b/code/cgame/cg_effects.c @@ -1,718 +1,718 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_effects.c -- these functions generate localentities, usually as a result -// of event processing - -#include "cg_local.h" - - -/* -================== -CG_BubbleTrail - -Bullets shot underwater -================== -*/ -void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { - vec3_t move; - vec3_t vec; - float len; - int i; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - VectorCopy (start, move); - VectorSubtract (end, start, vec); - len = VectorNormalize (vec); - - // advance a random amount first - i = rand() % (int)spacing; - VectorMA( move, i, vec, move ); - - VectorScale (vec, spacing, vec); - - for ( ; i < len; i += spacing ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = LEF_PUFF_DONT_SCALE; - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = cg.time; - le->endTime = cg.time + 1000 + random() * 250; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - re = &le->refEntity; - re->shaderTime = cg.time / 1000.0f; - - re->reType = RT_SPRITE; - re->rotation = 0; - re->radius = 3; - re->customShader = cgs.media.waterBubbleShader; - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - re->shaderRGBA[3] = 0xff; - - le->color[3] = 1.0; - - le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time; - VectorCopy( move, le->pos.trBase ); - le->pos.trDelta[0] = crandom()*5; - le->pos.trDelta[1] = crandom()*5; - le->pos.trDelta[2] = crandom()*5 + 6; - - VectorAdd (move, vec, move); - } -} - -/* -===================== -CG_SmokePuff - -Adds a smoke puff or blood trail localEntity. -===================== -*/ -localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, - float radius, - float r, float g, float b, float a, - float duration, - int startTime, - int fadeInTime, - int leFlags, - qhandle_t hShader ) { - static int seed = 0x92; - localEntity_t *le; - refEntity_t *re; -// int fadeInTime = startTime + duration / 2; - - le = CG_AllocLocalEntity(); - le->leFlags = leFlags; - le->radius = radius; - - re = &le->refEntity; - re->rotation = Q_random( &seed ) * 360; - re->radius = radius; - re->shaderTime = startTime / 1000.0f; - - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = startTime; - le->fadeInTime = fadeInTime; - le->endTime = startTime + duration; - if ( fadeInTime > startTime ) { - le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); - } - else { - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - } - le->color[0] = r; - le->color[1] = g; - le->color[2] = b; - le->color[3] = a; - - - le->pos.trType = TR_LINEAR; - le->pos.trTime = startTime; - VectorCopy( vel, le->pos.trDelta ); - VectorCopy( p, le->pos.trBase ); - - VectorCopy( p, re->origin ); - re->customShader = hShader; - - // rage pro can't alpha fade, so use a different shader - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { - re->customShader = cgs.media.smokePuffRageProShader; - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - re->shaderRGBA[3] = 0xff; - } else { - re->shaderRGBA[0] = le->color[0] * 0xff; - re->shaderRGBA[1] = le->color[1] * 0xff; - re->shaderRGBA[2] = le->color[2] * 0xff; - re->shaderRGBA[3] = 0xff; - } - - re->reType = RT_SPRITE; - re->radius = le->radius; - - return le; -} - -/* -================== -CG_SpawnEffect - -Player teleporting in or out -================== -*/ -void CG_SpawnEffect( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_FADE_RGB; - le->startTime = cg.time; - le->endTime = cg.time + 500; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - -#ifndef MISSIONPACK - re->customShader = cgs.media.teleportEffectShader; -#endif - re->hModel = cgs.media.teleportEffectModel; - AxisClear( re->axis ); - - VectorCopy( org, re->origin ); -#ifdef MISSIONPACK - re->origin[2] += 16; -#else - re->origin[2] -= 24; -#endif -} - - -#ifdef MISSIONPACK -/* -=============== -CG_LightningBoltBeam -=============== -*/ -void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { - localEntity_t *le; - refEntity_t *beam; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_SHOWREFENTITY; - le->startTime = cg.time; - le->endTime = cg.time + 50; - - beam = &le->refEntity; - - VectorCopy( start, beam->origin ); - // this is the end point - VectorCopy( end, beam->oldorigin ); - - beam->reType = RT_LIGHTNING; - beam->customShader = cgs.media.lightningShader; -} - -/* -================== -CG_KamikazeEffect -================== -*/ -void CG_KamikazeEffect( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_KAMIKAZE; - le->startTime = cg.time; - le->endTime = cg.time + 3000;//2250; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - VectorClear(le->angles.trBase); - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.kamikazeEffectModel; - - VectorCopy( org, re->origin ); - -} - -/* -================== -CG_ObeliskExplode -================== -*/ -void CG_ObeliskExplode( vec3_t org, int entityNum ) { - localEntity_t *le; - vec3_t origin; - - // create an explosion - VectorCopy( org, origin ); - origin[2] += 64; - le = CG_MakeExplosion( origin, vec3_origin, - cgs.media.dishFlashModel, - cgs.media.rocketExplosionShader, - 600, qtrue ); - le->light = 300; - le->lightColor[0] = 1; - le->lightColor[1] = 0.75; - le->lightColor[2] = 0.0; -} - -/* -================== -CG_ObeliskPain -================== -*/ -void CG_ObeliskPain( vec3_t org ) { - float r; - sfxHandle_t sfx; - - // hit sound - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.obeliskHitSound1; - } else if ( r == 2 ) { - sfx = cgs.media.obeliskHitSound2; - } else { - sfx = cgs.media.obeliskHitSound3; - } - trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); -} - - -/* -================== -CG_InvulnerabilityImpact -================== -*/ -void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { - localEntity_t *le; - refEntity_t *re; - int r; - sfxHandle_t sfx; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_INVULIMPACT; - le->startTime = cg.time; - le->endTime = cg.time + 1000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.invulnerabilityImpactModel; - - VectorCopy( org, re->origin ); - AnglesToAxis( angles, re->axis ); - - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.invulnerabilityImpactSound1; - } else if ( r == 2 ) { - sfx = cgs.media.invulnerabilityImpactSound2; - } else { - sfx = cgs.media.invulnerabilityImpactSound3; - } - trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); -} - -/* -================== -CG_InvulnerabilityJuiced -================== -*/ -void CG_InvulnerabilityJuiced( vec3_t org ) { - localEntity_t *le; - refEntity_t *re; - vec3_t angles; - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_INVULJUICED; - le->startTime = cg.time; - le->endTime = cg.time + 10000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - - re = &le->refEntity; - - re->reType = RT_MODEL; - re->shaderTime = cg.time / 1000.0f; - - re->hModel = cgs.media.invulnerabilityJuicedModel; - - VectorCopy( org, re->origin ); - VectorClear(angles); - AnglesToAxis( angles, re->axis ); - - trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); -} - -#endif - -/* -================== -CG_ScorePlum -================== -*/ -void CG_ScorePlum( int client, vec3_t org, int score ) { - localEntity_t *le; - refEntity_t *re; - vec3_t angles; - static vec3_t lastPos; - - // only visualize for the client that scored - if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { - return; - } - - le = CG_AllocLocalEntity(); - le->leFlags = 0; - le->leType = LE_SCOREPLUM; - le->startTime = cg.time; - le->endTime = cg.time + 4000; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - - - le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; - le->radius = score; - - VectorCopy( org, le->pos.trBase ); - if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { - le->pos.trBase[2] -= 20; - } - - //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); - VectorCopy(org, lastPos); - - - re = &le->refEntity; - - re->reType = RT_SPRITE; - re->radius = 16; - - VectorClear(angles); - AnglesToAxis( angles, re->axis ); -} - - -/* -==================== -CG_MakeExplosion -==================== -*/ -localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, - qhandle_t hModel, qhandle_t shader, - int msec, qboolean isSprite ) { - float ang; - localEntity_t *ex; - int offset; - vec3_t tmpVec, newOrigin; - - if ( msec <= 0 ) { - CG_Error( "CG_MakeExplosion: msec = %i", msec ); - } - - // skew the time a bit so they aren't all in sync - offset = rand() & 63; - - ex = CG_AllocLocalEntity(); - if ( isSprite ) { - ex->leType = LE_SPRITE_EXPLOSION; - - // randomly rotate sprite orientation - ex->refEntity.rotation = rand() % 360; - VectorScale( dir, 16, tmpVec ); - VectorAdd( tmpVec, origin, newOrigin ); - } else { - ex->leType = LE_EXPLOSION; - VectorCopy( origin, newOrigin ); - - // set axis with random rotate - if ( !dir ) { - AxisClear( ex->refEntity.axis ); - } else { - ang = rand() % 360; - VectorCopy( dir, ex->refEntity.axis[0] ); - RotateAroundDirection( ex->refEntity.axis, ang ); - } - } - - ex->startTime = cg.time - offset; - ex->endTime = ex->startTime + msec; - - // bias the time so all shader effects start correctly - ex->refEntity.shaderTime = ex->startTime / 1000.0f; - - ex->refEntity.hModel = hModel; - ex->refEntity.customShader = shader; - - // set origin - VectorCopy( newOrigin, ex->refEntity.origin ); - VectorCopy( newOrigin, ex->refEntity.oldorigin ); - - ex->color[0] = ex->color[1] = ex->color[2] = 1.0; - - return ex; -} - - -/* -================= -CG_Bleed - -This is the spurt of blood when a character gets hit -================= -*/ -void CG_Bleed( vec3_t origin, int entityNum ) { - localEntity_t *ex; - - if ( !cg_blood.integer ) { - return; - } - - ex = CG_AllocLocalEntity(); - ex->leType = LE_EXPLOSION; - - ex->startTime = cg.time; - ex->endTime = ex->startTime + 500; - - VectorCopy ( origin, ex->refEntity.origin); - ex->refEntity.reType = RT_SPRITE; - ex->refEntity.rotation = rand() % 360; - ex->refEntity.radius = 24; - - ex->refEntity.customShader = cgs.media.bloodExplosionShader; - - // don't show player's own blood in view - if ( entityNum == cg.snap->ps.clientNum ) { - ex->refEntity.renderfx |= RF_THIRD_PERSON; - } -} - - - -/* -================== -CG_LaunchGib -================== -*/ -void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + 5000 + random() * 3000; - - VectorCopy( origin, re->origin ); - AxisCopy( axisDefault, re->axis ); - re->hModel = hModel; - - le->pos.trType = TR_GRAVITY; - VectorCopy( origin, le->pos.trBase ); - VectorCopy( velocity, le->pos.trDelta ); - le->pos.trTime = cg.time; - - le->bounceFactor = 0.6f; - - le->leBounceSoundType = LEBS_BLOOD; - le->leMarkType = LEMT_BLOOD; -} - -/* -=================== -CG_GibPlayer - -Generated a bunch of gibs launching out from the bodies location -=================== -*/ -#define GIB_VELOCITY 250 -#define GIB_JUMP 250 -void CG_GibPlayer( vec3_t playerOrigin ) { - vec3_t origin, velocity; - - if ( !cg_blood.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - if ( rand() & 1 ) { - CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); - } else { - CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); - } - - // allow gibs to be turned off for speed - if ( !cg_gibs.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibArm ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibChest ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFist ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*GIB_VELOCITY; - velocity[1] = crandom()*GIB_VELOCITY; - velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; - CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); -} - -/* -================== -CG_LaunchGib -================== -*/ -void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { - localEntity_t *le; - refEntity_t *re; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + 10000 + random() * 6000; - - VectorCopy( origin, re->origin ); - AxisCopy( axisDefault, re->axis ); - re->hModel = hModel; - - le->pos.trType = TR_GRAVITY; - VectorCopy( origin, le->pos.trBase ); - VectorCopy( velocity, le->pos.trDelta ); - le->pos.trTime = cg.time; - - le->bounceFactor = 0.1f; - - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; -} - -#define EXP_VELOCITY 100 -#define EXP_JUMP 150 -/* -=================== -CG_GibPlayer - -Generated a bunch of gibs launching out from the bodies location -=================== -*/ -void CG_BigExplode( vec3_t playerOrigin ) { - vec3_t origin, velocity; - - if ( !cg_blood.integer ) { - return; - } - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY; - velocity[1] = crandom()*EXP_VELOCITY; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY; - velocity[1] = crandom()*EXP_VELOCITY; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*1.5; - velocity[1] = crandom()*EXP_VELOCITY*1.5; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*2.0; - velocity[1] = crandom()*EXP_VELOCITY*2.0; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); - - VectorCopy( playerOrigin, origin ); - velocity[0] = crandom()*EXP_VELOCITY*2.5; - velocity[1] = crandom()*EXP_VELOCITY*2.5; - velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; - CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale (vec, spacing, vec); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*5; + le->pos.trDelta[1] = crandom()*5; + le->pos.trDelta[2] = crandom()*5 + 6; + + VectorAdd (move, vec, move); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. +===================== +*/ +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; +// int fadeInTime = startTime + duration / 2; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->fadeInTime = fadeInTime; + le->endTime = startTime + duration; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->radius = le->radius; + + return le; +} + +/* +================== +CG_SpawnEffect + +Player teleporting in or out +================== +*/ +void CG_SpawnEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + +#ifndef MISSIONPACK + re->customShader = cgs.media.teleportEffectShader; +#endif + re->hModel = cgs.media.teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); +#ifdef MISSIONPACK + re->origin[2] += 16; +#else + re->origin[2] -= 24; +#endif +} + + +#ifdef MISSIONPACK +/* +=============== +CG_LightningBoltBeam +=============== +*/ +void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { + localEntity_t *le; + refEntity_t *beam; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SHOWREFENTITY; + le->startTime = cg.time; + le->endTime = cg.time + 50; + + beam = &le->refEntity; + + VectorCopy( start, beam->origin ); + // this is the end point + VectorCopy( end, beam->oldorigin ); + + beam->reType = RT_LIGHTNING; + beam->customShader = cgs.media.lightningShader; +} + +/* +================== +CG_KamikazeEffect +================== +*/ +void CG_KamikazeEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_KAMIKAZE; + le->startTime = cg.time; + le->endTime = cg.time + 3000;//2250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + VectorClear(le->angles.trBase); + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.kamikazeEffectModel; + + VectorCopy( org, re->origin ); + +} + +/* +================== +CG_ObeliskExplode +================== +*/ +void CG_ObeliskExplode( vec3_t org, int entityNum ) { + localEntity_t *le; + vec3_t origin; + + // create an explosion + VectorCopy( org, origin ); + origin[2] += 64; + le = CG_MakeExplosion( origin, vec3_origin, + cgs.media.dishFlashModel, + cgs.media.rocketExplosionShader, + 600, qtrue ); + le->light = 300; + le->lightColor[0] = 1; + le->lightColor[1] = 0.75; + le->lightColor[2] = 0.0; +} + +/* +================== +CG_ObeliskPain +================== +*/ +void CG_ObeliskPain( vec3_t org ) { + float r; + sfxHandle_t sfx; + + // hit sound + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.obeliskHitSound1; + } else if ( r == 2 ) { + sfx = cgs.media.obeliskHitSound2; + } else { + sfx = cgs.media.obeliskHitSound3; + } + trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + + +/* +================== +CG_InvulnerabilityImpact +================== +*/ +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { + localEntity_t *le; + refEntity_t *re; + int r; + sfxHandle_t sfx; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULIMPACT; + le->startTime = cg.time; + le->endTime = cg.time + 1000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityImpactModel; + + VectorCopy( org, re->origin ); + AnglesToAxis( angles, re->axis ); + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.invulnerabilityImpactSound1; + } else if ( r == 2 ) { + sfx = cgs.media.invulnerabilityImpactSound2; + } else { + sfx = cgs.media.invulnerabilityImpactSound3; + } + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + +/* +================== +CG_InvulnerabilityJuiced +================== +*/ +void CG_InvulnerabilityJuiced( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULJUICED; + le->startTime = cg.time; + le->endTime = cg.time + 10000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityJuicedModel; + + VectorCopy( org, re->origin ); + VectorClear(angles); + AnglesToAxis( angles, re->axis ); + + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); +} + +#endif + +/* +================== +CG_ScorePlum +================== +*/ +void CG_ScorePlum( int client, vec3_t org, int score ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + static vec3_t lastPos; + + // only visualize for the client that scored + if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { + return; + } + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SCOREPLUM; + le->startTime = cg.time; + le->endTime = cg.time + 4000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + le->radius = score; + + VectorCopy( org, le->pos.trBase ); + if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { + le->pos.trBase[2] -= 20; + } + + //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); + VectorCopy(org, lastPos); + + + re = &le->refEntity; + + re->reType = RT_SPRITE; + re->radius = 16; + + VectorClear(angles); + AnglesToAxis( angles, re->axis ); +} + + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, + int msec, qboolean isSprite ) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + ex->refEntity.shaderTime = ex->startTime / 1000.0f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, int entityNum ) { + localEntity_t *ex; + + if ( !cg_blood.integer ) { + return; + } + + ex = CG_AllocLocalEntity(); + ex->leType = LE_EXPLOSION; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 500; + + VectorCopy ( origin, ex->refEntity.origin); + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = rand() % 360; + ex->refEntity.radius = 24; + + ex->refEntity.customShader = cgs.media.bloodExplosionShader; + + // don't show player's own blood in view + if ( entityNum == cg.snap->ps.clientNum ) { + ex->refEntity.renderfx |= RF_THIRD_PERSON; + } +} + + + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.6f; + + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; +} + +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +#define GIB_VELOCITY 250 +#define GIB_JUMP 250 +void CG_GibPlayer( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + if ( rand() & 1 ) { + CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + } else { + CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + } + + // allow gibs to be turned off for speed + if ( !cg_gibs.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); +} + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 10000 + random() * 6000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.1f; + + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +#define EXP_VELOCITY 100 +#define EXP_JUMP 150 +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +void CG_BigExplode( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*1.5; + velocity[1] = crandom()*EXP_VELOCITY*1.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.0; + velocity[1] = crandom()*EXP_VELOCITY*2.0; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.5; + velocity[1] = crandom()*EXP_VELOCITY*2.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); +} + diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c index 5855089..aa6398f 100755 --- a/code/cgame/cg_ents.c +++ b/code/cgame/cg_ents.c @@ -1,1037 +1,1037 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_ents.c -- present snapshot entities, happens every single frame - -#include "cg_local.h" - - -/* -====================== -CG_PositionEntityOnTag - -Modifies the entities position and axis by the given -tag location -====================== -*/ -void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - - // lerp the tag - trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // had to cast away the const to avoid compiler problems... - MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); - entity->backlerp = parent->backlerp; -} - - -/* -====================== -CG_PositionRotatedEntityOnTag - -Modifies the entities position and axis by the given -tag location -====================== -*/ -void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ) { - int i; - orientation_t lerped; - vec3_t tempAxis[3]; - -//AxisClear( entity->axis ); - // lerp the tag - trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, - 1.0 - parent->backlerp, tagName ); - - // FIXME: allow origin offsets along tag? - VectorCopy( parent->origin, entity->origin ); - for ( i = 0 ; i < 3 ; i++ ) { - VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); - } - - // had to cast away the const to avoid compiler problems... - MatrixMultiply( entity->axis, lerped.axis, tempAxis ); - MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); -} - - - -/* -========================================================================== - -FUNCTIONS CALLED EACH FRAME - -========================================================================== -*/ - -/* -====================== -CG_SetEntitySoundPosition - -Also called by event processing code -====================== -*/ -void CG_SetEntitySoundPosition( centity_t *cent ) { - if ( cent->currentState.solid == SOLID_BMODEL ) { - vec3_t origin; - float *v; - - v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; - VectorAdd( cent->lerpOrigin, v, origin ); - trap_S_UpdateEntityPosition( cent->currentState.number, origin ); - } else { - trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); - } -} - -/* -================== -CG_EntityEffects - -Add continuous entity effects, like local entity emission and lighting -================== -*/ -static void CG_EntityEffects( centity_t *cent ) { - - // update sound origins - CG_SetEntitySoundPosition( cent ); - - // add loop sound - if ( cent->currentState.loopSound ) { - if (cent->currentState.eType != ET_SPEAKER) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, - cgs.gameSounds[ cent->currentState.loopSound ] ); - } else { - trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, - cgs.gameSounds[ cent->currentState.loopSound ] ); - } - } - - - // constant light glow - if ( cent->currentState.constantLight ) { - int cl; - int i, r, g, b; - - cl = cent->currentState.constantLight; - r = cl & 255; - g = ( cl >> 8 ) & 255; - b = ( cl >> 16 ) & 255; - i = ( ( cl >> 24 ) & 255 ) * 4; - trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); - } - -} - - -/* -================== -CG_General -================== -*/ -static void CG_General( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // if set to invisible, skip - if (!s1->modelindex) { - return; - } - - memset (&ent, 0, sizeof(ent)); - - // set frame - - ent.frame = s1->frame; - ent.oldframe = ent.frame; - ent.backlerp = 0; - - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - ent.hModel = cgs.gameModels[s1->modelindex]; - - // player model - if (s1->number == cg.snap->ps.clientNum) { - ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors - } - - // convert angles to axis - AnglesToAxis( cent->lerpAngles, ent.axis ); - - // add to refresh list - trap_R_AddRefEntityToScene (&ent); -} - -/* -================== -CG_Speaker - -Speaker entities can automatically play sounds -================== -*/ -static void CG_Speaker( centity_t *cent ) { - if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... - return; // not auto triggering - } - - if ( cg.time < cent->miscTime ) { - return; - } - - trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); - - // ent->s.frame = ent->wait * 10; - // ent->s.clientNum = ent->random * 10; - cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); -} - -/* -================== -CG_Item -================== -*/ -static void CG_Item( centity_t *cent ) { - refEntity_t ent; - entityState_t *es; - gitem_t *item; - int msec; - float frac; - float scale; - weaponInfo_t *wi; - - es = ¢->currentState; - if ( es->modelindex >= bg_numItems ) { - CG_Error( "Bad item index %i on entity", es->modelindex ); - } - - // if set to invisible, skip - if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { - return; - } - - item = &bg_itemlist[ es->modelindex ]; - if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { - memset( &ent, 0, sizeof( ent ) ); - ent.reType = RT_SPRITE; - VectorCopy( cent->lerpOrigin, ent.origin ); - ent.radius = 14; - ent.customShader = cg_items[es->modelindex].icon; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 255; - trap_R_AddRefEntityToScene(&ent); - return; - } - - // items bob up and down continuously - scale = 0.005 + cent->currentState.number * 0.00001; - cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; - - memset (&ent, 0, sizeof(ent)); - - // autorotate at one of two speeds - if ( item->giType == IT_HEALTH ) { - VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); - AxisCopy( cg.autoAxisFast, ent.axis ); - } else { - VectorCopy( cg.autoAngles, cent->lerpAngles ); - AxisCopy( cg.autoAxis, ent.axis ); - } - - wi = NULL; - // the weapons have their origin where they attatch to player - // models, so we need to offset them or they will rotate - // eccentricly - if ( item->giType == IT_WEAPON ) { - wi = &cg_weapons[item->giTag]; - cent->lerpOrigin[0] -= - wi->weaponMidpoint[0] * ent.axis[0][0] + - wi->weaponMidpoint[1] * ent.axis[1][0] + - wi->weaponMidpoint[2] * ent.axis[2][0]; - cent->lerpOrigin[1] -= - wi->weaponMidpoint[0] * ent.axis[0][1] + - wi->weaponMidpoint[1] * ent.axis[1][1] + - wi->weaponMidpoint[2] * ent.axis[2][1]; - cent->lerpOrigin[2] -= - wi->weaponMidpoint[0] * ent.axis[0][2] + - wi->weaponMidpoint[1] * ent.axis[1][2] + - wi->weaponMidpoint[2] * ent.axis[2][2]; - - cent->lerpOrigin[2] += 8; // an extra height boost - } - - ent.hModel = cg_items[es->modelindex].models[0]; - - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - ent.nonNormalizedAxes = qfalse; - - // if just respawned, slowly scale up - msec = cg.time - cent->miscTime; - if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { - frac = (float)msec / ITEM_SCALEUP_TIME; - VectorScale( ent.axis[0], frac, ent.axis[0] ); - VectorScale( ent.axis[1], frac, ent.axis[1] ); - VectorScale( ent.axis[2], frac, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } else { - frac = 1.0; - } - - // items without glow textures need to keep a minimum light value - // so they are always visible - if ( ( item->giType == IT_WEAPON ) || - ( item->giType == IT_ARMOR ) ) { - ent.renderfx |= RF_MINLIGHT; - } - - // increase the size of the weapons when they are presented as items - if ( item->giType == IT_WEAPON ) { - VectorScale( ent.axis[0], 1.5, ent.axis[0] ); - VectorScale( ent.axis[1], 1.5, ent.axis[1] ); - VectorScale( ent.axis[2], 1.5, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; -#ifdef MISSIONPACK - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); -#endif - } - -#ifdef MISSIONPACK - if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { - VectorScale( ent.axis[0], 2, ent.axis[0] ); - VectorScale( ent.axis[1], 2, ent.axis[1] ); - VectorScale( ent.axis[2], 2, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } -#endif - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); - -#ifdef MISSIONPACK - if ( item->giType == IT_WEAPON && wi->barrelModel ) { - refEntity_t barrel; - - memset( &barrel, 0, sizeof( barrel ) ); - - barrel.hModel = wi->barrelModel; - - VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); - barrel.shadowPlane = ent.shadowPlane; - barrel.renderfx = ent.renderfx; - - CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); - - AxisCopy( ent.axis, barrel.axis ); - barrel.nonNormalizedAxes = ent.nonNormalizedAxes; - - trap_R_AddRefEntityToScene( &barrel ); - } -#endif - - // accompanying rings / spheres for powerups - if ( !cg_simpleItems.integer ) - { - vec3_t spinAngles; - - VectorClear( spinAngles ); - - if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) - { - if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) - { - if ( item->giType == IT_POWERUP ) - { - ent.origin[2] += 12; - spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; - } - AnglesToAxis( spinAngles, ent.axis ); - - // scale up if respawning - if ( frac != 1.0 ) { - VectorScale( ent.axis[0], frac, ent.axis[0] ); - VectorScale( ent.axis[1], frac, ent.axis[1] ); - VectorScale( ent.axis[2], frac, ent.axis[2] ); - ent.nonNormalizedAxes = qtrue; - } - trap_R_AddRefEntityToScene( &ent ); - } - } - } -} - -//============================================================================ - -/* -=============== -CG_Missile -=============== -*/ -static void CG_Missile( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - const weaponInfo_t *weapon; -// int col; - - s1 = ¢->currentState; - if ( s1->weapon > WP_NUM_WEAPONS ) { - s1->weapon = 0; - } - weapon = &cg_weapons[s1->weapon]; - - // calculate the axis - VectorCopy( s1->angles, cent->lerpAngles); - - // add trails - if ( weapon->missileTrailFunc ) - { - weapon->missileTrailFunc( cent, weapon ); - } -/* - if ( cent->currentState.modelindex == TEAM_RED ) { - col = 1; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - col = 2; - } - else { - col = 0; - } - - // add dynamic light - if ( weapon->missileDlight ) { - trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, - weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); - } -*/ - // add dynamic light - if ( weapon->missileDlight ) { - trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, - weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); - } - - // add missile sound - if ( weapon->missileSound ) { - vec3_t velocity; - - BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); - - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); - } - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - if ( cent->currentState.weapon == WP_PLASMAGUN ) { - ent.reType = RT_SPRITE; - ent.radius = 16; - ent.rotation = 0; - ent.customShader = cgs.media.plasmaBallShader; - trap_R_AddRefEntityToScene( &ent ); - return; - } - - // flicker between two skins - ent.skinNum = cg.clientFrame & 1; - ent.hModel = weapon->missileModel; - ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; - -#ifdef MISSIONPACK - if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { - if (s1->generic1 == TEAM_BLUE) { - ent.hModel = cgs.media.blueProxMine; - } - } -#endif - - // convert direction of travel into axis - if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { - ent.axis[0][2] = 1; - } - - // spin as it moves - if ( s1->pos.trType != TR_STATIONARY ) { - RotateAroundDirection( ent.axis, cg.time / 4 ); - } else { -#ifdef MISSIONPACK - if ( s1->weapon == WP_PROX_LAUNCHER ) { - AnglesToAxis( cent->lerpAngles, ent.axis ); - } - else -#endif - { - RotateAroundDirection( ent.axis, s1->time ); - } - } - - // add to refresh list, possibly with quad glow - CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); -} - -/* -=============== -CG_Grapple - -This is called when the grapple is sitting up against the wall -=============== -*/ -static void CG_Grapple( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - const weaponInfo_t *weapon; - - s1 = ¢->currentState; - if ( s1->weapon > WP_NUM_WEAPONS ) { - s1->weapon = 0; - } - weapon = &cg_weapons[s1->weapon]; - - // calculate the axis - VectorCopy( s1->angles, cent->lerpAngles); - -#if 0 // FIXME add grapple pull sound here..? - // add missile sound - if ( weapon->missileSound ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); - } -#endif - - // Will draw cable if needed - CG_GrappleTrail ( cent, weapon ); - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - - // flicker between two skins - ent.skinNum = cg.clientFrame & 1; - ent.hModel = weapon->missileModel; - ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; - - // convert direction of travel into axis - if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { - ent.axis[0][2] = 1; - } - - trap_R_AddRefEntityToScene( &ent ); -} - -/* -=============== -CG_Mover -=============== -*/ -static void CG_Mover( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin); - VectorCopy( cent->lerpOrigin, ent.oldorigin); - AnglesToAxis( cent->lerpAngles, ent.axis ); - - ent.renderfx = RF_NOSHADOW; - - // flicker between two skins (FIXME?) - ent.skinNum = ( cg.time >> 6 ) & 1; - - // get the model, either as a bmodel or a modelindex - if ( s1->solid == SOLID_BMODEL ) { - ent.hModel = cgs.inlineDrawModel[s1->modelindex]; - } else { - ent.hModel = cgs.gameModels[s1->modelindex]; - } - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); - - // add the secondary model - if ( s1->modelindex2 ) { - ent.skinNum = 0; - ent.hModel = cgs.gameModels[s1->modelindex2]; - trap_R_AddRefEntityToScene(&ent); - } - -} - -/* -=============== -CG_Beam - -Also called as an event -=============== -*/ -void CG_Beam( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( s1->pos.trBase, ent.origin ); - VectorCopy( s1->origin2, ent.oldorigin ); - AxisClear( ent.axis ); - ent.reType = RT_BEAM; - - ent.renderfx = RF_NOSHADOW; - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); -} - - -/* -=============== -CG_Portal -=============== -*/ -static void CG_Portal( centity_t *cent ) { - refEntity_t ent; - entityState_t *s1; - - s1 = ¢->currentState; - - // create the render entity - memset (&ent, 0, sizeof(ent)); - VectorCopy( cent->lerpOrigin, ent.origin ); - VectorCopy( s1->origin2, ent.oldorigin ); - ByteToDir( s1->eventParm, ent.axis[0] ); - PerpendicularVector( ent.axis[1], ent.axis[0] ); - - // negating this tends to get the directions like they want - // we really should have a camera roll value - VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); - - CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); - ent.reType = RT_PORTALSURFACE; - ent.oldframe = s1->powerups; - ent.frame = s1->frame; // rotation speed - ent.skinNum = s1->clientNum/256.0 * 360; // roll offset - - // add to refresh list - trap_R_AddRefEntityToScene(&ent); -} - - -/* -========================= -CG_AdjustPositionForMover - -Also called by client movement prediction code -========================= -*/ -void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { - centity_t *cent; - vec3_t oldOrigin, origin, deltaOrigin; - vec3_t oldAngles, angles, deltaAngles; - - if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { - VectorCopy( in, out ); - return; - } - - cent = &cg_entities[ moverNum ]; - if ( cent->currentState.eType != ET_MOVER ) { - VectorCopy( in, out ); - return; - } - - BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); - - BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); - BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); - - VectorSubtract( origin, oldOrigin, deltaOrigin ); - VectorSubtract( angles, oldAngles, deltaAngles ); - - VectorAdd( in, deltaOrigin, out ); - - // FIXME: origin change when on a rotating object -} - - -/* -============================= -CG_InterpolateEntityPosition -============================= -*/ -static void CG_InterpolateEntityPosition( centity_t *cent ) { - vec3_t current, next; - float f; - - // it would be an internal error to find an entity that interpolates without - // a snapshot ahead of the current one - if ( cg.nextSnap == NULL ) { - CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); - } - - f = cg.frameInterpolation; - - // this will linearize a sine or parabolic curve, but it is important - // to not extrapolate player positions if more recent data is available - BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); - BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); - - cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); - cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); - cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); - - BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); - BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); - - cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); - cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); - cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); - -} - -/* -=============== -CG_CalcEntityLerpPositions - -=============== -*/ -static void CG_CalcEntityLerpPositions( centity_t *cent ) { - - // if this player does not want to see extrapolated players - if ( !cg_smoothClients.integer ) { - // make sure the clients use TR_INTERPOLATE - if ( cent->currentState.number < MAX_CLIENTS ) { - cent->currentState.pos.trType = TR_INTERPOLATE; - cent->nextState.pos.trType = TR_INTERPOLATE; - } - } - - if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { - CG_InterpolateEntityPosition( cent ); - return; - } - - // first see if we can interpolate between two snaps for - // linear extrapolated clients - if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && - cent->currentState.number < MAX_CLIENTS) { - CG_InterpolateEntityPosition( cent ); - return; - } - - // just use the current frame and evaluate as best we can - BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); - - // adjust for riding a mover if it wasn't rolled into the predicted - // player state - if ( cent != &cg.predictedPlayerEntity ) { - CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, - cg.snap->serverTime, cg.time, cent->lerpOrigin ); - } -} - -/* -=============== -CG_TeamBase -=============== -*/ -static void CG_TeamBase( centity_t *cent ) { - refEntity_t model; -#ifdef MISSIONPACK - vec3_t angles; - int t, h; - float c; - - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { -#else - if ( cgs.gametype == GT_CTF) { -#endif - // show the flag base - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - if ( cent->currentState.modelindex == TEAM_RED ) { - model.hModel = cgs.media.redFlagBaseModel; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - model.hModel = cgs.media.blueFlagBaseModel; - } - else { - model.hModel = cgs.media.neutralFlagBaseModel; - } - trap_R_AddRefEntityToScene( &model ); - } -#ifdef MISSIONPACK - else if ( cgs.gametype == GT_OBELISK ) { - // show the obelisk - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - - model.hModel = cgs.media.overloadBaseModel; - trap_R_AddRefEntityToScene( &model ); - // if hit - if ( cent->currentState.frame == 1) { - // show hit model - // modelindex2 is the health value of the obelisk - c = cent->currentState.modelindex2; - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = c; - model.shaderRGBA[2] = c; - model.shaderRGBA[3] = 0xff; - // - model.hModel = cgs.media.overloadEnergyModel; - trap_R_AddRefEntityToScene( &model ); - } - // if respawning - if ( cent->currentState.frame == 2) { - if ( !cent->miscTime ) { - cent->miscTime = cg.time; - } - t = cg.time - cent->miscTime; - h = (cg_obeliskRespawnDelay.integer - 5) * 1000; - // - if (t > h) { - c = (float) (t - h) / h; - if (c > 1) - c = 1; - } - else { - c = 0; - } - // show the lights - AnglesToAxis( cent->currentState.angles, model.axis ); - // - model.shaderRGBA[0] = c * 0xff; - model.shaderRGBA[1] = c * 0xff; - model.shaderRGBA[2] = c * 0xff; - model.shaderRGBA[3] = c * 0xff; - - model.hModel = cgs.media.overloadLightsModel; - trap_R_AddRefEntityToScene( &model ); - // show the target - if (t > h) { - if ( !cent->muzzleFlashTime ) { - trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); - cent->muzzleFlashTime = 1; - } - VectorCopy(cent->currentState.angles, angles); - angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; - AnglesToAxis( angles, model.axis ); - - VectorScale( model.axis[0], c, model.axis[0]); - VectorScale( model.axis[1], c, model.axis[1]); - VectorScale( model.axis[2], c, model.axis[2]); - - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = 0xff; - model.shaderRGBA[2] = 0xff; - model.shaderRGBA[3] = 0xff; - // - model.origin[2] += 56; - model.hModel = cgs.media.overloadTargetModel; - trap_R_AddRefEntityToScene( &model ); - } - else { - //FIXME: show animated smoke - } - } - else { - cent->miscTime = 0; - cent->muzzleFlashTime = 0; - // modelindex2 is the health value of the obelisk - c = cent->currentState.modelindex2; - model.shaderRGBA[0] = 0xff; - model.shaderRGBA[1] = c; - model.shaderRGBA[2] = c; - model.shaderRGBA[3] = 0xff; - // show the lights - model.hModel = cgs.media.overloadLightsModel; - trap_R_AddRefEntityToScene( &model ); - // show the target - model.origin[2] += 56; - model.hModel = cgs.media.overloadTargetModel; - trap_R_AddRefEntityToScene( &model ); - } - } - else if ( cgs.gametype == GT_HARVESTER ) { - // show harvester model - memset(&model, 0, sizeof(model)); - model.reType = RT_MODEL; - VectorCopy( cent->lerpOrigin, model.lightingOrigin ); - VectorCopy( cent->lerpOrigin, model.origin ); - AnglesToAxis( cent->currentState.angles, model.axis ); - - if ( cent->currentState.modelindex == TEAM_RED ) { - model.hModel = cgs.media.harvesterModel; - model.customSkin = cgs.media.harvesterRedSkin; - } - else if ( cent->currentState.modelindex == TEAM_BLUE ) { - model.hModel = cgs.media.harvesterModel; - model.customSkin = cgs.media.harvesterBlueSkin; - } - else { - model.hModel = cgs.media.harvesterNeutralModel; - model.customSkin = 0; - } - trap_R_AddRefEntityToScene( &model ); - } -#endif -} - -/* -=============== -CG_AddCEntity - -=============== -*/ -static void CG_AddCEntity( centity_t *cent ) { - // event-only entities will have been dealt with already - if ( cent->currentState.eType >= ET_EVENTS ) { - return; - } - - // calculate the current origin - CG_CalcEntityLerpPositions( cent ); - - // add automatic effects - CG_EntityEffects( cent ); - - switch ( cent->currentState.eType ) { - default: - CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); - break; - case ET_INVISIBLE: - case ET_PUSH_TRIGGER: - case ET_TELEPORT_TRIGGER: - break; - case ET_GENERAL: - CG_General( cent ); - break; - case ET_PLAYER: - CG_Player( cent ); - break; - case ET_ITEM: - CG_Item( cent ); - break; - case ET_MISSILE: - CG_Missile( cent ); - break; - case ET_MOVER: - CG_Mover( cent ); - break; - case ET_BEAM: - CG_Beam( cent ); - break; - case ET_PORTAL: - CG_Portal( cent ); - break; - case ET_SPEAKER: - CG_Speaker( cent ); - break; - case ET_GRAPPLE: - CG_Grapple( cent ); - break; - case ET_TEAM: - CG_TeamBase( cent ); - break; - } -} - -/* -=============== -CG_AddPacketEntities - -=============== -*/ -void CG_AddPacketEntities( void ) { - int num; - centity_t *cent; - playerState_t *ps; - - // set cg.frameInterpolation - if ( cg.nextSnap ) { - int delta; - - delta = (cg.nextSnap->serverTime - cg.snap->serverTime); - if ( delta == 0 ) { - cg.frameInterpolation = 0; - } else { - cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; - } - } else { - cg.frameInterpolation = 0; // actually, it should never be used, because - // no entities should be marked as interpolating - } - - // the auto-rotating items will all have the same axis - cg.autoAngles[0] = 0; - cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; - cg.autoAngles[2] = 0; - - cg.autoAnglesFast[0] = 0; - cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; - cg.autoAnglesFast[2] = 0; - - AnglesToAxis( cg.autoAngles, cg.autoAxis ); - AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); - - // generate and add the entity from the playerstate - ps = &cg.predictedPlayerState; - BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); - CG_AddCEntity( &cg.predictedPlayerEntity ); - - // lerp the non-predicted value for lightning gun origins - CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); - - // add each entity sent over by the server - for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { - cent = &cg_entities[ cg.snap->entities[ num ].number ]; - CG_AddCEntity( cent ); - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" + + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } else { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + + // update sound origins + CG_SetEntitySoundPosition( cent ); + + // add loop sound + if ( cent->currentState.loopSound ) { + if (cent->currentState.eType != ET_SPEAKER) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } else { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + } + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + memset (&ent, 0, sizeof(ent)); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg.time < cent->miscTime ) { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float frac; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 14; + ent.customShader = cg_items[es->modelindex].icon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(&ent); + return; + } + + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + // autorotate at one of two speeds + if ( item->giType == IT_HEALTH ) { + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); + } else { + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } + + wi = NULL; + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if ( item->giType == IT_WEAPON ) { + wi = &cg_weapons[item->giTag]; + cent->lerpOrigin[0] -= + wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= + wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= + wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + + cent->lerpOrigin[2] += 8; // an extra height boost + } + + ent.hModel = cg_items[es->modelindex].models[0]; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { + frac = (float)msec / ITEM_SCALEUP_TIME; + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } else { + frac = 1.0; + } + + // items without glow textures need to keep a minimum light value + // so they are always visible + if ( ( item->giType == IT_WEAPON ) || + ( item->giType == IT_ARMOR ) ) { + ent.renderfx |= RF_MINLIGHT; + } + + // increase the size of the weapons when they are presented as items + if ( item->giType == IT_WEAPON ) { + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; +#ifdef MISSIONPACK + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); +#endif + } + +#ifdef MISSIONPACK + if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { + VectorScale( ent.axis[0], 2, ent.axis[0] ); + VectorScale( ent.axis[1], 2, ent.axis[1] ); + VectorScale( ent.axis[2], 2, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } +#endif + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + +#ifdef MISSIONPACK + if ( item->giType == IT_WEAPON && wi->barrelModel ) { + refEntity_t barrel; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } +#endif + + // accompanying rings / spheres for powerups + if ( !cg_simpleItems.integer ) + { + vec3_t spinAngles; + + VectorClear( spinAngles ); + + if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) + { + if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) + { + if ( item->giType == IT_POWERUP ) + { + ent.origin[2] += 12; + spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; + } + AnglesToAxis( spinAngles, ent.axis ); + + // scale up if respawning + if ( frac != 1.0 ) { + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + trap_R_AddRefEntityToScene( &ent ); + } + } + } +} + +//============================================================================ + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; +// int col; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } +/* + if ( cent->currentState.modelindex == TEAM_RED ) { + col = 1; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + col = 2; + } + else { + col = 0; + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); + } +*/ + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if ( cent->currentState.weapon == WP_PLASMAGUN ) { + ent.reType = RT_SPRITE; + ent.radius = 16; + ent.rotation = 0; + ent.customShader = cgs.media.plasmaBallShader; + trap_R_AddRefEntityToScene( &ent ); + return; + } + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { + if (s1->generic1 == TEAM_BLUE) { + ent.hModel = cgs.media.blueProxMine; + } + } +#endif + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) { + RotateAroundDirection( ent.axis, cg.time / 4 ); + } else { +#ifdef MISSIONPACK + if ( s1->weapon == WP_PROX_LAUNCHER ) { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + else +#endif + { + RotateAroundDirection( ent.axis, s1->time ); + } + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); +} + +/* +=============== +CG_Grapple + +This is called when the grapple is sitting up against the wall +=============== +*/ +static void CG_Grapple( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + +#if 0 // FIXME add grapple pull sound here..? + // add missile sound + if ( weapon->missileSound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); + } +#endif + + // Will draw cable if needed + CG_GrappleTrail ( cent, weapon ); + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + trap_R_AddRefEntityToScene(&ent); + } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->powerups; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum/256.0 * 360; // roll offset + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + if ( cent->currentState.eType != ET_MOVER ) { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) { + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) { + CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) { + + // if this player does not want to see extrapolated players + if ( !cg_smoothClients.integer ) { + // make sure the clients use TR_INTERPOLATE + if ( cent->currentState.number < MAX_CLIENTS ) { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent != &cg.predictedPlayerEntity ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_TeamBase +=============== +*/ +static void CG_TeamBase( centity_t *cent ) { + refEntity_t model; +#ifdef MISSIONPACK + vec3_t angles; + int t, h; + float c; + + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { +#else + if ( cgs.gametype == GT_CTF) { +#endif + // show the flag base + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.redFlagBaseModel; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.blueFlagBaseModel; + } + else { + model.hModel = cgs.media.neutralFlagBaseModel; + } + trap_R_AddRefEntityToScene( &model ); + } +#ifdef MISSIONPACK + else if ( cgs.gametype == GT_OBELISK ) { + // show the obelisk + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + model.hModel = cgs.media.overloadBaseModel; + trap_R_AddRefEntityToScene( &model ); + // if hit + if ( cent->currentState.frame == 1) { + // show hit model + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // + model.hModel = cgs.media.overloadEnergyModel; + trap_R_AddRefEntityToScene( &model ); + } + // if respawning + if ( cent->currentState.frame == 2) { + if ( !cent->miscTime ) { + cent->miscTime = cg.time; + } + t = cg.time - cent->miscTime; + h = (cg_obeliskRespawnDelay.integer - 5) * 1000; + // + if (t > h) { + c = (float) (t - h) / h; + if (c > 1) + c = 1; + } + else { + c = 0; + } + // show the lights + AnglesToAxis( cent->currentState.angles, model.axis ); + // + model.shaderRGBA[0] = c * 0xff; + model.shaderRGBA[1] = c * 0xff; + model.shaderRGBA[2] = c * 0xff; + model.shaderRGBA[3] = c * 0xff; + + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + if (t > h) { + if ( !cent->muzzleFlashTime ) { + trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); + cent->muzzleFlashTime = 1; + } + VectorCopy(cent->currentState.angles, angles); + angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; + AnglesToAxis( angles, model.axis ); + + VectorScale( model.axis[0], c, model.axis[0]); + VectorScale( model.axis[1], c, model.axis[1]); + VectorScale( model.axis[2], c, model.axis[2]); + + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = 0xff; + model.shaderRGBA[2] = 0xff; + model.shaderRGBA[3] = 0xff; + // + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + else { + //FIXME: show animated smoke + } + } + else { + cent->miscTime = 0; + cent->muzzleFlashTime = 0; + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // show the lights + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + } + else if ( cgs.gametype == GT_HARVESTER ) { + // show harvester model + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterRedSkin; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterBlueSkin; + } + else { + model.hModel = cgs.media.harvesterNeutralModel; + model.customSkin = 0; + } + trap_R_AddRefEntityToScene( &model ); + } +#endif +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); + + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + break; + case ET_GENERAL: + CG_General( cent ); + break; + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_MISSILE: + CG_Missile( cent ); + break; + case ET_MOVER: + CG_Mover( cent ); + break; + case ET_BEAM: + CG_Beam( cent ); + break; + case ET_PORTAL: + CG_Portal( cent ); + break; + case ET_SPEAKER: + CG_Speaker( cent ); + break; + case ET_GRAPPLE: + CG_Grapple( cent ); + break; + case ET_TEAM: + CG_TeamBase( cent ); + break; + } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) { + int num; + centity_t *cent; + playerState_t *ps; + + // set cg.frameInterpolation + if ( cg.nextSnap ) { + int delta; + + delta = (cg.nextSnap->serverTime - cg.snap->serverTime); + if ( delta == 0 ) { + cg.frameInterpolation = 0; + } else { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } else { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg.autoAngles[0] = 0; + cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[2] = 0; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[2] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + CG_AddCEntity( cent ); + } +} + diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c index 86167c6..373c9a3 100755 --- a/code/cgame/cg_event.c +++ b/code/cgame/cg_event.c @@ -1,1205 +1,1205 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_event.c -- handle entity events at snapshot or playerstate transitions - -#include "cg_local.h" - -// for the voice chats -#ifdef MISSIONPACK // bk001205 -#include "../../ui/menudef.h" -#endif -//========================================================================== - -/* -=================== -CG_PlaceString - -Also called by scoreboard drawing -=================== -*/ -const char *CG_PlaceString( int rank ) { - static char str[64]; - char *s, *t; - - if ( rank & RANK_TIED_FLAG ) { - rank &= ~RANK_TIED_FLAG; - t = "Tied for "; - } else { - t = ""; - } - - if ( rank == 1 ) { - s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue - } else if ( rank == 2 ) { - s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red - } else if ( rank == 3 ) { - s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow - } else if ( rank == 11 ) { - s = "11th"; - } else if ( rank == 12 ) { - s = "12th"; - } else if ( rank == 13 ) { - s = "13th"; - } else if ( rank % 10 == 1 ) { - s = va("%ist", rank); - } else if ( rank % 10 == 2 ) { - s = va("%ind", rank); - } else if ( rank % 10 == 3 ) { - s = va("%ird", rank); - } else { - s = va("%ith", rank); - } - - Com_sprintf( str, sizeof( str ), "%s%s", t, s ); - return str; -} - -/* -============= -CG_Obituary -============= -*/ -static void CG_Obituary( entityState_t *ent ) { - int mod; - int target, attacker; - char *message; - char *message2; - const char *targetInfo; - const char *attackerInfo; - char targetName[32]; - char attackerName[32]; - gender_t gender; - clientInfo_t *ci; - - target = ent->otherEntityNum; - attacker = ent->otherEntityNum2; - mod = ent->eventParm; - - if ( target < 0 || target >= MAX_CLIENTS ) { - CG_Error( "CG_Obituary: target out of range" ); - } - ci = &cgs.clientinfo[target]; - - if ( attacker < 0 || attacker >= MAX_CLIENTS ) { - attacker = ENTITYNUM_WORLD; - attackerInfo = NULL; - } else { - attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); - } - - targetInfo = CG_ConfigString( CS_PLAYERS + target ); - if ( !targetInfo ) { - return; - } - Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); - strcat( targetName, S_COLOR_WHITE ); - - message2 = ""; - - // check for single client messages - - switch( mod ) { - case MOD_SUICIDE: - message = "suicides"; - break; - case MOD_FALLING: - message = "cratered"; - break; - case MOD_CRUSH: - message = "was squished"; - break; - case MOD_WATER: - message = "sank like a rock"; - break; - case MOD_SLIME: - message = "melted"; - break; - case MOD_LAVA: - message = "does a back flip into the lava"; - break; - case MOD_TARGET_LASER: - message = "saw the light"; - break; - case MOD_TRIGGER_HURT: - message = "was in the wrong place"; - break; - default: - message = NULL; - break; - } - - if (attacker == target) { - gender = ci->gender; - switch (mod) { -#ifdef MISSIONPACK - case MOD_KAMIKAZE: - message = "goes out with a bang"; - break; -#endif - case MOD_GRENADE_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "tripped on her own grenade"; - else if ( gender == GENDER_NEUTER ) - message = "tripped on its own grenade"; - else - message = "tripped on his own grenade"; - break; - case MOD_ROCKET_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "blew herself up"; - else if ( gender == GENDER_NEUTER ) - message = "blew itself up"; - else - message = "blew himself up"; - break; - case MOD_PLASMA_SPLASH: - if ( gender == GENDER_FEMALE ) - message = "melted herself"; - else if ( gender == GENDER_NEUTER ) - message = "melted itself"; - else - message = "melted himself"; - break; - case MOD_BFG_SPLASH: - message = "should have used a smaller gun"; - break; -#ifdef MISSIONPACK - case MOD_PROXIMITY_MINE: - if( gender == GENDER_FEMALE ) { - message = "found her prox mine"; - } else if ( gender == GENDER_NEUTER ) { - message = "found it's prox mine"; - } else { - message = "found his prox mine"; - } - break; -#endif - default: - if ( gender == GENDER_FEMALE ) - message = "killed herself"; - else if ( gender == GENDER_NEUTER ) - message = "killed itself"; - else - message = "killed himself"; - break; - } - } - - if (message) { - CG_Printf( "%s %s.\n", targetName, message); - return; - } - - // check for kill messages from the current clientNum - if ( attacker == cg.snap->ps.clientNum ) { - char *s; - - if ( cgs.gametype < GT_TEAM ) { - s = va("You fragged %s\n%s place with %i", targetName, - CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), - cg.snap->ps.persistant[PERS_SCORE] ); - } else { - s = va("You fragged %s", targetName ); - } -#ifdef MISSIONPACK - if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { - CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } -#else - CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); -#endif - - // print the text message as well - } - - // check for double client messages - if ( !attackerInfo ) { - attacker = ENTITYNUM_WORLD; - strcpy( attackerName, "noname" ); - } else { - Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); - strcat( attackerName, S_COLOR_WHITE ); - // check for kill messages about the current clientNum - if ( target == cg.snap->ps.clientNum ) { - Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); - } - } - - if ( attacker != ENTITYNUM_WORLD ) { - switch (mod) { - case MOD_GRAPPLE: - message = "was caught by"; - break; - case MOD_GAUNTLET: - message = "was pummeled by"; - break; - case MOD_MACHINEGUN: - message = "was machinegunned by"; - break; - case MOD_SHOTGUN: - message = "was gunned down by"; - break; - case MOD_GRENADE: - message = "ate"; - message2 = "'s grenade"; - break; - case MOD_GRENADE_SPLASH: - message = "was shredded by"; - message2 = "'s shrapnel"; - break; - case MOD_ROCKET: - message = "ate"; - message2 = "'s rocket"; - break; - case MOD_ROCKET_SPLASH: - message = "almost dodged"; - message2 = "'s rocket"; - break; - case MOD_PLASMA: - message = "was melted by"; - message2 = "'s plasmagun"; - break; - case MOD_PLASMA_SPLASH: - message = "was melted by"; - message2 = "'s plasmagun"; - break; - case MOD_RAILGUN: - message = "was railed by"; - break; - case MOD_LIGHTNING: - message = "was electrocuted by"; - break; - case MOD_BFG: - case MOD_BFG_SPLASH: - message = "was blasted by"; - message2 = "'s BFG"; - break; -#ifdef MISSIONPACK - case MOD_NAIL: - message = "was nailed by"; - break; - case MOD_CHAINGUN: - message = "got lead poisoning from"; - message2 = "'s Chaingun"; - break; - case MOD_PROXIMITY_MINE: - message = "was too close to"; - message2 = "'s Prox Mine"; - break; - case MOD_KAMIKAZE: - message = "falls to"; - message2 = "'s Kamikaze blast"; - break; - case MOD_JUICED: - message = "was juiced by"; - break; -#endif - case MOD_TELEFRAG: - message = "tried to invade"; - message2 = "'s personal space"; - break; - default: - message = "was killed by"; - break; - } - - if (message) { - CG_Printf( "%s %s %s%s\n", - targetName, message, attackerName, message2); - return; - } - } - - // we don't know what it was - CG_Printf( "%s died.\n", targetName ); -} - -//========================================================================== - -/* -=============== -CG_UseItem -=============== -*/ -static void CG_UseItem( centity_t *cent ) { - clientInfo_t *ci; - int itemNum, clientNum; - gitem_t *item; - entityState_t *es; - - es = ¢->currentState; - - itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; - if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { - itemNum = 0; - } - - // print a message if the local player - if ( es->number == cg.snap->ps.clientNum ) { - if ( !itemNum ) { - CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } else { - item = BG_FindItemForHoldable( itemNum ); - CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - } - } - - switch ( itemNum ) { - default: - case HI_NONE: - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); - break; - - case HI_TELEPORTER: - break; - - case HI_MEDKIT: - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - ci->medkitUsageTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); - break; - -#ifdef MISSIONPACK - case HI_KAMIKAZE: - break; - - case HI_PORTAL: - break; - case HI_INVULNERABILITY: - trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); - break; -#endif - } - -} - -/* -================ -CG_ItemPickup - -A new item was picked up this frame -================ -*/ -static void CG_ItemPickup( int itemNum ) { - cg.itemPickup = itemNum; - cg.itemPickupTime = cg.time; - cg.itemPickupBlendTime = cg.time; - // see if it should be the grabbed weapon - if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { - // select it immediately - if ( cg_autoswitch.integer && bg_itemlist[itemNum].giTag != WP_MACHINEGUN ) { - cg.weaponSelectTime = cg.time; - cg.weaponSelect = bg_itemlist[itemNum].giTag; - } - } - -} - - -/* -================ -CG_PainEvent - -Also called by playerstate transition -================ -*/ -void CG_PainEvent( centity_t *cent, int health ) { - char *snd; - - // don't do more than two pain sounds a second - if ( cg.time - cent->pe.painTime < 500 ) { - return; - } - - if ( health < 25 ) { - snd = "*pain25_1.wav"; - } else if ( health < 50 ) { - snd = "*pain50_1.wav"; - } else if ( health < 75 ) { - snd = "*pain75_1.wav"; - } else { - snd = "*pain100_1.wav"; - } - trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, - CG_CustomSound( cent->currentState.number, snd ) ); - - // save pain time for programitic twitch animation - cent->pe.painTime = cg.time; - cent->pe.painDirection ^= 1; -} - - - -/* -============== -CG_EntityEvent - -An entity has an event value -also called by CG_CheckPlayerstateEvents -============== -*/ -#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} -void CG_EntityEvent( centity_t *cent, vec3_t position ) { - entityState_t *es; - int event; - vec3_t dir; - const char *s; - int clientNum; - clientInfo_t *ci; - - es = ¢->currentState; - event = es->event & ~EV_EVENT_BITS; - - if ( cg_debugEvents.integer ) { - CG_Printf( "ent:%3i event:%3i ", es->number, event ); - } - - if ( !event ) { - DEBUGNAME("ZEROEVENT"); - return; - } - - clientNum = es->clientNum; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - switch ( event ) { - // - // movement generated events - // - case EV_FOOTSTEP: - DEBUGNAME("EV_FOOTSTEP"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ ci->footsteps ][rand()&3] ); - } - break; - case EV_FOOTSTEP_METAL: - DEBUGNAME("EV_FOOTSTEP_METAL"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); - } - break; - case EV_FOOTSPLASH: - DEBUGNAME("EV_FOOTSPLASH"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - case EV_FOOTWADE: - DEBUGNAME("EV_FOOTWADE"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - case EV_SWIM: - DEBUGNAME("EV_SWIM"); - if (cg_footsteps.integer) { - trap_S_StartSound (NULL, es->number, CHAN_BODY, - cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); - } - break; - - - case EV_FALL_SHORT: - DEBUGNAME("EV_FALL_SHORT"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -8; - cg.landTime = cg.time; - } - break; - case EV_FALL_MEDIUM: - DEBUGNAME("EV_FALL_MEDIUM"); - // use normal pain sound - trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -16; - cg.landTime = cg.time; - } - break; - case EV_FALL_FAR: - DEBUGNAME("EV_FALL_FAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); - cent->pe.painTime = cg.time; // don't play a pain sound right after this - if ( clientNum == cg.predictedPlayerState.clientNum ) { - // smooth landing z changes - cg.landChange = -24; - cg.landTime = cg.time; - } - break; - - case EV_STEP_4: - case EV_STEP_8: - case EV_STEP_12: - case EV_STEP_16: // smooth out step up transitions - DEBUGNAME("EV_STEP"); - { - float oldStep; - int delta; - int step; - - if ( clientNum != cg.predictedPlayerState.clientNum ) { - break; - } - // if we are interpolating, we don't need to smooth steps - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || - cg_nopredict.integer || cg_synchronousClients.integer ) { - break; - } - // check for stepping up before a previous step is completed - delta = cg.time - cg.stepTime; - if (delta < STEP_TIME) { - oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; - } else { - oldStep = 0; - } - - // add this amount - step = 4 * (event - EV_STEP_4 + 1 ); - cg.stepChange = oldStep + step; - if ( cg.stepChange > MAX_STEP_CHANGE ) { - cg.stepChange = MAX_STEP_CHANGE; - } - cg.stepTime = cg.time; - break; - } - - case EV_JUMP_PAD: - DEBUGNAME("EV_JUMP_PAD"); -// CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); - { - localEntity_t *smoke; - vec3_t up = {0, 0, 1}; - - - smoke = CG_SmokePuff( cent->lerpOrigin, up, - 32, - 1, 1, 1, 0.33f, - 1000, - cg.time, 0, - LEF_PUFF_DONT_SCALE, - cgs.media.smokePuffShader ); - } - - // boing sound at origin, jump sound on player - trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - break; - - case EV_JUMP: - DEBUGNAME("EV_JUMP"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); - break; - case EV_TAUNT: - DEBUGNAME("EV_TAUNT"); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); - break; -#ifdef MISSIONPACK - case EV_TAUNT_YES: - DEBUGNAME("EV_TAUNT_YES"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); - break; - case EV_TAUNT_NO: - DEBUGNAME("EV_TAUNT_NO"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); - break; - case EV_TAUNT_FOLLOWME: - DEBUGNAME("EV_TAUNT_FOLLOWME"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); - break; - case EV_TAUNT_GETFLAG: - DEBUGNAME("EV_TAUNT_GETFLAG"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); - break; - case EV_TAUNT_GUARDBASE: - DEBUGNAME("EV_TAUNT_GUARDBASE"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); - break; - case EV_TAUNT_PATROL: - DEBUGNAME("EV_TAUNT_PATROL"); - CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); - break; -#endif - case EV_WATER_TOUCH: - DEBUGNAME("EV_WATER_TOUCH"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); - break; - case EV_WATER_LEAVE: - DEBUGNAME("EV_WATER_LEAVE"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); - break; - case EV_WATER_UNDER: - DEBUGNAME("EV_WATER_UNDER"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); - break; - case EV_WATER_CLEAR: - DEBUGNAME("EV_WATER_CLEAR"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); - break; - - case EV_ITEM_PICKUP: - DEBUGNAME("EV_ITEM_PICKUP"); - { - gitem_t *item; - int index; - - index = es->eventParm; // player predicted - - if ( index < 1 || index >= bg_numItems ) { - break; - } - item = &bg_itemlist[ index ]; - - // powerups and team items will have a separate global sound, this one - // will be played at prediction time - if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); - } else if (item->giType == IT_PERSISTANT_POWERUP) { -#ifdef MISSIONPACK - switch (item->giTag ) { - case PW_SCOUT: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); - break; - case PW_GUARD: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); - break; - case PW_DOUBLER: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); - break; - case PW_AMMOREGEN: - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); - break; - } -#endif - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); - } - - // show icon and name on status bar - if ( es->number == cg.snap->ps.clientNum ) { - CG_ItemPickup( index ); - } - } - break; - - case EV_GLOBAL_ITEM_PICKUP: - DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); - { - gitem_t *item; - int index; - - index = es->eventParm; // player predicted - - if ( index < 1 || index >= bg_numItems ) { - break; - } - item = &bg_itemlist[ index ]; - // powerup pickups are global - if( item->pickup_sound ) { - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); - } - - // show icon and name on status bar - if ( es->number == cg.snap->ps.clientNum ) { - CG_ItemPickup( index ); - } - } - break; - - // - // weapon events - // - case EV_NOAMMO: - DEBUGNAME("EV_NOAMMO"); -// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); - if ( es->number == cg.snap->ps.clientNum ) { - CG_OutOfAmmoChange(); - } - break; - case EV_CHANGE_WEAPON: - DEBUGNAME("EV_CHANGE_WEAPON"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); - break; - case EV_FIRE_WEAPON: - DEBUGNAME("EV_FIRE_WEAPON"); - CG_FireWeapon( cent ); - break; - - case EV_USE_ITEM0: - DEBUGNAME("EV_USE_ITEM0"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM1: - DEBUGNAME("EV_USE_ITEM1"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM2: - DEBUGNAME("EV_USE_ITEM2"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM3: - DEBUGNAME("EV_USE_ITEM3"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM4: - DEBUGNAME("EV_USE_ITEM4"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM5: - DEBUGNAME("EV_USE_ITEM5"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM6: - DEBUGNAME("EV_USE_ITEM6"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM7: - DEBUGNAME("EV_USE_ITEM7"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM8: - DEBUGNAME("EV_USE_ITEM8"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM9: - DEBUGNAME("EV_USE_ITEM9"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM10: - DEBUGNAME("EV_USE_ITEM10"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM11: - DEBUGNAME("EV_USE_ITEM11"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM12: - DEBUGNAME("EV_USE_ITEM12"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM13: - DEBUGNAME("EV_USE_ITEM13"); - CG_UseItem( cent ); - break; - case EV_USE_ITEM14: - DEBUGNAME("EV_USE_ITEM14"); - CG_UseItem( cent ); - break; - - //================================================================= - - // - // other events - // - case EV_PLAYER_TELEPORT_IN: - DEBUGNAME("EV_PLAYER_TELEPORT_IN"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); - CG_SpawnEffect( position); - break; - - case EV_PLAYER_TELEPORT_OUT: - DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); - CG_SpawnEffect( position); - break; - - case EV_ITEM_POP: - DEBUGNAME("EV_ITEM_POP"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); - break; - case EV_ITEM_RESPAWN: - DEBUGNAME("EV_ITEM_RESPAWN"); - cent->miscTime = cg.time; // scale up from this - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); - break; - - case EV_GRENADE_BOUNCE: - DEBUGNAME("EV_GRENADE_BOUNCE"); - if ( rand() & 1 ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); - } - break; - -#ifdef MISSIONPACK - case EV_PROXIMITY_MINE_STICK: - DEBUGNAME("EV_PROXIMITY_MINE_STICK"); - if( es->eventParm & SURF_FLESH ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); - } else if( es->eventParm & SURF_METALSTEPS ) { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); - } else { - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); - } - break; - - case EV_PROXIMITY_MINE_TRIGGER: - DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); - trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); - break; - case EV_KAMIKAZE: - DEBUGNAME("EV_KAMIKAZE"); - CG_KamikazeEffect( cent->lerpOrigin ); - break; - case EV_OBELISKEXPLODE: - DEBUGNAME("EV_OBELISKEXPLODE"); - CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); - break; - case EV_OBELISKPAIN: - DEBUGNAME("EV_OBELISKPAIN"); - CG_ObeliskPain( cent->lerpOrigin ); - break; - case EV_INVUL_IMPACT: - DEBUGNAME("EV_INVUL_IMPACT"); - CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); - break; - case EV_JUICED: - DEBUGNAME("EV_JUICED"); - CG_InvulnerabilityJuiced( cent->lerpOrigin ); - break; - case EV_LIGHTNINGBOLT: - DEBUGNAME("EV_LIGHTNINGBOLT"); - CG_LightningBoltBeam(es->origin2, es->pos.trBase); - break; -#endif - case EV_SCOREPLUM: - DEBUGNAME("EV_SCOREPLUM"); - CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); - break; - - // - // missile impacts - // - case EV_MISSILE_HIT: - DEBUGNAME("EV_MISSILE_HIT"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); - break; - - case EV_MISSILE_MISS: - DEBUGNAME("EV_MISSILE_MISS"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); - break; - - case EV_MISSILE_MISS_METAL: - DEBUGNAME("EV_MISSILE_MISS_METAL"); - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); - break; - - case EV_RAILTRAIL: - DEBUGNAME("EV_RAILTRAIL"); - cent->currentState.weapon = WP_RAILGUN; - // if the end was on a nomark surface, don't make an explosion - CG_RailTrail( ci, es->origin2, es->pos.trBase ); - if ( es->eventParm != 255 ) { - ByteToDir( es->eventParm, dir ); - CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); - } - break; - - case EV_BULLET_HIT_WALL: - DEBUGNAME("EV_BULLET_HIT_WALL"); - ByteToDir( es->eventParm, dir ); - CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); - break; - - case EV_BULLET_HIT_FLESH: - DEBUGNAME("EV_BULLET_HIT_FLESH"); - CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); - break; - - case EV_SHOTGUN: - DEBUGNAME("EV_SHOTGUN"); - CG_ShotgunFire( es ); - break; - - case EV_GENERAL_SOUND: - DEBUGNAME("EV_GENERAL_SOUND"); - if ( cgs.gameSounds[ es->eventParm ] ) { - trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); - } else { - s = CG_ConfigString( CS_SOUNDS + es->eventParm ); - trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); - } - break; - - case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes - DEBUGNAME("EV_GLOBAL_SOUND"); - if ( cgs.gameSounds[ es->eventParm ] ) { - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); - } else { - s = CG_ConfigString( CS_SOUNDS + es->eventParm ); - trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); - } - break; - - case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes - { - DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); - switch( es->eventParm ) { - case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) - CG_AddBufferedSound( cgs.media.captureYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.captureOpponentSound ); - break; - case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) - CG_AddBufferedSound( cgs.media.captureYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.captureOpponentSound ); - break; - case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used - if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) - CG_AddBufferedSound( cgs.media.returnYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.returnOpponentSound ); - // - CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); - break; - case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned - if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) - CG_AddBufferedSound( cgs.media.returnYourTeamSound ); - else - CG_AddBufferedSound( cgs.media.returnOpponentSound ); - // - CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); - break; - - case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag - // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - } - else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); - } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); - } - } - break; - case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag - // if this player picked up the flag then a sound is played in CG_CheckLocalSounds - if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - } - else { - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); - } - else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { -#ifdef MISSIONPACK - if (cgs.gametype == GT_1FCTF) - CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); - else -#endif - CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); - } - } - break; - case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; - case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked - if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { - CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); - } - break; - - case GTS_REDTEAM_SCORED: - CG_AddBufferedSound(cgs.media.redScoredSound); - break; - case GTS_BLUETEAM_SCORED: - CG_AddBufferedSound(cgs.media.blueScoredSound); - break; - case GTS_REDTEAM_TOOK_LEAD: - CG_AddBufferedSound(cgs.media.redLeadsSound); - break; - case GTS_BLUETEAM_TOOK_LEAD: - CG_AddBufferedSound(cgs.media.blueLeadsSound); - break; - case GTS_TEAMS_ARE_TIED: - CG_AddBufferedSound( cgs.media.teamsTiedSound ); - break; -#ifdef MISSIONPACK - case GTS_KAMIKAZE: - trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); - break; -#endif - default: - break; - } - break; - } - - case EV_PAIN: - // local player sounds are triggered in CG_CheckLocalSounds, - // so ignore events on the player - DEBUGNAME("EV_PAIN"); - if ( cent->currentState.number != cg.snap->ps.clientNum ) { - CG_PainEvent( cent, es->eventParm ); - } - break; - - case EV_DEATH1: - case EV_DEATH2: - case EV_DEATH3: - DEBUGNAME("EV_DEATHx"); - trap_S_StartSound( NULL, es->number, CHAN_VOICE, - CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); - break; - - - case EV_OBITUARY: - DEBUGNAME("EV_OBITUARY"); - CG_Obituary( es ); - break; - - // - // powerup events - // - case EV_POWERUP_QUAD: - DEBUGNAME("EV_POWERUP_QUAD"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_QUAD; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); - break; - case EV_POWERUP_BATTLESUIT: - DEBUGNAME("EV_POWERUP_BATTLESUIT"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_BATTLESUIT; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); - break; - case EV_POWERUP_REGEN: - DEBUGNAME("EV_POWERUP_REGEN"); - if ( es->number == cg.snap->ps.clientNum ) { - cg.powerupActive = PW_REGEN; - cg.powerupTime = cg.time; - } - trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); - break; - - case EV_GIB_PLAYER: - DEBUGNAME("EV_GIB_PLAYER"); - // don't play gib sound when using the kamikaze because it interferes - // with the kamikaze sound, downside is that the gib sound will also - // not be played when someone is gibbed while just carrying the kamikaze - if ( !(es->eFlags & EF_KAMIKAZE) ) { - trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); - } - CG_GibPlayer( cent->lerpOrigin ); - break; - - case EV_STOPLOOPINGSOUND: - DEBUGNAME("EV_STOPLOOPINGSOUND"); - trap_S_StopLoopingSound( es->number ); - es->loopSound = 0; - break; - - case EV_DEBUG_LINE: - DEBUGNAME("EV_DEBUG_LINE"); - CG_Beam( cent ); - break; - - default: - DEBUGNAME("UNKNOWN"); - CG_Error( "Unknown event: %i", event ); - break; - } - -} - - -/* -============== -CG_CheckEvents - -============== -*/ -void CG_CheckEvents( centity_t *cent ) { - // check for event-only entities - if ( cent->currentState.eType > ET_EVENTS ) { - if ( cent->previousEvent ) { - return; // already fired - } - // if this is a player event set the entity number of the client entity number - if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { - cent->currentState.number = cent->currentState.otherEntityNum; - } - - cent->previousEvent = 1; - - cent->currentState.event = cent->currentState.eType - ET_EVENTS; - } else { - // check for events riding with another entity - if ( cent->currentState.event == cent->previousEvent ) { - return; - } - cent->previousEvent = cent->currentState.event; - if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { - return; - } - } - - // calculate the position at exactly the frame time - BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); - CG_SetEntitySoundPosition( cent ); - - CG_EntityEvent( cent, cent->lerpOrigin ); -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = "Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = "11th"; + } else if ( rank == 12 ) { + s = "12th"; + } else if ( rank == 13 ) { + s = "13th"; + } else if ( rank % 10 == 1 ) { + s = va("%ist", rank); + } else if ( rank % 10 == 2 ) { + s = va("%ind", rank); + } else if ( rank % 10 == 3 ) { + s = va("%ird", rank); + } else { + s = va("%ith", rank); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + gender_t gender; + clientInfo_t *ci; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) { + CG_Error( "CG_Obituary: target out of range" ); + } + ci = &cgs.clientinfo[target]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } else { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); + strcat( targetName, S_COLOR_WHITE ); + + message2 = ""; + + // check for single client messages + + switch( mod ) { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + default: + message = NULL; + break; + } + + if (attacker == target) { + gender = ci->gender; + switch (mod) { +#ifdef MISSIONPACK + case MOD_KAMIKAZE: + message = "goes out with a bang"; + break; +#endif + case MOD_GRENADE_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "tripped on her own grenade"; + else if ( gender == GENDER_NEUTER ) + message = "tripped on its own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_ROCKET_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if ( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + case MOD_PLASMA_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "melted herself"; + else if ( gender == GENDER_NEUTER ) + message = "melted itself"; + else + message = "melted himself"; + break; + case MOD_BFG_SPLASH: + message = "should have used a smaller gun"; + break; +#ifdef MISSIONPACK + case MOD_PROXIMITY_MINE: + if( gender == GENDER_FEMALE ) { + message = "found her prox mine"; + } else if ( gender == GENDER_NEUTER ) { + message = "found it's prox mine"; + } else { + message = "found his prox mine"; + } + break; +#endif + default: + if ( gender == GENDER_FEMALE ) + message = "killed herself"; + else if ( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if (message) { + CG_Printf( "%s %s.\n", targetName, message); + return; + } + + // check for kill messages from the current clientNum + if ( attacker == cg.snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM ) { + s = va("You fragged %s\n%s place with %i", targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + } else { + s = va("You fragged %s", targetName ); + } +#ifdef MISSIONPACK + if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } +#else + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +#endif + + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } else { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + } + + if ( attacker != ENTITYNUM_WORLD ) { + switch (mod) { + case MOD_GRAPPLE: + message = "was caught by"; + break; + case MOD_GAUNTLET: + message = "was pummeled by"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_GRENADE: + message = "ate"; + message2 = "'s grenade"; + break; + case MOD_GRENADE_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_ROCKET_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_PLASMA: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_PLASMA_SPLASH: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_LIGHTNING: + message = "was electrocuted by"; + break; + case MOD_BFG: + case MOD_BFG_SPLASH: + message = "was blasted by"; + message2 = "'s BFG"; + break; +#ifdef MISSIONPACK + case MOD_NAIL: + message = "was nailed by"; + break; + case MOD_CHAINGUN: + message = "got lead poisoning from"; + message2 = "'s Chaingun"; + break; + case MOD_PROXIMITY_MINE: + message = "was too close to"; + message2 = "'s Prox Mine"; + break; + case MOD_KAMIKAZE: + message = "falls to"; + message2 = "'s Kamikaze blast"; + break; + case MOD_JUICED: + message = "was juiced by"; + break; +#endif + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + default: + message = "was killed by"; + break; + } + + if (message) { + CG_Printf( "%s %s %s%s\n", + targetName, message, attackerName, message2); + return; + } + } + + // we don't know what it was + CG_Printf( "%s died.\n", targetName ); +} + +//========================================================================== + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { + clientInfo_t *ci; + int itemNum, clientNum; + gitem_t *item; + entityState_t *es; + + es = ¢->currentState; + + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + + // print a message if the local player + if ( es->number == cg.snap->ps.clientNum ) { + if ( !itemNum ) { + CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } else { + item = BG_FindItemForHoldable( itemNum ); + CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_TELEPORTER: + break; + + case HI_MEDKIT: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); + break; + +#ifdef MISSIONPACK + case HI_KAMIKAZE: + break; + + case HI_PORTAL: + break; + case HI_INVULNERABILITY: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); + break; +#endif + } + +} + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + // see if it should be the grabbed weapon + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { + // select it immediately + if ( cg_autoswitch.integer && bg_itemlist[itemNum].giTag != WP_MACHINEGUN ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) { + char *snd; + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25_1.wav"; + } else if ( health < 50 ) { + snd = "*pain50_1.wav"; + } else if ( health < 75 ) { + snd = "*pain75_1.wav"; + } else { + snd = "*pain100_1.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + + + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + switch ( event ) { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][rand()&3] ); + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME("EV_FOOTSTEP_METAL"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); + } + break; + case EV_FOOTSPLASH: + DEBUGNAME("EV_FOOTSPLASH"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + + + case EV_FALL_SHORT: + DEBUGNAME("EV_FALL_SHORT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + case EV_FALL_MEDIUM: + DEBUGNAME("EV_FALL_MEDIUM"); + // use normal pain sound + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME("EV_STEP"); + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + if (delta < STEP_TIME) { + oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + if ( cg.stepChange > MAX_STEP_CHANGE ) { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME("EV_JUMP_PAD"); +// CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); + { + localEntity_t *smoke; + vec3_t up = {0, 0, 1}; + + + smoke = CG_SmokePuff( cent->lerpOrigin, up, + 32, + 1, 1, 1, 0.33f, + 1000, + cg.time, 0, + LEF_PUFF_DONT_SCALE, + cgs.media.smokePuffShader ); + } + + // boing sound at origin, jump sound on player + trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; +#ifdef MISSIONPACK + case EV_TAUNT_YES: + DEBUGNAME("EV_TAUNT_YES"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); + break; + case EV_TAUNT_NO: + DEBUGNAME("EV_TAUNT_NO"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); + break; + case EV_TAUNT_FOLLOWME: + DEBUGNAME("EV_TAUNT_FOLLOWME"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); + break; + case EV_TAUNT_GETFLAG: + DEBUGNAME("EV_TAUNT_GETFLAG"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); + break; + case EV_TAUNT_GUARDBASE: + DEBUGNAME("EV_TAUNT_GUARDBASE"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); + break; + case EV_TAUNT_PATROL: + DEBUGNAME("EV_TAUNT_PATROL"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); + break; +#endif + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME("EV_WATER_LEAVE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME("EV_WATER_UNDER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + case EV_ITEM_PICKUP: + DEBUGNAME("EV_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + // powerups and team items will have a separate global sound, this one + // will be played at prediction time + if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); + } else if (item->giType == IT_PERSISTANT_POWERUP) { +#ifdef MISSIONPACK + switch (item->giTag ) { + case PW_SCOUT: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); + break; + case PW_GUARD: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); + break; + case PW_DOUBLER: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); + break; + case PW_AMMOREGEN: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); + break; + } +#endif + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + if( item->pickup_sound ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + // + // weapon events + // + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg.snap->ps.clientNum ) { + CG_OutOfAmmoChange(); + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + CG_FireWeapon( cent ); + break; + + case EV_USE_ITEM0: + DEBUGNAME("EV_USE_ITEM0"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM1: + DEBUGNAME("EV_USE_ITEM1"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM2: + DEBUGNAME("EV_USE_ITEM2"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM3: + DEBUGNAME("EV_USE_ITEM3"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM4: + DEBUGNAME("EV_USE_ITEM4"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM5: + DEBUGNAME("EV_USE_ITEM5"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM6: + DEBUGNAME("EV_USE_ITEM6"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM7: + DEBUGNAME("EV_USE_ITEM7"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM8: + DEBUGNAME("EV_USE_ITEM8"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM9: + DEBUGNAME("EV_USE_ITEM9"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM10: + DEBUGNAME("EV_USE_ITEM10"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM11: + DEBUGNAME("EV_USE_ITEM11"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM12: + DEBUGNAME("EV_USE_ITEM12"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM13: + DEBUGNAME("EV_USE_ITEM13"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM14: + DEBUGNAME("EV_USE_ITEM14"); + CG_UseItem( cent ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position); + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_SpawnEffect( position); + break; + + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + if ( rand() & 1 ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); + } + break; + +#ifdef MISSIONPACK + case EV_PROXIMITY_MINE_STICK: + DEBUGNAME("EV_PROXIMITY_MINE_STICK"); + if( es->eventParm & SURF_FLESH ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); + } else if( es->eventParm & SURF_METALSTEPS ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); + } + break; + + case EV_PROXIMITY_MINE_TRIGGER: + DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); + break; + case EV_KAMIKAZE: + DEBUGNAME("EV_KAMIKAZE"); + CG_KamikazeEffect( cent->lerpOrigin ); + break; + case EV_OBELISKEXPLODE: + DEBUGNAME("EV_OBELISKEXPLODE"); + CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); + break; + case EV_OBELISKPAIN: + DEBUGNAME("EV_OBELISKPAIN"); + CG_ObeliskPain( cent->lerpOrigin ); + break; + case EV_INVUL_IMPACT: + DEBUGNAME("EV_INVUL_IMPACT"); + CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); + break; + case EV_JUICED: + DEBUGNAME("EV_JUICED"); + CG_InvulnerabilityJuiced( cent->lerpOrigin ); + break; + case EV_LIGHTNINGBOLT: + DEBUGNAME("EV_LIGHTNINGBOLT"); + CG_LightningBoltBeam(es->origin2, es->pos.trBase); + break; +#endif + case EV_SCOREPLUM: + DEBUGNAME("EV_SCOREPLUM"); + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); + break; + + case EV_MISSILE_MISS_METAL: + DEBUGNAME("EV_MISSILE_MISS_METAL"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); + break; + + case EV_RAILTRAIL: + DEBUGNAME("EV_RAILTRAIL"); + cent->currentState.weapon = WP_RAILGUN; + // if the end was on a nomark surface, don't make an explosion + CG_RailTrail( ci, es->origin2, es->pos.trBase ); + if ( es->eventParm != 255 ) { + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); + } + break; + + case EV_BULLET_HIT_WALL: + DEBUGNAME("EV_BULLET_HIT_WALL"); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); + break; + + case EV_BULLET_HIT_FLESH: + DEBUGNAME("EV_BULLET_HIT_FLESH"); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); + break; + + case EV_SHOTGUN: + DEBUGNAME("EV_SHOTGUN"); + CG_ShotgunFire( es ); + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes + { + DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); + switch( es->eventParm ) { + case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); + break; + case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); + break; + + case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + + case GTS_REDTEAM_SCORED: + CG_AddBufferedSound(cgs.media.redScoredSound); + break; + case GTS_BLUETEAM_SCORED: + CG_AddBufferedSound(cgs.media.blueScoredSound); + break; + case GTS_REDTEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.redLeadsSound); + break; + case GTS_BLUETEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.blueLeadsSound); + break; + case GTS_TEAMS_ARE_TIED: + CG_AddBufferedSound( cgs.media.teamsTiedSound ); + break; +#ifdef MISSIONPACK + case GTS_KAMIKAZE: + trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); + break; +#endif + default: + break; + } + break; + } + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME("EV_DEATHx"); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); + break; + + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + // + // powerup events + // + case EV_POWERUP_QUAD: + DEBUGNAME("EV_POWERUP_QUAD"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_QUAD; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); + break; + case EV_POWERUP_BATTLESUIT: + DEBUGNAME("EV_POWERUP_BATTLESUIT"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_BATTLESUIT; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); + break; + case EV_POWERUP_REGEN: + DEBUGNAME("EV_POWERUP_REGEN"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_REGEN; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); + break; + + case EV_GIB_PLAYER: + DEBUGNAME("EV_GIB_PLAYER"); + // don't play gib sound when using the kamikaze because it interferes + // with the kamikaze sound, downside is that the gib sound will also + // not be played when someone is gibbed while just carrying the kamikaze + if ( !(es->eFlags & EF_KAMIKAZE) ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + } + CG_GibPlayer( cent->lerpOrigin ); + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + return; // already fired + } + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) { + return; + } + cent->previousEvent = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + return; + } + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); +} + diff --git a/code/cgame/cg_info.c b/code/cgame/cg_info.c index 04124d1..53f0b05 100755 --- a/code/cgame/cg_info.c +++ b/code/cgame/cg_info.c @@ -1,297 +1,297 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_info.c -- display information while data is being loading - -#include "cg_local.h" - -#define MAX_LOADING_PLAYER_ICONS 16 -#define MAX_LOADING_ITEM_ICONS 26 - -static int loadingPlayerIconCount; -static int loadingItemIconCount; -static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; -static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; - - -/* -=================== -CG_DrawLoadingIcons -=================== -*/ -static void CG_DrawLoadingIcons( void ) { - int n; - int x, y; - - for( n = 0; n < loadingPlayerIconCount; n++ ) { - x = 16 + n * 78; - y = 324-40; - CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); - } - - for( n = 0; n < loadingItemIconCount; n++ ) { - y = 400-40; - if( n >= 13 ) { - y += 40; - } - x = 16 + n % 13 * 48; - CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); - } -} - - -/* -====================== -CG_LoadingString - -====================== -*/ -void CG_LoadingString( const char *s ) { - Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); - - trap_UpdateScreen(); -} - -/* -=================== -CG_LoadingItem -=================== -*/ -void CG_LoadingItem( int itemNum ) { - gitem_t *item; - - item = &bg_itemlist[itemNum]; - - if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { - loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); - } - - CG_LoadingString( item->pickup_name ); -} - -/* -=================== -CG_LoadingClient -=================== -*/ -void CG_LoadingClient( int clientNum ) { - const char *info; - char *skin; - char personality[MAX_QPATH]; - char model[MAX_QPATH]; - char iconName[MAX_QPATH]; - - info = CG_ConfigString( CS_PLAYERS + clientNum ); - - if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { - Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); - skin = Q_strrchr( model, '/' ); - if ( skin ) { - *skin++ = '\0'; - } else { - skin = "default"; - } - - Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); - - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { - Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - } - if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { - Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); - loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); - } - if ( loadingPlayerIcons[loadingPlayerIconCount] ) { - loadingPlayerIconCount++; - } - } - - Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); - Q_CleanStr( personality ); - - if( cgs.gametype == GT_SINGLE_PLAYER ) { - trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); - } - - CG_LoadingString( personality ); -} - - -/* -==================== -CG_DrawInformation - -Draw all the status / pacifier stuff during level loading -==================== -*/ -void CG_DrawInformation( void ) { - const char *s; - const char *info; - const char *sysInfo; - int y; - int value; - qhandle_t levelshot; - qhandle_t detail; - char buf[1024]; - - info = CG_ConfigString( CS_SERVERINFO ); - sysInfo = CG_ConfigString( CS_SYSTEMINFO ); - - s = Info_ValueForKey( info, "mapname" ); - levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); - if ( !levelshot ) { - levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); - } - trap_R_SetColor( NULL ); - CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); - - // blend a detail texture over it - detail = trap_R_RegisterShader( "levelShotDetail" ); - trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); - - // draw the icons of things as they are loaded - CG_DrawLoadingIcons(); - - // the first 150 rows are reserved for the client connection - // screen to write into - if ( cg.infoScreenText[0] ) { - UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - } else { - UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - } - - // draw info string information - - y = 180-32; - - // don't print server lines if playing a local game - trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); - if ( !atoi( buf ) ) { - // server hostname - Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); - Q_CleanStr(buf); - UI_DrawProportionalString( 320, y, buf, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - - // pure server - s = Info_ValueForKey( sysInfo, "sv_pure" ); - if ( s[0] == '1' ) { - UI_DrawProportionalString( 320, y, "Pure Server", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // server-specific message of the day - s = CG_ConfigString( CS_MOTD ); - if ( s[0] ) { - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // some extra space after hostname and motd - y += 10; - } - - // map-specific message (long map name) - s = CG_ConfigString( CS_MESSAGE ); - if ( s[0] ) { - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // cheats warning - s = Info_ValueForKey( sysInfo, "sv_cheats" ); - if ( s[0] == '1' ) { - UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - // game type - switch ( cgs.gametype ) { - case GT_FFA: - s = "Free For All"; - break; - case GT_SINGLE_PLAYER: - s = "Single Player"; - break; - case GT_TOURNAMENT: - s = "Tournament"; - break; - case GT_TEAM: - s = "Team Deathmatch"; - break; - case GT_CTF: - s = "Capture The Flag"; - break; -#ifdef MISSIONPACK - case GT_1FCTF: - s = "One Flag CTF"; - break; - case GT_OBELISK: - s = "Overload"; - break; - case GT_HARVESTER: - s = "Harvester"; - break; -#endif - default: - s = "Unknown Gametype"; - break; - } - UI_DrawProportionalString( 320, y, s, - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - - value = atoi( Info_ValueForKey( info, "timelimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - - if (cgs.gametype < GT_CTF ) { - value = atoi( Info_ValueForKey( info, "fraglimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - } - - if (cgs.gametype >= GT_CTF) { - value = atoi( Info_ValueForKey( info, "capturelimit" ) ); - if ( value ) { - UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), - UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); - y += PROP_HEIGHT; - } - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#define MAX_LOADING_PLAYER_ICONS 16 +#define MAX_LOADING_ITEM_ICONS 26 + +static int loadingPlayerIconCount; +static int loadingItemIconCount; +static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; +static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; + + +/* +=================== +CG_DrawLoadingIcons +=================== +*/ +static void CG_DrawLoadingIcons( void ) { + int n; + int x, y; + + for( n = 0; n < loadingPlayerIconCount; n++ ) { + x = 16 + n * 78; + y = 324-40; + CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); + } + + for( n = 0; n < loadingItemIconCount; n++ ) { + y = 400-40; + if( n >= 13 ) { + y += 40; + } + x = 16 + n % 13 * 48; + CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); + } +} + + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + + item = &bg_itemlist[itemNum]; + + if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { + loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char *skin; + char personality[MAX_QPATH]; + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = "default"; + } + + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); + + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( loadingPlayerIcons[loadingPlayerIconCount] ) { + loadingPlayerIconCount++; + } + } + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + if( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); + } + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value; + qhandle_t levelshot; + qhandle_t detail; + char buf[1024]; + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); + } + trap_R_SetColor( NULL ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + // blend a detail texture over it + detail = trap_R_RegisterShader( "levelShotDetail" ); + trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); + + // draw the icons of things as they are loaded + CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into + if ( cg.infoScreenText[0] ) { + UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } else { + UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } + + // draw info string information + + y = 180-32; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); + if ( !atoi( buf ) ) { + // server hostname + Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); + Q_CleanStr(buf); + UI_DrawProportionalString( 320, y, buf, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "Pure Server", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // game type + switch ( cgs.gametype ) { + case GT_FFA: + s = "Free For All"; + break; + case GT_SINGLE_PLAYER: + s = "Single Player"; + break; + case GT_TOURNAMENT: + s = "Tournament"; + break; + case GT_TEAM: + s = "Team Deathmatch"; + break; + case GT_CTF: + s = "Capture The Flag"; + break; +#ifdef MISSIONPACK + case GT_1FCTF: + s = "One Flag CTF"; + break; + case GT_OBELISK: + s = "Overload"; + break; + case GT_HARVESTER: + s = "Harvester"; + break; +#endif + default: + s = "Unknown Gametype"; + break; + } + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + if (cgs.gametype < GT_CTF ) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } + + if (cgs.gametype >= GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } +} + diff --git a/code/cgame/cg_local.h b/code/cgame/cg_local.h index 4dd5c9d..a23f715 100755 --- a/code/cgame/cg_local.h +++ b/code/cgame/cg_local.h @@ -1,1669 +1,1669 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#include "../game/q_shared.h" -#include "tr_types.h" -#include "../game/bg_public.h" -#include "cg_public.h" - - -// The entire cgame module is unloaded and reloaded on each level change, -// so there is NO persistant data between levels on the client side. -// If you absolutely need something stored, it can either be kept -// by the server in the server stored userinfos, or stashed in a cvar. - -#ifdef MISSIONPACK -#define CG_FONT_THRESHOLD 0.1 -#endif - -#define POWERUP_BLINKS 5 - -#define POWERUP_BLINK_TIME 1000 -#define FADE_TIME 200 -#define PULSE_TIME 200 -#define DAMAGE_DEFLECT_TIME 100 -#define DAMAGE_RETURN_TIME 400 -#define DAMAGE_TIME 500 -#define LAND_DEFLECT_TIME 150 -#define LAND_RETURN_TIME 300 -#define STEP_TIME 200 -#define DUCK_TIME 100 -#define PAIN_TWITCH_TIME 200 -#define WEAPON_SELECT_TIME 1400 -#define ITEM_SCALEUP_TIME 1000 -#define ZOOM_TIME 150 -#define ITEM_BLOB_TIME 200 -#define MUZZLE_FLASH_TIME 20 -#define SINK_TIME 1000 // time for fragments to sink into ground before going away -#define ATTACKER_HEAD_TIME 10000 -#define REWARD_TIME 3000 - -#define PULSE_SCALE 1.5 // amount to scale up the icons when activating - -#define MAX_STEP_CHANGE 32 - -#define MAX_VERTS_ON_POLY 10 -#define MAX_MARK_POLYS 256 - -#define STAT_MINUS 10 // num frame for '-' stats digit - -#define ICON_SIZE 48 -#define CHAR_WIDTH 32 -#define CHAR_HEIGHT 48 -#define TEXT_ICON_SPACE 4 - -#define TEAMCHAT_WIDTH 80 -#define TEAMCHAT_HEIGHT 8 - -// very large characters -#define GIANT_WIDTH 32 -#define GIANT_HEIGHT 48 - -#define NUM_CROSSHAIRS 10 - -#define TEAM_OVERLAY_MAXNAME_WIDTH 12 -#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 - -#define DEFAULT_MODEL "sarge" -#ifdef MISSIONPACK -#define DEFAULT_TEAM_MODEL "james" -#define DEFAULT_TEAM_HEAD "*james" -#else -#define DEFAULT_TEAM_MODEL "sarge" -#define DEFAULT_TEAM_HEAD "sarge" -#endif - -#define DEFAULT_REDTEAM_NAME "Stroggs" -#define DEFAULT_BLUETEAM_NAME "Pagans" - -typedef enum { - FOOTSTEP_NORMAL, - FOOTSTEP_BOOT, - FOOTSTEP_FLESH, - FOOTSTEP_MECH, - FOOTSTEP_ENERGY, - FOOTSTEP_METAL, - FOOTSTEP_SPLASH, - - FOOTSTEP_TOTAL -} footstep_t; - -typedef enum { - IMPACTSOUND_DEFAULT, - IMPACTSOUND_METAL, - IMPACTSOUND_FLESH -} impactSound_t; - -//================================================= - -// player entities need to track more information -// than any other type of entity. - -// note that not every player entity is a client entity, -// because corpses after respawn are outside the normal -// client numbering range - -// when changing animation, set animationTime to frameTime + lerping time -// The current lerp will finish out, then it will lerp to the new animation -typedef struct { - int oldFrame; - int oldFrameTime; // time when ->oldFrame was exactly on - - int frame; - int frameTime; // time when ->frame will be exactly on - - float backlerp; - - float yawAngle; - qboolean yawing; - float pitchAngle; - qboolean pitching; - - int animationNumber; // may include ANIM_TOGGLEBIT - animation_t *animation; - int animationTime; // time when the first frame of the animation will be exact -} lerpFrame_t; - - -typedef struct { - lerpFrame_t legs, torso, flag; - int painTime; - int painDirection; // flip from 0 to 1 - int lightningFiring; - - // railgun trail spawning - vec3_t railgunImpact; - qboolean railgunFlash; - - // machinegun spinning - float barrelAngle; - int barrelTime; - qboolean barrelSpinning; -} playerEntity_t; - -//================================================= - - - -// centity_t have a direct corespondence with gentity_t in the game, but -// only the entityState_t is directly communicated to the cgame -typedef struct centity_s { - entityState_t currentState; // from cg.frame - entityState_t nextState; // from cg.nextFrame, if available - qboolean interpolate; // true if next is valid to interpolate to - qboolean currentValid; // true if cg.frame holds this entity - - int muzzleFlashTime; // move to playerEntity? - int previousEvent; - int teleportFlag; - - int trailTime; // so missile trails can handle dropped initial packets - int dustTrailTime; - int miscTime; - - int snapShotTime; // last time this entity was found in a snapshot - - playerEntity_t pe; - - int errorTime; // decay the error from this time - vec3_t errorOrigin; - vec3_t errorAngles; - - qboolean extrapolated; // false if origin / angles is an interpolation - vec3_t rawOrigin; - vec3_t rawAngles; - - vec3_t beamEnd; - - // exact interpolated position of entity on this frame - vec3_t lerpOrigin; - vec3_t lerpAngles; -} centity_t; - - -//====================================================================== - -// local entities are created as a result of events or predicted actions, -// and live independantly from all server transmitted entities - -typedef struct markPoly_s { - struct markPoly_s *prevMark, *nextMark; - int time; - qhandle_t markShader; - qboolean alphaFade; // fade alpha instead of rgb - float color[4]; - poly_t poly; - polyVert_t verts[MAX_VERTS_ON_POLY]; -} markPoly_t; - - -typedef enum { - LE_MARK, - LE_EXPLOSION, - LE_SPRITE_EXPLOSION, - LE_FRAGMENT, - LE_MOVE_SCALE_FADE, - LE_FALL_SCALE_FADE, - LE_FADE_RGB, - LE_SCALE_FADE, - LE_SCOREPLUM, -#ifdef MISSIONPACK - LE_KAMIKAZE, - LE_INVULIMPACT, - LE_INVULJUICED, - LE_SHOWREFENTITY -#endif -} leType_t; - -typedef enum { - LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time - LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells - LEF_SOUND1 = 0x0004, // sound 1 for kamikaze - LEF_SOUND2 = 0x0008 // sound 2 for kamikaze -} leFlag_t; - -typedef enum { - LEMT_NONE, - LEMT_BURN, - LEMT_BLOOD -} leMarkType_t; // fragment local entities can leave marks on walls - -typedef enum { - LEBS_NONE, - LEBS_BLOOD, - LEBS_BRASS -} leBounceSoundType_t; // fragment local entities can make sounds on impacts - -typedef struct localEntity_s { - struct localEntity_s *prev, *next; - leType_t leType; - int leFlags; - - int startTime; - int endTime; - int fadeInTime; - - float lifeRate; // 1.0 / (endTime - startTime) - - trajectory_t pos; - trajectory_t angles; - - float bounceFactor; // 0.0 = no bounce, 1.0 = perfect - - float color[4]; - - float radius; - - float light; - vec3_t lightColor; - - leMarkType_t leMarkType; // mark to leave on fragment impact - leBounceSoundType_t leBounceSoundType; - - refEntity_t refEntity; -} localEntity_t; - -//====================================================================== - - -typedef struct { - int client; - int score; - int ping; - int time; - int scoreFlags; - int powerUps; - int accuracy; - int impressiveCount; - int excellentCount; - int guantletCount; - int defendCount; - int assistCount; - int captures; - qboolean perfect; - int team; -} score_t; - -// each client has an associated clientInfo_t -// that contains media references necessary to present the -// client model and other color coded effects -// this is regenerated each time a client's configstring changes, -// usually as a result of a userinfo (name, model, etc) change -#define MAX_CUSTOM_SOUNDS 32 - -typedef struct { - qboolean infoValid; - - char name[MAX_QPATH]; - team_t team; - - int botSkill; // 0 = not bot, 1-5 = bot - - vec3_t color1; - vec3_t color2; - - int score; // updated by score servercmds - int location; // location index for team mode - int health; // you only get this info about your teammates - int armor; - int curWeapon; - - int handicap; - int wins, losses; // in tourney mode - - int teamTask; // task in teamplay (offence/defence) - qboolean teamLeader; // true when this is a team leader - - int powerups; // so can display quad/flag status - - int medkitUsageTime; - int invulnerabilityStartTime; - int invulnerabilityStopTime; - - int breathPuffTime; - - // when clientinfo is changed, the loading of models/skins/sounds - // can be deferred until you are dead, to prevent hitches in - // gameplay - char modelName[MAX_QPATH]; - char skinName[MAX_QPATH]; - char headModelName[MAX_QPATH]; - char headSkinName[MAX_QPATH]; - char redTeam[MAX_TEAMNAME]; - char blueTeam[MAX_TEAMNAME]; - qboolean deferred; - - qboolean newAnims; // true if using the new mission pack animations - qboolean fixedlegs; // true if legs yaw is always the same as torso yaw - qboolean fixedtorso; // true if torso never changes yaw - - vec3_t headOffset; // move head in icon views - footstep_t footsteps; - gender_t gender; // from model - - qhandle_t legsModel; - qhandle_t legsSkin; - - qhandle_t torsoModel; - qhandle_t torsoSkin; - - qhandle_t headModel; - qhandle_t headSkin; - - qhandle_t modelIcon; - - animation_t animations[MAX_TOTALANIMATIONS]; - - sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; -} clientInfo_t; - - -// each WP_* weapon enum has an associated weaponInfo_t -// that contains media references necessary to present the -// weapon and its effects -typedef struct weaponInfo_s { - qboolean registered; - gitem_t *item; - - qhandle_t handsModel; // the hands don't actually draw, they just position the weapon - qhandle_t weaponModel; - qhandle_t barrelModel; - qhandle_t flashModel; - - vec3_t weaponMidpoint; // so it will rotate centered instead of by tag - - float flashDlight; - vec3_t flashDlightColor; - sfxHandle_t flashSound[4]; // fast firing weapons randomly choose - - qhandle_t weaponIcon; - qhandle_t ammoIcon; - - qhandle_t ammoModel; - - qhandle_t missileModel; - sfxHandle_t missileSound; - void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); - float missileDlight; - vec3_t missileDlightColor; - int missileRenderfx; - - void (*ejectBrassFunc)( centity_t * ); - - float trailRadius; - float wiTrailTime; - - sfxHandle_t readySound; - sfxHandle_t firingSound; - qboolean loopFireSound; -} weaponInfo_t; - - -// each IT_* item has an associated itemInfo_t -// that constains media references necessary to present the -// item and its effects -typedef struct { - qboolean registered; - qhandle_t models[MAX_ITEM_MODELS]; - qhandle_t icon; -} itemInfo_t; - - -typedef struct { - int itemNum; -} powerupInfo_t; - - -#define MAX_SKULLTRAIL 10 - -typedef struct { - vec3_t positions[MAX_SKULLTRAIL]; - int numpositions; -} skulltrail_t; - - -#define MAX_REWARDSTACK 10 -#define MAX_SOUNDBUFFER 20 - -//====================================================================== - -// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action -// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after - -#define MAX_PREDICTED_EVENTS 16 - -typedef struct { - int clientFrame; // incremented each frame - - int clientNum; - - qboolean demoPlayback; - qboolean levelShot; // taking a level menu screenshot - int deferredPlayerLoading; - qboolean loading; // don't defer players at initial startup - qboolean intermissionStarted; // don't play voice rewards, because game will end shortly - - // there are only one or two snapshot_t that are relevent at a time - int latestSnapshotNum; // the number of snapshots the client system has received - int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet - - snapshot_t *snap; // cg.snap->serverTime <= cg.time - snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL - snapshot_t activeSnapshots[2]; - - float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) - - qboolean thisFrameTeleport; - qboolean nextFrameTeleport; - - int frametime; // cg.time - cg.oldTime - - int time; // this is the time value that the client - // is rendering at. - int oldTime; // time at last frame, used for missile trails and prediction checking - - int physicsTime; // either cg.snap->time or cg.nextSnap->time - - int timelimitWarnings; // 5 min, 1 min, overtime - int fraglimitWarnings; - - qboolean mapRestart; // set on a map restart to set back the weapon - - qboolean renderingThirdPerson; // during deaths, chasecams, etc - - // prediction state - qboolean hyperspace; // true if prediction has hit a trigger_teleport - playerState_t predictedPlayerState; - centity_t predictedPlayerEntity; - qboolean validPPS; // clear until the first call to CG_PredictPlayerState - int predictedErrorTime; - vec3_t predictedError; - - int eventSequence; - int predictableEvents[MAX_PREDICTED_EVENTS]; - - float stepChange; // for stair up smoothing - int stepTime; - - float duckChange; // for duck viewheight smoothing - int duckTime; - - float landChange; // for landing hard - int landTime; - - // input state sent to server - int weaponSelect; - - // auto rotating items - vec3_t autoAngles; - vec3_t autoAxis[3]; - vec3_t autoAnglesFast; - vec3_t autoAxisFast[3]; - - // view rendering - refdef_t refdef; - vec3_t refdefViewAngles; // will be converted to refdef.viewaxis - - // zoom key - qboolean zoomed; - int zoomTime; - float zoomSensitivity; - - // information screen text during loading - char infoScreenText[MAX_STRING_CHARS]; - - // scoreboard - int scoresRequestTime; - int numScores; - int selectedScore; - int teamScores[2]; - score_t scores[MAX_CLIENTS]; - qboolean showScores; - qboolean scoreBoardShowing; - int scoreFadeTime; - char killerName[MAX_NAME_LENGTH]; - char spectatorList[MAX_STRING_CHARS]; // list of names - int spectatorLen; // length of list - float spectatorWidth; // width in device units - int spectatorTime; // next time to offset - int spectatorPaintX; // current paint x - int spectatorPaintX2; // current paint x - int spectatorOffset; // current offset from start - int spectatorPaintLen; // current offset from start - - // skull trails - skulltrail_t skulltrails[MAX_CLIENTS]; - - // centerprinting - int centerPrintTime; - int centerPrintCharWidth; - int centerPrintY; - char centerPrint[1024]; - int centerPrintLines; - - // low ammo warning state - int lowAmmoWarning; // 1 = low, 2 = empty - - // kill timers for carnage reward - int lastKillTime; - - // crosshair client ID - int crosshairClientNum; - int crosshairClientTime; - - // powerup active flashing - int powerupActive; - int powerupTime; - - // attacking player - int attackerTime; - int voiceTime; - - // reward medals - int rewardStack; - int rewardTime; - int rewardCount[MAX_REWARDSTACK]; - qhandle_t rewardShader[MAX_REWARDSTACK]; - qhandle_t rewardSound[MAX_REWARDSTACK]; - - // sound buffer mainly for announcer sounds - int soundBufferIn; - int soundBufferOut; - int soundTime; - qhandle_t soundBuffer[MAX_SOUNDBUFFER]; - - // for voice chat buffer - int voiceChatTime; - int voiceChatBufferIn; - int voiceChatBufferOut; - - // warmup countdown - int warmup; - int warmupCount; - - //========================== - - int itemPickup; - int itemPickupTime; - int itemPickupBlendTime; // the pulse around the crosshair is timed seperately - - int weaponSelectTime; - int weaponAnimation; - int weaponAnimationTime; - - // blend blobs - float damageTime; - float damageX, damageY, damageValue; - - // status bar head - float headYaw; - float headEndPitch; - float headEndYaw; - int headEndTime; - float headStartPitch; - float headStartYaw; - int headStartTime; - - // view movement - float v_dmg_time; - float v_dmg_pitch; - float v_dmg_roll; - - vec3_t kick_angles; // weapon kicks - vec3_t kick_origin; - - // temp working variables for player view - float bobfracsin; - int bobcycle; - float xyspeed; - int nextOrbitTime; - - //qboolean cameraMode; // if rendering from a loaded camera - - - // development tool - refEntity_t testModelEntity; - char testModelName[MAX_QPATH]; - qboolean testGun; - -} cg_t; - - -// all of the model, shader, and sound references that are -// loaded at gamestate time are stored in cgMedia_t -// Other media that can be tied to clients, weapons, or items are -// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t -typedef struct { - qhandle_t charsetShader; - qhandle_t charsetProp; - qhandle_t charsetPropGlow; - qhandle_t charsetPropB; - qhandle_t whiteShader; - - qhandle_t redCubeModel; - qhandle_t blueCubeModel; - qhandle_t redCubeIcon; - qhandle_t blueCubeIcon; - qhandle_t redFlagModel; - qhandle_t blueFlagModel; - qhandle_t neutralFlagModel; - qhandle_t redFlagShader[3]; - qhandle_t blueFlagShader[3]; - qhandle_t flagShader[4]; - - qhandle_t flagPoleModel; - qhandle_t flagFlapModel; - - qhandle_t redFlagFlapSkin; - qhandle_t blueFlagFlapSkin; - qhandle_t neutralFlagFlapSkin; - - qhandle_t redFlagBaseModel; - qhandle_t blueFlagBaseModel; - qhandle_t neutralFlagBaseModel; - -#ifdef MISSIONPACK - qhandle_t overloadBaseModel; - qhandle_t overloadTargetModel; - qhandle_t overloadLightsModel; - qhandle_t overloadEnergyModel; - - qhandle_t harvesterModel; - qhandle_t harvesterRedSkin; - qhandle_t harvesterBlueSkin; - qhandle_t harvesterNeutralModel; -#endif - - qhandle_t armorModel; - qhandle_t armorIcon; - - qhandle_t teamStatusBar; - - qhandle_t deferShader; - - // gib explosions - qhandle_t gibAbdomen; - qhandle_t gibArm; - qhandle_t gibChest; - qhandle_t gibFist; - qhandle_t gibFoot; - qhandle_t gibForearm; - qhandle_t gibIntestine; - qhandle_t gibLeg; - qhandle_t gibSkull; - qhandle_t gibBrain; - - qhandle_t smoke2; - - qhandle_t machinegunBrassModel; - qhandle_t shotgunBrassModel; - - qhandle_t railRingsShader; - qhandle_t railCoreShader; - - qhandle_t lightningShader; - - qhandle_t friendShader; - - qhandle_t balloonShader; - qhandle_t connectionShader; - - qhandle_t selectShader; - qhandle_t viewBloodShader; - qhandle_t tracerShader; - qhandle_t crosshairShader[NUM_CROSSHAIRS]; - qhandle_t lagometerShader; - qhandle_t backTileShader; - qhandle_t noammoShader; - - qhandle_t smokePuffShader; - qhandle_t smokePuffRageProShader; - qhandle_t shotgunSmokePuffShader; - qhandle_t plasmaBallShader; - qhandle_t waterBubbleShader; - qhandle_t bloodTrailShader; -#ifdef MISSIONPACK - qhandle_t nailPuffShader; - qhandle_t blueProxMine; -#endif - - qhandle_t numberShaders[11]; - - qhandle_t shadowMarkShader; - - qhandle_t botSkillShaders[5]; - - // wall mark shaders - qhandle_t wakeMarkShader; - qhandle_t bloodMarkShader; - qhandle_t bulletMarkShader; - qhandle_t burnMarkShader; - qhandle_t holeMarkShader; - qhandle_t energyMarkShader; - - // powerup shaders - qhandle_t quadShader; - qhandle_t redQuadShader; - qhandle_t quadWeaponShader; - qhandle_t invisShader; - qhandle_t regenShader; - qhandle_t battleSuitShader; - qhandle_t battleWeaponShader; - qhandle_t hastePuffShader; - qhandle_t redKamikazeShader; - qhandle_t blueKamikazeShader; - - // weapon effect models - qhandle_t bulletFlashModel; - qhandle_t ringFlashModel; - qhandle_t dishFlashModel; - qhandle_t lightningExplosionModel; - - // weapon effect shaders - qhandle_t railExplosionShader; - qhandle_t plasmaExplosionShader; - qhandle_t bulletExplosionShader; - qhandle_t rocketExplosionShader; - qhandle_t grenadeExplosionShader; - qhandle_t bfgExplosionShader; - qhandle_t bloodExplosionShader; - - // special effects models - qhandle_t teleportEffectModel; - qhandle_t teleportEffectShader; -#ifdef MISSIONPACK - qhandle_t kamikazeEffectModel; - qhandle_t kamikazeShockWave; - qhandle_t kamikazeHeadModel; - qhandle_t kamikazeHeadTrail; - qhandle_t guardPowerupModel; - qhandle_t scoutPowerupModel; - qhandle_t doublerPowerupModel; - qhandle_t ammoRegenPowerupModel; - qhandle_t invulnerabilityImpactModel; - qhandle_t invulnerabilityJuicedModel; - qhandle_t medkitUsageModel; - qhandle_t dustPuffShader; - qhandle_t heartShader; -#endif - qhandle_t invulnerabilityPowerupModel; - - // scoreboard headers - qhandle_t scoreboardName; - qhandle_t scoreboardPing; - qhandle_t scoreboardScore; - qhandle_t scoreboardTime; - - // medals shown during gameplay - qhandle_t medalImpressive; - qhandle_t medalExcellent; - qhandle_t medalGauntlet; - qhandle_t medalDefend; - qhandle_t medalAssist; - qhandle_t medalCapture; - - // sounds - sfxHandle_t quadSound; - sfxHandle_t tracerSound; - sfxHandle_t selectSound; - sfxHandle_t useNothingSound; - sfxHandle_t wearOffSound; - sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; - sfxHandle_t sfx_lghit1; - sfxHandle_t sfx_lghit2; - sfxHandle_t sfx_lghit3; - sfxHandle_t sfx_ric1; - sfxHandle_t sfx_ric2; - sfxHandle_t sfx_ric3; - sfxHandle_t sfx_railg; - sfxHandle_t sfx_rockexp; - sfxHandle_t sfx_plasmaexp; -#ifdef MISSIONPACK - sfxHandle_t sfx_proxexp; - sfxHandle_t sfx_nghit; - sfxHandle_t sfx_nghitflesh; - sfxHandle_t sfx_nghitmetal; - sfxHandle_t sfx_chghit; - sfxHandle_t sfx_chghitflesh; - sfxHandle_t sfx_chghitmetal; - sfxHandle_t kamikazeExplodeSound; - sfxHandle_t kamikazeImplodeSound; - sfxHandle_t kamikazeFarSound; - sfxHandle_t useInvulnerabilitySound; - sfxHandle_t invulnerabilityImpactSound1; - sfxHandle_t invulnerabilityImpactSound2; - sfxHandle_t invulnerabilityImpactSound3; - sfxHandle_t invulnerabilityJuicedSound; - sfxHandle_t obeliskHitSound1; - sfxHandle_t obeliskHitSound2; - sfxHandle_t obeliskHitSound3; - sfxHandle_t obeliskRespawnSound; - sfxHandle_t winnerSound; - sfxHandle_t loserSound; - sfxHandle_t youSuckSound; -#endif - sfxHandle_t gibSound; - sfxHandle_t gibBounce1Sound; - sfxHandle_t gibBounce2Sound; - sfxHandle_t gibBounce3Sound; - sfxHandle_t teleInSound; - sfxHandle_t teleOutSound; - sfxHandle_t noAmmoSound; - sfxHandle_t respawnSound; - sfxHandle_t talkSound; - sfxHandle_t landSound; - sfxHandle_t fallSound; - sfxHandle_t jumpPadSound; - - sfxHandle_t oneMinuteSound; - sfxHandle_t fiveMinuteSound; - sfxHandle_t suddenDeathSound; - - sfxHandle_t threeFragSound; - sfxHandle_t twoFragSound; - sfxHandle_t oneFragSound; - - sfxHandle_t hitSound; - sfxHandle_t hitSoundHighArmor; - sfxHandle_t hitSoundLowArmor; - sfxHandle_t hitTeamSound; - sfxHandle_t impressiveSound; - sfxHandle_t excellentSound; - sfxHandle_t deniedSound; - sfxHandle_t humiliationSound; - sfxHandle_t assistSound; - sfxHandle_t defendSound; - sfxHandle_t firstImpressiveSound; - sfxHandle_t firstExcellentSound; - sfxHandle_t firstHumiliationSound; - - sfxHandle_t takenLeadSound; - sfxHandle_t tiedLeadSound; - sfxHandle_t lostLeadSound; - - sfxHandle_t voteNow; - sfxHandle_t votePassed; - sfxHandle_t voteFailed; - - sfxHandle_t watrInSound; - sfxHandle_t watrOutSound; - sfxHandle_t watrUnSound; - - sfxHandle_t flightSound; - sfxHandle_t medkitSound; - - sfxHandle_t weaponHoverSound; - - // teamplay sounds - sfxHandle_t captureAwardSound; - sfxHandle_t redScoredSound; - sfxHandle_t blueScoredSound; - sfxHandle_t redLeadsSound; - sfxHandle_t blueLeadsSound; - sfxHandle_t teamsTiedSound; - - sfxHandle_t captureYourTeamSound; - sfxHandle_t captureOpponentSound; - sfxHandle_t returnYourTeamSound; - sfxHandle_t returnOpponentSound; - sfxHandle_t takenYourTeamSound; - sfxHandle_t takenOpponentSound; - - sfxHandle_t redFlagReturnedSound; - sfxHandle_t blueFlagReturnedSound; - sfxHandle_t neutralFlagReturnedSound; - sfxHandle_t enemyTookYourFlagSound; - sfxHandle_t enemyTookTheFlagSound; - sfxHandle_t yourTeamTookEnemyFlagSound; - sfxHandle_t yourTeamTookTheFlagSound; - sfxHandle_t youHaveFlagSound; - sfxHandle_t yourBaseIsUnderAttackSound; - sfxHandle_t holyShitSound; - - // tournament sounds - sfxHandle_t count3Sound; - sfxHandle_t count2Sound; - sfxHandle_t count1Sound; - sfxHandle_t countFightSound; - sfxHandle_t countPrepareSound; - -#ifdef MISSIONPACK - // new stuff - qhandle_t patrolShader; - qhandle_t assaultShader; - qhandle_t campShader; - qhandle_t followShader; - qhandle_t defendShader; - qhandle_t teamLeaderShader; - qhandle_t retrieveShader; - qhandle_t escortShader; - qhandle_t flagShaders[3]; - sfxHandle_t countPrepareTeamSound; - - sfxHandle_t ammoregenSound; - sfxHandle_t doublerSound; - sfxHandle_t guardSound; - sfxHandle_t scoutSound; -#endif - qhandle_t cursor; - qhandle_t selectCursor; - qhandle_t sizeCursor; - - sfxHandle_t regenSound; - sfxHandle_t protectSound; - sfxHandle_t n_healthSound; - sfxHandle_t hgrenb1aSound; - sfxHandle_t hgrenb2aSound; - sfxHandle_t wstbimplSound; - sfxHandle_t wstbimpmSound; - sfxHandle_t wstbimpdSound; - sfxHandle_t wstbactvSound; - -} cgMedia_t; - - -// The client game static (cgs) structure hold everything -// loaded or calculated from the gamestate. It will NOT -// be cleared when a tournement restart is done, allowing -// all clients to begin playing instantly -typedef struct { - gameState_t gameState; // gamestate from server - glconfig_t glconfig; // rendering configuration - float screenXScale; // derived from glconfig - float screenYScale; - float screenXBias; - - int serverCommandSequence; // reliable command stream counter - int processedSnapshotNum;// the number of snapshots cgame has requested - - qboolean localServer; // detected on startup by checking sv_running - - // parsed from serverinfo - gametype_t gametype; - int dmflags; - int teamflags; - int fraglimit; - int capturelimit; - int timelimit; - int maxclients; - char mapname[MAX_QPATH]; - char redTeam[MAX_QPATH]; - char blueTeam[MAX_QPATH]; - - int voteTime; - int voteYes; - int voteNo; - qboolean voteModified; // beep whenever changed - char voteString[MAX_STRING_TOKENS]; - - int teamVoteTime[2]; - int teamVoteYes[2]; - int teamVoteNo[2]; - qboolean teamVoteModified[2]; // beep whenever changed - char teamVoteString[2][MAX_STRING_TOKENS]; - - int levelStartTime; - - int scores1, scores2; // from configstrings - int redflag, blueflag; // flag status from configstrings - int flagStatus; - - qboolean newHud; - - // - // locally derived information from gamestate - // - qhandle_t gameModels[MAX_MODELS]; - sfxHandle_t gameSounds[MAX_SOUNDS]; - - int numInlineModels; - qhandle_t inlineDrawModel[MAX_MODELS]; - vec3_t inlineModelMidpoints[MAX_MODELS]; - - clientInfo_t clientinfo[MAX_CLIENTS]; - - // teamchat width is *3 because of embedded color codes - char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; - int teamChatMsgTimes[TEAMCHAT_HEIGHT]; - int teamChatPos; - int teamLastChatPos; - - int cursorX; - int cursorY; - qboolean eventHandling; - qboolean mouseCaptured; - qboolean sizingHud; - void *capturedItem; - qhandle_t activeCursor; - - // orders - int currentOrder; - qboolean orderPending; - int orderTime; - int currentVoiceClient; - int acceptOrderTime; - int acceptTask; - int acceptLeader; - char acceptVoice[MAX_NAME_LENGTH]; - - // media - cgMedia_t media; - -} cgs_t; - -//============================================================================== - -extern cgs_t cgs; -extern cg_t cg; -extern centity_t cg_entities[MAX_GENTITIES]; -extern weaponInfo_t cg_weapons[MAX_WEAPONS]; -extern itemInfo_t cg_items[MAX_ITEMS]; -extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; - -extern vmCvar_t cg_centertime; -extern vmCvar_t cg_runpitch; -extern vmCvar_t cg_runroll; -extern vmCvar_t cg_bobup; -extern vmCvar_t cg_bobpitch; -extern vmCvar_t cg_bobroll; -extern vmCvar_t cg_swingSpeed; -extern vmCvar_t cg_shadows; -extern vmCvar_t cg_gibs; -extern vmCvar_t cg_drawTimer; -extern vmCvar_t cg_drawFPS; -extern vmCvar_t cg_drawSnapshot; -extern vmCvar_t cg_draw3dIcons; -extern vmCvar_t cg_drawIcons; -extern vmCvar_t cg_drawAmmoWarning; -extern vmCvar_t cg_drawCrosshair; -extern vmCvar_t cg_drawCrosshairNames; -extern vmCvar_t cg_drawRewards; -extern vmCvar_t cg_drawTeamOverlay; -extern vmCvar_t cg_teamOverlayUserinfo; -extern vmCvar_t cg_crosshairX; -extern vmCvar_t cg_crosshairY; -extern vmCvar_t cg_crosshairSize; -extern vmCvar_t cg_crosshairHealth; -extern vmCvar_t cg_drawStatus; -extern vmCvar_t cg_draw2D; -extern vmCvar_t cg_animSpeed; -extern vmCvar_t cg_debugAnim; -extern vmCvar_t cg_debugPosition; -extern vmCvar_t cg_debugEvents; -extern vmCvar_t cg_railTrailTime; -extern vmCvar_t cg_errorDecay; -extern vmCvar_t cg_nopredict; -extern vmCvar_t cg_noPlayerAnims; -extern vmCvar_t cg_showmiss; -extern vmCvar_t cg_footsteps; -extern vmCvar_t cg_addMarks; -extern vmCvar_t cg_brassTime; -extern vmCvar_t cg_gun_frame; -extern vmCvar_t cg_gun_x; -extern vmCvar_t cg_gun_y; -extern vmCvar_t cg_gun_z; -extern vmCvar_t cg_drawGun; -extern vmCvar_t cg_viewsize; -extern vmCvar_t cg_tracerChance; -extern vmCvar_t cg_tracerWidth; -extern vmCvar_t cg_tracerLength; -extern vmCvar_t cg_autoswitch; -extern vmCvar_t cg_ignore; -extern vmCvar_t cg_simpleItems; -extern vmCvar_t cg_fov; -extern vmCvar_t cg_zoomFov; -extern vmCvar_t cg_thirdPersonRange; -extern vmCvar_t cg_thirdPersonAngle; -extern vmCvar_t cg_thirdPerson; -extern vmCvar_t cg_stereoSeparation; -extern vmCvar_t cg_lagometer; -extern vmCvar_t cg_drawAttacker; -extern vmCvar_t cg_synchronousClients; -extern vmCvar_t cg_teamChatTime; -extern vmCvar_t cg_teamChatHeight; -extern vmCvar_t cg_stats; -extern vmCvar_t cg_forceModel; -extern vmCvar_t cg_buildScript; -extern vmCvar_t cg_paused; -extern vmCvar_t cg_blood; -extern vmCvar_t cg_predictItems; -extern vmCvar_t cg_deferPlayers; -extern vmCvar_t cg_drawFriend; -extern vmCvar_t cg_teamChatsOnly; -extern vmCvar_t cg_noVoiceChats; -extern vmCvar_t cg_noVoiceText; -extern vmCvar_t cg_scorePlum; -extern vmCvar_t cg_smoothClients; -extern vmCvar_t pmove_fixed; -extern vmCvar_t pmove_msec; -//extern vmCvar_t cg_pmove_fixed; -extern vmCvar_t cg_cameraOrbit; -extern vmCvar_t cg_cameraOrbitDelay; -extern vmCvar_t cg_timescaleFadeEnd; -extern vmCvar_t cg_timescaleFadeSpeed; -extern vmCvar_t cg_timescale; -extern vmCvar_t cg_cameraMode; -extern vmCvar_t cg_smallFont; -extern vmCvar_t cg_bigFont; -extern vmCvar_t cg_noTaunt; -extern vmCvar_t cg_noProjectileTrail; -extern vmCvar_t cg_oldRail; -extern vmCvar_t cg_oldRocket; -extern vmCvar_t cg_oldPlasma; -extern vmCvar_t cg_trueLightning; -#ifdef MISSIONPACK -extern vmCvar_t cg_redTeamName; -extern vmCvar_t cg_blueTeamName; -extern vmCvar_t cg_currentSelectedPlayer; -extern vmCvar_t cg_currentSelectedPlayerName; -extern vmCvar_t cg_singlePlayer; -extern vmCvar_t cg_enableDust; -extern vmCvar_t cg_enableBreath; -extern vmCvar_t cg_singlePlayerActive; -extern vmCvar_t cg_recordSPDemo; -extern vmCvar_t cg_recordSPDemoName; -extern vmCvar_t cg_obeliskRespawnDelay; -#endif - -// -// cg_main.c -// -const char *CG_ConfigString( int index ); -const char *CG_Argv( int arg ); - -void QDECL CG_Printf( const char *msg, ... ); -void QDECL CG_Error( const char *msg, ... ); - -void CG_StartMusic( void ); - -void CG_UpdateCvars( void ); - -int CG_CrosshairPlayer( void ); -int CG_LastAttacker( void ); -void CG_LoadMenus(const char *menuFile); -void CG_KeyEvent(int key, qboolean down); -void CG_MouseEvent(int x, int y); -void CG_EventHandling(int type); -void CG_RankRunFrame( void ); -void CG_SetScoreSelection(void *menu); -score_t *CG_GetSelectedScore(); -void CG_BuildSpectatorString(); - - -// -// cg_view.c -// -void CG_TestModel_f (void); -void CG_TestGun_f (void); -void CG_TestModelNextFrame_f (void); -void CG_TestModelPrevFrame_f (void); -void CG_TestModelNextSkin_f (void); -void CG_TestModelPrevSkin_f (void); -void CG_ZoomDown_f( void ); -void CG_ZoomUp_f( void ); -void CG_AddBufferedSound( sfxHandle_t sfx); - -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - - -// -// cg_drawtools.c -// -void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); -void CG_FillRect( float x, float y, float width, float height, const float *color ); -void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); -void CG_DrawString( float x, float y, const char *string, - float charWidth, float charHeight, const float *modulate ); - - -void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, - qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); -void CG_DrawBigString( int x, int y, const char *s, float alpha ); -void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); -void CG_DrawSmallString( int x, int y, const char *s, float alpha ); -void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); - -int CG_DrawStrlen( const char *str ); - -float *CG_FadeColor( int startMsec, int totalMsec ); -float *CG_TeamColor( int team ); -void CG_TileClear( void ); -void CG_ColorForHealth( vec4_t hcolor ); -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); - -void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); -void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); -void CG_DrawSides(float x, float y, float w, float h, float size); -void CG_DrawTopBottom(float x, float y, float w, float h, float size); - - -// -// cg_draw.c, cg_newDraw.c -// -extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; -extern int numSortedTeamPlayers; -extern int drawTeamOverlayModificationCount; -extern char systemChat[256]; -extern char teamChat1[256]; -extern char teamChat2[256]; - -void CG_AddLagometerFrameInfo( void ); -void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); -void CG_CenterPrint( const char *str, int y, int charWidth ); -void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); -void CG_DrawActive( stereoFrame_t stereoView ); -void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); -void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); -void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); -void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); -int CG_Text_Width(const char *text, float scale, int limit); -int CG_Text_Height(const char *text, float scale, int limit); -void CG_SelectPrevPlayer(); -void CG_SelectNextPlayer(); -float CG_GetValue(int ownerDraw); -qboolean CG_OwnerDrawVisible(int flags); -void CG_RunMenuScript(char **args); -void CG_ShowResponseHead(); -void CG_SetPrintString(int type, const char *p); -void CG_InitTeamChat(); -void CG_GetTeamColor(vec4_t *color); -const char *CG_GetGameStatusText(); -const char *CG_GetKillerText(); -void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); -void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); -void CG_CheckOrderPending(); -const char *CG_GameTypeString(); -qboolean CG_YourTeamHasFlag(); -qboolean CG_OtherTeamHasFlag(); -qhandle_t CG_StatusHandle(int task); - - - -// -// cg_player.c -// -void CG_Player( centity_t *cent ); -void CG_ResetPlayerEntity( centity_t *cent ); -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); -void CG_NewClientInfo( int clientNum ); -sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); - -// -// cg_predict.c -// -void CG_BuildSolidList( void ); -int CG_PointContents( const vec3_t point, int passEntityNum ); -void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask ); -void CG_PredictPlayerState( void ); -void CG_LoadDeferredPlayers( void ); - - -// -// cg_events.c -// -void CG_CheckEvents( centity_t *cent ); -const char *CG_PlaceString( int rank ); -void CG_EntityEvent( centity_t *cent, vec3_t position ); -void CG_PainEvent( centity_t *cent, int health ); - - -// -// cg_ents.c -// -void CG_SetEntitySoundPosition( centity_t *cent ); -void CG_AddPacketEntities( void ); -void CG_Beam( centity_t *cent ); -void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); - -void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ); -void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, - qhandle_t parentModel, char *tagName ); - - - -// -// cg_weapons.c -// -void CG_NextWeapon_f( void ); -void CG_PrevWeapon_f( void ); -void CG_Weapon_f( void ); - -void CG_RegisterWeapon( int weaponNum ); -void CG_RegisterItemVisuals( int itemNum ); - -void CG_FireWeapon( centity_t *cent ); -void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); -void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); -void CG_ShotgunFire( entityState_t *es ); -void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); - -void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); -void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); -void CG_AddViewWeapon (playerState_t *ps); -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); -void CG_DrawWeaponSelect( void ); - -void CG_OutOfAmmoChange( void ); // should this be in pmove? - -// -// cg_marks.c -// -void CG_InitMarkPolys( void ); -void CG_AddMarks( void ); -void CG_ImpactMark( qhandle_t markShader, - const vec3_t origin, const vec3_t dir, - float orientation, - float r, float g, float b, float a, - qboolean alphaFade, - float radius, qboolean temporary ); - -// -// cg_localents.c -// -void CG_InitLocalEntities( void ); -localEntity_t *CG_AllocLocalEntity( void ); -void CG_AddLocalEntities( void ); - -// -// cg_effects.c -// -localEntity_t *CG_SmokePuff( const vec3_t p, - const vec3_t vel, - float radius, - float r, float g, float b, float a, - float duration, - int startTime, - int fadeInTime, - int leFlags, - qhandle_t hShader ); -void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); -void CG_SpawnEffect( vec3_t org ); -#ifdef MISSIONPACK -void CG_KamikazeEffect( vec3_t org ); -void CG_ObeliskExplode( vec3_t org, int entityNum ); -void CG_ObeliskPain( vec3_t org ); -void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); -void CG_InvulnerabilityJuiced( vec3_t org ); -void CG_LightningBoltBeam( vec3_t start, vec3_t end ); -#endif -void CG_ScorePlum( int client, vec3_t org, int score ); - -void CG_GibPlayer( vec3_t playerOrigin ); -void CG_BigExplode( vec3_t playerOrigin ); - -void CG_Bleed( vec3_t origin, int entityNum ); - -localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, - qhandle_t hModel, qhandle_t shader, int msec, - qboolean isSprite ); - -// -// cg_snapshot.c -// -void CG_ProcessSnapshots( void ); - -// -// cg_info.c -// -void CG_LoadingString( const char *s ); -void CG_LoadingItem( int itemNum ); -void CG_LoadingClient( int clientNum ); -void CG_DrawInformation( void ); - -// -// cg_scoreboard.c -// -qboolean CG_DrawOldScoreboard( void ); -void CG_DrawOldTourneyScoreboard( void ); - -// -// cg_consolecmds.c -// -qboolean CG_ConsoleCommand( void ); -void CG_InitConsoleCommands( void ); - -// -// cg_servercmds.c -// -void CG_ExecuteNewServerCommands( int latestSequence ); -void CG_ParseServerinfo( void ); -void CG_SetConfigValues( void ); -void CG_LoadVoiceChats( void ); -void CG_ShaderStateChanged(void); -void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); -void CG_PlayBufferedVoiceChats( void ); - -// -// cg_playerstate.c -// -void CG_Respawn( void ); -void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); -void CG_CheckChangedPredictableEvents( playerState_t *ps ); - - -//=============================================== - -// -// system traps -// These functions are how the cgame communicates with the main game system -// - -// print message on the local console -void trap_Print( const char *fmt ); - -// abort the game -void trap_Error( const char *fmt ); - -// milliseconds should only be used for performance tuning, never -// for anything game related. Get time from the CG_DrawActiveFrame parameter -int trap_Milliseconds( void ); - -// console variable interaction -void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); -void trap_Cvar_Update( vmCvar_t *vmCvar ); -void trap_Cvar_Set( const char *var_name, const char *value ); -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); - -// ServerCommand and ConsoleCommand parameter access -int trap_Argc( void ); -void trap_Argv( int n, char *buffer, int bufferLength ); -void trap_Args( char *buffer, int bufferLength ); - -// filesystem access -// returns length of file -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); -void trap_FS_Read( void *buffer, int len, fileHandle_t f ); -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); -void trap_FS_FCloseFile( fileHandle_t f ); -int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t - -// add commands to the local console as if they were typed in -// for map changing, etc. The command is not executed immediately, -// but will be executed in order the next time console commands -// are processed -void trap_SendConsoleCommand( const char *text ); - -// register a command name so the console can perform command completion. -// FIXME: replace this with a normal console command "defineCommand"? -void trap_AddCommand( const char *cmdName ); - -// send a string to the server over the network -void trap_SendClientCommand( const char *s ); - -// force a screen update, only used during gamestate load -void trap_UpdateScreen( void ); - -// model collision -void trap_CM_LoadMap( const char *mapname ); -int trap_CM_NumInlineModels( void ); -clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels -clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); -int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); -int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); -void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ); -void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ); - -// Returns the projection of a polygon onto the solid brushes in the world -int trap_CM_MarkFragments( int numPoints, const vec3_t *points, - const vec3_t projection, - int maxPoints, vec3_t pointBuffer, - int maxFragments, markFragment_t *fragmentBuffer ); - -// normal sounds will have their volume dynamically changed as their entity -// moves and the listener moves -void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); -void trap_S_StopLoopingSound(int entnum); - -// a local sound is always played full volume -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); -void trap_S_ClearLoopingSounds( qboolean killall ); -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); -void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); - -// respatialize recalculates the volumes of sound as they should be heard by the -// given entityNum and position -void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found -void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music -void trap_S_StopBackgroundTrack( void ); - - -void trap_R_LoadWorldMap( const char *mapname ); - -// all media should be registered during level startup to prevent -// hitches during gameplay -qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found -qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found -qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found -qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found - -// a scene is built up by calls to R_ClearScene and the various R_Add functions. -// Nothing is drawn until R_RenderScene is called. -void trap_R_ClearScene( void ); -void trap_R_AddRefEntityToScene( const refEntity_t *re ); - -// polys are intended for simple wall marks, not really for doing -// significant construction -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); -void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); -int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); -void trap_R_RenderScene( const refdef_t *fd ); -void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 -void trap_R_DrawStretchPic( float x, float y, float w, float h, - float s1, float t1, float s2, float t2, qhandle_t hShader ); -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); -int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, - float frac, const char *tagName ); -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); - -// The glconfig_t will not change during the life of a cgame. -// If it needs to change, the entire cgame will be restarted, because -// all the qhandle_t are then invalid. -void trap_GetGlconfig( glconfig_t *glconfig ); - -// the gamestate should be grabbed at startup, and whenever a -// configstring changes -void trap_GetGameState( gameState_t *gamestate ); - -// cgame will poll each frame to see if a newer snapshot has arrived -// that it is interested in. The time is returned seperately so that -// snapshot latency can be calculated. -void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); - -// a snapshot get can fail if the snapshot (or the entties it holds) is so -// old that it has fallen out of the client system queue -qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); - -// retrieve a text command from the server stream -// the current snapshot will hold the number of the most recent command -// qfalse can be returned if the client system handled the command -// argc() / argv() can be used to examine the parameters of the command -qboolean trap_GetServerCommand( int serverCommandNumber ); - -// returns the most recent command number that can be passed to GetUserCmd -// this will always be at least one higher than the number in the current -// snapshot, and it may be quite a few higher if it is a fast computer on -// a lagged connection -int trap_GetCurrentCmdNumber( void ); - -qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); - -// used for the weapon select and zoom -void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); - -// aids for VM testing -void testPrintInt( char *string, int i ); -void testPrintFloat( char *string, float f ); - -int trap_MemoryRemaining( void ); -void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); -qboolean trap_Key_IsDown( int keynum ); -int trap_Key_GetCatcher( void ); -void trap_Key_SetCatcher( int catcher ); -int trap_Key_GetKey( const char *binding ); - - -typedef enum { - SYSTEM_PRINT, - CHAT_PRINT, - TEAMCHAT_PRINT -} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration - - -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); -e_status trap_CIN_StopCinematic(int handle); -e_status trap_CIN_RunCinematic (int handle); -void trap_CIN_DrawCinematic (int handle); -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); - -void trap_SnapVector( float *v ); - -qboolean trap_loadCamera(const char *name); -void trap_startCamera(int time); -qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); - -qboolean trap_GetEntityToken( char *buffer, int bufferSize ); - -void CG_ClearParticles (void); -void CG_AddParticles (void); -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); -void CG_AddParticleShrapnel (localEntity_t *le); -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); -extern qboolean initparticles; -int CG_NewParticleArea ( int num ); - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#include "../game/q_shared.h" +#include "tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" + + +// The entire cgame module is unloaded and reloaded on each level change, +// so there is NO persistant data between levels on the client side. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +#ifdef MISSIONPACK +#define CG_FONT_THRESHOLD 0.1 +#endif + +#define POWERUP_BLINKS 5 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define STEP_TIME 200 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define ITEM_SCALEUP_TIME 1000 +#define ZOOM_TIME 150 +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 20 +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_CROSSHAIRS 10 + +#define TEAM_OVERLAY_MAXNAME_WIDTH 12 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 + +#define DEFAULT_MODEL "sarge" +#ifdef MISSIONPACK +#define DEFAULT_TEAM_MODEL "james" +#define DEFAULT_TEAM_HEAD "*james" +#else +#define DEFAULT_TEAM_MODEL "sarge" +#define DEFAULT_TEAM_HEAD "sarge" +#endif + +#define DEFAULT_REDTEAM_NAME "Stroggs" +#define DEFAULT_BLUETEAM_NAME "Pagans" + +typedef enum { + FOOTSTEP_NORMAL, + FOOTSTEP_BOOT, + FOOTSTEP_FLESH, + FOOTSTEP_MECH, + FOOTSTEP_ENERGY, + FOOTSTEP_METAL, + FOOTSTEP_SPLASH, + + FOOTSTEP_TOTAL +} footstep_t; + +typedef enum { + IMPACTSOUND_DEFAULT, + IMPACTSOUND_METAL, + IMPACTSOUND_FLESH +} impactSound_t; + +//================================================= + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + + +typedef struct { + lerpFrame_t legs, torso, flag; + int painTime; + int painDirection; // flip from 0 to 1 + int lightningFiring; + + // railgun trail spawning + vec3_t railgunImpact; + qboolean railgunFlash; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_t; + +//================================================= + + + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int previousEvent; + int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int dustTrailTime; + int miscTime; + + int snapShotTime; // last time this entity was found in a snapshot + + playerEntity_t pe; + + int errorTime; // decay the error from this time + vec3_t errorOrigin; + vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independantly from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; +} markPoly_t; + + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_FRAGMENT, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCOREPLUM, +#ifdef MISSIONPACK + LE_KAMIKAZE, + LE_INVULIMPACT, + LE_INVULJUICED, + LE_SHOWREFENTITY +#endif +} leType_t; + +typedef enum { + LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time + LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells + LEF_SOUND1 = 0x0004, // sound 1 for kamikaze + LEF_SOUND2 = 0x0008 // sound 2 for kamikaze +} leFlag_t; + +typedef enum { + LEMT_NONE, + LEMT_BURN, + LEMT_BLOOD +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef enum { + LEBS_NONE, + LEBS_BLOOD, + LEBS_BRASS +} leBounceSoundType_t; // fragment local entities can make sounds on impacts + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + int fadeInTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + refEntity_t refEntity; +} localEntity_t; + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; + int powerUps; + int accuracy; + int impressiveCount; + int excellentCount; + int guantletCount; + int defendCount; + int assistCount; + int captures; + qboolean perfect; + int team; +} score_t; + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 + +typedef struct { + qboolean infoValid; + + char name[MAX_QPATH]; + team_t team; + + int botSkill; // 0 = not bot, 1-5 = bot + + vec3_t color1; + vec3_t color2; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int teamTask; // task in teamplay (offence/defence) + qboolean teamLeader; // true when this is a team leader + + int powerups; // so can display quad/flag status + + int medkitUsageTime; + int invulnerabilityStartTime; + int invulnerabilityStopTime; + + int breathPuffTime; + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char headModelName[MAX_QPATH]; + char headSkinName[MAX_QPATH]; + char redTeam[MAX_TEAMNAME]; + char blueTeam[MAX_TEAMNAME]; + qboolean deferred; + + qboolean newAnims; // true if using the new mission pack animations + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + + vec3_t headOffset; // move head in icon views + footstep_t footsteps; + gender_t gender; // from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + qhandle_t modelIcon; + + animation_t animations[MAX_TOTALANIMATIONS]; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; +} clientInfo_t; + + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + + void (*ejectBrassFunc)( centity_t * ); + + float trailRadius; + float wiTrailTime; + + sfxHandle_t readySound; + sfxHandle_t firingSound; + qboolean loopFireSound; +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t models[MAX_ITEM_MODELS]; + qhandle_t icon; +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + + +#define MAX_SKULLTRAIL 10 + +typedef struct { + vec3_t positions[MAX_SKULLTRAIL]; + int numpositions; +} skulltrail_t; + + +#define MAX_REWARDSTACK 10 +#define MAX_SOUNDBUFFER 20 + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + +typedef struct { + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[2]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + char spectatorList[MAX_STRING_CHARS]; // list of names + int spectatorLen; // length of list + float spectatorWidth; // width in device units + int spectatorTime; // next time to offset + int spectatorPaintX; // current paint x + int spectatorPaintX2; // current paint x + int spectatorOffset; // current offset from start + int spectatorPaintLen; // current offset from start + + // skull trails + skulltrail_t skulltrails[MAX_CLIENTS]; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + int voiceTime; + + // reward medals + int rewardStack; + int rewardTime; + int rewardCount[MAX_REWARDSTACK]; + qhandle_t rewardShader[MAX_REWARDSTACK]; + qhandle_t rewardSound[MAX_REWARDSTACK]; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[MAX_SOUNDBUFFER]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue; + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + //qboolean cameraMode; // if rendering from a loaded camera + + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + +} cg_t; + + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { + qhandle_t charsetShader; + qhandle_t charsetProp; + qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t whiteShader; + + qhandle_t redCubeModel; + qhandle_t blueCubeModel; + qhandle_t redCubeIcon; + qhandle_t blueCubeIcon; + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + qhandle_t neutralFlagModel; + qhandle_t redFlagShader[3]; + qhandle_t blueFlagShader[3]; + qhandle_t flagShader[4]; + + qhandle_t flagPoleModel; + qhandle_t flagFlapModel; + + qhandle_t redFlagFlapSkin; + qhandle_t blueFlagFlapSkin; + qhandle_t neutralFlagFlapSkin; + + qhandle_t redFlagBaseModel; + qhandle_t blueFlagBaseModel; + qhandle_t neutralFlagBaseModel; + +#ifdef MISSIONPACK + qhandle_t overloadBaseModel; + qhandle_t overloadTargetModel; + qhandle_t overloadLightsModel; + qhandle_t overloadEnergyModel; + + qhandle_t harvesterModel; + qhandle_t harvesterRedSkin; + qhandle_t harvesterBlueSkin; + qhandle_t harvesterNeutralModel; +#endif + + qhandle_t armorModel; + qhandle_t armorIcon; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + // gib explosions + qhandle_t gibAbdomen; + qhandle_t gibArm; + qhandle_t gibChest; + qhandle_t gibFist; + qhandle_t gibFoot; + qhandle_t gibForearm; + qhandle_t gibIntestine; + qhandle_t gibLeg; + qhandle_t gibSkull; + qhandle_t gibBrain; + + qhandle_t smoke2; + + qhandle_t machinegunBrassModel; + qhandle_t shotgunBrassModel; + + qhandle_t railRingsShader; + qhandle_t railCoreShader; + + qhandle_t lightningShader; + + qhandle_t friendShader; + + qhandle_t balloonShader; + qhandle_t connectionShader; + + qhandle_t selectShader; + qhandle_t viewBloodShader; + qhandle_t tracerShader; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t backTileShader; + qhandle_t noammoShader; + + qhandle_t smokePuffShader; + qhandle_t smokePuffRageProShader; + qhandle_t shotgunSmokePuffShader; + qhandle_t plasmaBallShader; + qhandle_t waterBubbleShader; + qhandle_t bloodTrailShader; +#ifdef MISSIONPACK + qhandle_t nailPuffShader; + qhandle_t blueProxMine; +#endif + + qhandle_t numberShaders[11]; + + qhandle_t shadowMarkShader; + + qhandle_t botSkillShaders[5]; + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t bloodMarkShader; + qhandle_t bulletMarkShader; + qhandle_t burnMarkShader; + qhandle_t holeMarkShader; + qhandle_t energyMarkShader; + + // powerup shaders + qhandle_t quadShader; + qhandle_t redQuadShader; + qhandle_t quadWeaponShader; + qhandle_t invisShader; + qhandle_t regenShader; + qhandle_t battleSuitShader; + qhandle_t battleWeaponShader; + qhandle_t hastePuffShader; + qhandle_t redKamikazeShader; + qhandle_t blueKamikazeShader; + + // weapon effect models + qhandle_t bulletFlashModel; + qhandle_t ringFlashModel; + qhandle_t dishFlashModel; + qhandle_t lightningExplosionModel; + + // weapon effect shaders + qhandle_t railExplosionShader; + qhandle_t plasmaExplosionShader; + qhandle_t bulletExplosionShader; + qhandle_t rocketExplosionShader; + qhandle_t grenadeExplosionShader; + qhandle_t bfgExplosionShader; + qhandle_t bloodExplosionShader; + + // special effects models + qhandle_t teleportEffectModel; + qhandle_t teleportEffectShader; +#ifdef MISSIONPACK + qhandle_t kamikazeEffectModel; + qhandle_t kamikazeShockWave; + qhandle_t kamikazeHeadModel; + qhandle_t kamikazeHeadTrail; + qhandle_t guardPowerupModel; + qhandle_t scoutPowerupModel; + qhandle_t doublerPowerupModel; + qhandle_t ammoRegenPowerupModel; + qhandle_t invulnerabilityImpactModel; + qhandle_t invulnerabilityJuicedModel; + qhandle_t medkitUsageModel; + qhandle_t dustPuffShader; + qhandle_t heartShader; +#endif + qhandle_t invulnerabilityPowerupModel; + + // scoreboard headers + qhandle_t scoreboardName; + qhandle_t scoreboardPing; + qhandle_t scoreboardScore; + qhandle_t scoreboardTime; + + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + qhandle_t medalDefend; + qhandle_t medalAssist; + qhandle_t medalCapture; + + // sounds + sfxHandle_t quadSound; + sfxHandle_t tracerSound; + sfxHandle_t selectSound; + sfxHandle_t useNothingSound; + sfxHandle_t wearOffSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + sfxHandle_t sfx_lghit1; + sfxHandle_t sfx_lghit2; + sfxHandle_t sfx_lghit3; + sfxHandle_t sfx_ric1; + sfxHandle_t sfx_ric2; + sfxHandle_t sfx_ric3; + sfxHandle_t sfx_railg; + sfxHandle_t sfx_rockexp; + sfxHandle_t sfx_plasmaexp; +#ifdef MISSIONPACK + sfxHandle_t sfx_proxexp; + sfxHandle_t sfx_nghit; + sfxHandle_t sfx_nghitflesh; + sfxHandle_t sfx_nghitmetal; + sfxHandle_t sfx_chghit; + sfxHandle_t sfx_chghitflesh; + sfxHandle_t sfx_chghitmetal; + sfxHandle_t kamikazeExplodeSound; + sfxHandle_t kamikazeImplodeSound; + sfxHandle_t kamikazeFarSound; + sfxHandle_t useInvulnerabilitySound; + sfxHandle_t invulnerabilityImpactSound1; + sfxHandle_t invulnerabilityImpactSound2; + sfxHandle_t invulnerabilityImpactSound3; + sfxHandle_t invulnerabilityJuicedSound; + sfxHandle_t obeliskHitSound1; + sfxHandle_t obeliskHitSound2; + sfxHandle_t obeliskHitSound3; + sfxHandle_t obeliskRespawnSound; + sfxHandle_t winnerSound; + sfxHandle_t loserSound; + sfxHandle_t youSuckSound; +#endif + sfxHandle_t gibSound; + sfxHandle_t gibBounce1Sound; + sfxHandle_t gibBounce2Sound; + sfxHandle_t gibBounce3Sound; + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t noAmmoSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + sfxHandle_t jumpPadSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + sfxHandle_t suddenDeathSound; + + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + + sfxHandle_t hitSound; + sfxHandle_t hitSoundHighArmor; + sfxHandle_t hitSoundLowArmor; + sfxHandle_t hitTeamSound; + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + sfxHandle_t assistSound; + sfxHandle_t defendSound; + sfxHandle_t firstImpressiveSound; + sfxHandle_t firstExcellentSound; + sfxHandle_t firstHumiliationSound; + + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + + sfxHandle_t voteNow; + sfxHandle_t votePassed; + sfxHandle_t voteFailed; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t flightSound; + sfxHandle_t medkitSound; + + sfxHandle_t weaponHoverSound; + + // teamplay sounds + sfxHandle_t captureAwardSound; + sfxHandle_t redScoredSound; + sfxHandle_t blueScoredSound; + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + sfxHandle_t captureYourTeamSound; + sfxHandle_t captureOpponentSound; + sfxHandle_t returnYourTeamSound; + sfxHandle_t returnOpponentSound; + sfxHandle_t takenYourTeamSound; + sfxHandle_t takenOpponentSound; + + sfxHandle_t redFlagReturnedSound; + sfxHandle_t blueFlagReturnedSound; + sfxHandle_t neutralFlagReturnedSound; + sfxHandle_t enemyTookYourFlagSound; + sfxHandle_t enemyTookTheFlagSound; + sfxHandle_t yourTeamTookEnemyFlagSound; + sfxHandle_t yourTeamTookTheFlagSound; + sfxHandle_t youHaveFlagSound; + sfxHandle_t yourBaseIsUnderAttackSound; + sfxHandle_t holyShitSound; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + sfxHandle_t countPrepareSound; + +#ifdef MISSIONPACK + // new stuff + qhandle_t patrolShader; + qhandle_t assaultShader; + qhandle_t campShader; + qhandle_t followShader; + qhandle_t defendShader; + qhandle_t teamLeaderShader; + qhandle_t retrieveShader; + qhandle_t escortShader; + qhandle_t flagShaders[3]; + sfxHandle_t countPrepareTeamSound; + + sfxHandle_t ammoregenSound; + sfxHandle_t doublerSound; + sfxHandle_t guardSound; + sfxHandle_t scoutSound; +#endif + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + sfxHandle_t regenSound; + sfxHandle_t protectSound; + sfxHandle_t n_healthSound; + sfxHandle_t hgrenb1aSound; + sfxHandle_t hgrenb2aSound; + sfxHandle_t wstbimplSound; + sfxHandle_t wstbimpmSound; + sfxHandle_t wstbimpdSound; + sfxHandle_t wstbactvSound; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum;// the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + gametype_t gametype; + int dmflags; + int teamflags; + int fraglimit; + int capturelimit; + int timelimit; + int maxclients; + char mapname[MAX_QPATH]; + char redTeam[MAX_QPATH]; + char blueTeam[MAX_QPATH]; + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int teamVoteTime[2]; + int teamVoteYes[2]; + int teamVoteNo[2]; + qboolean teamVoteModified[2]; // beep whenever changed + char teamVoteString[2][MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + int redflag, blueflag; // flag status from configstrings + int flagStatus; + + qboolean newHud; + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; + int teamChatMsgTimes[TEAMCHAT_HEIGHT]; + int teamChatPos; + int teamLastChatPos; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // orders + int currentOrder; + qboolean orderPending; + int orderTime; + int currentVoiceClient; + int acceptOrderTime; + int acceptTask; + int acceptLeader; + char acceptVoice[MAX_NAME_LENGTH]; + + // media + cgMedia_t media; + +} cgs_t; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_gibs; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_railTrailTime; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_tracerChance; +extern vmCvar_t cg_tracerWidth; +extern vmCvar_t cg_tracerLength; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_teamChatTime; +extern vmCvar_t cg_teamChatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +//extern vmCvar_t cg_pmove_fixed; +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_smallFont; +extern vmCvar_t cg_bigFont; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_noProjectileTrail; +extern vmCvar_t cg_oldRail; +extern vmCvar_t cg_oldRocket; +extern vmCvar_t cg_oldPlasma; +extern vmCvar_t cg_trueLightning; +#ifdef MISSIONPACK +extern vmCvar_t cg_redTeamName; +extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; +extern vmCvar_t cg_singlePlayer; +extern vmCvar_t cg_enableDust; +extern vmCvar_t cg_enableBreath; +extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; +extern vmCvar_t cg_obeliskRespawnDelay; +#endif + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( void ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus(const char *menuFile); +void CG_KeyEvent(int key, qboolean down); +void CG_MouseEvent(int x, int y); +void CG_EventHandling(int type); +void CG_RankRunFrame( void ); +void CG_SetScoreSelection(void *menu); +score_t *CG_GetSelectedScore(); +void CG_BuildSpectatorString(); + + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_ZoomDown_f( void ); +void CG_ZoomUp_f( void ); +void CG_AddBufferedSound( sfxHandle_t sfx); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + + +// +// cg_drawtools.c +// +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + + +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +void CG_DrawBigString( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); +void CG_DrawSmallString( int x, int y, const char *s, float alpha ); +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); + +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides(float x, float y, float w, float h, float size); +void CG_DrawTopBottom(float x, float y, float w, float h, float size); + + +// +// cg_draw.c, cg_newDraw.c +// +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern int drawTeamOverlayModificationCount; +extern char systemChat[256]; +extern char teamChat1[256]; +extern char teamChat2[256]; + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); +int CG_Text_Width(const char *text, float scale, int limit); +int CG_Text_Height(const char *text, float scale, int limit); +void CG_SelectPrevPlayer(); +void CG_SelectNextPlayer(); +float CG_GetValue(int ownerDraw); +qboolean CG_OwnerDrawVisible(int flags); +void CG_RunMenuScript(char **args); +void CG_ShowResponseHead(); +void CG_SetPrintString(int type, const char *p); +void CG_InitTeamChat(); +void CG_GetTeamColor(vec4_t *color); +const char *CG_GetGameStatusText(); +const char *CG_GetKillerText(); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); +void CG_CheckOrderPending(); +const char *CG_GameTypeString(); +qboolean CG_YourTeamHasFlag(); +qboolean CG_OtherTeamHasFlag(); +qhandle_t CG_StatusHandle(int task); + + + +// +// cg_player.c +// +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); +void CG_NewClientInfo( int clientNum ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); + + + +// +// cg_weapons.c +// +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); + +void CG_RegisterWeapon( int weaponNum ); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent ); +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); +void CG_ShotgunFire( entityState_t *es ); +void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); + +void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); +void CG_DrawWeaponSelect( void ); + +void CG_OutOfAmmoChange( void ); // should this be in pmove? + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); +void CG_SpawnEffect( vec3_t org ); +#ifdef MISSIONPACK +void CG_KamikazeEffect( vec3_t org ); +void CG_ObeliskExplode( vec3_t org, int entityNum ); +void CG_ObeliskPain( vec3_t org ); +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); +void CG_InvulnerabilityJuiced( vec3_t org ); +void CG_LightningBoltBeam( vec3_t start, vec3_t end ); +#endif +void CG_ScorePlum( int client, vec3_t org, int score ); + +void CG_GibPlayer( vec3_t playerOrigin ); +void CG_BigExplode( vec3_t playerOrigin ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, int msec, + qboolean isSprite ); + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawOldScoreboard( void ); +void CG_DrawOldTourneyScoreboard( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); +void CG_LoadVoiceChats( void ); +void CG_ShaderStateChanged(void); +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); +void CG_PlayBufferedVoiceChats( void ); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StopLoopingSound(int entnum); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( qboolean killall ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// respatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + + +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic(int handle); +e_status trap_CIN_RunCinematic (int handle); +void trap_CIN_DrawCinematic (int handle); +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); + +void trap_SnapVector( float *v ); + +qboolean trap_loadCamera(const char *name); +void trap_startCamera(int time); +qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +void CG_ClearParticles (void); +void CG_AddParticles (void); +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); +void CG_AddParticleShrapnel (localEntity_t *le); +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); +extern qboolean initparticles; +int CG_NewParticleArea ( int num ); + + diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c index 534b147..b14df64 100755 --- a/code/cgame/cg_localents.c +++ b/code/cgame/cg_localents.c @@ -1,886 +1,886 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - -// cg_localents.c -- every frame, generate renderer commands for locally -// processed entities, like smoke puffs, gibs, shells, etc. - -#include "cg_local.h" - -#define MAX_LOCAL_ENTITIES 512 -localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; -localEntity_t cg_activeLocalEntities; // double linked list -localEntity_t *cg_freeLocalEntities; // single linked list - -/* -=================== -CG_InitLocalEntities - -This is called at startup and for tournement restarts -=================== -*/ -void CG_InitLocalEntities( void ) { - int i; - - memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); - cg_activeLocalEntities.next = &cg_activeLocalEntities; - cg_activeLocalEntities.prev = &cg_activeLocalEntities; - cg_freeLocalEntities = cg_localEntities; - for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { - cg_localEntities[i].next = &cg_localEntities[i+1]; - } -} - - -/* -================== -CG_FreeLocalEntity -================== -*/ -void CG_FreeLocalEntity( localEntity_t *le ) { - if ( !le->prev ) { - CG_Error( "CG_FreeLocalEntity: not active" ); - } - - // remove from the doubly linked active list - le->prev->next = le->next; - le->next->prev = le->prev; - - // the free list is only singly linked - le->next = cg_freeLocalEntities; - cg_freeLocalEntities = le; -} - -/* -=================== -CG_AllocLocalEntity - -Will allways succeed, even if it requires freeing an old active entity -=================== -*/ -localEntity_t *CG_AllocLocalEntity( void ) { - localEntity_t *le; - - if ( !cg_freeLocalEntities ) { - // no free entities, so free the one at the end of the chain - // remove the oldest active entity - CG_FreeLocalEntity( cg_activeLocalEntities.prev ); - } - - le = cg_freeLocalEntities; - cg_freeLocalEntities = cg_freeLocalEntities->next; - - memset( le, 0, sizeof( *le ) ); - - // link into the active list - le->next = cg_activeLocalEntities.next; - le->prev = &cg_activeLocalEntities; - cg_activeLocalEntities.next->prev = le; - cg_activeLocalEntities.next = le; - return le; -} - - -/* -==================================================================================== - -FRAGMENT PROCESSING - -A fragment localentity interacts with the environment in some way (hitting walls), -or generates more localentities along a trail. - -==================================================================================== -*/ - -/* -================ -CG_BloodTrail - -Leave expanding blood puffs behind gibs -================ -*/ -void CG_BloodTrail( localEntity_t *le ) { - int t; - int t2; - int step; - vec3_t newOrigin; - localEntity_t *blood; - - step = 150; - t = step * ( (cg.time - cg.frametime + step ) / step ); - t2 = step * ( cg.time / step ); - - for ( ; t <= t2; t += step ) { - BG_EvaluateTrajectory( &le->pos, t, newOrigin ); - - blood = CG_SmokePuff( newOrigin, vec3_origin, - 20, // radius - 1, 1, 1, 1, // color - 2000, // trailTime - t, // startTime - 0, // fadeInTime - 0, // flags - cgs.media.bloodTrailShader ); - // use the optimized version - blood->leType = LE_FALL_SCALE_FADE; - // drop a total of 40 units over its lifetime - blood->pos.trDelta[2] = 40; - } -} - - -/* -================ -CG_FragmentBounceMark -================ -*/ -void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { - int radius; - - if ( le->leMarkType == LEMT_BLOOD ) { - - radius = 16 + (rand()&31); - CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, - 1,1,1,1, qtrue, radius, qfalse ); - } else if ( le->leMarkType == LEMT_BURN ) { - - radius = 8 + (rand()&15); - CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, - 1,1,1,1, qtrue, radius, qfalse ); - } - - - // don't allow a fragment to make multiple marks, or they - // pile up while settling - le->leMarkType = LEMT_NONE; -} - -/* -================ -CG_FragmentBounceSound -================ -*/ -void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { - if ( le->leBounceSoundType == LEBS_BLOOD ) { - // half the gibs will make splat sounds - if ( rand() & 1 ) { - int r = rand()&3; - sfxHandle_t s; - - if ( r == 0 ) { - s = cgs.media.gibBounce1Sound; - } else if ( r == 1 ) { - s = cgs.media.gibBounce2Sound; - } else { - s = cgs.media.gibBounce3Sound; - } - trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); - } - } else if ( le->leBounceSoundType == LEBS_BRASS ) { - - } - - // don't allow a fragment to make multiple bounce sounds, - // or it gets too noisy as they settle - le->leBounceSoundType = LEBS_NONE; -} - - -/* -================ -CG_ReflectVelocity -================ -*/ -void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { - vec3_t velocity; - float dot; - int hitTime; - - // reflect the velocity on the trace plane - hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; - BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); - dot = DotProduct( velocity, trace->plane.normal ); - VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); - - VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); - - VectorCopy( trace->endpos, le->pos.trBase ); - le->pos.trTime = cg.time; - - - // check for stop, making sure that even on low FPS systems it doesn't bobble - if ( trace->allsolid || - ( trace->plane.normal[2] > 0 && - ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { - le->pos.trType = TR_STATIONARY; - } else { - - } -} - -/* -================ -CG_AddFragment -================ -*/ -void CG_AddFragment( localEntity_t *le ) { - vec3_t newOrigin; - trace_t trace; - - if ( le->pos.trType == TR_STATIONARY ) { - // sink into the ground if near the removal time - int t; - float oldZ; - - t = le->endTime - cg.time; - if ( t < SINK_TIME ) { - // we must use an explicit lighting origin, otherwise the - // lighting would be lost as soon as the origin went - // into the ground - VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); - le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; - oldZ = le->refEntity.origin[2]; - le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); - trap_R_AddRefEntityToScene( &le->refEntity ); - le->refEntity.origin[2] = oldZ; - } else { - trap_R_AddRefEntityToScene( &le->refEntity ); - } - - return; - } - - // calculate new position - BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); - - // trace a line from previous position to new position - CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); - if ( trace.fraction == 1.0 ) { - // still in free fall - VectorCopy( newOrigin, le->refEntity.origin ); - - if ( le->leFlags & LEF_TUMBLE ) { - vec3_t angles; - - BG_EvaluateTrajectory( &le->angles, cg.time, angles ); - AnglesToAxis( angles, le->refEntity.axis ); - } - - trap_R_AddRefEntityToScene( &le->refEntity ); - - // add a blood trail - if ( le->leBounceSoundType == LEBS_BLOOD ) { - CG_BloodTrail( le ); - } - - return; - } - - // if it is in a nodrop zone, remove it - // this keeps gibs from waiting at the bottom of pits of death - // and floating levels - if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { - CG_FreeLocalEntity( le ); - return; - } - - // leave a mark - CG_FragmentBounceMark( le, &trace ); - - // do a bouncy sound - CG_FragmentBounceSound( le, &trace ); - - // reflect the velocity on the trace plane - CG_ReflectVelocity( le, &trace ); - - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -/* -===================================================================== - -TRIVIAL LOCAL ENTITIES - -These only do simple scaling or modulation before passing to the renderer -===================================================================== -*/ - -/* -==================== -CG_AddFadeRGB -==================== -*/ -void CG_AddFadeRGB( localEntity_t *le ) { - refEntity_t *re; - float c; - - re = &le->refEntity; - - c = ( le->endTime - cg.time ) * le->lifeRate; - c *= 0xff; - - re->shaderRGBA[0] = le->color[0] * c; - re->shaderRGBA[1] = le->color[1] * c; - re->shaderRGBA[2] = le->color[2] * c; - re->shaderRGBA[3] = le->color[3] * c; - - trap_R_AddRefEntityToScene( re ); -} - -/* -================== -CG_AddMoveScaleFade -================== -*/ -static void CG_AddMoveScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { - // fade / grow time - c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); - } - else { - // fade / grow time - c = ( le->endTime - cg.time ) * le->lifeRate; - } - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - - if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { - re->radius = le->radius * ( 1.0 - c ) + 8; - } - - BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - -/* -=================== -CG_AddScaleFade - -For rocket smokes that hang in place, fade out, and are -removed if the view passes through them. -There are often many of these, so it needs to be simple. -=================== -*/ -static void CG_AddScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - // fade / grow time - c = ( le->endTime - cg.time ) * le->lifeRate; - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - re->radius = le->radius * ( 1.0 - c ) + 8; - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - -/* -================= -CG_AddFallScaleFade - -This is just an optimized CG_AddMoveScaleFade -For blood mists that drift down, fade out, and are -removed if the view passes through them. -There are often 100+ of these, so it needs to be simple. -================= -*/ -static void CG_AddFallScaleFade( localEntity_t *le ) { - refEntity_t *re; - float c; - vec3_t delta; - float len; - - re = &le->refEntity; - - // fade time - c = ( le->endTime - cg.time ) * le->lifeRate; - - re->shaderRGBA[3] = 0xff * c * le->color[3]; - - re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; - - re->radius = le->radius * ( 1.0 - c ) + 16; - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( re->origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < le->radius ) { - CG_FreeLocalEntity( le ); - return; - } - - trap_R_AddRefEntityToScene( re ); -} - - - -/* -================ -CG_AddExplosion -================ -*/ -static void CG_AddExplosion( localEntity_t *ex ) { - refEntity_t *ent; - - ent = &ex->refEntity; - - // add the entity - trap_R_AddRefEntityToScene(ent); - - // add the dlight - if ( ex->light ) { - float light; - - light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); - if ( light < 0.5 ) { - light = 1.0; - } else { - light = 1.0 - ( light - 0.5 ) * 2; - } - light = ex->light * light; - trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); - } -} - -/* -================ -CG_AddSpriteExplosion -================ -*/ -static void CG_AddSpriteExplosion( localEntity_t *le ) { - refEntity_t re; - float c; - - re = le->refEntity; - - c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); - if ( c > 1 ) { - c = 1.0; // can happen during connection problems - } - - re.shaderRGBA[0] = 0xff; - re.shaderRGBA[1] = 0xff; - re.shaderRGBA[2] = 0xff; - re.shaderRGBA[3] = 0xff * c * 0.33; - - re.reType = RT_SPRITE; - re.radius = 42 * ( 1.0 - c ) + 30; - - trap_R_AddRefEntityToScene( &re ); - - // add the dlight - if ( le->light ) { - float light; - - light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); - if ( light < 0.5 ) { - light = 1.0; - } else { - light = 1.0 - ( light - 0.5 ) * 2; - } - light = le->light * light; - trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); - } -} - - -#ifdef MISSIONPACK -/* -==================== -CG_AddKamikaze -==================== -*/ -void CG_AddKamikaze( localEntity_t *le ) { - refEntity_t *re; - refEntity_t shockwave; - float c; - vec3_t test, axis[3]; - int t; - - re = &le->refEntity; - - t = cg.time - le->startTime; - VectorClear( test ); - AnglesToAxis( test, axis ); - - if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { - - if (!(le->leFlags & LEF_SOUND1)) { -// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); - trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); - le->leFlags |= LEF_SOUND1; - } - // 1st kamikaze shockwave - memset(&shockwave, 0, sizeof(shockwave)); - shockwave.hModel = cgs.media.kamikazeShockWave; - shockwave.reType = RT_MODEL; - shockwave.shaderTime = re->shaderTime; - VectorCopy(re->origin, shockwave.origin); - - c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); - VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); - VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); - VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); - shockwave.nonNormalizedAxes = qtrue; - - if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { - c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); - } - else { - c = 0; - } - c *= 0xff; - shockwave.shaderRGBA[0] = 0xff - c; - shockwave.shaderRGBA[1] = 0xff - c; - shockwave.shaderRGBA[2] = 0xff - c; - shockwave.shaderRGBA[3] = 0xff - c; - - trap_R_AddRefEntityToScene( &shockwave ); - } - - if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { - // explosion and implosion - c = ( le->endTime - cg.time ) * le->lifeRate; - c *= 0xff; - re->shaderRGBA[0] = le->color[0] * c; - re->shaderRGBA[1] = le->color[1] * c; - re->shaderRGBA[2] = le->color[2] * c; - re->shaderRGBA[3] = le->color[3] * c; - - if( t < KAMI_IMPLODE_STARTTIME ) { - c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); - } - else { - if (!(le->leFlags & LEF_SOUND2)) { -// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); - trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); - le->leFlags |= LEF_SOUND2; - } - c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); - } - VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); - VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); - VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); - re->nonNormalizedAxes = qtrue; - - trap_R_AddRefEntityToScene( re ); - // add the dlight - trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); - } - - if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { - // 2nd kamikaze shockwave - if (le->angles.trBase[0] == 0 && - le->angles.trBase[1] == 0 && - le->angles.trBase[2] == 0) { - le->angles.trBase[0] = random() * 360; - le->angles.trBase[1] = random() * 360; - le->angles.trBase[2] = random() * 360; - } - else { - c = 0; - } - memset(&shockwave, 0, sizeof(shockwave)); - shockwave.hModel = cgs.media.kamikazeShockWave; - shockwave.reType = RT_MODEL; - shockwave.shaderTime = re->shaderTime; - VectorCopy(re->origin, shockwave.origin); - - test[0] = le->angles.trBase[0]; - test[1] = le->angles.trBase[1]; - test[2] = le->angles.trBase[2]; - AnglesToAxis( test, axis ); - - c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); - VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); - VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); - VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); - shockwave.nonNormalizedAxes = qtrue; - - if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { - c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); - } - else { - c = 0; - } - c *= 0xff; - shockwave.shaderRGBA[0] = 0xff - c; - shockwave.shaderRGBA[1] = 0xff - c; - shockwave.shaderRGBA[2] = 0xff - c; - shockwave.shaderRGBA[3] = 0xff - c; - - trap_R_AddRefEntityToScene( &shockwave ); - } -} - -/* -=================== -CG_AddInvulnerabilityImpact -=================== -*/ -void CG_AddInvulnerabilityImpact( localEntity_t *le ) { - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -/* -=================== -CG_AddInvulnerabilityJuiced -=================== -*/ -void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { - int t; - - t = cg.time - le->startTime; - if ( t > 3000 ) { - le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; - le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; - le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; - } - if ( t > 5000 ) { - le->endTime = 0; - CG_GibPlayer( le->refEntity.origin ); - } - else { - trap_R_AddRefEntityToScene( &le->refEntity ); - } -} - -/* -=================== -CG_AddRefEntity -=================== -*/ -void CG_AddRefEntity( localEntity_t *le ) { - if (le->endTime < cg.time) { - CG_FreeLocalEntity( le ); - return; - } - trap_R_AddRefEntityToScene( &le->refEntity ); -} - -#endif -/* -=================== -CG_AddScorePlum -=================== -*/ -#define NUMBER_SIZE 8 - -void CG_AddScorePlum( localEntity_t *le ) { - refEntity_t *re; - vec3_t origin, delta, dir, vec, up = {0, 0, 1}; - float c, len; - int i, score, digits[10], numdigits, negative; - - re = &le->refEntity; - - c = ( le->endTime - cg.time ) * le->lifeRate; - - score = le->radius; - if (score < 0) { - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0x11; - re->shaderRGBA[2] = 0x11; - } - else { - re->shaderRGBA[0] = 0xff; - re->shaderRGBA[1] = 0xff; - re->shaderRGBA[2] = 0xff; - if (score >= 50) { - re->shaderRGBA[1] = 0; - } else if (score >= 20) { - re->shaderRGBA[0] = re->shaderRGBA[1] = 0; - } else if (score >= 10) { - re->shaderRGBA[2] = 0; - } else if (score >= 2) { - re->shaderRGBA[0] = re->shaderRGBA[2] = 0; - } - - } - if (c < 0.25) - re->shaderRGBA[3] = 0xff * 4 * c; - else - re->shaderRGBA[3] = 0xff; - - re->radius = NUMBER_SIZE / 2; - - VectorCopy(le->pos.trBase, origin); - origin[2] += 110 - c * 100; - - VectorSubtract(cg.refdef.vieworg, origin, dir); - CrossProduct(dir, up, vec); - VectorNormalize(vec); - - VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); - - // if the view would be "inside" the sprite, kill the sprite - // so it doesn't add too much overdraw - VectorSubtract( origin, cg.refdef.vieworg, delta ); - len = VectorLength( delta ); - if ( len < 20 ) { - CG_FreeLocalEntity( le ); - return; - } - - negative = qfalse; - if (score < 0) { - negative = qtrue; - score = -score; - } - - for (numdigits = 0; !(numdigits && !score); numdigits++) { - digits[numdigits] = score % 10; - score = score / 10; - } - - if (negative) { - digits[numdigits] = 10; - numdigits++; - } - - for (i = 0; i < numdigits; i++) { - VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); - re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; - trap_R_AddRefEntityToScene( re ); - } -} - - - - -//============================================================================== - -/* -=================== -CG_AddLocalEntities - -=================== -*/ -void CG_AddLocalEntities( void ) { - localEntity_t *le, *next; - - // walk the list backwards, so any new local entities generated - // (trails, marks, etc) will be present this frame - le = cg_activeLocalEntities.prev; - for ( ; le != &cg_activeLocalEntities ; le = next ) { - // grab next now, so if the local entity is freed we - // still have it - next = le->prev; - - if ( cg.time >= le->endTime ) { - CG_FreeLocalEntity( le ); - continue; - } - switch ( le->leType ) { - default: - CG_Error( "Bad leType: %i", le->leType ); - break; - - case LE_MARK: - break; - - case LE_SPRITE_EXPLOSION: - CG_AddSpriteExplosion( le ); - break; - - case LE_EXPLOSION: - CG_AddExplosion( le ); - break; - - case LE_FRAGMENT: // gibs and brass - CG_AddFragment( le ); - break; - - case LE_MOVE_SCALE_FADE: // water bubbles - CG_AddMoveScaleFade( le ); - break; - - case LE_FADE_RGB: // teleporters, railtrails - CG_AddFadeRGB( le ); - break; - - case LE_FALL_SCALE_FADE: // gib blood trails - CG_AddFallScaleFade( le ); - break; - - case LE_SCALE_FADE: // rocket trails - CG_AddScaleFade( le ); - break; - - case LE_SCOREPLUM: - CG_AddScorePlum( le ); - break; - -#ifdef MISSIONPACK - case LE_KAMIKAZE: - CG_AddKamikaze( le ); - break; - case LE_INVULIMPACT: - CG_AddInvulnerabilityImpact( le ); - break; - case LE_INVULJUICED: - CG_AddInvulnerabilityJuiced( le ); - break; - case LE_SHOWREFENTITY: - CG_AddRefEntity( le ); - break; -#endif - } - } -} - - - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, shells, etc. + +#include "cg_local.h" + +#define MAX_LOCAL_ENTITIES 512 +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + +/* +================ +CG_BloodTrail + +Leave expanding blood puffs behind gibs +================ +*/ +void CG_BloodTrail( localEntity_t *le ) { + int t; + int t2; + int step; + vec3_t newOrigin; + localEntity_t *blood; + + step = 150; + t = step * ( (cg.time - cg.frametime + step ) / step ); + t2 = step * ( cg.time / step ); + + for ( ; t <= t2; t += step ) { + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + + blood = CG_SmokePuff( newOrigin, vec3_origin, + 20, // radius + 1, 1, 1, 1, // color + 2000, // trailTime + t, // startTime + 0, // fadeInTime + 0, // flags + cgs.media.bloodTrailShader ); + // use the optimized version + blood->leType = LE_FALL_SCALE_FADE; + // drop a total of 40 units over its lifetime + blood->pos.trDelta[2] = 40; + } +} + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) { + + radius = 16 + (rand()&31); + CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } else if ( le->leMarkType == LEMT_BURN ) { + + radius = 8 + (rand()&15); + CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } + + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { + if ( le->leBounceSoundType == LEBS_BLOOD ) { + // half the gibs will make splat sounds + if ( rand() & 1 ) { + int r = rand()&3; + sfxHandle_t s; + + if ( r == 0 ) { + s = cgs.media.gibBounce1Sound; + } else if ( r == 1 ) { + s = cgs.media.gibBounce2Sound; + } else { + s = cgs.media.gibBounce3Sound; + } + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + } else if ( le->leBounceSoundType == LEBS_BRASS ) { + + } + + // don't allow a fragment to make multiple bounce sounds, + // or it gets too noisy as they settle + le->leBounceSoundType = LEBS_NONE; +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg.time; + + + // check for stop, making sure that even on low FPS systems it doesn't bobble + if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { + le->pos.trType = TR_STATIONARY; + } else { + + } +} + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + + // add a blood trail + if ( le->leBounceSoundType == LEBS_BLOOD ) { + CG_BloodTrail( le ); + } + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); + } + else { + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + } + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + re->radius = le->radius * ( 1.0 - c ) + 8; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +static void CG_AddExplosion( localEntity_t *ex ) { + refEntity_t *ent; + + ent = &ex->refEntity; + + // add the entity + trap_R_AddRefEntityToScene(ent); + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +static void CG_AddSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 42 * ( 1.0 - c ) + 30; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + + +#ifdef MISSIONPACK +/* +==================== +CG_AddKamikaze +==================== +*/ +void CG_AddKamikaze( localEntity_t *le ) { + refEntity_t *re; + refEntity_t shockwave; + float c; + vec3_t test, axis[3]; + int t; + + re = &le->refEntity; + + t = cg.time - le->startTime; + VectorClear( test ); + AnglesToAxis( test, axis ); + + if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { + + if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND1; + } + // 1st kamikaze shockwave + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } + + if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { + // explosion and implosion + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + if( t < KAMI_IMPLODE_STARTTIME ) { + c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); + } + else { + if (!(le->leFlags & LEF_SOUND2)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND2; + } + c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); + } + VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); + VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); + VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); + re->nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( re ); + // add the dlight + trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); + } + + if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { + // 2nd kamikaze shockwave + if (le->angles.trBase[0] == 0 && + le->angles.trBase[1] == 0 && + le->angles.trBase[2] == 0) { + le->angles.trBase[0] = random() * 360; + le->angles.trBase[1] = random() * 360; + le->angles.trBase[2] = random() * 360; + } + else { + c = 0; + } + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + test[0] = le->angles.trBase[0]; + test[1] = le->angles.trBase[1]; + test[2] = le->angles.trBase[2]; + AnglesToAxis( test, axis ); + + c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } +} + +/* +=================== +CG_AddInvulnerabilityImpact +=================== +*/ +void CG_AddInvulnerabilityImpact( localEntity_t *le ) { + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +=================== +CG_AddInvulnerabilityJuiced +=================== +*/ +void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { + int t; + + t = cg.time - le->startTime; + if ( t > 3000 ) { + le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; + } + if ( t > 5000 ) { + le->endTime = 0; + CG_GibPlayer( le->refEntity.origin ); + } + else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } +} + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) { + if (le->endTime < cg.time) { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +#endif +/* +=================== +CG_AddScorePlum +=================== +*/ +#define NUMBER_SIZE 8 + +void CG_AddScorePlum( localEntity_t *le ) { + refEntity_t *re; + vec3_t origin, delta, dir, vec, up = {0, 0, 1}; + float c, len; + int i, score, digits[10], numdigits, negative; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + + score = le->radius; + if (score < 0) { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0x11; + re->shaderRGBA[2] = 0x11; + } + else { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + if (score >= 50) { + re->shaderRGBA[1] = 0; + } else if (score >= 20) { + re->shaderRGBA[0] = re->shaderRGBA[1] = 0; + } else if (score >= 10) { + re->shaderRGBA[2] = 0; + } else if (score >= 2) { + re->shaderRGBA[0] = re->shaderRGBA[2] = 0; + } + + } + if (c < 0.25) + re->shaderRGBA[3] = 0xff * 4 * c; + else + re->shaderRGBA[3] = 0xff; + + re->radius = NUMBER_SIZE / 2; + + VectorCopy(le->pos.trBase, origin); + origin[2] += 110 - c * 100; + + VectorSubtract(cg.refdef.vieworg, origin, dir); + CrossProduct(dir, up, vec); + VectorNormalize(vec); + + VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < 20 ) { + CG_FreeLocalEntity( le ); + return; + } + + negative = qfalse; + if (score < 0) { + negative = qtrue; + score = -score; + } + + for (numdigits = 0; !(numdigits && !score); numdigits++) { + digits[numdigits] = score % 10; + score = score / 10; + } + + if (negative) { + digits[numdigits] = 10; + numdigits++; + } + + for (i = 0; i < numdigits; i++) { + VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); + re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; + trap_R_AddRefEntityToScene( re ); + } +} + + + + +//============================================================================== + +/* +=================== +CG_AddLocalEntities + +=================== +*/ +void CG_AddLocalEntities( void ) { + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg.time >= le->endTime ) { + CG_FreeLocalEntity( le ); + continue; + } + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_SCOREPLUM: + CG_AddScorePlum( le ); + break; + +#ifdef MISSIONPACK + case LE_KAMIKAZE: + CG_AddKamikaze( le ); + break; + case LE_INVULIMPACT: + CG_AddInvulnerabilityImpact( le ); + break; + case LE_INVULJUICED: + CG_AddInvulnerabilityJuiced( le ); + break; + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; +#endif + } + } +} + + + + diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c index aa9ec27..7212996 100755 --- a/code/cgame/cg_main.c +++ b/code/cgame/cg_main.c @@ -1,1997 +1,1997 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_main.c -- initialization and primary entry point for cgame -#include "cg_local.h" - -#ifdef MISSIONPACK -#include "../ui/ui_shared.h" -// display context for new ui stuff -displayContextDef_t cgDC; -#endif - -int forceModelModificationCount = -1; - -void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); -void CG_Shutdown( void ); - - -/* -================ -vmMain - -This is the only way control passes into the module. -This must be the very first function compiled into the .q3vm file -================ -*/ -int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { - - switch ( command ) { - case CG_INIT: - CG_Init( arg0, arg1, arg2 ); - return 0; - case CG_SHUTDOWN: - CG_Shutdown(); - return 0; - case CG_CONSOLE_COMMAND: - return CG_ConsoleCommand(); - case CG_DRAW_ACTIVE_FRAME: - CG_DrawActiveFrame( arg0, arg1, arg2 ); - return 0; - case CG_CROSSHAIR_PLAYER: - return CG_CrosshairPlayer(); - case CG_LAST_ATTACKER: - return CG_LastAttacker(); - case CG_KEY_EVENT: - CG_KeyEvent(arg0, arg1); - return 0; - case CG_MOUSE_EVENT: -#ifdef MISSIONPACK - cgDC.cursorx = cgs.cursorX; - cgDC.cursory = cgs.cursorY; -#endif - CG_MouseEvent(arg0, arg1); - return 0; - case CG_EVENT_HANDLING: - CG_EventHandling(arg0); - return 0; - default: - CG_Error( "vmMain: unknown command %i", command ); - break; - } - return -1; -} - - -cg_t cg; -cgs_t cgs; -centity_t cg_entities[MAX_GENTITIES]; -weaponInfo_t cg_weapons[MAX_WEAPONS]; -itemInfo_t cg_items[MAX_ITEMS]; - - -vmCvar_t cg_railTrailTime; -vmCvar_t cg_centertime; -vmCvar_t cg_runpitch; -vmCvar_t cg_runroll; -vmCvar_t cg_bobup; -vmCvar_t cg_bobpitch; -vmCvar_t cg_bobroll; -vmCvar_t cg_swingSpeed; -vmCvar_t cg_shadows; -vmCvar_t cg_gibs; -vmCvar_t cg_drawTimer; -vmCvar_t cg_drawFPS; -vmCvar_t cg_drawSnapshot; -vmCvar_t cg_draw3dIcons; -vmCvar_t cg_drawIcons; -vmCvar_t cg_drawAmmoWarning; -vmCvar_t cg_drawCrosshair; -vmCvar_t cg_drawCrosshairNames; -vmCvar_t cg_drawRewards; -vmCvar_t cg_crosshairSize; -vmCvar_t cg_crosshairX; -vmCvar_t cg_crosshairY; -vmCvar_t cg_crosshairHealth; -vmCvar_t cg_draw2D; -vmCvar_t cg_drawStatus; -vmCvar_t cg_animSpeed; -vmCvar_t cg_debugAnim; -vmCvar_t cg_debugPosition; -vmCvar_t cg_debugEvents; -vmCvar_t cg_errorDecay; -vmCvar_t cg_nopredict; -vmCvar_t cg_noPlayerAnims; -vmCvar_t cg_showmiss; -vmCvar_t cg_footsteps; -vmCvar_t cg_addMarks; -vmCvar_t cg_brassTime; -vmCvar_t cg_viewsize; -vmCvar_t cg_drawGun; -vmCvar_t cg_gun_frame; -vmCvar_t cg_gun_x; -vmCvar_t cg_gun_y; -vmCvar_t cg_gun_z; -vmCvar_t cg_tracerChance; -vmCvar_t cg_tracerWidth; -vmCvar_t cg_tracerLength; -vmCvar_t cg_autoswitch; -vmCvar_t cg_ignore; -vmCvar_t cg_simpleItems; -vmCvar_t cg_fov; -vmCvar_t cg_zoomFov; -vmCvar_t cg_thirdPerson; -vmCvar_t cg_thirdPersonRange; -vmCvar_t cg_thirdPersonAngle; -vmCvar_t cg_stereoSeparation; -vmCvar_t cg_lagometer; -vmCvar_t cg_drawAttacker; -vmCvar_t cg_synchronousClients; -vmCvar_t cg_teamChatTime; -vmCvar_t cg_teamChatHeight; -vmCvar_t cg_stats; -vmCvar_t cg_buildScript; -vmCvar_t cg_forceModel; -vmCvar_t cg_paused; -vmCvar_t cg_blood; -vmCvar_t cg_predictItems; -vmCvar_t cg_deferPlayers; -vmCvar_t cg_drawTeamOverlay; -vmCvar_t cg_teamOverlayUserinfo; -vmCvar_t cg_drawFriend; -vmCvar_t cg_teamChatsOnly; -vmCvar_t cg_noVoiceChats; -vmCvar_t cg_noVoiceText; -vmCvar_t cg_hudFiles; -vmCvar_t cg_scorePlum; -vmCvar_t cg_smoothClients; -vmCvar_t pmove_fixed; -//vmCvar_t cg_pmove_fixed; -vmCvar_t pmove_msec; -vmCvar_t cg_pmove_msec; -vmCvar_t cg_cameraMode; -vmCvar_t cg_cameraOrbit; -vmCvar_t cg_cameraOrbitDelay; -vmCvar_t cg_timescaleFadeEnd; -vmCvar_t cg_timescaleFadeSpeed; -vmCvar_t cg_timescale; -vmCvar_t cg_smallFont; -vmCvar_t cg_bigFont; -vmCvar_t cg_noTaunt; -vmCvar_t cg_noProjectileTrail; -vmCvar_t cg_oldRail; -vmCvar_t cg_oldRocket; -vmCvar_t cg_oldPlasma; -vmCvar_t cg_trueLightning; - -#ifdef MISSIONPACK -vmCvar_t cg_redTeamName; -vmCvar_t cg_blueTeamName; -vmCvar_t cg_currentSelectedPlayer; -vmCvar_t cg_currentSelectedPlayerName; -vmCvar_t cg_singlePlayer; -vmCvar_t cg_enableDust; -vmCvar_t cg_enableBreath; -vmCvar_t cg_singlePlayerActive; -vmCvar_t cg_recordSPDemo; -vmCvar_t cg_recordSPDemoName; -vmCvar_t cg_obeliskRespawnDelay; -#endif - -typedef struct { - vmCvar_t *vmCvar; - char *cvarName; - char *defaultString; - int cvarFlags; -} cvarTable_t; - -static cvarTable_t cvarTable[] = { // bk001129 - { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging - { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, - { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, - { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, - { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, - { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, - { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, - { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, - { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, - { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, - { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, - { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, - { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, - { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, - { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, - { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, - { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, - { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, - { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, - { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, - { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, - { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, - { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, - { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, - { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, - { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, - { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, - { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, - { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, - { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, - { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, - { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, - { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, - { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, - { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, - { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, - { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, - { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, - { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, - { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, - { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, - { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, - { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, - { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, - { &cg_errorDecay, "cg_errordecay", "100", 0 }, - { &cg_nopredict, "cg_nopredict", "0", 0 }, - { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, - { &cg_showmiss, "cg_showmiss", "0", 0 }, - { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, - { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, - { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, - { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, - { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, - { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, - { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, - { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, - { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, - { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, - { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, -#ifdef MISSIONPACK - { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, -#else - { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, -#endif - { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, - { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, - { &cg_stats, "cg_stats", "0", 0 }, - { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, - { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, - { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, - { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, - // the following variables are created in other parts of the system, - // but we also reference them here - { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures - { &cg_paused, "cl_paused", "0", CVAR_ROM }, - { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, - { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo -#ifdef MISSIONPACK - { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, - { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, - { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, - { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, - { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, - { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, - { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, - { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, - { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, - { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, - { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, - { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, -#endif - { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, - { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, - { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, - { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, - { &cg_timescale, "timescale", "1", 0}, - { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, - { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, - { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, - - { &pmove_fixed, "pmove_fixed", "0", 0}, - { &pmove_msec, "pmove_msec", "8", 0}, - { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, - { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, - { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, - { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, - { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, - { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, - { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, - { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} -// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } -}; - -static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); - -/* -================= -CG_RegisterCvars -================= -*/ -void CG_RegisterCvars( void ) { - int i; - cvarTable_t *cv; - char var[MAX_TOKEN_CHARS]; - - for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { - trap_Cvar_Register( cv->vmCvar, cv->cvarName, - cv->defaultString, cv->cvarFlags ); - } - - // see if we are also running the server on this machine - trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); - cgs.localServer = atoi( var ); - - forceModelModificationCount = cg_forceModel.modificationCount; - - trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); - trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); -} - -/* -=================== -CG_ForceModelChange -=================== -*/ -static void CG_ForceModelChange( void ) { - int i; - - for (i=0 ; ivmCvar ); - } - - // check for modications here - - // If team overlay is on, ask for updates from the server. If its off, - // let the server know so we don't receive it - if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { - drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; - - if ( cg_drawTeamOverlay.integer > 0 ) { - trap_Cvar_Set( "teamoverlay", "1" ); - } else { - trap_Cvar_Set( "teamoverlay", "0" ); - } - // FIXME E3 HACK - trap_Cvar_Set( "teamoverlay", "1" ); - } - - // if force model changed - if ( forceModelModificationCount != cg_forceModel.modificationCount ) { - forceModelModificationCount = cg_forceModel.modificationCount; - CG_ForceModelChange(); - } -} - -int CG_CrosshairPlayer( void ) { - if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { - return -1; - } - return cg.crosshairClientNum; -} - -int CG_LastAttacker( void ) { - if ( !cg.attackerTime ) { - return -1; - } - return cg.snap->ps.persistant[PERS_ATTACKER]; -} - -void QDECL CG_Printf( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - trap_Print( text ); -} - -void QDECL CG_Error( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - trap_Error( text ); -} - -#ifndef CGAME_HARD_LINKED -// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) - -void QDECL Com_Error( int level, const char *error, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, error); - vsprintf (text, error, argptr); - va_end (argptr); - - CG_Error( "%s", text); -} - -void QDECL Com_Printf( const char *msg, ... ) { - va_list argptr; - char text[1024]; - - va_start (argptr, msg); - vsprintf (text, msg, argptr); - va_end (argptr); - - CG_Printf ("%s", text); -} - -#endif - -/* -================ -CG_Argv -================ -*/ -const char *CG_Argv( int arg ) { - static char buffer[MAX_STRING_CHARS]; - - trap_Argv( arg, buffer, sizeof( buffer ) ); - - return buffer; -} - - -//======================================================================== - -/* -================= -CG_RegisterItemSounds - -The server says this item is used on this level -================= -*/ -static void CG_RegisterItemSounds( int itemNum ) { - gitem_t *item; - char data[MAX_QPATH]; - char *s, *start; - int len; - - item = &bg_itemlist[ itemNum ]; - - if( item->pickup_sound ) { - trap_S_RegisterSound( item->pickup_sound, qfalse ); - } - - // parse the space seperated precache string for other media - s = item->sounds; - if (!s || !s[0]) - return; - - while (*s) { - start = s; - while (*s && *s != ' ') { - s++; - } - - len = s-start; - if (len >= MAX_QPATH || len < 5) { - CG_Error( "PrecacheItem: %s has bad precache string", - item->classname); - return; - } - memcpy (data, start, len); - data[len] = 0; - if ( *s ) { - s++; - } - - if ( !strcmp(data+len-3, "wav" )) { - trap_S_RegisterSound( data, qfalse ); - } - } -} - - -/* -================= -CG_RegisterSounds - -called during a precache command -================= -*/ -static void CG_RegisterSounds( void ) { - int i; - char items[MAX_ITEMS+1]; - char name[MAX_QPATH]; - const char *soundName; - - // voice commands -#ifdef MISSIONPACK - CG_LoadVoiceChats(); -#endif - - cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); - cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); - cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); - cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); - cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); - cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); - cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); - cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); - cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); - cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); - cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); -#ifdef MISSIONPACK - cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); -#endif - - if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { - - cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); - cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); - cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); - cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); - cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); - - cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); - cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); - - cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); - cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); - - cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); - cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - - cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); - cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); - - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { - cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); - cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); - cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); - cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { - // FIXME: get a replacement for this sound ? - cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); - cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); - } - - if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { - cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); - cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); - } - - if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { - cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); - } -#else - cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); - cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); - cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); - cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); - cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); -#endif - } - - cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); - cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); - cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); - cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); - cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); - cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); - cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); - cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); - -#ifdef MISSIONPACK - cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); - cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); - cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); - cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); - cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); - cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); - cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); - cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); - cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); - - cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); - cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); - cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); - cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); -#endif - - cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); - cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); - cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); - - cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); - - cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); - cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); - - cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); -#ifdef MISSIONPACK - cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); - cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); -#endif - - cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); - cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); - cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); - cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); - cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); - cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); -#ifdef MISSIONPACK - cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); - cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); - cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); -#endif - - cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); - cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); - cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); - -#ifdef MISSIONPACK - cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); - cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); - cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); -#endif - - cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); - cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); - cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); - - cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); - - for (i=0 ; i<4 ; i++) { - Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); - - Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); - cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); - } - - // only register the items that the server says we need - strcpy( items, CG_ConfigString( CS_ITEMS ) ); - - for ( i = 1 ; i < bg_numItems ; i++ ) { -// if ( items[ i ] == '1' || cg_buildScript.integer ) { - CG_RegisterItemSounds( i ); -// } - } - - for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { - soundName = CG_ConfigString( CS_SOUNDS+i ); - if ( !soundName[0] ) { - break; - } - if ( soundName[0] == '*' ) { - continue; // custom sound - } - cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); - } - - // FIXME: only needed with item - cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); - cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); - cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); - cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); - cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); - cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); - cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); - cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); - cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); -#ifdef MISSIONPACK - cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); - cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); - cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); - cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); - cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); - cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); - cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); - cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); - cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); - cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); - cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); - cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); - cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); - cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); - - cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); - cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); - cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); - cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); -#endif - - cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); - cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); - cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); - cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); - cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); - -#ifdef MISSIONPACK - trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); - trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); - - trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); - trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); -#endif - -} - - -//=================================================================================== - - -/* -================= -CG_RegisterGraphics - -This function may execute for a couple of minutes with a slow disk. -================= -*/ -static void CG_RegisterGraphics( void ) { - int i; - char items[MAX_ITEMS+1]; - static char *sb_nums[11] = { - "gfx/2d/numbers/zero_32b", - "gfx/2d/numbers/one_32b", - "gfx/2d/numbers/two_32b", - "gfx/2d/numbers/three_32b", - "gfx/2d/numbers/four_32b", - "gfx/2d/numbers/five_32b", - "gfx/2d/numbers/six_32b", - "gfx/2d/numbers/seven_32b", - "gfx/2d/numbers/eight_32b", - "gfx/2d/numbers/nine_32b", - "gfx/2d/numbers/minus_32b", - }; - - // clear any references to old media - memset( &cg.refdef, 0, sizeof( cg.refdef ) ); - trap_R_ClearScene(); - - CG_LoadingString( cgs.mapname ); - - trap_R_LoadWorldMap( cgs.mapname ); - - // precache status bar pics - CG_LoadingString( "game media" ); - - for ( i=0 ; i<11 ; i++) { - cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); - } - - cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); - cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); - cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); - cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); - cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); - - cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); - - cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); - - cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); - cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); - cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); - cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); - - cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); - cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); - cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); -#ifdef MISSIONPACK - cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); - cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); -#endif - cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); - cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); - cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); - cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); - - cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); - - cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); - cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); - - for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { - cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); - } - - cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); - cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); - - // powerup shaders - cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); - cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); - cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); - cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); - cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); - cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); - cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { -#else - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { -#endif - cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); - cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); - cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); - cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { -#else - if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { -#endif - cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); - cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); - cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); - cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); - cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); - cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); - cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); - cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); -#ifdef MISSIONPACK - cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); - cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); - - cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); - cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); - cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); - - cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); - cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); - cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); -#endif - } - -#ifdef MISSIONPACK - if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { - cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); - cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); - cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); - cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); - cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); - } - - if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { - cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); - cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); - cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); - cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); - } - - if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { - cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); - cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); - cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); - cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); - } - - cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); - cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); -#endif - - if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { - cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); - cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); - cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); -#ifdef MISSIONPACK - cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); -#endif - } - - cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); - cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); - - cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); - cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - - cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); - cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); - cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); - cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); - cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); - cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); - cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); - cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); - cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); - cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); - - cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - - cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); - - cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); - - cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); - cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); - cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); -#ifdef MISSIONPACK - cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); -#else - cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); - cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); -#endif -#ifdef MISSIONPACK - cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); - cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); - cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); - cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); - cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); - cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); - cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); - cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); - cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); - cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); - cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); - cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); - -#endif - - cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); - cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); - cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); - cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); - cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); - cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); - cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); - - - memset( cg_items, 0, sizeof( cg_items ) ); - memset( cg_weapons, 0, sizeof( cg_weapons ) ); - - // only register the items that the server says we need - strcpy( items, CG_ConfigString( CS_ITEMS) ); - - for ( i = 1 ; i < bg_numItems ; i++ ) { - if ( items[ i ] == '1' || cg_buildScript.integer ) { - CG_LoadingItem( i ); - CG_RegisterItemVisuals( i ); - } - } - - // wall marks - cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); - cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); - cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); - cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); - cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); - cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); - cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); - - // register the inline models - cgs.numInlineModels = trap_CM_NumInlineModels(); - for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { - char name[10]; - vec3_t mins, maxs; - int j; - - Com_sprintf( name, sizeof(name), "*%i", i ); - cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); - trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); - for ( j = 0 ; j < 3 ; j++ ) { - cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); - } - } - - // register all the server specified models - for (i=1 ; i= MAX_CONFIGSTRINGS ) { - CG_Error( "CG_ConfigString: bad index: %i", index ); - } - return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; -} - -//================================================================== - -/* -====================== -CG_StartMusic - -====================== -*/ -void CG_StartMusic( void ) { - char *s; - char parm1[MAX_QPATH], parm2[MAX_QPATH]; - - // start the background music - s = (char *)CG_ConfigString( CS_MUSIC ); - Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); - Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); - - trap_S_StartBackgroundTrack( parm1, parm2 ); -} -#ifdef MISSIONPACK -char *CG_GetMenuBuffer(const char *filename) { - int len; - fileHandle_t f; - static char buf[MAX_MENUFILE]; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); - return NULL; - } - if ( len >= MAX_MENUFILE ) { - trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); - trap_FS_FCloseFile( f ); - return NULL; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - return buf; -} - -// -// ============================== -// new hud stuff ( mission pack ) -// ============================== -// -qboolean CG_Asset_Parse(int handle) { - pc_token_t token; - const char *tempStr; - - if (!trap_PC_ReadToken(handle, &token)) - return qfalse; - if (Q_stricmp(token.string, "{") != 0) { - return qfalse; - } - - while ( 1 ) { - if (!trap_PC_ReadToken(handle, &token)) - return qfalse; - - if (Q_stricmp(token.string, "}") == 0) { - return qtrue; - } - - // font - if (Q_stricmp(token.string, "font") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); - continue; - } - - // smallFont - if (Q_stricmp(token.string, "smallFont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); - continue; - } - - // font - if (Q_stricmp(token.string, "bigfont") == 0) { - int pointSize; - if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { - return qfalse; - } - cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); - continue; - } - - // gradientbar - if (Q_stricmp(token.string, "gradientbar") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); - continue; - } - - // enterMenuSound - if (Q_stricmp(token.string, "menuEnterSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // exitMenuSound - if (Q_stricmp(token.string, "menuExitSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // itemFocusSound - if (Q_stricmp(token.string, "itemFocusSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - // menuBuzzSound - if (Q_stricmp(token.string, "menuBuzzSound") == 0) { - if (!PC_String_Parse(handle, &tempStr)) { - return qfalse; - } - cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); - continue; - } - - if (Q_stricmp(token.string, "cursor") == 0) { - if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { - return qfalse; - } - cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); - continue; - } - - if (Q_stricmp(token.string, "fadeClamp") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "fadeCycle") == 0) { - if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "fadeAmount") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowX") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowY") == 0) { - if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { - return qfalse; - } - continue; - } - - if (Q_stricmp(token.string, "shadowColor") == 0) { - if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { - return qfalse; - } - cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; - continue; - } - } - return qfalse; // bk001204 - why not? -} - -void CG_ParseMenu(const char *menuFile) { - pc_token_t token; - int handle; - - handle = trap_PC_LoadSource(menuFile); - if (!handle) - handle = trap_PC_LoadSource("ui/testhud.menu"); - if (!handle) - return; - - while ( 1 ) { - if (!trap_PC_ReadToken( handle, &token )) { - break; - } - - //if ( Q_stricmp( token, "{" ) ) { - // Com_Printf( "Missing { in menu file\n" ); - // break; - //} - - //if ( menuCount == MAX_MENUS ) { - // Com_Printf( "Too many menus!\n" ); - // break; - //} - - if ( token.string[0] == '}' ) { - break; - } - - if (Q_stricmp(token.string, "assetGlobalDef") == 0) { - if (CG_Asset_Parse(handle)) { - continue; - } else { - break; - } - } - - - if (Q_stricmp(token.string, "menudef") == 0) { - // start a new menu - Menu_New(handle); - } - } - trap_PC_FreeSource(handle); -} - -qboolean CG_Load_Menu(char **p) { - char *token; - - token = COM_ParseExt(p, qtrue); - - if (token[0] != '{') { - return qfalse; - } - - while ( 1 ) { - - token = COM_ParseExt(p, qtrue); - - if (Q_stricmp(token, "}") == 0) { - return qtrue; - } - - if ( !token || token[0] == 0 ) { - return qfalse; - } - - CG_ParseMenu(token); - } - return qfalse; -} - - - -void CG_LoadMenus(const char *menuFile) { - char *token; - char *p; - int len, start; - fileHandle_t f; - static char buf[MAX_MENUDEFFILE]; - - start = trap_Milliseconds(); - - len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); - if ( !f ) { - trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); - len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); - if (!f) { - trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); - } - } - - if ( len >= MAX_MENUDEFFILE ) { - trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); - trap_FS_FCloseFile( f ); - return; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - COM_Compress(buf); - - Menu_Reset(); - - p = buf; - - while ( 1 ) { - token = COM_ParseExt( &p, qtrue ); - if( !token || token[0] == 0 || token[0] == '}') { - break; - } - - //if ( Q_stricmp( token, "{" ) ) { - // Com_Printf( "Missing { in menu file\n" ); - // break; - //} - - //if ( menuCount == MAX_MENUS ) { - // Com_Printf( "Too many menus!\n" ); - // break; - //} - - if ( Q_stricmp( token, "}" ) == 0 ) { - break; - } - - if (Q_stricmp(token, "loadmenu") == 0) { - if (CG_Load_Menu(&p)) { - continue; - } else { - break; - } - } - } - - Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); - -} - - - -static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { - return qfalse; -} - - -static int CG_FeederCount(float feederID) { - int i, count; - count = 0; - if (feederID == FEEDER_REDTEAM_LIST) { - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_RED) { - count++; - } - } - } else if (feederID == FEEDER_BLUETEAM_LIST) { - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_BLUE) { - count++; - } - } - } else if (feederID == FEEDER_SCOREBOARD) { - return cg.numScores; - } - return count; -} - - -void CG_SetScoreSelection(void *p) { - menuDef_t *menu = (menuDef_t*)p; - playerState_t *ps = &cg.snap->ps; - int i, red, blue; - red = blue = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == TEAM_RED) { - red++; - } else if (cg.scores[i].team == TEAM_BLUE) { - blue++; - } - if (ps->clientNum == cg.scores[i].client) { - cg.selectedScore = i; - } - } - - if (menu == NULL) { - // just interested in setting the selected score - return; - } - - if ( cgs.gametype >= GT_TEAM ) { - int feeder = FEEDER_REDTEAM_LIST; - i = red; - if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { - feeder = FEEDER_BLUETEAM_LIST; - i = blue; - } - Menu_SetFeederSelection(menu, feeder, i, NULL); - } else { - Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); - } -} - -// FIXME: might need to cache this info -static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { - int i, count; - if ( cgs.gametype >= GT_TEAM ) { - count = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == team) { - if (count == index) { - *scoreIndex = i; - return &cgs.clientinfo[cg.scores[i].client]; - } - count++; - } - } - } - *scoreIndex = index; - return &cgs.clientinfo[ cg.scores[index].client ]; -} - -static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { - gitem_t *item; - int scoreIndex = 0; - clientInfo_t *info = NULL; - int team = -1; - score_t *sp = NULL; - - *handle = -1; - - if (feederID == FEEDER_REDTEAM_LIST) { - team = TEAM_RED; - } else if (feederID == FEEDER_BLUETEAM_LIST) { - team = TEAM_BLUE; - } - - info = CG_InfoFromScoreIndex(index, team, &scoreIndex); - sp = &cg.scores[scoreIndex]; - - if (info && info->infoValid) { - switch (column) { - case 0: - if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { - item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { - item = BG_FindItemForPowerup( PW_REDFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { - item = BG_FindItemForPowerup( PW_BLUEFLAG ); - *handle = cg_items[ ITEM_INDEX(item) ].icon; - } else { - if ( info->botSkill > 0 && info->botSkill <= 5 ) { - *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; - } else if ( info->handicap < 100 ) { - return va("%i", info->handicap ); - } - } - break; - case 1: - if (team == -1) { - return ""; - } else { - *handle = CG_StatusHandle(info->teamTask); - } - break; - case 2: - if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { - return "Ready"; - } - if (team == -1) { - if (cgs.gametype == GT_TOURNAMENT) { - return va("%i/%i", info->wins, info->losses); - } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { - return "Spectator"; - } else { - return ""; - } - } else { - if (info->teamLeader) { - return "Leader"; - } - } - break; - case 3: - return info->name; - break; - case 4: - return va("%i", info->score); - break; - case 5: - return va("%4i", sp->time); - break; - case 6: - if ( sp->ping == -1 ) { - return "connecting"; - } - return va("%4i", sp->ping); - break; - } - } - - return ""; -} - -static qhandle_t CG_FeederItemImage(float feederID, int index) { - return 0; -} - -static void CG_FeederSelection(float feederID, int index) { - if ( cgs.gametype >= GT_TEAM ) { - int i, count; - int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; - count = 0; - for (i = 0; i < cg.numScores; i++) { - if (cg.scores[i].team == team) { - if (index == count) { - cg.selectedScore = i; - } - count++; - } - } - } else { - cg.selectedScore = index; - } -} -#endif - -#ifdef MISSIONPACK // bk001204 - only needed there -static float CG_Cvar_Get(const char *cvar) { - char buff[128]; - memset(buff, 0, sizeof(buff)); - trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); - return atof(buff); -} -#endif - -#ifdef MISSIONPACK -void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { - CG_Text_Paint(x, y, scale, color, text, 0, limit, style); -} - -static int CG_OwnerDrawWidth(int ownerDraw, float scale) { - switch (ownerDraw) { - case CG_GAME_TYPE: - return CG_Text_Width(CG_GameTypeString(), scale, 0); - case CG_GAME_STATUS: - return CG_Text_Width(CG_GetGameStatusText(), scale, 0); - break; - case CG_KILLER: - return CG_Text_Width(CG_GetKillerText(), scale, 0); - break; - case CG_RED_NAME: - return CG_Text_Width(cg_redTeamName.string, scale, 0); - break; - case CG_BLUE_NAME: - return CG_Text_Width(cg_blueTeamName.string, scale, 0); - break; - - - } - return 0; -} - -static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { - return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); -} - -static void CG_StopCinematic(int handle) { - trap_CIN_StopCinematic(handle); -} - -static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { - trap_CIN_SetExtents(handle, x, y, w, h); - trap_CIN_DrawCinematic(handle); -} - -static void CG_RunCinematicFrame(int handle) { - trap_CIN_RunCinematic(handle); -} - -/* -================= -CG_LoadHudMenu(); - -================= -*/ -void CG_LoadHudMenu() { - char buff[1024]; - const char *hudSet; - - cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; - cgDC.setColor = &trap_R_SetColor; - cgDC.drawHandlePic = &CG_DrawPic; - cgDC.drawStretchPic = &trap_R_DrawStretchPic; - cgDC.drawText = &CG_Text_Paint; - cgDC.textWidth = &CG_Text_Width; - cgDC.textHeight = &CG_Text_Height; - cgDC.registerModel = &trap_R_RegisterModel; - cgDC.modelBounds = &trap_R_ModelBounds; - cgDC.fillRect = &CG_FillRect; - cgDC.drawRect = &CG_DrawRect; - cgDC.drawSides = &CG_DrawSides; - cgDC.drawTopBottom = &CG_DrawTopBottom; - cgDC.clearScene = &trap_R_ClearScene; - cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; - cgDC.renderScene = &trap_R_RenderScene; - cgDC.registerFont = &trap_R_RegisterFont; - cgDC.ownerDrawItem = &CG_OwnerDraw; - cgDC.getValue = &CG_GetValue; - cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; - cgDC.runScript = &CG_RunMenuScript; - cgDC.getTeamColor = &CG_GetTeamColor; - cgDC.setCVar = trap_Cvar_Set; - cgDC.getCVarString = trap_Cvar_VariableStringBuffer; - cgDC.getCVarValue = CG_Cvar_Get; - cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; - //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; - //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; - cgDC.startLocalSound = &trap_S_StartLocalSound; - cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; - cgDC.feederCount = &CG_FeederCount; - cgDC.feederItemImage = &CG_FeederItemImage; - cgDC.feederItemText = &CG_FeederItemText; - cgDC.feederSelection = &CG_FeederSelection; - //cgDC.setBinding = &trap_Key_SetBinding; - //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; - //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; - //cgDC.executeText = &trap_Cmd_ExecuteText; - cgDC.Error = &Com_Error; - cgDC.Print = &Com_Printf; - cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; - //cgDC.Pause = &CG_Pause; - cgDC.registerSound = &trap_S_RegisterSound; - cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; - cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; - cgDC.playCinematic = &CG_PlayCinematic; - cgDC.stopCinematic = &CG_StopCinematic; - cgDC.drawCinematic = &CG_DrawCinematic; - cgDC.runCinematicFrame = &CG_RunCinematicFrame; - - Init_Display(&cgDC); - - Menu_Reset(); - - trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); - hudSet = buff; - if (hudSet[0] == '\0') { - hudSet = "ui/hud.txt"; - } - - CG_LoadMenus(hudSet); -} - -void CG_AssetCache() { - //if (Assets.textFont == NULL) { - // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); - //} - //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); - //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); - cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); - cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); - cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); - cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); - cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); - cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); - cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); - cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); - cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); - cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); - cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); - cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); - cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); - cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); - cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); - cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); - cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); -} -#endif -/* -================= -CG_Init - -Called after every level change or subsystem restart -Will perform callbacks to make the loading info screen update. -================= -*/ -void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { - const char *s; - - // clear everything - memset( &cgs, 0, sizeof( cgs ) ); - memset( &cg, 0, sizeof( cg ) ); - memset( cg_entities, 0, sizeof(cg_entities) ); - memset( cg_weapons, 0, sizeof(cg_weapons) ); - memset( cg_items, 0, sizeof(cg_items) ); - - cg.clientNum = clientNum; - - cgs.processedSnapshotNum = serverMessageNum; - cgs.serverCommandSequence = serverCommandSequence; - - // load a few needed things before we do any screen updates - cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); - cgs.media.whiteShader = trap_R_RegisterShader( "white" ); - cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); - cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); - cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); - - CG_RegisterCvars(); - - CG_InitConsoleCommands(); - - cg.weaponSelect = WP_MACHINEGUN; - - cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for - cgs.flagStatus = -1; - // old servers - - // get the rendering configuration from the client system - trap_GetGlconfig( &cgs.glconfig ); - cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; - cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; - - // get the gamestate from the client system - trap_GetGameState( &cgs.gameState ); - - // check version - s = CG_ConfigString( CS_GAME_VERSION ); - if ( strcmp( s, GAME_VERSION ) ) { - CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); - } - - s = CG_ConfigString( CS_LEVEL_START_TIME ); - cgs.levelStartTime = atoi( s ); - - CG_ParseServerinfo(); - - // load the new map - CG_LoadingString( "collision map" ); - - trap_CM_LoadMap( cgs.mapname ); - -#ifdef MISSIONPACK - String_Init(); -#endif - - cg.loading = qtrue; // force players to load instead of defer - - CG_LoadingString( "sounds" ); - - CG_RegisterSounds(); - - CG_LoadingString( "graphics" ); - - CG_RegisterGraphics(); - - CG_LoadingString( "clients" ); - - CG_RegisterClients(); // if low on memory, some clients will be deferred - -#ifdef MISSIONPACK - CG_AssetCache(); - CG_LoadHudMenu(); // load new hud stuff -#endif - - cg.loading = qfalse; // future players will be deferred - - CG_InitLocalEntities(); - - CG_InitMarkPolys(); - - // remove the last loading update - cg.infoScreenText[0] = 0; - - // Make sure we have update values (scores) - CG_SetConfigValues(); - - CG_StartMusic(); - - CG_LoadingString( "" ); - -#ifdef MISSIONPACK - CG_InitTeamChat(); -#endif - - CG_ShaderStateChanged(); - - trap_S_ClearLoopingSounds( qtrue ); -} - -/* -================= -CG_Shutdown - -Called before every level change or subsystem restart -================= -*/ -void CG_Shutdown( void ) { - // some mods may need to do cleanup work here, - // like closing files or archiving session data -} - - -/* -================== -CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor - -*/ -#ifndef MISSIONPACK -void CG_EventHandling(int type) { -} - - - -void CG_KeyEvent(int key, qboolean down) { -} - -void CG_MouseEvent(int x, int y) { -} -#endif - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_main.c -- initialization and primary entry point for cgame +#include "cg_local.h" + +#ifdef MISSIONPACK +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; +#endif + +int forceModelModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { + + switch ( command ) { + case CG_INIT: + CG_Init( arg0, arg1, arg2 ); + return 0; + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + case CG_DRAW_ACTIVE_FRAME: + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + case CG_KEY_EVENT: + CG_KeyEvent(arg0, arg1); + return 0; + case CG_MOUSE_EVENT: +#ifdef MISSIONPACK + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; +#endif + CG_MouseEvent(arg0, arg1); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + + +vmCvar_t cg_railTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_brassTime; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_drawFriend; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noVoiceChats; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_scorePlum; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +//vmCvar_t cg_pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_smallFont; +vmCvar_t cg_bigFont; +vmCvar_t cg_noTaunt; +vmCvar_t cg_noProjectileTrail; +vmCvar_t cg_oldRail; +vmCvar_t cg_oldRocket; +vmCvar_t cg_oldPlasma; +vmCvar_t cg_trueLightning; + +#ifdef MISSIONPACK +vmCvar_t cg_redTeamName; +vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_singlePlayer; +vmCvar_t cg_enableDust; +vmCvar_t cg_enableBreath; +vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; +vmCvar_t cg_obeliskRespawnDelay; +#endif + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { // bk001129 + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, + { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, + { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, +#ifdef MISSIONPACK + { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, +#else + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, +#endif + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + // the following variables are created in other parts of the system, + // but we also reference them here + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo +#ifdef MISSIONPACK + { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, + { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, + { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, + { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, +#endif + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescale, "timescale", "1", 0}, + { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, + { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, + { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, + { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, + { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, + { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} +// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); +} + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) { + int i; + + for (i=0 ; ivmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + // FIXME E3 HACK + trap_Cvar_Set( "teamoverlay", "1" ); + } + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } +} + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound, qfalse ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if (!s || !s[0]) + return; + + while (*s) { + start = s; + while (*s && *s != ' ') { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) { + trap_S_RegisterSound( data, qfalse ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + // voice commands +#ifdef MISSIONPACK + CG_LoadVoiceChats(); +#endif + + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); + cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); + cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); + cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + + cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); + cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); + + cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); + cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); + + cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); + + cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); + cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + + cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); + cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); + + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); + cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); + cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); + cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + // FIXME: get a replacement for this sound ? + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); + } + + if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); + } +#else + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); +#endif + } + + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); + cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); + cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); + cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); + cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); + +#ifdef MISSIONPACK + cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); + cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); + cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); + cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); + cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); + cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); + cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); + cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); + cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); + + cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); + cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); + cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); + cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); +#endif + + cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); + + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); + + cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); +#ifdef MISSIONPACK + cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); + cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); +#endif + + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); + cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); + cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); + cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); + cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); + cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); + cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); + cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); +#endif + + cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); + +#ifdef MISSIONPACK + cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); + cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); + cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); +#endif + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); + + cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); + + for (i=0 ; i<4 ; i++) { + Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); + } + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { +// if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_RegisterItemSounds( i ); +// } + } + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS+i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) { + continue; // custom sound + } + cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); + } + + // FIXME: only needed with item + cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); + cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); + cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); + cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); + cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); + cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); + cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); + cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); + cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); +#ifdef MISSIONPACK + cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); + cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); + cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); + cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); + cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); + cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); + cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); + cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); + cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); + cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); + cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); + cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); + cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); + cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); + + cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); + cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); + cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); + cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); +#endif + + cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); + cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); + cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); + cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); + cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); + +#ifdef MISSIONPACK + trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); + + trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); +#endif + +} + + +//=================================================================================== + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) { + int i; + char items[MAX_ITEMS+1]; + static char *sb_nums[11] = { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + // precache status bar pics + CG_LoadingString( "game media" ); + + for ( i=0 ; i<11 ; i++) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); + cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); + cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); + cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); + cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); + + cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); + + cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + + cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); + cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); + cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); + cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); + + cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); + cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); + cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); +#ifdef MISSIONPACK + cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); + cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); +#endif + cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); + cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); + cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); + cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); + + cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); + } + + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); + + // powerup shaders + cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); + cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); + cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); + cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); + cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); + cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); + cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); + cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); + cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); + cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); + cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); + cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); + cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); +#ifdef MISSIONPACK + cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); + cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); + + cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); + cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); + cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); + + cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); + cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); + cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); +#endif + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); + cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); + cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); + cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); + cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); + cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); + } + + if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { + cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); + cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); + cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); + cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); + } + + cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); + cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); + cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); +#ifdef MISSIONPACK + cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); +#endif + } + + cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); + cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); + + cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); + cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); + cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); + cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); + cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); + cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); + cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); + cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); + cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); + cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); + cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); + + cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); + + cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); + cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); + cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); +#ifdef MISSIONPACK + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); +#else + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); + cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); +#endif +#ifdef MISSIONPACK + cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); + cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); + cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); + cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); + cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); + cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); + cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); + cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); + cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); + cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); + cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); + cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); + +#endif + + cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); + cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); + cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); + cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); + cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); + + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + // wall marks + cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); + cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + // register all the server specified models + for (i=1 ; i= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} +#ifdef MISSIONPACK +char *CG_GetMenuBuffer(const char *filename) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + const char *tempStr; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "{") != 0) { + return qfalse; + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) { + return qtrue; + } + + // font + if (Q_stricmp(token.string, "font") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); + continue; + } + + // font + if (Q_stricmp(token.string, "bigfont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); + continue; + } + + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); + continue; + } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) { + if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { + return qfalse; + } + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + return qfalse; // bk001204 - why not? +} + +void CG_ParseMenu(const char *menuFile) { + pc_token_t token; + int handle; + + handle = trap_PC_LoadSource(menuFile); + if (!handle) + handle = trap_PC_LoadSource("ui/testhud.menu"); + if (!handle) + return; + + while ( 1 ) { + if (!trap_PC_ReadToken( handle, &token )) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if (Q_stricmp(token.string, "assetGlobalDef") == 0) { + if (CG_Asset_Parse(handle)) { + continue; + } else { + break; + } + } + + + if (Q_stricmp(token.string, "menudef") == 0) { + // start a new menu + Menu_New(handle); + } + } + trap_PC_FreeSource(handle); +} + +qboolean CG_Load_Menu(char **p) { + char *token; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu(token); + } + return qfalse; +} + + + +void CG_LoadMenus(const char *menuFile) { + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + start = trap_Milliseconds(); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + if ( !f ) { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); + if (!f) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress(buf); + + Menu_Reset(); + + p = buf; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if (Q_stricmp(token, "loadmenu") == 0) { + if (CG_Load_Menu(&p)) { + continue; + } else { + break; + } + } + } + + Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + +} + + + +static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { + return qfalse; +} + + +static int CG_FeederCount(float feederID) { + int i, count; + count = 0; + if (feederID == FEEDER_REDTEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + count++; + } + } + } else if (feederID == FEEDER_BLUETEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_BLUE) { + count++; + } + } + } else if (feederID == FEEDER_SCOREBOARD) { + return cg.numScores; + } + return count; +} + + +void CG_SetScoreSelection(void *p) { + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg.snap->ps; + int i, red, blue; + red = blue = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + red++; + } else if (cg.scores[i].team == TEAM_BLUE) { + blue++; + } + if (ps->clientNum == cg.scores[i].client) { + cg.selectedScore = i; + } + } + + if (menu == NULL) { + // just interested in setting the selected score + return; + } + + if ( cgs.gametype >= GT_TEAM ) { + int feeder = FEEDER_REDTEAM_LIST; + i = red; + if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { + feeder = FEEDER_BLUETEAM_LIST; + i = blue; + } + Menu_SetFeederSelection(menu, feeder, i, NULL); + } else { + Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); + } +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { + int i, count; + if ( cgs.gametype >= GT_TEAM ) { + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (count == index) { + *scoreIndex = i; + return &cgs.clientinfo[cg.scores[i].client]; + } + count++; + } + } + } + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[index].client ]; +} + +static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { + gitem_t *item; + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle = -1; + + if (feederID == FEEDER_REDTEAM_LIST) { + team = TEAM_RED; + } else if (feederID == FEEDER_BLUETEAM_LIST) { + team = TEAM_BLUE; + } + + info = CG_InfoFromScoreIndex(index, team, &scoreIndex); + sp = &cg.scores[scoreIndex]; + + if (info && info->infoValid) { + switch (column) { + case 0: + if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else { + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va("%i", info->handicap ); + } + } + break; + case 1: + if (team == -1) { + return ""; + } else { + *handle = CG_StatusHandle(info->teamTask); + } + break; + case 2: + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { + return "Ready"; + } + if (team == -1) { + if (cgs.gametype == GT_TOURNAMENT) { + return va("%i/%i", info->wins, info->losses); + } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { + return "Spectator"; + } else { + return ""; + } + } else { + if (info->teamLeader) { + return "Leader"; + } + } + break; + case 3: + return info->name; + break; + case 4: + return va("%i", info->score); + break; + case 5: + return va("%4i", sp->time); + break; + case 6: + if ( sp->ping == -1 ) { + return "connecting"; + } + return va("%4i", sp->ping); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage(float feederID, int index) { + return 0; +} + +static void CG_FeederSelection(float feederID, int index) { + if ( cgs.gametype >= GT_TEAM ) { + int i, count; + int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (index == count) { + cg.selectedScore = i; + } + count++; + } + } + } else { + cg.selectedScore = index; + } +} +#endif + +#ifdef MISSIONPACK // bk001204 - only needed there +static float CG_Cvar_Get(const char *cvar) { + char buff[128]; + memset(buff, 0, sizeof(buff)); + trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); + return atof(buff); +} +#endif + +#ifdef MISSIONPACK +void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { + CG_Text_Paint(x, y, scale, color, text, 0, limit, style); +} + +static int CG_OwnerDrawWidth(int ownerDraw, float scale) { + switch (ownerDraw) { + case CG_GAME_TYPE: + return CG_Text_Width(CG_GameTypeString(), scale, 0); + case CG_GAME_STATUS: + return CG_Text_Width(CG_GetGameStatusText(), scale, 0); + break; + case CG_KILLER: + return CG_Text_Width(CG_GetKillerText(), scale, 0); + break; + case CG_RED_NAME: + return CG_Text_Width(cg_redTeamName.string, scale, 0); + break; + case CG_BLUE_NAME: + return CG_Text_Width(cg_blueTeamName.string, scale, 0); + break; + + + } + return 0; +} + +static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); +} + +static void CG_StopCinematic(int handle) { + trap_CIN_StopCinematic(handle); +} + +static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); +} + +static void CG_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() { + char buff[1024]; + const char *hudSet; + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_Text_Paint; + cgDC.textWidth = &CG_Text_Width; + cgDC.textHeight = &CG_Text_Height; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; + //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display(&cgDC); + + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); +} + +void CG_AssetCache() { + //if (Assets.textFont == NULL) { + // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} +#endif +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { + const char *s; + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof(cg_entities) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + memset( cg_items, 0, sizeof(cg_items) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); + cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + cg.weaponSelect = WP_MACHINEGUN; + + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + cgs.flagStatus = -1; + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + +#ifdef MISSIONPACK + String_Init(); +#endif + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadingString( "sounds" ); + + CG_RegisterSounds(); + + CG_LoadingString( "graphics" ); + + CG_RegisterGraphics(); + + CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + +#ifdef MISSIONPACK + CG_AssetCache(); + CG_LoadHudMenu(); // load new hud stuff +#endif + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + CG_LoadingString( "" ); + +#ifdef MISSIONPACK + CG_InitTeamChat(); +#endif + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds( qtrue ); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +#ifndef MISSIONPACK +void CG_EventHandling(int type) { +} + + + +void CG_KeyEvent(int key, qboolean down) { +} + +void CG_MouseEvent(int x, int y) { +} +#endif + diff --git a/code/cgame/cg_marks.c b/code/cgame/cg_marks.c index 4c648d2..d0fecaa 100755 --- a/code/cgame/cg_marks.c +++ b/code/cgame/cg_marks.c @@ -1,2274 +1,2274 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_marks.c -- wall marks - -#include "cg_local.h" - -/* -=================================================================== - -MARK POLYS - -=================================================================== -*/ - - -markPoly_t cg_activeMarkPolys; // double linked list -markPoly_t *cg_freeMarkPolys; // single linked list -markPoly_t cg_markPolys[MAX_MARK_POLYS]; -static int markTotal; - -/* -=================== -CG_InitMarkPolys - -This is called at startup and for tournement restarts -=================== -*/ -void CG_InitMarkPolys( void ) { - int i; - - memset( cg_markPolys, 0, sizeof(cg_markPolys) ); - - cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; - cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; - cg_freeMarkPolys = cg_markPolys; - for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { - cg_markPolys[i].nextMark = &cg_markPolys[i+1]; - } -} - - -/* -================== -CG_FreeMarkPoly -================== -*/ -void CG_FreeMarkPoly( markPoly_t *le ) { - if ( !le->prevMark ) { - CG_Error( "CG_FreeLocalEntity: not active" ); - } - - // remove from the doubly linked active list - le->prevMark->nextMark = le->nextMark; - le->nextMark->prevMark = le->prevMark; - - // the free list is only singly linked - le->nextMark = cg_freeMarkPolys; - cg_freeMarkPolys = le; -} - -/* -=================== -CG_AllocMark - -Will allways succeed, even if it requires freeing an old active mark -=================== -*/ -markPoly_t *CG_AllocMark( void ) { - markPoly_t *le; - int time; - - if ( !cg_freeMarkPolys ) { - // no free entities, so free the one at the end of the chain - // remove the oldest active entity - time = cg_activeMarkPolys.prevMark->time; - while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { - CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); - } - } - - le = cg_freeMarkPolys; - cg_freeMarkPolys = cg_freeMarkPolys->nextMark; - - memset( le, 0, sizeof( *le ) ); - - // link into the active list - le->nextMark = cg_activeMarkPolys.nextMark; - le->prevMark = &cg_activeMarkPolys; - cg_activeMarkPolys.nextMark->prevMark = le; - cg_activeMarkPolys.nextMark = le; - return le; -} - - - -/* -================= -CG_ImpactMark - -origin should be a point within a unit of the plane -dir should be the plane normal - -temporary marks will not be stored or randomly oriented, but immediately -passed to the renderer. -================= -*/ -#define MAX_MARK_FRAGMENTS 128 -#define MAX_MARK_POINTS 384 - -void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, - float orientation, float red, float green, float blue, float alpha, - qboolean alphaFade, float radius, qboolean temporary ) { - vec3_t axis[3]; - float texCoordScale; - vec3_t originalPoints[4]; - byte colors[4]; - int i, j; - int numFragments; - markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; - vec3_t markPoints[MAX_MARK_POINTS]; - vec3_t projection; - - if ( !cg_addMarks.integer ) { - return; - } - - if ( radius <= 0 ) { - CG_Error( "CG_ImpactMark called with <= 0 radius" ); - } - - //if ( markTotal >= MAX_MARK_POLYS ) { - // return; - //} - - // create the texture axis - VectorNormalize2( dir, axis[0] ); - PerpendicularVector( axis[1], axis[0] ); - RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); - CrossProduct( axis[0], axis[2], axis[1] ); - - texCoordScale = 0.5 * 1.0 / radius; - - // create the full polygon - for ( i = 0 ; i < 3 ; i++ ) { - originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; - originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; - originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; - originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; - } - - // get the fragments - VectorScale( dir, -20, projection ); - numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, - projection, MAX_MARK_POINTS, markPoints[0], - MAX_MARK_FRAGMENTS, markFragments ); - - colors[0] = red * 255; - colors[1] = green * 255; - colors[2] = blue * 255; - colors[3] = alpha * 255; - - for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { - polyVert_t *v; - polyVert_t verts[MAX_VERTS_ON_POLY]; - markPoly_t *mark; - - // we have an upper limit on the complexity of polygons - // that we store persistantly - if ( mf->numPoints > MAX_VERTS_ON_POLY ) { - mf->numPoints = MAX_VERTS_ON_POLY; - } - for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { - vec3_t delta; - - VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); - - VectorSubtract( v->xyz, origin, delta ); - v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; - v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; - *(int *)v->modulate = *(int *)colors; - } - - // if it is a temporary (shadow) mark, add it immediately and forget about it - if ( temporary ) { - trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); - continue; - } - - // otherwise save it persistantly - mark = CG_AllocMark(); - mark->time = cg.time; - mark->alphaFade = alphaFade; - mark->markShader = markShader; - mark->poly.numVerts = mf->numPoints; - mark->color[0] = red; - mark->color[1] = green; - mark->color[2] = blue; - mark->color[3] = alpha; - memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); - markTotal++; - } -} - - -/* -=============== -CG_AddMarks -=============== -*/ -#define MARK_TOTAL_TIME 10000 -#define MARK_FADE_TIME 1000 - -void CG_AddMarks( void ) { - int j; - markPoly_t *mp, *next; - int t; - int fade; - - if ( !cg_addMarks.integer ) { - return; - } - - mp = cg_activeMarkPolys.nextMark; - for ( ; mp != &cg_activeMarkPolys ; mp = next ) { - // grab next now, so if the local entity is freed we - // still have it - next = mp->nextMark; - - // see if it is time to completely remove it - if ( cg.time > mp->time + MARK_TOTAL_TIME ) { - CG_FreeMarkPoly( mp ); - continue; - } - - // fade out the energy bursts - if ( mp->markShader == cgs.media.energyMarkShader ) { - - fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); - if ( fade < 255 ) { - if ( fade < 0 ) { - fade = 0; - } - if ( mp->verts[0].modulate[0] != 0 ) { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[0] = mp->color[0] * fade; - mp->verts[j].modulate[1] = mp->color[1] * fade; - mp->verts[j].modulate[2] = mp->color[2] * fade; - } - } - } - } - - // fade all marks out with time - t = mp->time + MARK_TOTAL_TIME - cg.time; - if ( t < MARK_FADE_TIME ) { - fade = 255 * t / MARK_FADE_TIME; - if ( mp->alphaFade ) { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[3] = fade; - } - } else { - for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { - mp->verts[j].modulate[0] = mp->color[0] * fade; - mp->verts[j].modulate[1] = mp->color[1] * fade; - mp->verts[j].modulate[2] = mp->color[2] * fade; - } - } - } - - - trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); - } -} - -// cg_particles.c - -#define BLOODRED 2 -#define EMISIVEFADE 3 -#define GREY75 4 - -typedef struct particle_s -{ - struct particle_s *next; - - float time; - float endtime; - - vec3_t org; - vec3_t vel; - vec3_t accel; - int color; - float colorvel; - float alpha; - float alphavel; - int type; - qhandle_t pshader; - - float height; - float width; - - float endheight; - float endwidth; - - float start; - float end; - - float startfade; - qboolean rotate; - int snum; - - qboolean link; - - // Ridah - int shaderAnim; - int roll; - - int accumroll; - -} cparticle_t; - -typedef enum -{ - P_NONE, - P_WEATHER, - P_FLAT, - P_SMOKE, - P_ROTATE, - P_WEATHER_TURBULENT, - P_ANIM, // Ridah - P_BAT, - P_BLEED, - P_FLAT_SCALEUP, - P_FLAT_SCALEUP_FADE, - P_WEATHER_FLURRY, - P_SMOKE_IMPACT, - P_BUBBLE, - P_BUBBLE_TURBULENT, - P_SPRITE -} particle_type_t; - -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23 -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.0f -}; -static int numShaderAnims; -// done. - -#define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 - -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; - -qboolean initparticles = qfalse; -vec3_t pvforward, pvright, pvup; -vec3_t rforward, rright, rup; - -float oldtime; - -/* -=============== -CL_ClearParticles -=============== -*/ -void CG_ClearParticles (void) -{ - int i; - - memset( particles, 0, sizeof(particles) ); - - free_particles = &particles[0]; - active_particles = NULL; - - for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY - || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - {// create a front facing polygon - - if (p->type != P_WEATHER_FLURRY) - { - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - if (org[2] > p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - p->org[2] = ( p->start + crandom () * 4 ); - - - if (p->type == P_BUBBLE_TURBULENT) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - } - } - else - { - if (org[2] < p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - while (p->org[2] < p->end) - { - p->org[2] += (p->start - p->end); - } - - - if (p->type == P_WEATHER_TURBULENT) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - } - } - - - // Rafael snow pvs check - if (!p->link) - return; - - p->alpha = 1; - } - - // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp - if (Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - // done. - - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255 * p->alpha; - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy( point, TRIverts[0].xyz ); - TRIverts[0].st[0] = 1; - TRIverts[0].st[1] = 0; - TRIverts[0].modulate[0] = 255; - TRIverts[0].modulate[1] = 255; - TRIverts[0].modulate[2] = 255; - TRIverts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - VectorCopy (point, TRIverts[1].xyz); - TRIverts[1].st[0] = 0; - TRIverts[1].st[1] = 0; - TRIverts[1].modulate[0] = 255; - TRIverts[1].modulate[1] = 255; - TRIverts[1].modulate[2] = 255; - TRIverts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - VectorCopy (point, TRIverts[2].xyz); - TRIverts[2].st[0] = 0; - TRIverts[2].st[1] = 1; - TRIverts[2].modulate[0] = 255; - TRIverts[2].modulate[1] = 255; - TRIverts[2].modulate[2] = 255; - TRIverts[2].modulate[3] = 255 * p->alpha; - } - - } - else if (p->type == P_SPRITE) - { - vec3_t rr, ru; - vec3_t rotate_ang; - - VectorSet (color, 1.0, 1.0, 0.5); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) - {// create a front rotating facing polygon - - if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - if (cg.time > p->startfade) - { - invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); - - if (p->color == EMISIVEFADE) - { - float fval; - fval = (invratio * invratio); - if (fval < 0) - fval = 0; - VectorSet (color, fval , fval , fval ); - } - invratio *= p->alpha; - } - else - invratio = 1 * p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - invratio = 1; - - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; - - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, p->width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, p->height, pvup, point); - VectorMA (point, -p->width, pvright, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255 * invratio; - - } - else if (p->type == P_BLEED) - { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; - - alpha = p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - alpha = 1; - - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - else - { - VectorCopy (pvup, ru); - VectorCopy (pvright, rr); - } - - VectorMA (org, -p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 111; - verts[0].modulate[1] = 19; - verts[0].modulate[2] = 9; - verts[0].modulate[3] = 255 * alpha; - - VectorMA (org, -p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 111; - verts[1].modulate[1] = 19; - verts[1].modulate[2] = 9; - verts[1].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 111; - verts[2].modulate[1] = 19; - verts[2].modulate[2] = 9; - verts[2].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 111; - verts[3].modulate[1] = 19; - verts[3].modulate[2] = 9; - verts[3].modulate[3] = 255 * alpha; - - } - else if (p->type == P_FLAT_SCALEUP) - { - float width, height; - float sinR, cosR; - - if (p->color == BLOODRED) - VectorSet (color, 1, 1, 1); - else - VectorSet (color, 0.5, 0.5, 0.5); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (width > p->endwidth) - width = p->endwidth; - - if (height > p->endheight) - height = p->endheight; - - sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); - cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= sinR; - verts[0].xyz[1] -= cosR; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= cosR; - verts[1].xyz[1] += sinR; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += sinR; - verts[2].xyz[1] += cosR; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += cosR; - verts[3].xyz[1] -= sinR; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255; - } - else if (p->type == P_FLAT) - { - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= p->height; - verts[0].xyz[1] -= p->width; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= p->height; - verts[1].xyz[1] += p->width; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += p->height; - verts[2].xyz[1] += p->width; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += p->height; - verts[3].xyz[1] -= p->width; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { - return; - } - - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, pvup, point); - VectorMA (point, -width, pvright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, pvup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, pvright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, pvup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - // done. - - if (!p->pshader) { -// (SA) temp commented out for DM -// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); - return; - } - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); - else - trap_R_AddPolyToScene( p->pshader, 4, verts ); - -} - -// Ridah, made this static so it doesn't interfere with other files -static float roll = 0.0; - -/* -=============== -CG_AddParticles -=============== -*/ -void CG_AddParticles (void) -{ - cparticle_t *p, *next; - float alpha; - float time, time2; - vec3_t org; - int color; - cparticle_t *active, *tail; - int type; - vec3_t rotate_ang; - - if (!initparticles) - CG_ClearParticles (); - - VectorCopy( cg.refdef.viewaxis[0], pvforward ); - VectorCopy( cg.refdef.viewaxis[1], pvright ); - VectorCopy( cg.refdef.viewaxis[2], pvup ); - - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; - rotate_ang[ROLL] += (roll*0.9); - AngleVectors ( rotate_ang, rforward, rright, rup); - - oldtime = cg.time; - - active = NULL; - tail = NULL; - - for (p=active_particles ; p ; p=next) - { - - next = p->next; - - time = (cg.time - p->time)*0.001; - - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) - { // faded out - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - - } - - if (p->type == P_WEATHER_FLURRY) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - } - - - if (p->type == P_FLAT_SCALEUP_FADE) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - } - - if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { - // temporary sprite - CG_AddParticleToScene (p, p->org, alpha); - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - p->next = NULL; - if (!tail) - active = tail = p; - else - { - tail->next = p; - tail = p; - } - - if (alpha > 1.0) - alpha = 1; - - color = p->color; - - time2 = time*time; - - org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; - org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; - org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; - - type = p->type; - - CG_AddParticleToScene (p, org, alpha); - } - - active_particles = active; -} - -/* -====================== -CG_AddParticles -====================== -*/ -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - qboolean turb = qtrue; - - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; - - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } - - p->vel[2] = -20; - - p->type = P_WEATHER_FLURRY; - - if (turb) - p->vel[2] = -10; - - VectorCopy(cent->currentState.origin, p->org); - - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); - p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); - p->vel[2] += cent->currentState.angles[2]; - - if (turb) - { - p->accel[0] = crandom () * 16; - p->accel[1] = crandom () * 16; - } - -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - p->height = 1; - p->width = 1; - - p->vel[2] = -50; - - if (turb) - { - p->type = P_WEATHER_TURBULENT; - p->vel[2] = -50 * 1.3; - } - else - { - p->type = P_WEATHER; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); - - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; - } - else - { - p->type = P_BUBBLE; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; - - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} - - -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ - - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 1.0; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->accel[2] = -60; - p->vel[2] += -20; - -} - -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) -{ - cparticle_t *p; - int anim; - - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); - - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!Q_stricmp( animStr, shaderAnimNames[anim] )) - break; - } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); - return; - } - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.5; - p->alphavel = 0; - - if (duration < 0) { - duration *= -1; - p->roll = 0; - } else { - p->roll = crandom()*179; - } - - p->shaderAnim = anim; - - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction - - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; - - p->endtime = cg.time + duration; - - p->type = P_ANIM; - - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); - -} - -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; -} -// done. - -int CG_NewParticleArea (int num) -{ - // const char *str; - char *str; - char *token; - int type; - vec3_t origin, origin2; - int i; - float range = 0; - int turb; - int numparticles; - int snum; - - str = (char *) CG_ConfigString (num); - if (!str[0]) - return (0); - - // returns type 128 64 or 32 - token = COM_Parse (&str); - type = atoi (token); - - if (type == 1) - range = 128; - else if (type == 2) - range = 64; - else if (type == 3) - range = 32; - else if (type == 0) - range = 256; - else if (type == 4) - range = 8; - else if (type == 5) - range = 16; - else if (type == 6) - range = 32; - else if (type == 7) - range = 64; - - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin[i] = atof (token); - } - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin2[i] = atof (token); - } - - token = COM_Parse (&str); - numparticles = atoi (token); - - token = COM_Parse (&str); - turb = atoi (token); - - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - } - - return (1); -} - -void CG_SnowLink (centity_t *cent, qboolean particleOn) -{ - cparticle_t *p, *next; - int id; - - id = cent->currentState.frame; - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) - { - if (p->snum == id) - { - if (particleOn) - p->link = qtrue; - else - p->link = qfalse; - } - } - - } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; - - p->pshader = pshader; - - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; - - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; - - p->endheight = p->height *2; - p->endwidth = p->width * 2; - - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; - - VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; - - p->width = 4; - p->height = 4; - - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; - - p->type = P_SMOKE; - - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; - -} - -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; - - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - p->startfade = p->endtime; - - p->width = 1; - p->height = 3; - - p->endheight = 3; - p->endwidth = 1; - - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); - - p->snum = 1.0f; - - VectorClear( p->accel ); - - p->accel[2] = -20; - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - if (cent->currentState.angles2[2]) - p->endtime = cg.time + cent->currentState.angles2[2]; - else - p->endtime = cg.time + 60000; - - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; - - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; - - p->endheight = 16; - p->endwidth = 16; - } - - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - -void CG_OilSlickRemove (centity_t *cent) -{ - cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } - } - - } -} - -qboolean ValidBloodPool (vec3_t start) -{ -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - p->endtime = cg.time + 350 + (crandom() * 100); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - p->endheight = LARGESIZE; - p->endwidth = LARGESIZE; - - p->type = P_SMOKE; - - VectorCopy( origin, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -1; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) -{ - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; - - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - -} - -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; - - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = rand()%179; - - p->pshader = pshader; - - if (duration > 0) - p->endtime = cg.time + duration; - else - p->endtime = duration; - - p->startfade = cg.time; - - p->width = size; - p->height = size; - - p->endheight = size; - p->endwidth = size; - - p->type = P_SPRITE; - - VectorCopy( origin, p->org ); - - p->rotate = qfalse; -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; +static int markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + + memset( cg_markPolys, 0, sizeof(cg_markPolys) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { + cg_markPolys[i].nextMark = &cg_markPolys[i+1]; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) { + markPoly_t *le; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec3_t markPoints[MAX_MARK_POINTS]; + vec3_t projection; + + if ( !cg_addMarks.integer ) { + return; + } + + if ( radius <= 0 ) { + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + } + + //if ( markTotal >= MAX_MARK_POLYS ) { + // return; + //} + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark(); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + markTotal++; + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 10000 +#define MARK_FADE_TIME 1000 + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_addMarks.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg.time > mp->time + MARK_TOTAL_TIME ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + if ( mp->markShader == cgs.media.energyMarkShader ) { + + fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg.time; + if ( t < MARK_FADE_TIME ) { + fade = 255 * t / MARK_FADE_TIME; + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } else { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + +// cg_particles.c + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23 +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.0f +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t pvforward, pvright, pvup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 0.5); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (pvup, ru); + VectorCopy (pvright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], pvforward ); + VectorCopy( cg.refdef.viewaxis[1], pvright ); + VectorCopy( cg.refdef.viewaxis[2], pvup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!Q_stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.5; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} + diff --git a/code/cgame/cg_newdraw.c b/code/cgame/cg_newdraw.c index 28335fe..c78fff9 100755 --- a/code/cgame/cg_newdraw.c +++ b/code/cgame/cg_newdraw.c @@ -1,1852 +1,1852 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#ifndef MISSIONPACK // bk001204 -#error This file not be used for classic Q3A. -#endif - -#include "cg_local.h" -#include "../ui/ui_shared.h" - -extern displayContextDef_t cgDC; - - -// set in CG_ParseTeamInfo - -//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; -//static int numSortedTeamPlayers; -int drawTeamOverlayModificationCount = -1; - -//static char systemChat[256]; -//static char teamChat1[256]; -//static char teamChat2[256]; - -void CG_InitTeamChat() { - memset(teamChat1, 0, sizeof(teamChat1)); - memset(teamChat2, 0, sizeof(teamChat2)); - memset(systemChat, 0, sizeof(systemChat)); -} - -void CG_SetPrintString(int type, const char *p) { - if (type == SYSTEM_PRINT) { - strcpy(systemChat, p); - } else { - strcpy(teamChat2, teamChat1); - strcpy(teamChat1, p); - } -} - -void CG_CheckOrderPending() { - if (cgs.gametype < GT_CTF) { - return; - } - if (cgs.orderPending) { - //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; - const char *p1, *p2, *b; - p1 = p2 = b = NULL; - switch (cgs.currentOrder) { - case TEAMTASK_OFFENSE: - p1 = VOICECHAT_ONOFFENSE; - p2 = VOICECHAT_OFFENSE; - b = "+button7; wait; -button7"; - break; - case TEAMTASK_DEFENSE: - p1 = VOICECHAT_ONDEFENSE; - p2 = VOICECHAT_DEFEND; - b = "+button8; wait; -button8"; - break; - case TEAMTASK_PATROL: - p1 = VOICECHAT_ONPATROL; - p2 = VOICECHAT_PATROL; - b = "+button9; wait; -button9"; - break; - case TEAMTASK_FOLLOW: - p1 = VOICECHAT_ONFOLLOW; - p2 = VOICECHAT_FOLLOWME; - b = "+button10; wait; -button10"; - break; - case TEAMTASK_CAMP: - p1 = VOICECHAT_ONCAMPING; - p2 = VOICECHAT_CAMP; - break; - case TEAMTASK_RETRIEVE: - p1 = VOICECHAT_ONGETFLAG; - p2 = VOICECHAT_RETURNFLAG; - break; - case TEAMTASK_ESCORT: - p1 = VOICECHAT_ONFOLLOWCARRIER; - p2 = VOICECHAT_FOLLOWFLAGCARRIER; - break; - } - - if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { - // to everyone - trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); - } else { - // for the player self - if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { - trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); - //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); - trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); - } else if (p2) { - //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); - trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); - } - } - if (b) { - trap_SendConsoleCommand(b); - } - cgs.orderPending = qfalse; - } -} - -static void CG_SetSelectedPlayerName() { - if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; - if (ci) { - trap_Cvar_Set("cg_selectedPlayerName", ci->name); - trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); - cgs.currentOrder = ci->teamTask; - } - } else { - trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); - } -} -int CG_GetSelectedPlayer() { - if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer = 0; - } - return cg_currentSelectedPlayer.integer; -} - -void CG_SelectNextPlayer() { - CG_CheckOrderPending(); - if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer++; - } else { - cg_currentSelectedPlayer.integer = 0; - } - CG_SetSelectedPlayerName(); -} - -void CG_SelectPrevPlayer() { - CG_CheckOrderPending(); - if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { - cg_currentSelectedPlayer.integer--; - } else { - cg_currentSelectedPlayer.integer = numSortedTeamPlayers; - } - CG_SetSelectedPlayerName(); -} - - -static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { - centity_t *cent; - playerState_t *ps; - vec3_t angles; - vec3_t origin; - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses - CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); - } else if (cg_draw3dIcons.integer) { - VectorClear( angles ); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); - } - -} - -static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - char num[16]; - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - value = ps->stats[STAT_ARMOR]; - - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - -#ifndef MISSIONPACK // bk001206 -static float healthColors[4][4] = { -// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; - // bk0101016 - float const - { 1.0f, 0.69f, 0.0f, 1.0f } , // normal - { 1.0f, 0.2f, 0.2f, 1.0f }, // low health - { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing - { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 -#endif - -static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { - centity_t *cent; - playerState_t *ps; - vec3_t angles; - vec3_t origin; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses - qhandle_t icon; - icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; - if ( icon ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); - } - } else if (cg_draw3dIcons.integer) { - if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { - VectorClear( angles ); - origin[0] = 70; - origin[1] = 0; - origin[2] = 0; - angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); - } - } -} - -static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - char num[16]; - int value; - centity_t *cent; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - if ( cent->currentState.weapon ) { - value = ps->ammo[cent->currentState.weapon]; - if ( value > -1 ) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } - } - -} - - - -static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { - vec3_t angles; - float size, stretch; - float frac; - float x = rect->x; - - VectorClear( angles ); - - if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { - frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; - size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); - - stretch = size - rect->w * 1.25; - // kick in the direction of damage - x -= stretch * 0.5 + cg.damageX * stretch * 0.5; - - cg.headStartYaw = 180 + cg.damageX * 45; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - - cg.headStartTime = cg.time; - cg.headEndTime = cg.time + 100 + random() * 2000; - } else { - if ( cg.time >= cg.headEndTime ) { - // select a new head angle - cg.headStartYaw = cg.headEndYaw; - cg.headStartPitch = cg.headEndPitch; - cg.headStartTime = cg.headEndTime; - cg.headEndTime = cg.time + 100 + random() * 2000; - - cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); - cg.headEndPitch = 5 * cos( crandom()*M_PI ); - } - - size = rect->w * 1.25; - } - - // if the server was frozen for a while we may have a bad head start time - if ( cg.headStartTime > cg.time ) { - cg.headStartTime = cg.time; - } - - frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); - frac = frac * frac * ( 3 - 2 * frac ); - angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; - angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; - - CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); -} - -static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - clientInfo_t *ci; - int value; - char num[16]; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", ci->health); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } -} - -static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - clientInfo_t *ci; - int value; - char num[16]; - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if (ci->armor > 0) { - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", ci->armor); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } - } - } -} - -qhandle_t CG_StatusHandle(int task) { - qhandle_t h = cgs.media.assaultShader; - switch (task) { - case TEAMTASK_OFFENSE : - h = cgs.media.assaultShader; - break; - case TEAMTASK_DEFENSE : - h = cgs.media.defendShader; - break; - case TEAMTASK_PATROL : - h = cgs.media.patrolShader; - break; - case TEAMTASK_FOLLOW : - h = cgs.media.followShader; - break; - case TEAMTASK_CAMP : - h = cgs.media.campShader; - break; - case TEAMTASK_RETRIEVE : - h = cgs.media.retrieveShader; - break; - case TEAMTASK_ESCORT : - h = cgs.media.escortShader; - break; - default : - h = cgs.media.assaultShader; - break; - } - return h; -} - -static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { - clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - qhandle_t h; - if (cgs.orderPending) { - // blink the icon - if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { - return; - } - h = CG_StatusHandle(cgs.currentOrder); - } else { - h = CG_StatusHandle(ci->teamTask); - } - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); - } -} - - -static void CG_DrawPlayerStatus( rectDef_t *rect ) { - clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; - if (ci) { - qhandle_t h = CG_StatusHandle(ci->teamTask); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); - } -} - - -static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { - clientInfo_t *ci; - ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); - if (ci) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); - } -} - -static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - clientInfo_t *ci; - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); - } -} - -static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; - if (ci) { - const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); - } -} - - - -static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { - clientInfo_t *ci; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); - } - } -} - -static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - char num[16]; - int value = cg.snap->ps.persistant[PERS_SCORE]; - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - -static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { - int value; - vec3_t origin, angles; - - value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; - if ( value ) { - CG_RegisterItemVisuals( value ); - - if (qtrue) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); - } else { - VectorClear( angles ); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); - } - } - -} - - -static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { - clientInfo_t *ci; - int j; - float x, y; - - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - if (ci) { - x = rect->x; - y = rect->y; - - for (j = 0; j < PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - gitem_t *item; - item = BG_FindItemForPowerup( j ); - if (item) { - CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); - x += 3; - y += 3; - return; - } - } - } - - } -} - - -static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { - clipHandle_t cm; - clientInfo_t *ci; - float len; - vec3_t origin; - vec3_t mins, maxs, angles; - - - ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); - - if (ci) { - if ( cg_draw3dIcons.integer ) { - cm = ci->headModel; - if ( !cm ) { - return; - } - - // offset the origin y and z to center the head - trap_R_ModelBounds( cm, mins, maxs ); - - origin[2] = -0.5 * ( mins[2] + maxs[2] ); - origin[1] = 0.5 * ( mins[1] + maxs[1] ); - - // calculate distance so the head nearly fills the box - // assume heads are taller than wide - len = 0.7 * ( maxs[2] - mins[2] ); - origin[0] = len / 0.268; // len / tan( fov/2 ) - - // allow per-model tweaking - VectorAdd( origin, ci->headOffset, origin ); - - angles[PITCH] = 0; - angles[YAW] = 180; - angles[ROLL] = 0; - - CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); - } else if ( cg_drawIcons.integer ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); - } - - // if they are deferred, draw a cross out - if ( ci->deferred ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); - } - } - -} - - -static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - playerState_t *ps; - int value; - char num[16]; - - ps = &cg.snap->ps; - - value = ps->stats[STAT_HEALTH]; - - if (shader) { - trap_R_SetColor( color ); - CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); - trap_R_SetColor( NULL ); - } else { - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); - } -} - - -static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - int value; - char num[16]; - if ( cgs.scores1 == SCORE_NOT_PRESENT ) { - Com_sprintf (num, sizeof(num), "-"); - } - else { - Com_sprintf (num, sizeof(num), "%i", cgs.scores1); - } - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); -} - -static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - int value; - char num[16]; - - if ( cgs.scores2 == SCORE_NOT_PRESENT ) { - Com_sprintf (num, sizeof(num), "-"); - } - else { - Com_sprintf (num, sizeof(num), "%i", cgs.scores2); - } - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); -} - -// FIXME: team name support -static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); -} - -static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); -} - -static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); - return; - } - } -} - -static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { - if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { - if (cgs.gametype == GT_HARVESTER) { - vec4_t color = {0, 0, 1, 1}; - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); - trap_R_SetColor(NULL); - } - return; - } - if (shader) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - } else { - gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); - if (item) { - vec4_t color = {0, 0, 1, 1}; - trap_R_SetColor(color); - if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); - } - trap_R_SetColor(NULL); - } - } -} - -static void CG_DrawBlueFlagHead(rectDef_t *rect) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { - vec3_t angles; - VectorClear( angles ); - angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; - CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); - return; - } - } -} - -static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); - return; - } - } -} - -static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { - if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { - if (cgs.gametype == GT_HARVESTER) { - vec4_t color = {1, 0, 0, 1}; - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); - trap_R_SetColor(NULL); - } - return; - } - if (shader) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - } else { - gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); - if (item) { - vec4_t color = {1, 0, 0, 1}; - trap_R_SetColor(color); - if( cgs.redflag >= 0 && cgs.redflag <= 2) { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); - } else { - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); - } - trap_R_SetColor(NULL); - } - } -} - -static void CG_DrawRedFlagHead(rectDef_t *rect) { - int i; - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { - vec3_t angles; - VectorClear( angles ); - angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; - CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); - return; - } - } -} - -static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { - char num[16]; - vec3_t origin, angles; - qhandle_t handle; - int value = cg.snap->ps.generic1; - - if (cgs.gametype != GT_HARVESTER) { - return; - } - - if( value > 99 ) { - value = 99; - } - - Com_sprintf (num, sizeof(num), "%i", value); - value = CG_Text_Width(num, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); - - if (cg_drawIcons.integer) { - if (!force2D && cg_draw3dIcons.integer) { - VectorClear(angles); - origin[0] = 90; - origin[1] = 0; - origin[2] = -10; - angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeModel; - } else { - handle = cgs.media.blueCubeModel; - } - CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); - } else { - if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { - handle = cgs.media.redCubeIcon; - } else { - handle = cgs.media.blueCubeIcon; - } - CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); - } - } -} - -static void CG_OneFlagStatus(rectDef_t *rect) { - if (cgs.gametype != GT_1FCTF) { - return; - } else { - gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); - if (item) { - if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { - vec4_t color = {1, 1, 1, 1}; - int index = 0; - if (cgs.flagStatus == FLAG_TAKEN_RED) { - color[1] = color[2] = 0; - index = 1; - } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { - color[0] = color[1] = 0; - index = 1; - } else if (cgs.flagStatus == FLAG_DROPPED) { - index = 2; - } - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); - } - } - } -} - - -static void CG_DrawCTFPowerUp(rectDef_t *rect) { - int value; - - if (cgs.gametype < GT_CTF) { - return; - } - value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; - if ( value ) { - CG_RegisterItemVisuals( value ); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); - } -} - - - -static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { - CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); -} - -static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { - char num[16]; - int sorted[MAX_POWERUPS]; - int sortedTime[MAX_POWERUPS]; - int i, j, k; - int active; - playerState_t *ps; - int t; - gitem_t *item; - float f; - rectDef_t r2; - float *inc; - r2.x = rect->x; - r2.y = rect->y; - r2.w = rect->w; - r2.h = rect->h; - - inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; - - ps = &cg.snap->ps; - - if ( ps->stats[STAT_HEALTH] <= 0 ) { - return; - } - - // sort the list by time remaining - active = 0; - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - if ( !ps->powerups[ i ] ) { - continue; - } - t = ps->powerups[ i ] - cg.time; - // ZOID--don't draw if the power up has unlimited time (999 seconds) - // This is true of the CTF flags - if ( t <= 0 || t >= 999000) { - continue; - } - - // insert into the list - for ( j = 0 ; j < active ; j++ ) { - if ( sortedTime[j] >= t ) { - for ( k = active - 1 ; k >= j ; k-- ) { - sorted[k+1] = sorted[k]; - sortedTime[k+1] = sortedTime[k]; - } - break; - } - } - sorted[j] = i; - sortedTime[j] = t; - active++; - } - - // draw the icons and timers - for ( i = 0 ; i < active ; i++ ) { - item = BG_FindItemForPowerup( sorted[i] ); - - if (item) { - t = ps->powerups[ sorted[i] ]; - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - trap_R_SetColor( NULL ); - } else { - vec4_t modulate; - - f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; - f -= (int)f; - modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; - trap_R_SetColor( modulate ); - } - - CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); - - Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); - CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); - *inc += r2.w + special; - } - - } - trap_R_SetColor( NULL ); - -} - -float CG_GetValue(int ownerDraw) { - centity_t *cent; - clientInfo_t *ci; - playerState_t *ps; - - cent = &cg_entities[cg.snap->ps.clientNum]; - ps = &cg.snap->ps; - - switch (ownerDraw) { - case CG_SELECTEDPLAYER_ARMOR: - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - return ci->armor; - break; - case CG_SELECTEDPLAYER_HEALTH: - ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; - return ci->health; - break; - case CG_PLAYER_ARMOR_VALUE: - return ps->stats[STAT_ARMOR]; - break; - case CG_PLAYER_AMMO_VALUE: - if ( cent->currentState.weapon ) { - return ps->ammo[cent->currentState.weapon]; - } - break; - case CG_PLAYER_SCORE: - return cg.snap->ps.persistant[PERS_SCORE]; - break; - case CG_PLAYER_HEALTH: - return ps->stats[STAT_HEALTH]; - break; - case CG_RED_SCORE: - return cgs.scores1; - break; - case CG_BLUE_SCORE: - return cgs.scores2; - break; - default: - break; - } - return -1; -} - -qboolean CG_OtherTeamHasFlag() { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - int team = cg.snap->ps.persistant[PERS_TEAM]; - if (cgs.gametype == GT_1FCTF) { - if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { - return qtrue; - } else { - return qfalse; - } - } else { - if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { - return qtrue; - } else { - return qfalse; - } - } - } - return qfalse; -} - -qboolean CG_YourTeamHasFlag() { - if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { - int team = cg.snap->ps.persistant[PERS_TEAM]; - if (cgs.gametype == GT_1FCTF) { - if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { - return qtrue; - } else { - return qfalse; - } - } else { - if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { - return qtrue; - } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { - return qtrue; - } else { - return qfalse; - } - } - } - return qfalse; -} - -// THINKABOUTME: should these be exclusive or inclusive.. -// -qboolean CG_OwnerDrawVisible(int flags) { - - if (flags & CG_SHOW_TEAMINFO) { - return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); - } - - if (flags & CG_SHOW_NOTEAMINFO) { - return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); - } - - if (flags & CG_SHOW_OTHERTEAMHASFLAG) { - return CG_OtherTeamHasFlag(); - } - - if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { - return CG_YourTeamHasFlag(); - } - - if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { - if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { - return qtrue; - } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { - return qtrue; - } - return qfalse; - } - - if (flags & CG_SHOW_ANYTEAMGAME) { - if( cgs.gametype >= GT_TEAM) { - return qtrue; - } - } - - if (flags & CG_SHOW_ANYNONTEAMGAME) { - if( cgs.gametype < GT_TEAM) { - return qtrue; - } - } - - if (flags & CG_SHOW_HARVESTER) { - if( cgs.gametype == GT_HARVESTER ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_ONEFLAG) { - if( cgs.gametype == GT_1FCTF ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_CTF) { - if( cgs.gametype == GT_CTF ) { - return qtrue; - } - } - - if (flags & CG_SHOW_OBELISK) { - if( cgs.gametype == GT_OBELISK ) { - return qtrue; - } else { - return qfalse; - } - } - - if (flags & CG_SHOW_HEALTHCRITICAL) { - if (cg.snap->ps.stats[STAT_HEALTH] < 25) { - return qtrue; - } - } - - if (flags & CG_SHOW_HEALTHOK) { - if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { - return qtrue; - } - } - - if (flags & CG_SHOW_SINGLEPLAYER) { - if( cgs.gametype == GT_SINGLE_PLAYER ) { - return qtrue; - } - } - - if (flags & CG_SHOW_TOURNAMENT) { - if( cgs.gametype == GT_TOURNAMENT ) { - return qtrue; - } - } - - if (flags & CG_SHOW_DURINGINCOMINGVOICE) { - } - - if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { - if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { - return qtrue; - } - } - return qfalse; -} - - - -static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { - int adj = (force2D) ? 0 : 2; - if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); - } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); - } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { - CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); - } -} - -static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); -} - -static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); -} - -static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); -} - -const char *CG_GetKillerText() { - const char *s = ""; - if ( cg.killerName[0] ) { - s = va("Fragged by %s", cg.killerName ); - } - return s; -} - - -static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - // fragged by ... line - if ( cg.killerName[0] ) { - int x = rect->x + rect->w / 2; - CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); - } - -} - - -static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); -} - -static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - if (cgs.scores1 != SCORE_NOT_PRESENT) { - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); - } -} - -static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { - if (cgs.scores2 != SCORE_NOT_PRESENT) { - CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); - } -} - -const char *CG_GetGameStatusText() { - const char *s = ""; - if ( cgs.gametype < GT_TEAM) { - if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); - } - } else { - if ( cg.teamScores[0] == cg.teamScores[1] ) { - s = va("Teams are tied at %i", cg.teamScores[0] ); - } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { - s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); - } else { - s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); - } - } - return s; -} - -static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); -} - -const char *CG_GameTypeString() { - if ( cgs.gametype == GT_FFA ) { - return "Free For All"; - } else if ( cgs.gametype == GT_TEAM ) { - return "Team Deathmatch"; - } else if ( cgs.gametype == GT_CTF ) { - return "Capture the Flag"; - } else if ( cgs.gametype == GT_1FCTF ) { - return "One Flag CTF"; - } else if ( cgs.gametype == GT_OBELISK ) { - return "Overload"; - } else if ( cgs.gametype == GT_HARVESTER ) { - return "Harvester"; - } - return ""; -} -static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { - CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); -} - -static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { - int len, count; - vec4_t newColor; - glyphInfo_t *glyph; - if (text) { -// TTimo: FIXME -// const unsigned char *s = text; // bk001206 - unsigned - const char *s = text; - float max = *maxX; - float useScale; - fontInfo_t *font = &cgDC.Assets.textFont; - if (scale <= cg_smallFont.value) { - font = &cgDC.Assets.smallFont; - } else if (scale > cg_bigFont.value) { - font = &cgDC.Assets.bigFont; - } - useScale = scale * font->glyphScale; - trap_R_SetColor( color ); - len = strlen(text); - if (limit > 0 && len > limit) { - len = limit; - } - count = 0; - while (s && *s && count < len) { - glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build - if ( Q_IsColorString( s ) ) { - memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); - newColor[3] = color[3]; - trap_R_SetColor( newColor ); - s += 2; - continue; - } else { - float yadj = useScale * glyph->top; - if (CG_Text_Width(s, useScale, 1) + x > max) { - *maxX = 0; - break; - } - CG_Text_PaintChar(x, y - yadj, - glyph->imageWidth, - glyph->imageHeight, - useScale, - glyph->s, - glyph->t, - glyph->s2, - glyph->t2, - glyph->glyph); - x += (glyph->xSkip * useScale) + adjust; - *maxX = x; - count++; - s++; - } - } - trap_R_SetColor( NULL ); - } - -} - - - -#define PIC_WIDTH 12 - -void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { - int xx; - float y; - int i, j, len, count; - const char *p; - vec4_t hcolor; - float pwidth, lwidth, maxx, leftOver; - clientInfo_t *ci; - gitem_t *item; - qhandle_t h; - - // max player name width - pwidth = 0; - count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - len = CG_Text_Width( ci->name, scale, 0); - if (len > pwidth) - pwidth = len; - } - } - - // max location name width - lwidth = 0; - for (i = 1; i < MAX_LOCATIONS; i++) { - p = CG_ConfigString(CS_LOCATIONS + i); - if (p && *p) { - len = CG_Text_Width(p, scale, 0); - if (len > lwidth) - lwidth = len; - } - } - - y = rect->y; - - for (i = 0; i < count; i++) { - ci = cgs.clientinfo + sortedTeamPlayers[i]; - if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { - - xx = rect->x + 1; - for (j = 0; j <= PW_NUM_POWERUPS; j++) { - if (ci->powerups & (1 << j)) { - - item = BG_FindItemForPowerup( j ); - - if (item) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); - xx += PIC_WIDTH; - } - } - } - - // FIXME: max of 3 powerups shown properly - xx = rect->x + (PIC_WIDTH * 3) + 2; - - CG_GetColorForHealth( ci->health, ci->armor, hcolor ); - trap_R_SetColor(hcolor); - CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); - - //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); - //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); - - // draw weapon icon - xx += PIC_WIDTH + 1; - -// weapon used is not that useful, use the space for task -#if 0 - if ( cg_weapons[ci->curWeapon].weaponIcon ) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); - } else { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); - } -#endif - - trap_R_SetColor(NULL); - if (cgs.orderPending) { - // blink the icon - if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { - h = 0; - } else { - h = CG_StatusHandle(cgs.currentOrder); - } - } else { - h = CG_StatusHandle(ci->teamTask); - } - - if (h) { - CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); - } - - xx += PIC_WIDTH + 1; - - leftOver = rect->w - xx; - maxx = xx + leftOver / 3; - - - - CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); - - p = CG_ConfigString(CS_LOCATIONS + ci->location); - if (!p || !*p) { - p = "unknown"; - } - - xx += leftOver / 3 + 2; - maxx = rect->w - 4; - - CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); - y += text_y + 2; - if ( y + text_y + 2 > rect->y + rect->h ) { - break; - } - - } - } -} - - -void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - if (cg.spectatorLen) { - float maxX; - - if (cg.spectatorWidth == -1) { - cg.spectatorWidth = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } - - if (cg.spectatorOffset > cg.spectatorLen) { - cg.spectatorOffset = 0; - cg.spectatorPaintX = rect->x + 1; - cg.spectatorPaintX2 = -1; - } - - if (cg.time > cg.spectatorTime) { - cg.spectatorTime = cg.time + 10; - if (cg.spectatorPaintX <= rect->x + 2) { - if (cg.spectatorOffset < cg.spectatorLen) { - cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; - cg.spectatorOffset++; - } else { - cg.spectatorOffset = 0; - if (cg.spectatorPaintX2 >= 0) { - cg.spectatorPaintX = cg.spectatorPaintX2; - } else { - cg.spectatorPaintX = rect->x + rect->w - 2; - } - cg.spectatorPaintX2 = -1; - } - } else { - cg.spectatorPaintX--; - if (cg.spectatorPaintX2 >= 0) { - cg.spectatorPaintX2--; - } - } - } - - maxX = rect->x + rect->w - 2; - CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); - if (cg.spectatorPaintX2 >= 0) { - float maxX2 = rect->x + rect->w - 2; - CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); - } - if (cg.spectatorOffset && maxX > 0) { - // if we have an offset ( we are skipping the first part of the string ) and we fit the string - if (cg.spectatorPaintX2 == -1) { - cg.spectatorPaintX2 = rect->x + rect->w - 2; - } - } else { - cg.spectatorPaintX2 = -1; - } - - } -} - - - -void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { - score_t *score = &cg.scores[cg.selectedScore]; - float value = 0; - char *text = NULL; - color[3] = 0.25; - - switch (ownerDraw) { - case CG_ACCURACY: - value = score->accuracy; - break; - case CG_ASSISTS: - value = score->assistCount; - break; - case CG_DEFEND: - value = score->defendCount; - break; - case CG_EXCELLENT: - value = score->excellentCount; - break; - case CG_IMPRESSIVE: - value = score->impressiveCount; - break; - case CG_PERFECT: - value = score->perfect; - break; - case CG_GAUNTLET: - value = score->guantletCount; - break; - case CG_CAPTURES: - value = score->captures; - break; - } - - if (value > 0) { - if (ownerDraw != CG_PERFECT) { - if (ownerDraw == CG_ACCURACY) { - text = va("%i%%", (int)value); - if (value > 50) { - color[3] = 1.0; - } - } else { - text = va("%i", (int)value); - color[3] = 1.0; - } - } else { - if (value) { - color[3] = 1.0; - } - text = "Wow"; - } - } - - trap_R_SetColor(color); - CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); - - if (text) { - color[3] = 1.0; - value = CG_Text_Width(text, scale, 0); - CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); - } - trap_R_SetColor(NULL); - -} - - -// -void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { - rectDef_t rect; - - if ( cg_drawStatus.integer == 0 ) { - return; - } - - //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { - // return; - //} - - rect.x = x; - rect.y = y; - rect.w = w; - rect.h = h; - - switch (ownerDraw) { - case CG_PLAYER_ARMOR_ICON: - CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_ARMOR_ICON2D: - CG_DrawPlayerArmorIcon(&rect, qtrue); - break; - case CG_PLAYER_ARMOR_VALUE: - CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); - break; - case CG_PLAYER_AMMO_ICON: - CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_AMMO_ICON2D: - CG_DrawPlayerAmmoIcon(&rect, qtrue); - break; - case CG_PLAYER_AMMO_VALUE: - CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_HEAD: - CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); - break; - case CG_VOICE_HEAD: - CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); - break; - case CG_VOICE_NAME: - CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); - break; - case CG_SELECTEDPLAYER_STATUS: - CG_DrawSelectedPlayerStatus(&rect); - break; - case CG_SELECTEDPLAYER_ARMOR: - CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_HEALTH: - CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); - break; - case CG_SELECTEDPLAYER_NAME: - CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); - break; - case CG_SELECTEDPLAYER_LOCATION: - CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); - break; - case CG_SELECTEDPLAYER_WEAPON: - CG_DrawSelectedPlayerWeapon(&rect); - break; - case CG_SELECTEDPLAYER_POWERUP: - CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_HEAD: - CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_ITEM: - CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); - break; - case CG_PLAYER_SCORE: - CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); - break; - case CG_PLAYER_HEALTH: - CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); - break; - case CG_RED_SCORE: - CG_DrawRedScore(&rect, scale, color, shader, textStyle); - break; - case CG_BLUE_SCORE: - CG_DrawBlueScore(&rect, scale, color, shader, textStyle); - break; - case CG_RED_NAME: - CG_DrawRedName(&rect, scale, color, textStyle); - break; - case CG_BLUE_NAME: - CG_DrawBlueName(&rect, scale, color, textStyle); - break; - case CG_BLUE_FLAGHEAD: - CG_DrawBlueFlagHead(&rect); - break; - case CG_BLUE_FLAGSTATUS: - CG_DrawBlueFlagStatus(&rect, shader); - break; - case CG_BLUE_FLAGNAME: - CG_DrawBlueFlagName(&rect, scale, color, textStyle); - break; - case CG_RED_FLAGHEAD: - CG_DrawRedFlagHead(&rect); - break; - case CG_RED_FLAGSTATUS: - CG_DrawRedFlagStatus(&rect, shader); - break; - case CG_RED_FLAGNAME: - CG_DrawRedFlagName(&rect, scale, color, textStyle); - break; - case CG_HARVESTER_SKULLS: - CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); - break; - case CG_HARVESTER_SKULLS2D: - CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); - break; - case CG_ONEFLAG_STATUS: - CG_OneFlagStatus(&rect); - break; - case CG_PLAYER_LOCATION: - CG_DrawPlayerLocation(&rect, scale, color, textStyle); - break; - case CG_TEAM_COLOR: - CG_DrawTeamColor(&rect, color); - break; - case CG_CTF_POWERUP: - CG_DrawCTFPowerUp(&rect); - break; - case CG_AREA_POWERUP: - CG_DrawAreaPowerUp(&rect, align, special, scale, color); - break; - case CG_PLAYER_STATUS: - CG_DrawPlayerStatus(&rect); - break; - case CG_PLAYER_HASFLAG: - CG_DrawPlayerHasFlag(&rect, qfalse); - break; - case CG_PLAYER_HASFLAG2D: - CG_DrawPlayerHasFlag(&rect, qtrue); - break; - case CG_AREA_SYSTEMCHAT: - CG_DrawAreaSystemChat(&rect, scale, color, shader); - break; - case CG_AREA_TEAMCHAT: - CG_DrawAreaTeamChat(&rect, scale, color, shader); - break; - case CG_AREA_CHAT: - CG_DrawAreaChat(&rect, scale, color, shader); - break; - case CG_GAME_TYPE: - CG_DrawGameType(&rect, scale, color, shader, textStyle); - break; - case CG_GAME_STATUS: - CG_DrawGameStatus(&rect, scale, color, shader, textStyle); - break; - case CG_KILLER: - CG_DrawKiller(&rect, scale, color, shader, textStyle); - break; - case CG_ACCURACY: - case CG_ASSISTS: - case CG_DEFEND: - case CG_EXCELLENT: - case CG_IMPRESSIVE: - case CG_PERFECT: - case CG_GAUNTLET: - case CG_CAPTURES: - CG_DrawMedal(ownerDraw, &rect, scale, color, shader); - break; - case CG_SPECTATORS: - CG_DrawTeamSpectators(&rect, scale, color, shader); - break; - case CG_TEAMINFO: - if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { - CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); - } - break; - case CG_CAPFRAGLIMIT: - CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); - break; - case CG_1STPLACE: - CG_Draw1stPlace(&rect, scale, color, shader, textStyle); - break; - case CG_2NDPLACE: - CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); - break; - default: - break; - } -} - -void CG_MouseEvent(int x, int y) { - int n; - - if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { - trap_Key_SetCatcher(0); - return; - } - - cgs.cursorX+= x; - if (cgs.cursorX < 0) - cgs.cursorX = 0; - else if (cgs.cursorX > 640) - cgs.cursorX = 640; - - cgs.cursorY += y; - if (cgs.cursorY < 0) - cgs.cursorY = 0; - else if (cgs.cursorY > 480) - cgs.cursorY = 480; - - n = Display_CursorType(cgs.cursorX, cgs.cursorY); - cgs.activeCursor = 0; - if (n == CURSOR_ARROW) { - cgs.activeCursor = cgs.media.selectCursor; - } else if (n == CURSOR_SIZER) { - cgs.activeCursor = cgs.media.sizeCursor; - } - - if (cgs.capturedItem) { - Display_MouseMove(cgs.capturedItem, x, y); - } else { - Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); - } - -} - -/* -================== -CG_HideTeamMenus -================== - -*/ -void CG_HideTeamMenu() { - Menus_CloseByName("teamMenu"); - Menus_CloseByName("getMenu"); -} - -/* -================== -CG_ShowTeamMenus -================== - -*/ -void CG_ShowTeamMenu() { - Menus_OpenByName("teamMenu"); -} - - - - -/* -================== -CG_EventHandling -================== - type 0 - no event handling - 1 - team menu - 2 - hud editor - -*/ -void CG_EventHandling(int type) { - cgs.eventHandling = type; - if (type == CGAME_EVENT_NONE) { - CG_HideTeamMenu(); - } else if (type == CGAME_EVENT_TEAMMENU) { - //CG_ShowTeamMenu(); - } else if (type == CGAME_EVENT_SCOREBOARD) { - } - -} - - - -void CG_KeyEvent(int key, qboolean down) { - - if (!down) { - return; - } - - if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { - CG_EventHandling(CGAME_EVENT_NONE); - trap_Key_SetCatcher(0); - return; - } - - //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { - // if we see this then we should always be visible - // CG_EventHandling(CGAME_EVENT_NONE); - // trap_Key_SetCatcher(0); - //} - - - - Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); - - if (cgs.capturedItem) { - cgs.capturedItem = NULL; - } else { - if (key == K_MOUSE2 && down) { - cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); - } - } -} - -int CG_ClientNumFromName(const char *p) { - int i; - for (i = 0; i < cgs.maxclients; i++) { - if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { - return i; - } - } - return -1; -} - -void CG_ShowResponseHead() { - Menus_OpenByName("voiceMenu"); - trap_Cvar_Set("cl_conXOffset", "72"); - cg.voiceTime = cg.time; -} - -void CG_RunMenuScript(char **args) { -} - - -void CG_GetTeamColor(vec4_t *color) { - if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { - (*color)[0] = 1.0f; - (*color)[3] = 0.25f; - (*color)[1] = (*color)[2] = 0.0f; - } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { - (*color)[0] = (*color)[1] = 0.0f; - (*color)[2] = 1.0f; - (*color)[3] = 0.25f; - } else { - (*color)[0] = (*color)[2] = 0.0f; - (*color)[1] = 0.17f; - (*color)[3] = 0.25f; - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef MISSIONPACK // bk001204 +#error This file not be used for classic Q3A. +#endif + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + + +// set in CG_ParseTeamInfo + +//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; +//static int numSortedTeamPlayers; +int drawTeamOverlayModificationCount = -1; + +//static char systemChat[256]; +//static char teamChat1[256]; +//static char teamChat2[256]; + +void CG_InitTeamChat() { + memset(teamChat1, 0, sizeof(teamChat1)); + memset(teamChat2, 0, sizeof(teamChat2)); + memset(systemChat, 0, sizeof(systemChat)); +} + +void CG_SetPrintString(int type, const char *p) { + if (type == SYSTEM_PRINT) { + strcpy(systemChat, p); + } else { + strcpy(teamChat2, teamChat1); + strcpy(teamChat1, p); + } +} + +void CG_CheckOrderPending() { + if (cgs.gametype < GT_CTF) { + return; + } + if (cgs.orderPending) { + //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + const char *p1, *p2, *b; + p1 = p2 = b = NULL; + switch (cgs.currentOrder) { + case TEAMTASK_OFFENSE: + p1 = VOICECHAT_ONOFFENSE; + p2 = VOICECHAT_OFFENSE; + b = "+button7; wait; -button7"; + break; + case TEAMTASK_DEFENSE: + p1 = VOICECHAT_ONDEFENSE; + p2 = VOICECHAT_DEFEND; + b = "+button8; wait; -button8"; + break; + case TEAMTASK_PATROL: + p1 = VOICECHAT_ONPATROL; + p2 = VOICECHAT_PATROL; + b = "+button9; wait; -button9"; + break; + case TEAMTASK_FOLLOW: + p1 = VOICECHAT_ONFOLLOW; + p2 = VOICECHAT_FOLLOWME; + b = "+button10; wait; -button10"; + break; + case TEAMTASK_CAMP: + p1 = VOICECHAT_ONCAMPING; + p2 = VOICECHAT_CAMP; + break; + case TEAMTASK_RETRIEVE: + p1 = VOICECHAT_ONGETFLAG; + p2 = VOICECHAT_RETURNFLAG; + break; + case TEAMTASK_ESCORT: + p1 = VOICECHAT_ONFOLLOWCARRIER; + p2 = VOICECHAT_FOLLOWFLAGCARRIER; + break; + } + + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + // to everyone + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); + } else { + // for the player self + if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { + trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); + //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); + } else if (p2) { + //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); + trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); + } + } + if (b) { + trap_SendConsoleCommand(b); + } + cgs.orderPending = qfalse; + } +} + +static void CG_SetSelectedPlayerName() { + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + if (ci) { + trap_Cvar_Set("cg_selectedPlayerName", ci->name); + trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); + cgs.currentOrder = ci->teamTask; + } + } else { + trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); + } +} +int CG_GetSelectedPlayer() { + if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +void CG_SelectNextPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer++; + } else { + cg_currentSelectedPlayer.integer = 0; + } + CG_SetSelectedPlayerName(); +} + +void CG_SelectPrevPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer--; + } else { + cg_currentSelectedPlayer.integer = numSortedTeamPlayers; + } + CG_SetSelectedPlayerName(); +} + + +static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); + } else if (cg_draw3dIcons.integer) { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); + } + +} + +static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + value = ps->stats[STAT_ARMOR]; + + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +#ifndef MISSIONPACK // bk001206 +static float healthColors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + // bk0101016 - float const + { 1.0f, 0.69f, 0.0f, 1.0f } , // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 +#endif + +static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + qhandle_t icon; + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); + } + } else if (cg_draw3dIcons.integer) { + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + VectorClear( angles ); + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + } +} + +static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } + +} + + + +static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { + vec3_t angles; + float size, stretch; + float frac; + float x = rect->x; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - rect->w * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = rect->w * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); +} + +static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->health); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } +} + +static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (ci->armor > 0) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->armor); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } +} + +qhandle_t CG_StatusHandle(int task) { + qhandle_t h = cgs.media.assaultShader; + switch (task) { + case TEAMTASK_OFFENSE : + h = cgs.media.assaultShader; + break; + case TEAMTASK_DEFENSE : + h = cgs.media.defendShader; + break; + case TEAMTASK_PATROL : + h = cgs.media.patrolShader; + break; + case TEAMTASK_FOLLOW : + h = cgs.media.followShader; + break; + case TEAMTASK_CAMP : + h = cgs.media.campShader; + break; + case TEAMTASK_RETRIEVE : + h = cgs.media.retrieveShader; + break; + case TEAMTASK_ESCORT : + h = cgs.media.escortShader; + break; + default : + h = cgs.media.assaultShader; + break; + } + return h; +} + +static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + qhandle_t h; + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + return; + } + h = CG_StatusHandle(cgs.currentOrder); + } else { + h = CG_StatusHandle(ci->teamTask); + } + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); + } +} + + +static void CG_DrawPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + qhandle_t h = CG_StatusHandle(ci->teamTask); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); + } +} + + +static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { + clientInfo_t *ci; + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + if (ci) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); + } +} + +static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + + + +static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { + clientInfo_t *ci; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); + } + } +} + +static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value = cg.snap->ps.persistant[PERS_SCORE]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { + int value; + vec3_t origin, angles; + + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) { + CG_RegisterItemVisuals( value ); + + if (qtrue) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } else { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); + } + } + +} + + +static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { + clientInfo_t *ci; + int j; + float x, y; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + x = rect->x; + y = rect->y; + + for (j = 0; j < PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + gitem_t *item; + item = BG_FindItemForPowerup( j ); + if (item) { + CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); + x += 3; + y += 3; + return; + } + } + } + + } +} + + +static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs, angles; + + + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + + if (ci) { + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); + } + } + +} + + +static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + playerState_t *ps; + int value; + char num[16]; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + + +static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + if ( cgs.scores1 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores1); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores2); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +// FIXME: team name support +static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); +} + +static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); +} + +static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); + if (item) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawBlueFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); + if (item) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + if( cgs.redflag >= 0 && cgs.redflag <= 2) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawRedFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { + char num[16]; + vec3_t origin, angles; + qhandle_t handle; + int value = cg.snap->ps.generic1; + + if (cgs.gametype != GT_HARVESTER) { + return; + } + + if( value > 99 ) { + value = 99; + } + + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); + + if (cg_drawIcons.integer) { + if (!force2D && cg_draw3dIcons.integer) { + VectorClear(angles); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); + } else { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); + } + } +} + +static void CG_OneFlagStatus(rectDef_t *rect) { + if (cgs.gametype != GT_1FCTF) { + return; + } else { + gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + if (item) { + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { + vec4_t color = {1, 1, 1, 1}; + int index = 0; + if (cgs.flagStatus == FLAG_TAKEN_RED) { + color[1] = color[2] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { + color[0] = color[1] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_DROPPED) { + index = 2; + } + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); + } + } + } +} + + +static void CG_DrawCTFPowerUp(rectDef_t *rect) { + int value; + + if (cgs.gametype < GT_CTF) { + return; + } + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } +} + + + +static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { + CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); +} + +static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { + char num[16]; + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + float f; + rectDef_t r2; + float *inc; + r2.x = rect->x; + r2.y = rect->y; + r2.w = rect->w; + r2.h = rect->h; + + inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t <= 0 || t >= 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + for ( i = 0 ; i < active ; i++ ) { + item = BG_FindItemForPowerup( sorted[i] ); + + if (item) { + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); + + Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); + CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); + *inc += r2.w + special; + } + + } + trap_R_SetColor( NULL ); + +} + +float CG_GetValue(int ownerDraw) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + switch (ownerDraw) { + case CG_SELECTEDPLAYER_ARMOR: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->armor; + break; + case CG_SELECTEDPLAYER_HEALTH: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->health; + break; + case CG_PLAYER_ARMOR_VALUE: + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_AMMO_VALUE: + if ( cent->currentState.weapon ) { + return ps->ammo[cent->currentState.weapon]; + } + break; + case CG_PLAYER_SCORE: + return cg.snap->ps.persistant[PERS_SCORE]; + break; + case CG_PLAYER_HEALTH: + return ps->stats[STAT_HEALTH]; + break; + case CG_RED_SCORE: + return cgs.scores1; + break; + case CG_BLUE_SCORE: + return cgs.scores2; + break; + default: + break; + } + return -1; +} + +qboolean CG_OtherTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +qboolean CG_YourTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +// THINKABOUTME: should these be exclusive or inclusive.. +// +qboolean CG_OwnerDrawVisible(int flags) { + + if (flags & CG_SHOW_TEAMINFO) { + return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_NOTEAMINFO) { + return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_OTHERTEAMHASFLAG) { + return CG_OtherTeamHasFlag(); + } + + if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { + return CG_YourTeamHasFlag(); + } + + if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { + if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { + return qtrue; + } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { + return qtrue; + } + return qfalse; + } + + if (flags & CG_SHOW_ANYTEAMGAME) { + if( cgs.gametype >= GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_ANYNONTEAMGAME) { + if( cgs.gametype < GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_HARVESTER) { + if( cgs.gametype == GT_HARVESTER ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_ONEFLAG) { + if( cgs.gametype == GT_1FCTF ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_CTF) { + if( cgs.gametype == GT_CTF ) { + return qtrue; + } + } + + if (flags & CG_SHOW_OBELISK) { + if( cgs.gametype == GT_OBELISK ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_HEALTHCRITICAL) { + if (cg.snap->ps.stats[STAT_HEALTH] < 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_HEALTHOK) { + if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_SINGLEPLAYER) { + if( cgs.gametype == GT_SINGLE_PLAYER ) { + return qtrue; + } + } + + if (flags & CG_SHOW_TOURNAMENT) { + if( cgs.gametype == GT_TOURNAMENT ) { + return qtrue; + } + } + + if (flags & CG_SHOW_DURINGINCOMINGVOICE) { + } + + if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + return qtrue; + } + } + return qfalse; +} + + + +static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { + int adj = (force2D) ? 0 : 2; + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); + } +} + +static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); +} + +static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); +} + +static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); +} + +const char *CG_GetKillerText() { + const char *s = ""; + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + } + return s; +} + + +static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + // fragged by ... line + if ( cg.killerName[0] ) { + int x = rect->x + rect->w / 2; + CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); + } + +} + + +static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); +} + +static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores1 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); + } +} + +static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores2 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); + } +} + +const char *CG_GetGameStatusText() { + const char *s = ""; + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); + } + } + return s; +} + +static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); +} + +const char *CG_GameTypeString() { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; + } else if ( cgs.gametype == GT_1FCTF ) { + return "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + return "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + return "Harvester"; + } + return ""; +} +static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); +} + +static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; // bk001206 - unsigned + const char *s = text; + float max = *maxX; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + trap_R_SetColor( color ); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (CG_Text_Width(s, useScale, 1) + x > max) { + *maxX = 0; + break; + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + x += (glyph->xSkip * useScale) + adjust; + *maxX = x; + count++; + s++; + } + } + trap_R_SetColor( NULL ); + } + +} + + + +#define PIC_WIDTH 12 + +void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { + int xx; + float y; + int i, j, len, count; + const char *p; + vec4_t hcolor; + float pwidth, lwidth, maxx, leftOver; + clientInfo_t *ci; + gitem_t *item; + qhandle_t h; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + len = CG_Text_Width( ci->name, scale, 0); + if (len > pwidth) + pwidth = len; + } + } + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_Text_Width(p, scale, 0); + if (len > lwidth) + lwidth = len; + } + } + + y = rect->y; + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + xx = rect->x + 1; + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); + xx += PIC_WIDTH; + } + } + } + + // FIXME: max of 3 powerups shown properly + xx = rect->x + (PIC_WIDTH * 3) + 2; + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + trap_R_SetColor(hcolor); + CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); + + //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); + + // draw weapon icon + xx += PIC_WIDTH + 1; + +// weapon used is not that useful, use the space for task +#if 0 + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); + } +#endif + + trap_R_SetColor(NULL); + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + h = 0; + } else { + h = CG_StatusHandle(cgs.currentOrder); + } + } else { + h = CG_StatusHandle(ci->teamTask); + } + + if (h) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); + } + + xx += PIC_WIDTH + 1; + + leftOver = rect->w - xx; + maxx = xx + leftOver / 3; + + + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); + + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + + xx += leftOver / 3 + 2; + maxx = rect->w - 4; + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); + y += text_y + 2; + if ( y + text_y + 2 > rect->y + rect->h ) { + break; + } + + } + } +} + + +void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + if (cg.spectatorLen) { + float maxX; + + if (cg.spectatorWidth == -1) { + cg.spectatorWidth = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.spectatorOffset > cg.spectatorLen) { + cg.spectatorOffset = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.time > cg.spectatorTime) { + cg.spectatorTime = cg.time + 10; + if (cg.spectatorPaintX <= rect->x + 2) { + if (cg.spectatorOffset < cg.spectatorLen) { + cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; + cg.spectatorOffset++; + } else { + cg.spectatorOffset = 0; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX = cg.spectatorPaintX2; + } else { + cg.spectatorPaintX = rect->x + rect->w - 2; + } + cg.spectatorPaintX2 = -1; + } + } else { + cg.spectatorPaintX--; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX2--; + } + } + } + + maxX = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); + if (cg.spectatorPaintX2 >= 0) { + float maxX2 = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); + } + if (cg.spectatorOffset && maxX > 0) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if (cg.spectatorPaintX2 == -1) { + cg.spectatorPaintX2 = rect->x + rect->w - 2; + } + } else { + cg.spectatorPaintX2 = -1; + } + + } +} + + + +void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + score_t *score = &cg.scores[cg.selectedScore]; + float value = 0; + char *text = NULL; + color[3] = 0.25; + + switch (ownerDraw) { + case CG_ACCURACY: + value = score->accuracy; + break; + case CG_ASSISTS: + value = score->assistCount; + break; + case CG_DEFEND: + value = score->defendCount; + break; + case CG_EXCELLENT: + value = score->excellentCount; + break; + case CG_IMPRESSIVE: + value = score->impressiveCount; + break; + case CG_PERFECT: + value = score->perfect; + break; + case CG_GAUNTLET: + value = score->guantletCount; + break; + case CG_CAPTURES: + value = score->captures; + break; + } + + if (value > 0) { + if (ownerDraw != CG_PERFECT) { + if (ownerDraw == CG_ACCURACY) { + text = va("%i%%", (int)value); + if (value > 50) { + color[3] = 1.0; + } + } else { + text = va("%i", (int)value); + color[3] = 1.0; + } + } else { + if (value) { + color[3] = 1.0; + } + text = "Wow"; + } + } + + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + + if (text) { + color[3] = 1.0; + value = CG_Text_Width(text, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); + } + trap_R_SetColor(NULL); + +} + + +// +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { + rectDef_t rect; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { + // return; + //} + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) { + case CG_PLAYER_ARMOR_ICON: + CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ARMOR_ICON2D: + CG_DrawPlayerArmorIcon(&rect, qtrue); + break; + case CG_PLAYER_ARMOR_VALUE: + CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_AMMO_ICON: + CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_AMMO_ICON2D: + CG_DrawPlayerAmmoIcon(&rect, qtrue); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); + break; + case CG_VOICE_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); + break; + case CG_VOICE_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); + break; + case CG_SELECTEDPLAYER_STATUS: + CG_DrawSelectedPlayerStatus(&rect); + break; + case CG_SELECTEDPLAYER_ARMOR: + CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEALTH: + CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); + break; + case CG_SELECTEDPLAYER_LOCATION: + CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_SELECTEDPLAYER_WEAPON: + CG_DrawSelectedPlayerWeapon(&rect); + break; + case CG_SELECTEDPLAYER_POWERUP: + CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_HEAD: + CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ITEM: + CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_SCORE: + CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_RED_SCORE: + CG_DrawRedScore(&rect, scale, color, shader, textStyle); + break; + case CG_BLUE_SCORE: + CG_DrawBlueScore(&rect, scale, color, shader, textStyle); + break; + case CG_RED_NAME: + CG_DrawRedName(&rect, scale, color, textStyle); + break; + case CG_BLUE_NAME: + CG_DrawBlueName(&rect, scale, color, textStyle); + break; + case CG_BLUE_FLAGHEAD: + CG_DrawBlueFlagHead(&rect); + break; + case CG_BLUE_FLAGSTATUS: + CG_DrawBlueFlagStatus(&rect, shader); + break; + case CG_BLUE_FLAGNAME: + CG_DrawBlueFlagName(&rect, scale, color, textStyle); + break; + case CG_RED_FLAGHEAD: + CG_DrawRedFlagHead(&rect); + break; + case CG_RED_FLAGSTATUS: + CG_DrawRedFlagStatus(&rect, shader); + break; + case CG_RED_FLAGNAME: + CG_DrawRedFlagName(&rect, scale, color, textStyle); + break; + case CG_HARVESTER_SKULLS: + CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); + break; + case CG_HARVESTER_SKULLS2D: + CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); + break; + case CG_ONEFLAG_STATUS: + CG_OneFlagStatus(&rect); + break; + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor(&rect, color); + break; + case CG_CTF_POWERUP: + CG_DrawCTFPowerUp(&rect); + break; + case CG_AREA_POWERUP: + CG_DrawAreaPowerUp(&rect, align, special, scale, color); + break; + case CG_PLAYER_STATUS: + CG_DrawPlayerStatus(&rect); + break; + case CG_PLAYER_HASFLAG: + CG_DrawPlayerHasFlag(&rect, qfalse); + break; + case CG_PLAYER_HASFLAG2D: + CG_DrawPlayerHasFlag(&rect, qtrue); + break; + case CG_AREA_SYSTEMCHAT: + CG_DrawAreaSystemChat(&rect, scale, color, shader); + break; + case CG_AREA_TEAMCHAT: + CG_DrawAreaTeamChat(&rect, scale, color, shader); + break; + case CG_AREA_CHAT: + CG_DrawAreaChat(&rect, scale, color, shader); + break; + case CG_GAME_TYPE: + CG_DrawGameType(&rect, scale, color, shader, textStyle); + break; + case CG_GAME_STATUS: + CG_DrawGameStatus(&rect, scale, color, shader, textStyle); + break; + case CG_KILLER: + CG_DrawKiller(&rect, scale, color, shader, textStyle); + break; + case CG_ACCURACY: + case CG_ASSISTS: + case CG_DEFEND: + case CG_EXCELLENT: + case CG_IMPRESSIVE: + case CG_PERFECT: + case CG_GAUNTLET: + case CG_CAPTURES: + CG_DrawMedal(ownerDraw, &rect, scale, color, shader); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators(&rect, scale, color, shader); + break; + case CG_TEAMINFO: + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); + } + break; + case CG_CAPFRAGLIMIT: + CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); + break; + case CG_1STPLACE: + CG_Draw1stPlace(&rect, scale, color, shader, textStyle); + break; + case CG_2NDPLACE: + CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); + break; + default: + break; + } +} + +void CG_MouseEvent(int x, int y) { + int n; + + if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { + trap_Key_SetCatcher(0); + return; + } + + cgs.cursorX+= x; + if (cgs.cursorX < 0) + cgs.cursorX = 0; + else if (cgs.cursorX > 640) + cgs.cursorX = 640; + + cgs.cursorY += y; + if (cgs.cursorY < 0) + cgs.cursorY = 0; + else if (cgs.cursorY > 480) + cgs.cursorY = 480; + + n = Display_CursorType(cgs.cursorX, cgs.cursorY); + cgs.activeCursor = 0; + if (n == CURSOR_ARROW) { + cgs.activeCursor = cgs.media.selectCursor; + } else if (n == CURSOR_SIZER) { + cgs.activeCursor = cgs.media.sizeCursor; + } + + if (cgs.capturedItem) { + Display_MouseMove(cgs.capturedItem, x, y); + } else { + Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); + } + +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu() { + Menus_CloseByName("teamMenu"); + Menus_CloseByName("getMenu"); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu() { + Menus_OpenByName("teamMenu"); +} + + + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling(int type) { + cgs.eventHandling = type; + if (type == CGAME_EVENT_NONE) { + CG_HideTeamMenu(); + } else if (type == CGAME_EVENT_TEAMMENU) { + //CG_ShowTeamMenu(); + } else if (type == CGAME_EVENT_SCOREBOARD) { + } + +} + + + +void CG_KeyEvent(int key, qboolean down) { + + if (!down) { + return; + } + + if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + return; + } + + //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { + // if we see this then we should always be visible + // CG_EventHandling(CGAME_EVENT_NONE); + // trap_Key_SetCatcher(0); + //} + + + + Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); + + if (cgs.capturedItem) { + cgs.capturedItem = NULL; + } else { + if (key == K_MOUSE2 && down) { + cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); + } + } +} + +int CG_ClientNumFromName(const char *p) { + int i; + for (i = 0; i < cgs.maxclients; i++) { + if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { + return i; + } + } + return -1; +} + +void CG_ShowResponseHead() { + Menus_OpenByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "72"); + cg.voiceTime = cg.time; +} + +void CG_RunMenuScript(char **args) { +} + + +void CG_GetTeamColor(vec4_t *color) { + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { + (*color)[0] = 1.0f; + (*color)[3] = 0.25f; + (*color)[1] = (*color)[2] = 0.0f; + } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { + (*color)[0] = (*color)[1] = 0.0f; + (*color)[2] = 1.0f; + (*color)[3] = 0.25f; + } else { + (*color)[0] = (*color)[2] = 0.0f; + (*color)[1] = 0.17f; + (*color)[3] = 0.25f; + } +} + diff --git a/code/cgame/cg_particles.c b/code/cgame/cg_particles.c index b3ce322..d9c3e7e 100755 --- a/code/cgame/cg_particles.c +++ b/code/cgame/cg_particles.c @@ -1,2018 +1,2018 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// Rafael particles -// cg_particles.c - -#include "cg_local.h" - -#define BLOODRED 2 -#define EMISIVEFADE 3 -#define GREY75 4 - -typedef struct particle_s -{ - struct particle_s *next; - - float time; - float endtime; - - vec3_t org; - vec3_t vel; - vec3_t accel; - int color; - float colorvel; - float alpha; - float alphavel; - int type; - qhandle_t pshader; - - float height; - float width; - - float endheight; - float endwidth; - - float start; - float end; - - float startfade; - qboolean rotate; - int snum; - - qboolean link; - - // Ridah - int shaderAnim; - int roll; - - int accumroll; - -} cparticle_t; - -typedef enum -{ - P_NONE, - P_WEATHER, - P_FLAT, - P_SMOKE, - P_ROTATE, - P_WEATHER_TURBULENT, - P_ANIM, // Ridah - P_BAT, - P_BLEED, - P_FLAT_SCALEUP, - P_FLAT_SCALEUP_FADE, - P_WEATHER_FLURRY, - P_SMOKE_IMPACT, - P_BUBBLE, - P_BUBBLE_TURBULENT, - P_SPRITE -} particle_type_t; - -#define MAX_SHADER_ANIMS 32 -#define MAX_SHADER_ANIM_FRAMES 64 - -static char *shaderAnimNames[MAX_SHADER_ANIMS] = { - "explode1", - "blacksmokeanim", - "twiltb2", - "expblue", - "blacksmokeanimb", // uses 'explode1' sequence - "blood", - NULL -}; -static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; -static int shaderAnimCounts[MAX_SHADER_ANIMS] = { - 23, - 25, - 45, - 25, - 23, - 5, -}; -static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { - 1.405f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, - 1.0f, -}; -static int numShaderAnims; -// done. - -#define PARTICLE_GRAVITY 40 -#define MAX_PARTICLES 1024 * 8 - -cparticle_t *active_particles, *free_particles; -cparticle_t particles[MAX_PARTICLES]; -int cl_numparticles = MAX_PARTICLES; - -qboolean initparticles = qfalse; -vec3_t vforward, vright, vup; -vec3_t rforward, rright, rup; - -float oldtime; - -/* -=============== -CL_ClearParticles -=============== -*/ -void CG_ClearParticles (void) -{ - int i; - - memset( particles, 0, sizeof(particles) ); - - free_particles = &particles[0]; - active_particles = NULL; - - for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY - || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - {// create a front facing polygon - - if (p->type != P_WEATHER_FLURRY) - { - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - if (org[2] > p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - p->org[2] = ( p->start + crandom () * 4 ); - - - if (p->type == P_BUBBLE_TURBULENT) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - } - } - else - { - if (org[2] < p->end) - { - p->time = cg.time; - VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground - - while (p->org[2] < p->end) - { - p->org[2] += (p->start - p->end); - } - - - if (p->type == P_WEATHER_TURBULENT) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - } - } - - - // Rafael snow pvs check - if (!p->link) - return; - - p->alpha = 1; - } - - // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp - if (Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - // done. - - if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, -p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255 * p->alpha; - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy( point, TRIverts[0].xyz ); - TRIverts[0].st[0] = 1; - TRIverts[0].st[1] = 0; - TRIverts[0].modulate[0] = 255; - TRIverts[0].modulate[1] = 255; - TRIverts[0].modulate[2] = 255; - TRIverts[0].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - VectorCopy (point, TRIverts[1].xyz); - TRIverts[1].st[0] = 0; - TRIverts[1].st[1] = 0; - TRIverts[1].modulate[0] = 255; - TRIverts[1].modulate[1] = 255; - TRIverts[1].modulate[2] = 255; - TRIverts[1].modulate[3] = 255 * p->alpha; - - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - VectorCopy (point, TRIverts[2].xyz); - TRIverts[2].st[0] = 0; - TRIverts[2].st[1] = 1; - TRIverts[2].modulate[0] = 255; - TRIverts[2].modulate[1] = 255; - TRIverts[2].modulate[2] = 255; - TRIverts[2].modulate[3] = 255 * p->alpha; - } - - } - else if (p->type == P_SPRITE) - { - vec3_t rr, ru; - vec3_t rotate_ang; - - VectorSet (color, 1.0, 1.0, 1.0); - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, vup, point); - VectorMA (point, -width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, vup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, vup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) - {// create a front rotating facing polygon - - if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { - return; - } - - if (p->color == BLOODRED) - VectorSet (color, 0.22f, 0.0f, 0.0f); - else if (p->color == GREY75) - { - float len; - float greyit; - float val; - len = Distance (cg.snap->ps.origin, org); - if (!len) - len = 1; - - val = 4096/len; - greyit = 0.25 * val; - if (greyit > 0.5) - greyit = 0.5; - - VectorSet (color, greyit, greyit, greyit); - } - else - VectorSet (color, 1.0, 1.0, 1.0); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - if (cg.time > p->startfade) - { - invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); - - if (p->color == EMISIVEFADE) - { - float fval; - fval = (invratio * invratio); - if (fval < 0) - fval = 0; - VectorSet (color, fval , fval , fval ); - } - invratio *= p->alpha; - } - else - invratio = 1 * p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - invratio = 1; - - if (invratio > 1) - invratio = 1; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (p->type != P_SMOKE_IMPACT) - { - vec3_t temp; - - vectoangles (rforward, temp); - p->accumroll += p->roll; - temp[ROLL] += p->accumroll * 0.1; - AngleVectors ( temp, NULL, rright2, rup2); - } - else - { - VectorCopy (rright, rright2); - VectorCopy (rup, rup2); - } - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, -p->width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, -height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, -p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, width, rright2, point); - } - else - { - VectorMA (org, p->height, vup, point); - VectorMA (point, p->width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255 * invratio; - - if (p->rotate) - { - VectorMA (org, height, rup2, point); - VectorMA (point, -width, rright2, point); - } - else - { - VectorMA (org, p->height, vup, point); - VectorMA (point, -p->width, vright, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255 * invratio; - - } - else if (p->type == P_BLEED) - { - vec3_t rr, ru; - vec3_t rotate_ang; - float alpha; - - alpha = p->alpha; - - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) - alpha = 1; - - if (p->roll) - { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - else - { - VectorCopy (vup, ru); - VectorCopy (vright, rr); - } - - VectorMA (org, -p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 111; - verts[0].modulate[1] = 19; - verts[0].modulate[2] = 9; - verts[0].modulate[3] = 255 * alpha; - - VectorMA (org, -p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 111; - verts[1].modulate[1] = 19; - verts[1].modulate[2] = 9; - verts[1].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, p->width, rr, point); - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 111; - verts[2].modulate[1] = 19; - verts[2].modulate[2] = 9; - verts[2].modulate[3] = 255 * alpha; - - VectorMA (org, p->height, ru, point); - VectorMA (point, -p->width, rr, point); - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 111; - verts[3].modulate[1] = 19; - verts[3].modulate[2] = 9; - verts[3].modulate[3] = 255 * alpha; - - } - else if (p->type == P_FLAT_SCALEUP) - { - float width, height; - float sinR, cosR; - - if (p->color == BLOODRED) - VectorSet (color, 1, 1, 1); - else - VectorSet (color, 0.5, 0.5, 0.5); - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - if (width > p->endwidth) - width = p->endwidth; - - if (height > p->endheight) - height = p->endheight; - - sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); - cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= sinR; - verts[0].xyz[1] -= cosR; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255 * color[0]; - verts[0].modulate[1] = 255 * color[1]; - verts[0].modulate[2] = 255 * color[2]; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= cosR; - verts[1].xyz[1] += sinR; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255 * color[0]; - verts[1].modulate[1] = 255 * color[1]; - verts[1].modulate[2] = 255 * color[2]; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += sinR; - verts[2].xyz[1] += cosR; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255 * color[0]; - verts[2].modulate[1] = 255 * color[1]; - verts[2].modulate[2] = 255 * color[2]; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += cosR; - verts[3].xyz[1] -= sinR; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255 * color[0]; - verts[3].modulate[1] = 255 * color[1]; - verts[3].modulate[2] = 255 * color[2]; - verts[3].modulate[3] = 255; - } - else if (p->type == P_FLAT) - { - - VectorCopy (org, verts[0].xyz); - verts[0].xyz[0] -= p->height; - verts[0].xyz[1] -= p->width; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy (org, verts[1].xyz); - verts[1].xyz[0] -= p->height; - verts[1].xyz[1] += p->width; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy (org, verts[2].xyz); - verts[2].xyz[0] += p->height; - verts[2].xyz[1] += p->width; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy (org, verts[3].xyz); - verts[3].xyz[0] += p->height; - verts[3].xyz[1] -= p->width; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - } - // Ridah - else if (p->type == P_ANIM) { - vec3_t rr, ru; - vec3_t rotate_ang; - int i, j; - - time = cg.time - p->time; - time2 = p->endtime - p->time; - ratio = time / time2; - if (ratio >= 1.0f) { - ratio = 0.9999f; - } - - width = p->width + ( ratio * ( p->endwidth - p->width) ); - height = p->height + ( ratio * ( p->endheight - p->height) ); - - // if we are "inside" this sprite, don't draw - if (Distance( cg.snap->ps.origin, org ) < width/1.5) { - return; - } - - i = p->shaderAnim; - j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); - p->pshader = shaderAnims[i][j]; - - if (p->roll) { - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - rotate_ang[ROLL] += p->roll; - AngleVectors ( rotate_ang, NULL, rr, ru); - } - - if (p->roll) { - VectorMA (org, -height, ru, point); - VectorMA (point, -width, rr, point); - } else { - VectorMA (org, -height, vup, point); - VectorMA (point, -width, vright, point); - } - VectorCopy (point, verts[0].xyz); - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*height, ru, point); - } else { - VectorMA (point, 2*height, vup, point); - } - VectorCopy (point, verts[1].xyz); - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, 2*width, rr, point); - } else { - VectorMA (point, 2*width, vright, point); - } - VectorCopy (point, verts[2].xyz); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - if (p->roll) { - VectorMA (point, -2*height, ru, point); - } else { - VectorMA (point, -2*height, vup, point); - } - VectorCopy (point, verts[3].xyz); - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - } - // done. - - if (!p->pshader) { -// (SA) temp commented out for DM -// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); - return; - } - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) - trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); - else - trap_R_AddPolyToScene( p->pshader, 4, verts ); - -} - -// Ridah, made this static so it doesn't interfere with other files -static float roll = 0.0; - -/* -=============== -CG_AddParticles -=============== -*/ -void CG_AddParticles (void) -{ - cparticle_t *p, *next; - float alpha; - float time, time2; - vec3_t org; - int color; - cparticle_t *active, *tail; - int type; - vec3_t rotate_ang; - - if (!initparticles) - CG_ClearParticles (); - - VectorCopy( cg.refdef.viewaxis[0], vforward ); - VectorCopy( cg.refdef.viewaxis[1], vright ); - VectorCopy( cg.refdef.viewaxis[2], vup ); - - vectoangles( cg.refdef.viewaxis[0], rotate_ang ); - roll += ((cg.time - oldtime) * 0.1) ; - rotate_ang[ROLL] += (roll*0.9); - AngleVectors ( rotate_ang, rforward, rright, rup); - - oldtime = cg.time; - - active = NULL; - tail = NULL; - - for (p=active_particles ; p ; p=next) - { - - next = p->next; - - time = (cg.time - p->time)*0.001; - - alpha = p->alpha + time*p->alphavel; - if (alpha <= 0) - { // faded out - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - - } - - if (p->type == P_WEATHER_FLURRY) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - - continue; - } - } - - - if (p->type == P_FLAT_SCALEUP_FADE) - { - if (cg.time > p->endtime) - { - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - } - - if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { - // temporary sprite - CG_AddParticleToScene (p, p->org, alpha); - p->next = free_particles; - free_particles = p; - p->type = 0; - p->color = 0; - p->alpha = 0; - continue; - } - - p->next = NULL; - if (!tail) - active = tail = p; - else - { - tail->next = p; - tail = p; - } - - if (alpha > 1.0) - alpha = 1; - - color = p->color; - - time2 = time*time; - - org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; - org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; - org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; - - type = p->type; - - CG_AddParticleToScene (p, org, alpha); - } - - active_particles = active; -} - -/* -====================== -CG_AddParticles -====================== -*/ -void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - qboolean turb = qtrue; - - if (!pshader) - CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.90f; - p->alphavel = 0; - - p->start = cent->currentState.origin2[0]; - p->end = cent->currentState.origin2[1]; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->pshader = pshader; - - if (rand()%100 > 90) - { - p->height = 32; - p->width = 32; - p->alpha = 0.10f; - } - else - { - p->height = 1; - p->width = 1; - } - - p->vel[2] = -20; - - p->type = P_WEATHER_FLURRY; - - if (turb) - p->vel[2] = -10; - - VectorCopy(cent->currentState.origin, p->org); - - p->org[0] = p->org[0]; - p->org[1] = p->org[1]; - p->org[2] = p->org[2]; - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); - p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); - p->vel[2] += cent->currentState.angles[2]; - - if (turb) - { - p->accel[0] = crandom () * 16; - p->accel[1] = crandom () * 16; - } - -} - -void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - p->height = 1; - p->width = 1; - - p->vel[2] = -50; - - if (turb) - { - p->type = P_WEATHER_TURBULENT; - p->vel[2] = -50 * 1.3; - } - else - { - p->type = P_WEATHER; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 16; - p->vel[1] = crandom() * 16; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) -{ - cparticle_t *p; - float randsize; - - if (!pshader) - CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->color = 0; - p->alpha = 0.40f; - p->alphavel = 0; - p->start = origin[2]; - p->end = origin2[2]; - p->pshader = pshader; - - randsize = 1 + (crandom() * 0.5); - - p->height = randsize; - p->width = randsize; - - p->vel[2] = 50 + ( crandom() * 10 ); - - if (turb) - { - p->type = P_BUBBLE_TURBULENT; - p->vel[2] = 50 * 1.3; - } - else - { - p->type = P_BUBBLE; - } - - VectorCopy(origin, p->org); - - p->org[0] = p->org[0] + ( crandom() * range); - p->org[1] = p->org[1] + ( crandom() * range); - p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); - - p->vel[0] = p->vel[1] = 0; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - if (turb) - { - p->vel[0] = crandom() * 4; - p->vel[1] = crandom() * 4; - } - - // Rafael snow pvs check - p->snum = snum; - p->link = qtrue; - -} - -void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) -{ - - // using cent->density = enttime - // cent->frame = startfade - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleSmoke == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + cent->currentState.time; - p->startfade = cg.time + cent->currentState.time2; - - p->color = 0; - p->alpha = 1.0; - p->alphavel = 0; - p->start = cent->currentState.origin[2]; - p->end = cent->currentState.origin2[2]; - p->pshader = pshader; - p->rotate = qfalse; - p->height = 8; - p->width = 8; - p->endheight = 32; - p->endwidth = 32; - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org); - - p->vel[0] = p->vel[1] = 0; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[2] = 5; - - if (cent->currentState.frame == 1)// reverse gravity - p->vel[2] *= -1; - - p->roll = 8 + (crandom() * 4); -} - - -void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) -{ - - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 1.0; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->accel[2] = -60; - p->vel[2] += -20; - -} - -/* -====================== -CG_ParticleExplosion -====================== -*/ - -void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) -{ - cparticle_t *p; - int anim; - - if (animStr < (char *)10) - CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); - - // find the animation string - for (anim=0; shaderAnimNames[anim]; anim++) { - if (!stricmp( animStr, shaderAnimNames[anim] )) - break; - } - if (!shaderAnimNames[anim]) { - CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); - return; - } - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - - if (duration < 0) { - duration *= -1; - p->roll = 0; - } else { - p->roll = crandom()*179; - } - - p->shaderAnim = anim; - - p->width = sizeStart; - p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction - - p->endheight = sizeEnd; - p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; - - p->endtime = cg.time + duration; - - p->type = P_ANIM; - - VectorCopy( origin, p->org ); - VectorCopy( vel, p->vel ); - VectorClear( p->accel ); - -} - -// Rafael Shrapnel -void CG_AddParticleShrapnel (localEntity_t *le) -{ - return; -} -// done. - -int CG_NewParticleArea (int num) -{ - // const char *str; - char *str; - char *token; - int type; - vec3_t origin, origin2; - int i; - float range = 0; - int turb; - int numparticles; - int snum; - - str = (char *) CG_ConfigString (num); - if (!str[0]) - return (0); - - // returns type 128 64 or 32 - token = COM_Parse (&str); - type = atoi (token); - - if (type == 1) - range = 128; - else if (type == 2) - range = 64; - else if (type == 3) - range = 32; - else if (type == 0) - range = 256; - else if (type == 4) - range = 8; - else if (type == 5) - range = 16; - else if (type == 6) - range = 32; - else if (type == 7) - range = 64; - - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin[i] = atof (token); - } - - for (i=0; i<3; i++) - { - token = COM_Parse (&str); - origin2[i] = atof (token); - } - - token = COM_Parse (&str); - numparticles = atoi (token); - - token = COM_Parse (&str); - turb = atoi (token); - - token = COM_Parse (&str); - snum = atoi (token); - - for (i=0; i= 4) - CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - else - CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); - } - - return (1); -} - -void CG_SnowLink (centity_t *cent, qboolean particleOn) -{ - cparticle_t *p, *next; - int id; - - id = cent->currentState.frame; - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) - { - if (p->snum == id) - { - if (particleOn) - p->link = qtrue; - else - p->link = qfalse; - } - } - - } -} - -void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 0.25; - p->alphavel = 0; - p->roll = crandom()*179; - - p->pshader = pshader; - - p->endtime = cg.time + 1000; - p->startfade = cg.time + 100; - - p->width = rand()%4 + 8; - p->height = rand()%4 + 8; - - p->endheight = p->height *2; - p->endwidth = p->width * 2; - - p->endtime = cg.time + 500; - - p->type = P_SMOKE_IMPACT; - - VectorCopy( origin, p->org ); - VectorSet(p->vel, 0, 0, 20); - VectorSet(p->accel, 0, 0, 20); - - p->rotate = qtrue; -} - -void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - if (fleshEntityNum) - p->startfade = cg.time; - else - p->startfade = cg.time + 100; - - p->width = 4; - p->height = 4; - - p->endheight = 4+rand()%3; - p->endwidth = p->endheight; - - p->type = P_SMOKE; - - VectorCopy( start, p->org ); - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -20; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - p->alpha = 0.75; - -} - -void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - int time; - int time2; - float ratio; - - float duration = 1500; - - time = cg.time; - time2 = cg.time + cent->currentState.time; - - ratio =(float)1 - ((float)time / (float)time2); - - if (!pshader) - CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - p->endtime = cg.time + duration; - - p->startfade = p->endtime; - - p->width = 1; - p->height = 3; - - p->endheight = 3; - p->endwidth = 1; - - p->type = P_SMOKE; - - VectorCopy(cent->currentState.origin, p->org ); - - p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); - p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); - p->vel[2] = (cent->currentState.origin2[2]); - - p->snum = 1.0f; - - VectorClear( p->accel ); - - p->accel[2] = -20; - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - - -void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - if (cent->currentState.angles2[2]) - p->endtime = cg.time + cent->currentState.angles2[2]; - else - p->endtime = cg.time + 60000; - - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) - { - p->width = cent->currentState.angles2[0]; - p->height = cent->currentState.angles2[0]; - - p->endheight = cent->currentState.angles2[1]; - p->endwidth = cent->currentState.angles2[1]; - } - else - { - p->width = 8; - p->height = 8; - - p->endheight = 16; - p->endwidth = 16; - } - - p->type = P_FLAT_SCALEUP; - - p->snum = 1.0; - - VectorCopy(cent->currentState.origin, p->org ); - - p->org[2]+= 0.55 + (crandom() * 0.5); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - -} - -void CG_OilSlickRemove (centity_t *cent) -{ - cparticle_t *p, *next; - int id; - - id = 1.0f; - - if (!id) - CG_Printf ("CG_OilSlickRevove NULL id\n"); - - for (p=active_particles ; p ; p=next) - { - next = p->next; - - if (p->type == P_FLAT_SCALEUP) - { - if (p->snum == id) - { - p->endtime = cg.time + 100; - p->startfade = p->endtime; - p->type = P_FLAT_SCALEUP_FADE; - - } - } - - } -} - -qboolean ValidBloodPool (vec3_t start) -{ -#define EXTRUDE_DIST 0.5 - - vec3_t angles; - vec3_t right, up; - vec3_t this_pos, x_pos, center_pos, end_pos; - float x, y; - float fwidth, fheight; - trace_t trace; - vec3_t normal; - - fwidth = 16; - fheight = 16; - - VectorSet (normal, 0, 0, 1); - - vectoangles (normal, angles); - AngleVectors (angles, NULL, right, up); - - VectorMA (start, EXTRUDE_DIST, normal, center_pos); - - for (x= -fwidth/2; xendpos, start); - legit = ValidBloodPool (start); - - if (!legit) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + 3000; - p->startfade = p->endtime; - - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = pshader; - - rndSize = 0.4 + random()*0.6; - - p->width = 8*rndSize; - p->height = 8*rndSize; - - p->endheight = 16*rndSize; - p->endwidth = 16*rndSize; - - p->type = P_FLAT_SCALEUP; - - VectorCopy(start, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = 0; - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - p->color = BLOODRED; -} - -#define NORMALSIZE 16 -#define LARGESIZE 32 - -void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - p->endtime = cg.time + 350 + (crandom() * 100); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - p->endheight = LARGESIZE; - p->endwidth = LARGESIZE; - - p->type = P_SMOKE; - - VectorCopy( origin, p->org ); - - p->vel[0] = 0; - p->vel[1] = 0; - p->vel[2] = -1; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->color = BLOODRED; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) -{ - cparticle_t *p; - - if (!free_particles) - return; - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - - p->endtime = cg.time + duration; - p->startfade = cg.time + duration/2; - - p->color = EMISIVEFADE; - p->alpha = 0.4f; - p->alphavel = 0; - - p->height = 0.5; - p->width = 0.5; - p->endheight = 0.5; - p->endwidth = 0.5; - - p->pshader = cgs.media.tracerShader; - - p->type = P_SMOKE; - - VectorCopy(org, p->org); - - p->org[0] += (crandom() * x); - p->org[1] += (crandom() * y); - - p->vel[0] = vel[0]; - p->vel[1] = vel[1]; - p->vel[2] = vel[2]; - - p->accel[0] = p->accel[1] = p->accel[2] = 0; - - p->vel[0] += (crandom() * 4); - p->vel[1] += (crandom() * 4); - p->vel[2] += (20 + (crandom() * 10)) * speed; - - p->accel[0] = crandom () * 4; - p->accel[1] = crandom () * 4; - -} - -void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) -{ - float length; - float dist; - float crittersize; - vec3_t angles, forward; - vec3_t point; - cparticle_t *p; - int i; - - dist = 0; - - VectorNegate (dir, dir); - length = VectorLength (dir); - vectoangles (dir, angles); - AngleVectors (angles, forward, NULL, NULL); - - crittersize = LARGESIZE; - - if (length) - dist = length / crittersize; - - if (dist < 1) - dist = 1; - - VectorCopy (origin, point); - - for (i=0; inext; - p->next = active_particles; - active_particles = p; - - p->time = cg.time; - p->alpha = 5.0; - p->alphavel = 0; - p->roll = 0; - - p->pshader = cgs.media.smokePuffShader; - - // RF, stay around for long enough to expand and dissipate naturally - if (length) - p->endtime = cg.time + 4500 + (crandom() * 3500); - else - p->endtime = cg.time + 750 + (crandom() * 500); - - p->startfade = cg.time; - - p->width = LARGESIZE; - p->height = LARGESIZE; - - // RF, expand while falling - p->endheight = LARGESIZE*3.0; - p->endwidth = LARGESIZE*3.0; - - if (!length) - { - p->width *= 0.2f; - p->height *= 0.2f; - - p->endheight = NORMALSIZE; - p->endwidth = NORMALSIZE; - } - - p->type = P_SMOKE; - - VectorCopy( point, p->org ); - - p->vel[0] = crandom()*6; - p->vel[1] = crandom()*6; - p->vel[2] = random()*20; - - // RF, add some gravity/randomness - p->accel[0] = crandom()*3; - p->accel[1] = crandom()*3; - p->accel[2] = -PARTICLE_GRAVITY*0.4; - - VectorClear( p->accel ); - - p->rotate = qfalse; - - p->roll = rand()%179; - - p->alpha = 0.75; - - } - - -} - -void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) -{ - cparticle_t *p; - - if (!pshader) - CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); - - if (!free_particles) - return; - - p = free_particles; - free_particles = p->next; - p->next = active_particles; - active_particles = p; - p->time = cg.time; - p->alpha = 1.0; - p->alphavel = 0; - p->roll = rand()%179; - - p->pshader = pshader; - - if (duration > 0) - p->endtime = cg.time + duration; - else - p->endtime = duration; - - p->startfade = cg.time; - - p->width = size; - p->height = size; - - p->endheight = size; - p->endwidth = size; - - p->type = P_SPRITE; - - VectorCopy( origin, p->org ); - - p->rotate = qfalse; -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// Rafael particles +// cg_particles.c + +#include "cg_local.h" + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + "blacksmokeanim", + "twiltb2", + "expblue", + "blacksmokeanimb", // uses 'explode1' sequence + "blood", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23, + 25, + 45, + 25, + 23, + 5, +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.405f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 * 8 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t vforward, vright, vup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 1.0); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (vup, ru); + VectorCopy (vright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], vforward ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} diff --git a/code/cgame/cg_players.c b/code/cgame/cg_players.c index 2fd7b73..6987442 100755 --- a/code/cgame/cg_players.c +++ b/code/cgame/cg_players.c @@ -1,2619 +1,2619 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_players.c -- handle the media and animation for player entities -#include "cg_local.h" - -char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { - "*death1.wav", - "*death2.wav", - "*death3.wav", - "*jump1.wav", - "*pain25_1.wav", - "*pain50_1.wav", - "*pain75_1.wav", - "*pain100_1.wav", - "*falling1.wav", - "*gasp.wav", - "*drown.wav", - "*fall1.wav", - "*taunt.wav" -}; - - -/* -================ -CG_CustomSound - -================ -*/ -sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { - clientInfo_t *ci; - int i; - - if ( soundName[0] != '*' ) { - return trap_S_RegisterSound( soundName, qfalse ); - } - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { - if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { - return ci->sounds[i]; - } - } - - CG_Error( "Unknown custom sound: %s", soundName ); - return 0; -} - - - -/* -============================================================================= - -CLIENT INFO - -============================================================================= -*/ - -/* -====================== -CG_ParseAnimationFile - -Read a configuration file containing animation coutns and rates -models/players/visor/animation.cfg, etc -====================== -*/ -static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { - char *text_p, *prev; - int len; - int i; - char *token; - float fps; - int skip; - char text[20000]; - fileHandle_t f; - animation_t *animations; - - animations = ci->animations; - - // load the file - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( len <= 0 ) { - return qfalse; - } - if ( len >= sizeof( text ) - 1 ) { - CG_Printf( "File %s too long\n", filename ); - return qfalse; - } - trap_FS_Read( text, len, f ); - text[len] = 0; - trap_FS_FCloseFile( f ); - - // parse the text - text_p = text; - skip = 0; // quite the compiler warning - - ci->footsteps = FOOTSTEP_NORMAL; - VectorClear( ci->headOffset ); - ci->gender = GENDER_MALE; - ci->fixedlegs = qfalse; - ci->fixedtorso = qfalse; - - // read optional parameters - while ( 1 ) { - prev = text_p; // so we can unget - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( !Q_stricmp( token, "footsteps" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { - ci->footsteps = FOOTSTEP_NORMAL; - } else if ( !Q_stricmp( token, "boot" ) ) { - ci->footsteps = FOOTSTEP_BOOT; - } else if ( !Q_stricmp( token, "flesh" ) ) { - ci->footsteps = FOOTSTEP_FLESH; - } else if ( !Q_stricmp( token, "mech" ) ) { - ci->footsteps = FOOTSTEP_MECH; - } else if ( !Q_stricmp( token, "energy" ) ) { - ci->footsteps = FOOTSTEP_ENERGY; - } else { - CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); - } - continue; - } else if ( !Q_stricmp( token, "headoffset" ) ) { - for ( i = 0 ; i < 3 ; i++ ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - ci->headOffset[i] = atof( token ); - } - continue; - } else if ( !Q_stricmp( token, "sex" ) ) { - token = COM_Parse( &text_p ); - if ( !token ) { - break; - } - if ( token[0] == 'f' || token[0] == 'F' ) { - ci->gender = GENDER_FEMALE; - } else if ( token[0] == 'n' || token[0] == 'N' ) { - ci->gender = GENDER_NEUTER; - } else { - ci->gender = GENDER_MALE; - } - continue; - } else if ( !Q_stricmp( token, "fixedlegs" ) ) { - ci->fixedlegs = qtrue; - continue; - } else if ( !Q_stricmp( token, "fixedtorso" ) ) { - ci->fixedtorso = qtrue; - continue; - } - - // if it is a number, start parsing animations - if ( token[0] >= '0' && token[0] <= '9' ) { - text_p = prev; // unget the token - break; - } - Com_Printf( "unknown token '%s' is %s\n", token, filename ); - } - - // read information for each frame - for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { - - token = COM_Parse( &text_p ); - if ( !*token ) { - if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { - animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; - animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; - animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; - animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; - animations[i].numFrames = animations[TORSO_GESTURE].numFrames; - animations[i].reversed = qfalse; - animations[i].flipflop = qfalse; - continue; - } - break; - } - animations[i].firstFrame = atoi( token ); - // leg only frames are adjusted to not count the upper body only frames - if ( i == LEGS_WALKCR ) { - skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; - } - if ( i >= LEGS_WALKCR && i0) { - return qtrue; - } - return qfalse; -} - -/* -========================== -CG_FindClientModelFile -========================== -*/ -static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { - char *team, *charactersFolder; - int i; - - if ( cgs.gametype >= GT_TEAM ) { - switch ( ci->team ) { - case TEAM_BLUE: { - team = "blue"; - break; - } - default: { - team = "red"; - break; - } - } - } - else { - team = "default"; - } - charactersFolder = ""; - while(1) { - for ( i = 0; i < 2; i++ ) { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); - } - else { - // "models/players/characters/james/lower_lily_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( cgs.gametype >= GT_TEAM ) { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); - } - else { - // "models/players/characters/james/lower_red.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); - } - } - else { - if ( i == 0 && teamName && *teamName ) { - // "models/players/characters/james/stroggs/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); - } - else { - // "models/players/characters/james/lower_lily.skin" - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); - } - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( !teamName || !*teamName ) { - break; - } - } - // if tried the heads folder first - if ( charactersFolder[0] ) { - break; - } - charactersFolder = "characters/"; - } - - return qfalse; -} - -/* -========================== -CG_FindClientHeadFile -========================== -*/ -static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { - char *team, *headsFolder; - int i; - - if ( cgs.gametype >= GT_TEAM ) { - switch ( ci->team ) { - case TEAM_BLUE: { - team = "blue"; - break; - } - default: { - team = "red"; - break; - } - } - } - else { - team = "default"; - } - - if ( headModelName[0] == '*' ) { - headsFolder = "heads/"; - headModelName++; - } - else { - headsFolder = ""; - } - while(1) { - for ( i = 0; i < 2; i++ ) { - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( cgs.gametype >= GT_TEAM ) { - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); - } - } - else { - if ( i == 0 && teamName && *teamName ) { - Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); - } - else { - Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); - } - } - if ( CG_FileExists( filename ) ) { - return qtrue; - } - if ( !teamName || !*teamName ) { - break; - } - } - // if tried the heads folder first - if ( headsFolder[0] ) { - break; - } - headsFolder = "heads/"; - } - - return qfalse; -} - -/* -========================== -CG_RegisterClientSkin -========================== -*/ -static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { - char filename[MAX_QPATH]; - - /* - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); - ci->legsSkin = trap_R_RegisterSkin( filename ); - if (!ci->legsSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); - ci->legsSkin = trap_R_RegisterSkin( filename ); - if (!ci->legsSkin) { - Com_Printf( "Leg skin load failure: %s\n", filename ); - } - } - - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); - ci->torsoSkin = trap_R_RegisterSkin( filename ); - if (!ci->torsoSkin) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); - ci->torsoSkin = trap_R_RegisterSkin( filename ); - if (!ci->torsoSkin) { - Com_Printf( "Torso skin load failure: %s\n", filename ); - } - } - */ - if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { - ci->legsSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->legsSkin) { - Com_Printf( "Leg skin load failure: %s\n", filename ); - } - - if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { - ci->torsoSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->torsoSkin) { - Com_Printf( "Torso skin load failure: %s\n", filename ); - } - - if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { - ci->headSkin = trap_R_RegisterSkin( filename ); - } - if (!ci->headSkin) { - Com_Printf( "Head skin load failure: %s\n", filename ); - } - - // if any skins failed to load - if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { - return qfalse; - } - return qtrue; -} - -/* -========================== -CG_RegisterClientModelname -========================== -*/ -static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { - char filename[MAX_QPATH*2]; - const char *headName; - char newTeamName[MAX_QPATH*2]; - - if ( headModelName[0] == '\0' ) { - headName = modelName; - } - else { - headName = headModelName; - } - Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); - ci->legsModel = trap_R_RegisterModel( filename ); - if ( !ci->legsModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); - ci->legsModel = trap_R_RegisterModel( filename ); - if ( !ci->legsModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); - ci->torsoModel = trap_R_RegisterModel( filename ); - if ( !ci->torsoModel ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); - ci->torsoModel = trap_R_RegisterModel( filename ); - if ( !ci->torsoModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - } - - if( headName[0] == '*' ) { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); - } - else { - Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); - } - ci->headModel = trap_R_RegisterModel( filename ); - // if the head model could not be found and we didn't load from the heads folder try to load from there - if ( !ci->headModel && headName[0] != '*' ) { - Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); - ci->headModel = trap_R_RegisterModel( filename ); - } - if ( !ci->headModel ) { - Com_Printf( "Failed to load model file %s\n", filename ); - return qfalse; - } - - // if any skins failed to load, return failure - if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { - if ( teamName && *teamName) { - Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); - if( ci->team == TEAM_BLUE ) { - Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); - } - else { - Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); - } - if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { - Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); - return qfalse; - } - } else { - Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); - return qfalse; - } - } - - // load the animations - Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); - if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); - if ( !CG_ParseAnimationFile( filename, ci ) ) { - Com_Printf( "Failed to load animation file %s\n", filename ); - return qfalse; - } - } - - if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - } - else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { - ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); - } - - if ( !ci->modelIcon ) { - return qfalse; - } - - return qtrue; -} - -/* -==================== -CG_ColorFromString -==================== -*/ -static void CG_ColorFromString( const char *v, vec3_t color ) { - int val; - - VectorClear( color ); - - val = atoi( v ); - - if ( val < 1 || val > 7 ) { - VectorSet( color, 1, 1, 1 ); - return; - } - - if ( val & 1 ) { - color[2] = 1.0f; - } - if ( val & 2 ) { - color[1] = 1.0f; - } - if ( val & 4 ) { - color[0] = 1.0f; - } -} - -/* -=================== -CG_LoadClientInfo - -Load it now, taking the disk hits. -This will usually be deferred to a safe time -=================== -*/ -static void CG_LoadClientInfo( clientInfo_t *ci ) { - const char *dir, *fallback; - int i, modelloaded; - const char *s; - int clientNum; - char teamname[MAX_QPATH]; - - teamname[0] = 0; -#ifdef MISSIONPACK - if( cgs.gametype >= GT_TEAM) { - if( ci->team == TEAM_BLUE ) { - Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); - } else { - Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); - } - } - if( teamname[0] ) { - strcat( teamname, "/" ); - } -#endif - modelloaded = qtrue; - if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { - if ( cg_buildScript.integer ) { - CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); - } - - // fall back to default team name - if( cgs.gametype >= GT_TEAM) { - // keep skin name - if( ci->team == TEAM_BLUE ) { - Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); - } else { - Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); - } - if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { - CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); - } - } else { - if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { - CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); - } - } - modelloaded = qfalse; - } - - ci->newAnims = qfalse; - if ( ci->torsoModel ) { - orientation_t tag; - // if the torso model has the "tag_flag" - if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { - ci->newAnims = qtrue; - } - } - - // sounds - dir = ci->modelName; - fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; - - for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { - s = cg_customSoundNames[i]; - if ( !s ) { - break; - } - ci->sounds[i] = 0; - // if the model didn't load use the sounds of the default model - if (modelloaded) { - ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); - } - if ( !ci->sounds[i] ) { - ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); - } - } - - ci->deferred = qfalse; - - // reset any existing players and bodies, because they might be in bad - // frames for this new model - clientNum = ci - cgs.clientinfo; - for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { - if ( cg_entities[i].currentState.clientNum == clientNum - && cg_entities[i].currentState.eType == ET_PLAYER ) { - CG_ResetPlayerEntity( &cg_entities[i] ); - } - } -} - -/* -====================== -CG_CopyClientInfoModel -====================== -*/ -static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { - VectorCopy( from->headOffset, to->headOffset ); - to->footsteps = from->footsteps; - to->gender = from->gender; - - to->legsModel = from->legsModel; - to->legsSkin = from->legsSkin; - to->torsoModel = from->torsoModel; - to->torsoSkin = from->torsoSkin; - to->headModel = from->headModel; - to->headSkin = from->headSkin; - to->modelIcon = from->modelIcon; - - to->newAnims = from->newAnims; - - memcpy( to->animations, from->animations, sizeof( to->animations ) ); - memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); -} - -/* -====================== -CG_ScanForExistingClientInfo -====================== -*/ -static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { - int i; - clientInfo_t *match; - - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid ) { - continue; - } - if ( match->deferred ) { - continue; - } - if ( !Q_stricmp( ci->modelName, match->modelName ) - && !Q_stricmp( ci->skinName, match->skinName ) - && !Q_stricmp( ci->headModelName, match->headModelName ) - && !Q_stricmp( ci->headSkinName, match->headSkinName ) - && !Q_stricmp( ci->blueTeam, match->blueTeam ) - && !Q_stricmp( ci->redTeam, match->redTeam ) - && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { - // this clientinfo is identical, so use it's handles - - ci->deferred = qfalse; - - CG_CopyClientInfoModel( match, ci ); - - return qtrue; - } - } - - // nothing matches, so defer the load - return qfalse; -} - -/* -====================== -CG_SetDeferredClientInfo - -We aren't going to load it now, so grab some other -client's info to use until we have some spare time. -====================== -*/ -static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { - int i; - clientInfo_t *match; - - // if someone else is already the same models and skins we - // can just load the client info - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid || match->deferred ) { - continue; - } - if ( Q_stricmp( ci->skinName, match->skinName ) || - Q_stricmp( ci->modelName, match->modelName ) || -// Q_stricmp( ci->headModelName, match->headModelName ) || -// Q_stricmp( ci->headSkinName, match->headSkinName ) || - (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { - continue; - } - // just load the real info cause it uses the same models and skins - CG_LoadClientInfo( ci ); - return; - } - - // if we are in teamplay, only grab a model if the skin is correct - if ( cgs.gametype >= GT_TEAM ) { - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid || match->deferred ) { - continue; - } - if ( Q_stricmp( ci->skinName, match->skinName ) || - (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { - continue; - } - ci->deferred = qtrue; - CG_CopyClientInfoModel( match, ci ); - return; - } - // load the full model, because we don't ever want to show - // an improper team skin. This will cause a hitch for the first - // player, when the second enters. Combat shouldn't be going on - // yet, so it shouldn't matter - CG_LoadClientInfo( ci ); - return; - } - - // find the first valid clientinfo and grab its stuff - for ( i = 0 ; i < cgs.maxclients ; i++ ) { - match = &cgs.clientinfo[ i ]; - if ( !match->infoValid ) { - continue; - } - - ci->deferred = qtrue; - CG_CopyClientInfoModel( match, ci ); - return; - } - - // we should never get here... - CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); - - CG_LoadClientInfo( ci ); -} - - -/* -====================== -CG_NewClientInfo -====================== -*/ -void CG_NewClientInfo( int clientNum ) { - clientInfo_t *ci; - clientInfo_t newInfo; - const char *configstring; - const char *v; - char *slash; - - ci = &cgs.clientinfo[clientNum]; - - configstring = CG_ConfigString( clientNum + CS_PLAYERS ); - if ( !configstring[0] ) { - memset( ci, 0, sizeof( *ci ) ); - return; // player just left - } - - // build into a temp buffer so the defer checks can use - // the old value - memset( &newInfo, 0, sizeof( newInfo ) ); - - // isolate the player's name - v = Info_ValueForKey(configstring, "n"); - Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); - - // colors - v = Info_ValueForKey( configstring, "c1" ); - CG_ColorFromString( v, newInfo.color1 ); - - v = Info_ValueForKey( configstring, "c2" ); - CG_ColorFromString( v, newInfo.color2 ); - - // bot skill - v = Info_ValueForKey( configstring, "skill" ); - newInfo.botSkill = atoi( v ); - - // handicap - v = Info_ValueForKey( configstring, "hc" ); - newInfo.handicap = atoi( v ); - - // wins - v = Info_ValueForKey( configstring, "w" ); - newInfo.wins = atoi( v ); - - // losses - v = Info_ValueForKey( configstring, "l" ); - newInfo.losses = atoi( v ); - - // team - v = Info_ValueForKey( configstring, "t" ); - newInfo.team = atoi( v ); - - // team task - v = Info_ValueForKey( configstring, "tt" ); - newInfo.teamTask = atoi(v); - - // team leader - v = Info_ValueForKey( configstring, "tl" ); - newInfo.teamLeader = atoi(v); - - v = Info_ValueForKey( configstring, "g_redteam" ); - Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); - - v = Info_ValueForKey( configstring, "g_blueteam" ); - Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); - - // model - v = Info_ValueForKey( configstring, "model" ); - if ( cg_forceModel.integer ) { - // forcemodel makes everyone use a single model - // to prevent load hitches - char modelStr[MAX_QPATH]; - char *skin; - - if( cgs.gametype >= GT_TEAM ) { - Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); - Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - } else { - trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); - if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { - skin = "default"; - } else { - *skin++ = 0; - } - - Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); - Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); - } - - if ( cgs.gametype >= GT_TEAM ) { - // keep skin name - slash = strchr( v, '/' ); - if ( slash ) { - Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); - } - } - } else { - Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); - - slash = strchr( newInfo.modelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); - } else { - Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); - // truncate modelName - *slash = 0; - } - } - - // head model - v = Info_ValueForKey( configstring, "hmodel" ); - if ( cg_forceModel.integer ) { - // forcemodel makes everyone use a single model - // to prevent load hitches - char modelStr[MAX_QPATH]; - char *skin; - - if( cgs.gametype >= GT_TEAM ) { - Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else { - trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); - if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { - skin = "default"; - } else { - *skin++ = 0; - } - - Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); - Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); - } - - if ( cgs.gametype >= GT_TEAM ) { - // keep skin name - slash = strchr( v, '/' ); - if ( slash ) { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - } - } - } else { - Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); - - slash = strchr( newInfo.headModelName, '/' ); - if ( !slash ) { - // modelName didn not include a skin name - Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); - } else { - Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); - // truncate modelName - *slash = 0; - } - } - - // scan for an existing clientinfo that matches this modelname - // so we can avoid loading checks if possible - if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { - qboolean forceDefer; - - forceDefer = trap_MemoryRemaining() < 4000000; - - // if we are defering loads, just have it pick the first valid - if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { - // keep whatever they had if it won't violate team skins - CG_SetDeferredClientInfo( &newInfo ); - // if we are low on memory, leave them with this model - if ( forceDefer ) { - CG_Printf( "Memory is low. Using deferred model.\n" ); - newInfo.deferred = qfalse; - } - } else { - CG_LoadClientInfo( &newInfo ); - } - } - - // replace whatever was there with the new one - newInfo.infoValid = qtrue; - *ci = newInfo; -} - - - -/* -====================== -CG_LoadDeferredPlayers - -Called each frame when a player is dead -and the scoreboard is up -so deferred players can be loaded -====================== -*/ -void CG_LoadDeferredPlayers( void ) { - int i; - clientInfo_t *ci; - - // scan for a deferred player to load - for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { - if ( ci->infoValid && ci->deferred ) { - // if we are low on memory, leave it deferred - if ( trap_MemoryRemaining() < 4000000 ) { - CG_Printf( "Memory is low. Using deferred model.\n" ); - ci->deferred = qfalse; - continue; - } - CG_LoadClientInfo( ci ); -// break; - } - } -} - -/* -============================================================================= - -PLAYER ANIMATION - -============================================================================= -*/ - - -/* -=============== -CG_SetLerpFrameAnimation - -may include ANIM_TOGGLEBIT -=============== -*/ -static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { - animation_t *anim; - - lf->animationNumber = newAnimation; - newAnimation &= ~ANIM_TOGGLEBIT; - - if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { - CG_Error( "Bad animation number: %i", newAnimation ); - } - - anim = &ci->animations[ newAnimation ]; - - lf->animation = anim; - lf->animationTime = lf->frameTime + anim->initialLerp; - - if ( cg_debugAnim.integer ) { - CG_Printf( "Anim: %i\n", newAnimation ); - } -} - -/* -=============== -CG_RunLerpFrame - -Sets cg.snap, cg.oldFrame, and cg.backlerp -cg.time should be between oldFrameTime and frameTime after exit -=============== -*/ -static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { - int f, numFrames; - animation_t *anim; - - // debugging tool to get no animations - if ( cg_animSpeed.integer == 0 ) { - lf->oldFrame = lf->frame = lf->backlerp = 0; - return; - } - - // see if the animation sequence is switching - if ( newAnimation != lf->animationNumber || !lf->animation ) { - CG_SetLerpFrameAnimation( ci, lf, newAnimation ); - } - - // if we have passed the current frame, move it to - // oldFrame and calculate a new frame - if ( cg.time >= lf->frameTime ) { - lf->oldFrame = lf->frame; - lf->oldFrameTime = lf->frameTime; - - // get the next frame based on the animation - anim = lf->animation; - if ( !anim->frameLerp ) { - return; // shouldn't happen - } - if ( cg.time < lf->animationTime ) { - lf->frameTime = lf->animationTime; // initial lerp - } else { - lf->frameTime = lf->oldFrameTime + anim->frameLerp; - } - f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; - f *= speedScale; // adjust for haste, etc - - numFrames = anim->numFrames; - if (anim->flipflop) { - numFrames *= 2; - } - if ( f >= numFrames ) { - f -= numFrames; - if ( anim->loopFrames ) { - f %= anim->loopFrames; - f += anim->numFrames - anim->loopFrames; - } else { - f = numFrames - 1; - // the animation is stuck at the end, so it - // can immediately transition to another sequence - lf->frameTime = cg.time; - } - } - if ( anim->reversed ) { - lf->frame = anim->firstFrame + anim->numFrames - 1 - f; - } - else if (anim->flipflop && f>=anim->numFrames) { - lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); - } - else { - lf->frame = anim->firstFrame + f; - } - if ( cg.time > lf->frameTime ) { - lf->frameTime = cg.time; - if ( cg_debugAnim.integer ) { - CG_Printf( "Clamp lf->frameTime\n"); - } - } - } - - if ( lf->frameTime > cg.time + 200 ) { - lf->frameTime = cg.time; - } - - if ( lf->oldFrameTime > cg.time ) { - lf->oldFrameTime = cg.time; - } - // calculate current lerp value - if ( lf->frameTime == lf->oldFrameTime ) { - lf->backlerp = 0; - } else { - lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); - } -} - - -/* -=============== -CG_ClearLerpFrame -=============== -*/ -static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { - lf->frameTime = lf->oldFrameTime = cg.time; - CG_SetLerpFrameAnimation( ci, lf, animationNumber ); - lf->oldFrame = lf->frame = lf->animation->firstFrame; -} - - -/* -=============== -CG_PlayerAnimation -=============== -*/ -static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, - int *torsoOld, int *torso, float *torsoBackLerp ) { - clientInfo_t *ci; - int clientNum; - float speedScale; - - clientNum = cent->currentState.clientNum; - - if ( cg_noPlayerAnims.integer ) { - *legsOld = *legs = *torsoOld = *torso = 0; - return; - } - - if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { - speedScale = 1.5; - } else { - speedScale = 1; - } - - ci = &cgs.clientinfo[ clientNum ]; - - // do the shuffle turn frames locally - if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { - CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); - } else { - CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); - } - - *legsOld = cent->pe.legs.oldFrame; - *legs = cent->pe.legs.frame; - *legsBackLerp = cent->pe.legs.backlerp; - - CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); - - *torsoOld = cent->pe.torso.oldFrame; - *torso = cent->pe.torso.frame; - *torsoBackLerp = cent->pe.torso.backlerp; -} - -/* -============================================================================= - -PLAYER ANGLES - -============================================================================= -*/ - -/* -================== -CG_SwingAngles -================== -*/ -static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, - float speed, float *angle, qboolean *swinging ) { - float swing; - float move; - float scale; - - if ( !*swinging ) { - // see if a swing should be started - swing = AngleSubtract( *angle, destination ); - if ( swing > swingTolerance || swing < -swingTolerance ) { - *swinging = qtrue; - } - } - - if ( !*swinging ) { - return; - } - - // modify the speed depending on the delta - // so it doesn't seem so linear - swing = AngleSubtract( destination, *angle ); - scale = fabs( swing ); - if ( scale < swingTolerance * 0.5 ) { - scale = 0.5; - } else if ( scale < swingTolerance ) { - scale = 1.0; - } else { - scale = 2.0; - } - - // swing towards the destination angle - if ( swing >= 0 ) { - move = cg.frametime * scale * speed; - if ( move >= swing ) { - move = swing; - *swinging = qfalse; - } - *angle = AngleMod( *angle + move ); - } else if ( swing < 0 ) { - move = cg.frametime * scale * -speed; - if ( move <= swing ) { - move = swing; - *swinging = qfalse; - } - *angle = AngleMod( *angle + move ); - } - - // clamp to no more than tolerance - swing = AngleSubtract( destination, *angle ); - if ( swing > clampTolerance ) { - *angle = AngleMod( destination - (clampTolerance - 1) ); - } else if ( swing < -clampTolerance ) { - *angle = AngleMod( destination + (clampTolerance - 1) ); - } -} - -/* -================= -CG_AddPainTwitch -================= -*/ -static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { - int t; - float f; - - t = cg.time - cent->pe.painTime; - if ( t >= PAIN_TWITCH_TIME ) { - return; - } - - f = 1.0 - (float)t / PAIN_TWITCH_TIME; - - if ( cent->pe.painDirection ) { - torsoAngles[ROLL] += 20 * f; - } else { - torsoAngles[ROLL] -= 20 * f; - } -} - - -/* -=============== -CG_PlayerAngles - -Handles seperate torso motion - - legs pivot based on direction of movement - - head always looks exactly at cent->lerpAngles - - if motion < 20 degrees, show in head only - if < 45 degrees, also show in torso -=============== -*/ -static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { - vec3_t legsAngles, torsoAngles, headAngles; - float dest; - static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; - vec3_t velocity; - float speed; - int dir, clientNum; - clientInfo_t *ci; - - VectorCopy( cent->lerpAngles, headAngles ); - headAngles[YAW] = AngleMod( headAngles[YAW] ); - VectorClear( legsAngles ); - VectorClear( torsoAngles ); - - // --------- yaw ------------- - - // allow yaw to drift a bit - if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE - || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { - // if not standing still, always point all in the same direction - cent->pe.torso.yawing = qtrue; // always center - cent->pe.torso.pitching = qtrue; // always center - cent->pe.legs.yawing = qtrue; // always center - } - - // adjust legs for movement dir - if ( cent->currentState.eFlags & EF_DEAD ) { - // don't let dead bodies twitch - dir = 0; - } else { - dir = cent->currentState.angles2[YAW]; - if ( dir < 0 || dir > 7 ) { - CG_Error( "Bad player movement angle" ); - } - } - legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; - torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; - - // torso - CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); - CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); - - torsoAngles[YAW] = cent->pe.torso.yawAngle; - legsAngles[YAW] = cent->pe.legs.yawAngle; - - - // --------- pitch ------------- - - // only show a fraction of the pitch angle in the torso - if ( headAngles[PITCH] > 180 ) { - dest = (-360 + headAngles[PITCH]) * 0.75f; - } else { - dest = headAngles[PITCH] * 0.75f; - } - CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); - torsoAngles[PITCH] = cent->pe.torso.pitchAngle; - - // - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - if ( ci->fixedtorso ) { - torsoAngles[PITCH] = 0.0f; - } - } - - // --------- roll ------------- - - - // lean towards the direction of travel - VectorCopy( cent->currentState.pos.trDelta, velocity ); - speed = VectorNormalize( velocity ); - if ( speed ) { - vec3_t axis[3]; - float side; - - speed *= 0.05f; - - AnglesToAxis( legsAngles, axis ); - side = speed * DotProduct( velocity, axis[1] ); - legsAngles[ROLL] -= side; - - side = speed * DotProduct( velocity, axis[0] ); - legsAngles[PITCH] += side; - } - - // - clientNum = cent->currentState.clientNum; - if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { - ci = &cgs.clientinfo[ clientNum ]; - if ( ci->fixedlegs ) { - legsAngles[YAW] = torsoAngles[YAW]; - legsAngles[PITCH] = 0.0f; - legsAngles[ROLL] = 0.0f; - } - } - - // pain twitch - CG_AddPainTwitch( cent, torsoAngles ); - - // pull the angles back out of the hierarchial chain - AnglesSubtract( headAngles, torsoAngles, headAngles ); - AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); - AnglesToAxis( legsAngles, legs ); - AnglesToAxis( torsoAngles, torso ); - AnglesToAxis( headAngles, head ); -} - - -//========================================================================== - -/* -=============== -CG_HasteTrail -=============== -*/ -static void CG_HasteTrail( centity_t *cent ) { - localEntity_t *smoke; - vec3_t origin; - int anim; - - if ( cent->trailTime > cg.time ) { - return; - } - anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; - if ( anim != LEGS_RUN && anim != LEGS_BACK ) { - return; - } - - cent->trailTime += 100; - if ( cent->trailTime < cg.time ) { - cent->trailTime = cg.time; - } - - VectorCopy( cent->lerpOrigin, origin ); - origin[2] -= 16; - - smoke = CG_SmokePuff( origin, vec3_origin, - 8, - 1, 1, 1, 1, - 500, - cg.time, - 0, - 0, - cgs.media.hastePuffShader ); - - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; -} - -#ifdef MISSIONPACK -/* -=============== -CG_BreathPuffs -=============== -*/ -static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { - clientInfo_t *ci; - vec3_t up, origin; - int contents; - - ci = &cgs.clientinfo[ cent->currentState.number ]; - - if (!cg_enableBreath.integer) { - return; - } - if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { - return; - } - if ( cent->currentState.eFlags & EF_DEAD ) { - return; - } - contents = trap_CM_PointContents( head->origin, 0 ); - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - return; - } - if ( ci->breathPuffTime > cg.time ) { - return; - } - - VectorSet( up, 0, 0, 8 ); - VectorMA(head->origin, 8, head->axis[0], origin); - VectorMA(origin, -4, head->axis[2], origin); - CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); - ci->breathPuffTime = cg.time + 2000; -} - -/* -=============== -CG_DustTrail -=============== -*/ -static void CG_DustTrail( centity_t *cent ) { - int anim; - localEntity_t *dust; - vec3_t end, vel; - trace_t tr; - - if (!cg_enableDust.integer) - return; - - if ( cent->dustTrailTime > cg.time ) { - return; - } - - anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; - if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { - return; - } - - cent->dustTrailTime += 40; - if ( cent->dustTrailTime < cg.time ) { - cent->dustTrailTime = cg.time; - } - - VectorCopy(cent->currentState.pos.trBase, end); - end[2] -= 64; - CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); - - if ( !(tr.surfaceFlags & SURF_DUST) ) - return; - - VectorCopy( cent->currentState.pos.trBase, end ); - end[2] -= 16; - - VectorSet(vel, 0, 0, -30); - dust = CG_SmokePuff( end, vel, - 24, - .8f, .8f, 0.7f, 0.33f, - 500, - cg.time, - 0, - 0, - cgs.media.dustPuffShader ); -} - -#endif - -/* -=============== -CG_TrailItem -=============== -*/ -static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { - refEntity_t ent; - vec3_t angles; - vec3_t axis[3]; - - VectorCopy( cent->lerpAngles, angles ); - angles[PITCH] = 0; - angles[ROLL] = 0; - AnglesToAxis( angles, axis ); - - memset( &ent, 0, sizeof( ent ) ); - VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); - ent.origin[2] += 16; - angles[YAW] += 90; - AnglesToAxis( angles, ent.axis ); - - ent.hModel = hModel; - trap_R_AddRefEntityToScene( &ent ); -} - - -/* -=============== -CG_PlayerFlag -=============== -*/ -static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { - clientInfo_t *ci; - refEntity_t pole; - refEntity_t flag; - vec3_t angles, dir; - int legsAnim, flagAnim, updateangles; - float angle, d; - - // show the flag pole model - memset( &pole, 0, sizeof(pole) ); - pole.hModel = cgs.media.flagPoleModel; - VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); - pole.shadowPlane = torso->shadowPlane; - pole.renderfx = torso->renderfx; - CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); - trap_R_AddRefEntityToScene( &pole ); - - // show the flag model - memset( &flag, 0, sizeof(flag) ); - flag.hModel = cgs.media.flagFlapModel; - flag.customSkin = hSkin; - VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); - flag.shadowPlane = torso->shadowPlane; - flag.renderfx = torso->renderfx; - - VectorClear(angles); - - updateangles = qfalse; - legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; - if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { - flagAnim = FLAG_STAND; - } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { - flagAnim = FLAG_STAND; - updateangles = qtrue; - } else { - flagAnim = FLAG_RUN; - updateangles = qtrue; - } - - if ( updateangles ) { - - VectorCopy( cent->currentState.pos.trDelta, dir ); - // add gravity - dir[2] += 100; - VectorNormalize( dir ); - d = DotProduct(pole.axis[2], dir); - // if there is anough movement orthogonal to the flag pole - if (fabs(d) < 0.9) { - // - d = DotProduct(pole.axis[0], dir); - if (d > 1.0f) { - d = 1.0f; - } - else if (d < -1.0f) { - d = -1.0f; - } - angle = acos(d); - - d = DotProduct(pole.axis[1], dir); - if (d < 0) { - angles[YAW] = 360 - angle * 180 / M_PI; - } - else { - angles[YAW] = angle * 180 / M_PI; - } - if (angles[YAW] < 0) - angles[YAW] += 360; - if (angles[YAW] > 360) - angles[YAW] -= 360; - - //vectoangles( cent->currentState.pos.trDelta, tmpangles ); - //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; - // change the yaw angle - CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); - } - - /* - d = DotProduct(pole.axis[2], dir); - angle = Q_acos(d); - - d = DotProduct(pole.axis[1], dir); - if (d < 0) { - angle = 360 - angle * 180 / M_PI; - } - else { - angle = angle * 180 / M_PI; - } - if (angle > 340 && angle < 20) { - flagAnim = FLAG_RUNUP; - } - if (angle > 160 && angle < 200) { - flagAnim = FLAG_RUNDOWN; - } - */ - } - - // set the yaw angle - angles[YAW] = cent->pe.flag.yawAngle; - // lerp the flag animation frames - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); - flag.oldframe = cent->pe.flag.oldFrame; - flag.frame = cent->pe.flag.frame; - flag.backlerp = cent->pe.flag.backlerp; - - AnglesToAxis( angles, flag.axis ); - CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); - - trap_R_AddRefEntityToScene( &flag ); -} - - -#ifdef MISSIONPACK // bk001204 -/* -=============== -CG_PlayerTokens -=============== -*/ -static void CG_PlayerTokens( centity_t *cent, int renderfx ) { - int tokens, i, j; - float angle; - refEntity_t ent; - vec3_t dir, origin; - skulltrail_t *trail; - trail = &cg.skulltrails[cent->currentState.number]; - tokens = cent->currentState.generic1; - if ( !tokens ) { - trail->numpositions = 0; - return; - } - - if ( tokens > MAX_SKULLTRAIL ) { - tokens = MAX_SKULLTRAIL; - } - - // add skulls if there are more than last time - for (i = 0; i < tokens - trail->numpositions; i++) { - for (j = trail->numpositions; j > 0; j--) { - VectorCopy(trail->positions[j-1], trail->positions[j]); - } - VectorCopy(cent->lerpOrigin, trail->positions[0]); - } - trail->numpositions = tokens; - - // move all the skulls along the trail - VectorCopy(cent->lerpOrigin, origin); - for (i = 0; i < trail->numpositions; i++) { - VectorSubtract(trail->positions[i], origin, dir); - if (VectorNormalize(dir) > 30) { - VectorMA(origin, 30, dir, trail->positions[i]); - } - VectorCopy(trail->positions[i], origin); - } - - memset( &ent, 0, sizeof( ent ) ); - if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { - ent.hModel = cgs.media.redCubeModel; - } else { - ent.hModel = cgs.media.blueCubeModel; - } - ent.renderfx = renderfx; - - VectorCopy(cent->lerpOrigin, origin); - for (i = 0; i < trail->numpositions; i++) { - VectorSubtract(origin, trail->positions[i], ent.axis[0]); - ent.axis[0][2] = 0; - VectorNormalize(ent.axis[0]); - VectorSet(ent.axis[2], 0, 0, 1); - CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); - - VectorCopy(trail->positions[i], ent.origin); - angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; - ent.origin[2] += sin(angle) * 10; - trap_R_AddRefEntityToScene( &ent ); - VectorCopy(trail->positions[i], origin); - } -} -#endif - - -/* -=============== -CG_PlayerPowerups -=============== -*/ -static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { - int powerups; - clientInfo_t *ci; - - powerups = cent->currentState.powerups; - if ( !powerups ) { - return; - } - - // quad gives a dlight - if ( powerups & ( 1 << PW_QUAD ) ) { - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); - } - - // flight plays a looped sound - if ( powerups & ( 1 << PW_FLIGHT ) ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); - } - - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - // redflag - if ( powerups & ( 1 << PW_REDFLAG ) ) { - if (ci->newAnims) { - CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.redFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); - } - - // blueflag - if ( powerups & ( 1 << PW_BLUEFLAG ) ) { - if (ci->newAnims){ - CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.blueFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); - } - - // neutralflag - if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if (ci->newAnims) { - CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); - } - else { - CG_TrailItem( cent, cgs.media.neutralFlagModel ); - } - trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); - } - - // haste leaves smoke trails - if ( powerups & ( 1 << PW_HASTE ) ) { - CG_HasteTrail( cent ); - } -} - - -/* -=============== -CG_PlayerFloatSprite - -Float a sprite over the player's head -=============== -*/ -static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { - int rf; - refEntity_t ent; - - if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { - rf = RF_THIRD_PERSON; // only show in mirrors - } else { - rf = 0; - } - - memset( &ent, 0, sizeof( ent ) ); - VectorCopy( cent->lerpOrigin, ent.origin ); - ent.origin[2] += 48; - ent.reType = RT_SPRITE; - ent.customShader = shader; - ent.radius = 10; - ent.renderfx = rf; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 255; - trap_R_AddRefEntityToScene( &ent ); -} - - - -/* -=============== -CG_PlayerSprites - -Float sprites over the player's head -=============== -*/ -static void CG_PlayerSprites( centity_t *cent ) { - int team; - - if ( cent->currentState.eFlags & EF_CONNECTION ) { - CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); - return; - } - - if ( cent->currentState.eFlags & EF_TALK ) { - CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { - CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { - CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { - CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { - CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { - CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); - return; - } - - if ( cent->currentState.eFlags & EF_AWARD_CAP ) { - CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); - return; - } - - team = cgs.clientinfo[ cent->currentState.clientNum ].team; - if ( !(cent->currentState.eFlags & EF_DEAD) && - cg.snap->ps.persistant[PERS_TEAM] == team && - cgs.gametype >= GT_TEAM) { - if (cg_drawFriend.integer) { - CG_PlayerFloatSprite( cent, cgs.media.friendShader ); - } - return; - } -} - -/* -=============== -CG_PlayerShadow - -Returns the Z component of the surface being shadowed - - should it return a full plane instead of a Z? -=============== -*/ -#define SHADOW_DISTANCE 128 -static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { - vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; - trace_t trace; - float alpha; - - *shadowPlane = 0; - - if ( cg_shadows.integer == 0 ) { - return qfalse; - } - - // no shadows when invisible - if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { - return qfalse; - } - - // send a trace down from the player to the ground - VectorCopy( cent->lerpOrigin, end ); - end[2] -= SHADOW_DISTANCE; - - trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); - - // no shadow if too high - if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { - return qfalse; - } - - *shadowPlane = trace.endpos[2] + 1; - - if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows - return qtrue; - } - - // fade the shadow out with height - alpha = 1.0 - trace.fraction; - - // bk0101022 - hack / FPE - bogus planes? - //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) - - // add the mark as a temporary, so it goes directly to the renderer - // without taking a spot in the cg_marks array - CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, - cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); - - return qtrue; -} - - -/* -=============== -CG_PlayerSplash - -Draw a mark at the water surface -=============== -*/ -static void CG_PlayerSplash( centity_t *cent ) { - vec3_t start, end; - trace_t trace; - int contents; - polyVert_t verts[4]; - - if ( !cg_shadows.integer ) { - return; - } - - VectorCopy( cent->lerpOrigin, end ); - end[2] -= 24; - - // if the feet aren't in liquid, don't make a mark - // this won't handle moving water brushes, but they wouldn't draw right anyway... - contents = trap_CM_PointContents( end, 0 ); - if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { - return; - } - - VectorCopy( cent->lerpOrigin, start ); - start[2] += 32; - - // if the head isn't out of liquid, don't make a mark - contents = trap_CM_PointContents( start, 0 ); - if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - return; - } - - // trace down to find the surface - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); - - if ( trace.fraction == 1.0 ) { - return; - } - - // create a mark polygon - VectorCopy( trace.endpos, verts[0].xyz ); - verts[0].xyz[0] -= 32; - verts[0].xyz[1] -= 32; - verts[0].st[0] = 0; - verts[0].st[1] = 0; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[1].xyz ); - verts[1].xyz[0] -= 32; - verts[1].xyz[1] += 32; - verts[1].st[0] = 0; - verts[1].st[1] = 1; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[2].xyz ); - verts[2].xyz[0] += 32; - verts[2].xyz[1] += 32; - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorCopy( trace.endpos, verts[3].xyz ); - verts[3].xyz[0] += 32; - verts[3].xyz[1] -= 32; - verts[3].st[0] = 1; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); -} - - - -/* -=============== -CG_AddRefEntityWithPowerups - -Adds a piece with modifications or duplications for powerups -Also called by CG_Missile for quad rockets, but nobody can tell... -=============== -*/ -void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { - - if ( state->powerups & ( 1 << PW_INVIS ) ) { - ent->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( ent ); - } else { - /* - if ( state->eFlags & EF_KAMIKAZE ) { - if (team == TEAM_BLUE) - ent->customShader = cgs.media.blueKamikazeShader; - else - ent->customShader = cgs.media.redKamikazeShader; - trap_R_AddRefEntityToScene( ent ); - } - else {*/ - trap_R_AddRefEntityToScene( ent ); - //} - - if ( state->powerups & ( 1 << PW_QUAD ) ) - { - if (team == TEAM_RED) - ent->customShader = cgs.media.redQuadShader; - else - ent->customShader = cgs.media.quadShader; - trap_R_AddRefEntityToScene( ent ); - } - if ( state->powerups & ( 1 << PW_REGEN ) ) { - if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { - ent->customShader = cgs.media.regenShader; - trap_R_AddRefEntityToScene( ent ); - } - } - if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { - ent->customShader = cgs.media.battleSuitShader; - trap_R_AddRefEntityToScene( ent ); - } - } -} - -/* -================= -CG_LightVerts -================= -*/ -int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) -{ - int i, j; - float incoming; - vec3_t ambientLight; - vec3_t lightDir; - vec3_t directedLight; - - trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); - - for (i = 0; i < numVerts; i++) { - incoming = DotProduct (normal, lightDir); - if ( incoming <= 0 ) { - verts[i].modulate[0] = ambientLight[0]; - verts[i].modulate[1] = ambientLight[1]; - verts[i].modulate[2] = ambientLight[2]; - verts[i].modulate[3] = 255; - continue; - } - j = ( ambientLight[0] + incoming * directedLight[0] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[0] = j; - - j = ( ambientLight[1] + incoming * directedLight[1] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[1] = j; - - j = ( ambientLight[2] + incoming * directedLight[2] ); - if ( j > 255 ) { - j = 255; - } - verts[i].modulate[2] = j; - - verts[i].modulate[3] = 255; - } - return qtrue; -} - -/* -=============== -CG_Player -=============== -*/ -void CG_Player( centity_t *cent ) { - clientInfo_t *ci; - refEntity_t legs; - refEntity_t torso; - refEntity_t head; - int clientNum; - int renderfx; - qboolean shadow; - float shadowPlane; -#ifdef MISSIONPACK - refEntity_t skull; - refEntity_t powerup; - int t; - float c; - float angle; - vec3_t dir, angles; -#endif - - // the client number is stored in clientNum. It can't be derived - // from the entity number, because a single client may have - // multiple corpses on the level using the same clientinfo - clientNum = cent->currentState.clientNum; - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - CG_Error( "Bad clientNum on player entity"); - } - ci = &cgs.clientinfo[ clientNum ]; - - // it is possible to see corpses from disconnected players that may - // not have valid clientinfo - if ( !ci->infoValid ) { - return; - } - - // get the player model information - renderfx = 0; - if ( cent->currentState.number == cg.snap->ps.clientNum) { - if (!cg.renderingThirdPerson) { - renderfx = RF_THIRD_PERSON; // only draw in mirrors - } else { - if (cg_cameraMode.integer) { - return; - } - } - } - - - memset( &legs, 0, sizeof(legs) ); - memset( &torso, 0, sizeof(torso) ); - memset( &head, 0, sizeof(head) ); - - // get the rotation information - CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); - - // get the animation state (after rotation, to allow feet shuffle) - CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, - &torso.oldframe, &torso.frame, &torso.backlerp ); - - // add the talk baloon or disconnect icon - CG_PlayerSprites( cent ); - - // add the shadow - shadow = CG_PlayerShadow( cent, &shadowPlane ); - - // add a water splash if partially in and out of water - CG_PlayerSplash( cent ); - - if ( cg_shadows.integer == 3 && shadow ) { - renderfx |= RF_SHADOW_PLANE; - } - renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all -#ifdef MISSIONPACK - if( cgs.gametype == GT_HARVESTER ) { - CG_PlayerTokens( cent, renderfx ); - } -#endif - // - // add the legs - // - legs.hModel = ci->legsModel; - legs.customSkin = ci->legsSkin; - - VectorCopy( cent->lerpOrigin, legs.origin ); - - VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); - legs.shadowPlane = shadowPlane; - legs.renderfx = renderfx; - VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all - - CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); - - // if the model failed, allow the default nullmodel to be displayed - if (!legs.hModel) { - return; - } - - // - // add the torso - // - torso.hModel = ci->torsoModel; - if (!torso.hModel) { - return; - } - - torso.customSkin = ci->torsoSkin; - - VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); - - CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); - - torso.shadowPlane = shadowPlane; - torso.renderfx = renderfx; - - CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); - -#ifdef MISSIONPACK - if ( cent->currentState.eFlags & EF_KAMIKAZE ) { - - memset( &skull, 0, sizeof(skull) ); - - VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); - skull.shadowPlane = shadowPlane; - skull.renderfx = renderfx; - - if ( cent->currentState.eFlags & EF_DEAD ) { - // one skull bobbing above the dead body - angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; - dir[2] = 15 + sin(angle) * 8; - VectorAdd(torso.origin, dir, skull.origin); - - dir[2] = 0; - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - } - else { - // three skulls spinning around the player - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; - dir[0] = cos(angle) * 20; - dir[1] = sin(angle) * 20; - dir[2] = cos(angle) * 20; - VectorAdd(torso.origin, dir, skull.origin); - - angles[0] = sin(angle) * 30; - angles[1] = (angle * 180 / M_PI) + 90; - if (angles[1] > 360) - angles[1] -= 360; - angles[2] = 0; - AnglesToAxis( angles, skull.axis ); - - /* - dir[2] = 0; - VectorInverse(dir); - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - */ - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - // flip the trail because this skull is spinning in the other direction - VectorInverse(skull.axis[1]); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - - angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - dir[2] = cos(angle) * 20; - VectorAdd(torso.origin, dir, skull.origin); - - angles[0] = cos(angle - 0.5 * M_PI) * 30; - angles[1] = 360 - (angle * 180 / M_PI); - if (angles[1] > 360) - angles[1] -= 360; - angles[2] = 0; - AnglesToAxis( angles, skull.axis ); - - /* - dir[2] = 0; - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - */ - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - - angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; - if (angle > M_PI * 2) - angle -= (float)M_PI * 2; - dir[0] = sin(angle) * 20; - dir[1] = cos(angle) * 20; - dir[2] = 0; - VectorAdd(torso.origin, dir, skull.origin); - - VectorCopy(dir, skull.axis[1]); - VectorNormalize(skull.axis[1]); - VectorSet(skull.axis[2], 0, 0, 1); - CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); - - skull.hModel = cgs.media.kamikazeHeadModel; - trap_R_AddRefEntityToScene( &skull ); - skull.hModel = cgs.media.kamikazeHeadTrail; - trap_R_AddRefEntityToScene( &skull ); - } - } - - if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.guardPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.scoutPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.doublerPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.ammoRegenPowerupModel; - powerup.frame = 0; - powerup.oldframe = 0; - powerup.customSkin = 0; - trap_R_AddRefEntityToScene( &powerup ); - } - if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { - if ( !ci->invulnerabilityStartTime ) { - ci->invulnerabilityStartTime = cg.time; - } - ci->invulnerabilityStopTime = cg.time; - } - else { - ci->invulnerabilityStartTime = 0; - } - if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || - cg.time - ci->invulnerabilityStopTime < 250 ) { - - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.invulnerabilityPowerupModel; - powerup.customSkin = 0; - // always draw - powerup.renderfx &= ~RF_THIRD_PERSON; - VectorCopy(cent->lerpOrigin, powerup.origin); - - if ( cg.time - ci->invulnerabilityStartTime < 250 ) { - c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; - } - else if (cg.time - ci->invulnerabilityStopTime < 250 ) { - c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; - } - else { - c = 1; - } - VectorSet( powerup.axis[0], c, 0, 0 ); - VectorSet( powerup.axis[1], 0, c, 0 ); - VectorSet( powerup.axis[2], 0, 0, c ); - trap_R_AddRefEntityToScene( &powerup ); - } - - t = cg.time - ci->medkitUsageTime; - if ( ci->medkitUsageTime && t < 500 ) { - memcpy(&powerup, &torso, sizeof(torso)); - powerup.hModel = cgs.media.medkitUsageModel; - powerup.customSkin = 0; - // always draw - powerup.renderfx &= ~RF_THIRD_PERSON; - VectorClear(angles); - AnglesToAxis(angles, powerup.axis); - VectorCopy(cent->lerpOrigin, powerup.origin); - powerup.origin[2] += -24 + (float) t * 80 / 500; - if ( t > 400 ) { - c = (float) (t - 1000) * 0xff / 100; - powerup.shaderRGBA[0] = 0xff - c; - powerup.shaderRGBA[1] = 0xff - c; - powerup.shaderRGBA[2] = 0xff - c; - powerup.shaderRGBA[3] = 0xff - c; - } - else { - powerup.shaderRGBA[0] = 0xff; - powerup.shaderRGBA[1] = 0xff; - powerup.shaderRGBA[2] = 0xff; - powerup.shaderRGBA[3] = 0xff; - } - trap_R_AddRefEntityToScene( &powerup ); - } -#endif // MISSIONPACK - - // - // add the head - // - head.hModel = ci->headModel; - if (!head.hModel) { - return; - } - head.customSkin = ci->headSkin; - - VectorCopy( cent->lerpOrigin, head.lightingOrigin ); - - CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); - - head.shadowPlane = shadowPlane; - head.renderfx = renderfx; - - CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); - -#ifdef MISSIONPACK - CG_BreathPuffs(cent, &head); - - CG_DustTrail(cent); -#endif - - // - // add the gun / barrel / flash - // - CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); - - // add powerups floating behind the player - CG_PlayerPowerups( cent, &torso ); -} - - -//===================================================================== - -/* -=============== -CG_ResetPlayerEntity - -A player just came into view or teleported, so reset all animation info -=============== -*/ -void CG_ResetPlayerEntity( centity_t *cent ) { - cent->errorTime = -99999; // guarantee no error decay added - cent->extrapolated = qfalse; - - CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); - CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); - - BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); - BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); - - VectorCopy( cent->lerpOrigin, cent->rawOrigin ); - VectorCopy( cent->lerpAngles, cent->rawAngles ); - - memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); - cent->pe.legs.yawAngle = cent->rawAngles[YAW]; - cent->pe.legs.yawing = qfalse; - cent->pe.legs.pitchAngle = 0; - cent->pe.legs.pitching = qfalse; - - memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); - cent->pe.torso.yawAngle = cent->rawAngles[YAW]; - cent->pe.torso.yawing = qfalse; - cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; - cent->pe.torso.pitching = qfalse; - - if ( cg_debugPosition.integer ) { - CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_players.c -- handle the media and animation for player entities +#include "cg_local.h" + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25_1.wav", + "*pain50_1.wav", + "*pain75_1.wav", + "*pain100_1.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt.wav" +}; + + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName, qfalse ); + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { + if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { + return ci->sounds[i]; + } + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + animation_t *animations; + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + ci->fixedlegs = qfalse; + ci->fixedtorso = qfalse; + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + ci->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "boot" ) ) { + ci->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { + ci->footsteps = FOOTSTEP_FLESH; + } else if ( !Q_stricmp( token, "mech" ) ) { + ci->footsteps = FOOTSTEP_MECH; + } else if ( !Q_stricmp( token, "energy" ) ) { + ci->footsteps = FOOTSTEP_ENERGY; + } else { + CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + ci->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + ci->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + ci->gender = GENDER_NEUTER; + } else { + ci->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "fixedlegs" ) ) { + ci->fixedlegs = qtrue; + continue; + } else if ( !Q_stricmp( token, "fixedtorso" ) ) { + ci->fixedtorso = qtrue; + continue; + } + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + // read information for each frame + for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !*token ) { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { + animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; + animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; + animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; + animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; + animations[i].numFrames = animations[TORSO_GESTURE].numFrames; + animations[i].reversed = qfalse; + animations[i].flipflop = qfalse; + continue; + } + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR && i0) { + return qtrue; + } + return qfalse; +} + +/* +========================== +CG_FindClientModelFile +========================== +*/ +static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { + char *team, *charactersFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + team = "default"; + } + charactersFolder = ""; + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); + } + else { + // "models/players/characters/james/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); + } + else { + // "models/players/characters/james/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); + } + } + else { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); + } + else { + // "models/players/characters/james/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); + } + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( charactersFolder[0] ) { + break; + } + charactersFolder = "characters/"; + } + + return qfalse; +} + +/* +========================== +CG_FindClientHeadFile +========================== +*/ +static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { + char *team, *headsFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + team = "default"; + } + + if ( headModelName[0] == '*' ) { + headsFolder = "heads/"; + headModelName++; + } + else { + headsFolder = ""; + } + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); + } + } + else { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); + } + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( headsFolder[0] ) { + break; + } + headsFolder = "heads/"; + } + + return qfalse; +} + +/* +========================== +CG_RegisterClientSkin +========================== +*/ +static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { + char filename[MAX_QPATH]; + + /* + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + } + + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + } + */ + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { + ci->legsSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { + ci->torsoSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { + ci->headSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->headSkin) { + Com_Printf( "Head skin load failure: %s\n", filename ); + } + + // if any skins failed to load + if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { + return qfalse; + } + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { + char filename[MAX_QPATH*2]; + const char *headName; + char newTeamName[MAX_QPATH*2]; + + if ( headModelName[0] == '\0' ) { + headName = modelName; + } + else { + headName = headModelName; + } + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + if( headName[0] == '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); + } + else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); + } + ci->headModel = trap_R_RegisterModel( filename ); + // if the head model could not be found and we didn't load from the heads folder try to load from there + if ( !ci->headModel && headName[0] != '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); + ci->headModel = trap_R_RegisterModel( filename ); + } + if ( !ci->headModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // if any skins failed to load, return failure + if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { + if ( teamName && *teamName) { + Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); + if( ci->team == TEAM_BLUE ) { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); + } + else { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); + } + if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { + Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); + return qfalse; + } + } else { + Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); + return qfalse; + } + } + + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + + if ( !ci->modelIcon ) { + return qfalse; + } + + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) { + const char *dir, *fallback; + int i, modelloaded; + const char *s; + int clientNum; + char teamname[MAX_QPATH]; + + teamname[0] = 0; +#ifdef MISSIONPACK + if( cgs.gametype >= GT_TEAM) { + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); + } + } + if( teamname[0] ) { + strcat( teamname, "/" ); + } +#endif + modelloaded = qtrue; + if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + } + + // fall back to default team name + if( cgs.gametype >= GT_TEAM) { + // keep skin name + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); + } + if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { + CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); + } + } else { + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + modelloaded = qfalse; + } + + ci->newAnims = qfalse; + if ( ci->torsoModel ) { + orientation_t tag; + // if the torso model has the "tag_flag" + if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { + ci->newAnims = qtrue; + } + } + + // sounds + dir = ci->modelName; + fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + ci->sounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (modelloaded) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); + } + if ( !ci->sounds[i] ) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); + } + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { + VectorCopy( from->headOffset, to->headOffset ); + to->footsteps = from->footsteps; + to->gender = from->gender; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->modelIcon = from->modelIcon; + + to->newAnims = from->newAnims; + + memcpy( to->animations, from->animations, sizeof( to->animations ) ); + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( match->deferred ) { + continue; + } + if ( !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) + && !Q_stricmp( ci->headModelName, match->headModelName ) + && !Q_stricmp( ci->headSkinName, match->headSkinName ) + && !Q_stricmp( ci->blueTeam, match->blueTeam ) + && !Q_stricmp( ci->redTeam, match->redTeam ) + && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + // if someone else is already the same models and skins we + // can just load the client info + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + Q_stricmp( ci->modelName, match->modelName ) || +// Q_stricmp( ci->headModelName, match->headModelName ) || +// Q_stricmp( ci->headSkinName, match->headSkinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + // just load the real info cause it uses the same models and skins + CG_LoadClientInfo( ci ); + return; + } + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + + CG_LoadClientInfo( ci ); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + + ci = &cgs.clientinfo[clientNum]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // colors + v = Info_ValueForKey( configstring, "c1" ); + CG_ColorFromString( v, newInfo.color1 ); + + v = Info_ValueForKey( configstring, "c2" ); + CG_ColorFromString( v, newInfo.color2 ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // team task + v = Info_ValueForKey( configstring, "tt" ); + newInfo.teamTask = atoi(v); + + // team leader + v = Info_ValueForKey( configstring, "tl" ); + newInfo.teamLeader = atoi(v); + + v = Info_ValueForKey( configstring, "g_redteam" ); + Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); + + v = Info_ValueForKey( configstring, "g_blueteam" ); + Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + } + + // head model + v = Info_ValueForKey( configstring, "hmodel" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); + Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + } + } + } else { + Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + + slash = strchr( newInfo.headModelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + // truncate modelName + *slash = 0; + } + } + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { + qboolean forceDefer; + + forceDefer = trap_MemoryRemaining() < 4000000; + + // if we are defering loads, just have it pick the first valid + if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + // if we are low on memory, leave them with this model + if ( forceDefer ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } else { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + + + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + CG_LoadClientInfo( ci ); +// break; + } + } +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer ) { + CG_Printf( "Anim: %i\n", newAnimation ); + } +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f, numFrames; + animation_t *anim; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= speedScale; // adjust for haste, etc + + numFrames = anim->numFrames; + if (anim->flipflop) { + numFrames *= 2; + } + if ( f >= numFrames ) { + f -= numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + if ( anim->reversed ) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + } + else if (anim->flipflop && f>=anim->numFrames) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); + } + else { + lf->frame = anim->firstFrame + f; + } + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n"); + } + } + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + float speedScale; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { + speedScale = 1.5; + } else { + speedScale = 1; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { + CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); + } else { + CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) { + float swing; + float move; + float scale; + + if ( !*swinging ) { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + } + } + + if ( !*swinging ) { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) { + scale = 0.5; + } else if ( scale < swingTolerance ) { + scale = 1.0; + } else { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = cg.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = cg.frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) { + *angle = AngleMod( destination - (clampTolerance - 1) ); + } else if ( swing < -clampTolerance ) { + *angle = AngleMod( destination + (clampTolerance - 1) ); + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { + int t; + float f; + + t = cg.time - cent->pe.painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + vec3_t velocity; + float speed; + int dir, clientNum; + clientInfo_t *ci; + + VectorCopy( cent->lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { + // if not standing still, always point all in the same direction + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + } + + // adjust legs for movement dir + if ( cent->currentState.eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + dir = cent->currentState.angles2[YAW]; + if ( dir < 0 || dir > 7 ) { + CG_Error( "Bad player movement angle" ); + } + } + legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; + torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; + + // torso + CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + + torsoAngles[YAW] = cent->pe.torso.yawAngle; + legsAngles[YAW] = cent->pe.legs.yawAngle; + + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75f; + } else { + dest = headAngles[PITCH] * 0.75f; + } + CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedtorso ) { + torsoAngles[PITCH] = 0.0f; + } + } + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05f; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedlegs ) { + legsAngles[YAW] = torsoAngles[YAW]; + legsAngles[PITCH] = 0.0f; + legsAngles[ROLL] = 0.0f; + } + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +//========================================================================== + +/* +=============== +CG_HasteTrail +=============== +*/ +static void CG_HasteTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + int anim; + + if ( cent->trailTime > cg.time ) { + return; + } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_RUN && anim != LEGS_BACK ) { + return; + } + + cent->trailTime += 100; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= 16; + + smoke = CG_SmokePuff( origin, vec3_origin, + 8, + 1, 1, 1, 1, + 500, + cg.time, + 0, + 0, + cgs.media.hastePuffShader ); + + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} + +#ifdef MISSIONPACK +/* +=============== +CG_BreathPuffs +=============== +*/ +static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { + clientInfo_t *ci; + vec3_t up, origin; + int contents; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + + if (!cg_enableBreath.integer) { + return; + } + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { + return; + } + if ( cent->currentState.eFlags & EF_DEAD ) { + return; + } + contents = trap_CM_PointContents( head->origin, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + if ( ci->breathPuffTime > cg.time ) { + return; + } + + VectorSet( up, 0, 0, 8 ); + VectorMA(head->origin, 8, head->axis[0], origin); + VectorMA(origin, -4, head->axis[2], origin); + CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + ci->breathPuffTime = cg.time + 2000; +} + +/* +=============== +CG_DustTrail +=============== +*/ +static void CG_DustTrail( centity_t *cent ) { + int anim; + localEntity_t *dust; + vec3_t end, vel; + trace_t tr; + + if (!cg_enableDust.integer) + return; + + if ( cent->dustTrailTime > cg.time ) { + return; + } + + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { + return; + } + + cent->dustTrailTime += 40; + if ( cent->dustTrailTime < cg.time ) { + cent->dustTrailTime = cg.time; + } + + VectorCopy(cent->currentState.pos.trBase, end); + end[2] -= 64; + CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); + + if ( !(tr.surfaceFlags & SURF_DUST) ) + return; + + VectorCopy( cent->currentState.pos.trBase, end ); + end[2] -= 16; + + VectorSet(vel, 0, 0, -30); + dust = CG_SmokePuff( end, vel, + 24, + .8f, .8f, 0.7f, 0.33f, + 500, + cg.time, + 0, + 0, + cgs.media.dustPuffShader ); +} + +#endif + +/* +=============== +CG_TrailItem +=============== +*/ +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, axis ); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); + ent.origin[2] += 16; + angles[YAW] += 90; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerFlag +=============== +*/ +static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { + clientInfo_t *ci; + refEntity_t pole; + refEntity_t flag; + vec3_t angles, dir; + int legsAnim, flagAnim, updateangles; + float angle, d; + + // show the flag pole model + memset( &pole, 0, sizeof(pole) ); + pole.hModel = cgs.media.flagPoleModel; + VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); + pole.shadowPlane = torso->shadowPlane; + pole.renderfx = torso->renderfx; + CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); + trap_R_AddRefEntityToScene( &pole ); + + // show the flag model + memset( &flag, 0, sizeof(flag) ); + flag.hModel = cgs.media.flagFlapModel; + flag.customSkin = hSkin; + VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); + flag.shadowPlane = torso->shadowPlane; + flag.renderfx = torso->renderfx; + + VectorClear(angles); + + updateangles = qfalse; + legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { + flagAnim = FLAG_STAND; + } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { + flagAnim = FLAG_STAND; + updateangles = qtrue; + } else { + flagAnim = FLAG_RUN; + updateangles = qtrue; + } + + if ( updateangles ) { + + VectorCopy( cent->currentState.pos.trDelta, dir ); + // add gravity + dir[2] += 100; + VectorNormalize( dir ); + d = DotProduct(pole.axis[2], dir); + // if there is anough movement orthogonal to the flag pole + if (fabs(d) < 0.9) { + // + d = DotProduct(pole.axis[0], dir); + if (d > 1.0f) { + d = 1.0f; + } + else if (d < -1.0f) { + d = -1.0f; + } + angle = acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angles[YAW] = 360 - angle * 180 / M_PI; + } + else { + angles[YAW] = angle * 180 / M_PI; + } + if (angles[YAW] < 0) + angles[YAW] += 360; + if (angles[YAW] > 360) + angles[YAW] -= 360; + + //vectoangles( cent->currentState.pos.trDelta, tmpangles ); + //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; + // change the yaw angle + CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); + } + + /* + d = DotProduct(pole.axis[2], dir); + angle = Q_acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angle = 360 - angle * 180 / M_PI; + } + else { + angle = angle * 180 / M_PI; + } + if (angle > 340 && angle < 20) { + flagAnim = FLAG_RUNUP; + } + if (angle > 160 && angle < 200) { + flagAnim = FLAG_RUNDOWN; + } + */ + } + + // set the yaw angle + angles[YAW] = cent->pe.flag.yawAngle; + // lerp the flag animation frames + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); + flag.oldframe = cent->pe.flag.oldFrame; + flag.frame = cent->pe.flag.frame; + flag.backlerp = cent->pe.flag.backlerp; + + AnglesToAxis( angles, flag.axis ); + CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); + + trap_R_AddRefEntityToScene( &flag ); +} + + +#ifdef MISSIONPACK // bk001204 +/* +=============== +CG_PlayerTokens +=============== +*/ +static void CG_PlayerTokens( centity_t *cent, int renderfx ) { + int tokens, i, j; + float angle; + refEntity_t ent; + vec3_t dir, origin; + skulltrail_t *trail; + trail = &cg.skulltrails[cent->currentState.number]; + tokens = cent->currentState.generic1; + if ( !tokens ) { + trail->numpositions = 0; + return; + } + + if ( tokens > MAX_SKULLTRAIL ) { + tokens = MAX_SKULLTRAIL; + } + + // add skulls if there are more than last time + for (i = 0; i < tokens - trail->numpositions; i++) { + for (j = trail->numpositions; j > 0; j--) { + VectorCopy(trail->positions[j-1], trail->positions[j]); + } + VectorCopy(cent->lerpOrigin, trail->positions[0]); + } + trail->numpositions = tokens; + + // move all the skulls along the trail + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(trail->positions[i], origin, dir); + if (VectorNormalize(dir) > 30) { + VectorMA(origin, 30, dir, trail->positions[i]); + } + VectorCopy(trail->positions[i], origin); + } + + memset( &ent, 0, sizeof( ent ) ); + if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { + ent.hModel = cgs.media.redCubeModel; + } else { + ent.hModel = cgs.media.blueCubeModel; + } + ent.renderfx = renderfx; + + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(origin, trail->positions[i], ent.axis[0]); + ent.axis[0][2] = 0; + VectorNormalize(ent.axis[0]); + VectorSet(ent.axis[2], 0, 0, 1); + CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + + VectorCopy(trail->positions[i], ent.origin); + angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; + ent.origin[2] += sin(angle) * 10; + trap_R_AddRefEntityToScene( &ent ); + VectorCopy(trail->positions[i], origin); + } +} +#endif + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { + int powerups; + clientInfo_t *ci; + + powerups = cent->currentState.powerups; + if ( !powerups ) { + return; + } + + // quad gives a dlight + if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); + } + + // flight plays a looped sound + if ( powerups & ( 1 << PW_FLIGHT ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); + } + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.redFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + if (ci->newAnims){ + CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.blueFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); + } + + // neutralflag + if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.neutralFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); + } + + // haste leaves smoke trails + if ( powerups & ( 1 << PW_HASTE ) ) { + CG_HasteTrail( cent ); + } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { + int team; + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + + if ( cent->currentState.eFlags & EF_TALK ) { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { + CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { + CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { + CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { + CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_CAP ) { + CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); + return; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametype >= GT_TEAM) { + if (cg_drawFriend.integer) { + CG_PlayerFloatSprite( cent, cgs.media.friendShader ); + } + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; + trace_t trace; + float alpha; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when invisible + if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { + return qfalse; + } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { + return qfalse; + } + + *shadowPlane = trace.endpos[2] + 1; + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // bk0101022 - hack / FPE - bogus planes? + //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + + + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { + + if ( state->powerups & ( 1 << PW_INVIS ) ) { + ent->customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( ent ); + } else { + /* + if ( state->eFlags & EF_KAMIKAZE ) { + if (team == TEAM_BLUE) + ent->customShader = cgs.media.blueKamikazeShader; + else + ent->customShader = cgs.media.redKamikazeShader; + trap_R_AddRefEntityToScene( ent ); + } + else {*/ + trap_R_AddRefEntityToScene( ent ); + //} + + if ( state->powerups & ( 1 << PW_QUAD ) ) + { + if (team == TEAM_RED) + ent->customShader = cgs.media.redQuadShader; + else + ent->customShader = cgs.media.quadShader; + trap_R_AddRefEntityToScene( ent ); + } + if ( state->powerups & ( 1 << PW_REGEN ) ) { + if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { + ent->customShader = cgs.media.regenShader; + trap_R_AddRefEntityToScene( ent ); + } + } + if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { + ent->customShader = cgs.media.battleSuitShader; + trap_R_AddRefEntityToScene( ent ); + } + } +} + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ + int i, j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + + trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); + + for (i = 0; i < numVerts; i++) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + verts[i].modulate[0] = ambientLight[0]; + verts[i].modulate[1] = ambientLight[1]; + verts[i].modulate[2] = ambientLight[2]; + verts[i].modulate[3] = 255; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[0] = j; + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[1] = j; + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[2] = j; + + verts[i].modulate[3] = 255; + } + return qtrue; +} + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int clientNum; + int renderfx; + qboolean shadow; + float shadowPlane; +#ifdef MISSIONPACK + refEntity_t skull; + refEntity_t powerup; + int t; + float c; + float angle; + vec3_t dir, angles; +#endif + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + ci = &cgs.clientinfo[ clientNum ]; + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum) { + if (!cg.renderingThirdPerson) { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } else { + if (cg_cameraMode.integer) { + return; + } + } + } + + + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + // get the rotation information + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + if ( cg_shadows.integer == 3 && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + CG_PlayerTokens( cent, renderfx ); + } +#endif + // + // add the legs + // + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); + + // if the model failed, allow the default nullmodel to be displayed + if (!legs.hModel) { + return; + } + + // + // add the torso + // + torso.hModel = ci->torsoModel; + if (!torso.hModel) { + return; + } + + torso.customSkin = ci->torsoSkin; + + VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); + +#ifdef MISSIONPACK + if ( cent->currentState.eFlags & EF_KAMIKAZE ) { + + memset( &skull, 0, sizeof(skull) ); + + VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); + skull.shadowPlane = shadowPlane; + skull.renderfx = renderfx; + + if ( cent->currentState.eFlags & EF_DEAD ) { + // one skull bobbing above the dead body + angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[2] = 15 + sin(angle) * 8; + VectorAdd(torso.origin, dir, skull.origin); + + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + else { + // three skulls spinning around the player + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorInverse(dir); + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + // flip the trail because this skull is spinning in the other direction + VectorInverse(skull.axis[1]); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = cos(angle - 0.5 * M_PI) * 30; + angles[1] = 360 - (angle * 180 / M_PI); + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = 0; + VectorAdd(torso.origin, dir, skull.origin); + + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + } + + if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.guardPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.scoutPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.doublerPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.ammoRegenPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { + if ( !ci->invulnerabilityStartTime ) { + ci->invulnerabilityStartTime = cg.time; + } + ci->invulnerabilityStopTime = cg.time; + } + else { + ci->invulnerabilityStartTime = 0; + } + if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || + cg.time - ci->invulnerabilityStopTime < 250 ) { + + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.invulnerabilityPowerupModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorCopy(cent->lerpOrigin, powerup.origin); + + if ( cg.time - ci->invulnerabilityStartTime < 250 ) { + c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; + } + else if (cg.time - ci->invulnerabilityStopTime < 250 ) { + c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; + } + else { + c = 1; + } + VectorSet( powerup.axis[0], c, 0, 0 ); + VectorSet( powerup.axis[1], 0, c, 0 ); + VectorSet( powerup.axis[2], 0, 0, c ); + trap_R_AddRefEntityToScene( &powerup ); + } + + t = cg.time - ci->medkitUsageTime; + if ( ci->medkitUsageTime && t < 500 ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.medkitUsageModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorClear(angles); + AnglesToAxis(angles, powerup.axis); + VectorCopy(cent->lerpOrigin, powerup.origin); + powerup.origin[2] += -24 + (float) t * 80 / 500; + if ( t > 400 ) { + c = (float) (t - 1000) * 0xff / 100; + powerup.shaderRGBA[0] = 0xff - c; + powerup.shaderRGBA[1] = 0xff - c; + powerup.shaderRGBA[2] = 0xff - c; + powerup.shaderRGBA[3] = 0xff - c; + } + else { + powerup.shaderRGBA[0] = 0xff; + powerup.shaderRGBA[1] = 0xff; + powerup.shaderRGBA[2] = 0xff; + powerup.shaderRGBA[3] = 0xff; + } + trap_R_AddRefEntityToScene( &powerup ); + } +#endif // MISSIONPACK + + // + // add the head + // + head.hModel = ci->headModel; + if (!head.hModel) { + return; + } + head.customSkin = ci->headSkin; + + VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); + +#ifdef MISSIONPACK + CG_BreathPuffs(cent, &head); + + CG_DustTrail(cent); +#endif + + // + // add the gun / barrel / flash + // + CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); + + // add powerups floating behind the player + CG_PlayerPowerups( cent, &torso ); +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) { + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c index 1e0d61a..16f3608 100755 --- a/code/cgame/cg_playerstate.c +++ b/code/cgame/cg_playerstate.c @@ -1,526 +1,526 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_playerstate.c -- this file acts on changes in a new playerState_t -// With normal play, this will be done after local prediction, but when -// following another player or playing back a demo, it will be checked -// when the snapshot transitions like all the other entities - -#include "cg_local.h" - -/* -============== -CG_CheckAmmo - -If the ammo has gone low enough to generate the warning, play a sound -============== -*/ -void CG_CheckAmmo( void ) { - int i; - int total; - int previous; - int weapons; - - // see about how many seconds of ammo we have remaining - weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; - total = 0; - for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { - if ( ! ( weapons & ( 1 << i ) ) ) { - continue; - } - switch ( i ) { - case WP_ROCKET_LAUNCHER: - case WP_GRENADE_LAUNCHER: - case WP_RAILGUN: - case WP_SHOTGUN: -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: -#endif - total += cg.snap->ps.ammo[i] * 1000; - break; - default: - total += cg.snap->ps.ammo[i] * 200; - break; - } - if ( total >= 5000 ) { - cg.lowAmmoWarning = 0; - return; - } - } - - previous = cg.lowAmmoWarning; - - if ( total == 0 ) { - cg.lowAmmoWarning = 2; - } else { - cg.lowAmmoWarning = 1; - } - - // play a sound on transitions - if ( cg.lowAmmoWarning != previous ) { - trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); - } -} - -/* -============== -CG_DamageFeedback -============== -*/ -void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { - float left, front, up; - float kick; - int health; - float scale; - vec3_t dir; - vec3_t angles; - float dist; - float yaw, pitch; - - // show the attacking player's head and name in corner - cg.attackerTime = cg.time; - - // the lower on health you are, the greater the view kick will be - health = cg.snap->ps.stats[STAT_HEALTH]; - if ( health < 40 ) { - scale = 1; - } else { - scale = 40.0 / health; - } - kick = damage * scale; - - if (kick < 5) - kick = 5; - if (kick > 10) - kick = 10; - - // if yaw and pitch are both 255, make the damage always centered (falling, etc) - if ( yawByte == 255 && pitchByte == 255 ) { - cg.damageX = 0; - cg.damageY = 0; - cg.v_dmg_roll = 0; - cg.v_dmg_pitch = -kick; - } else { - // positional - pitch = pitchByte / 255.0 * 360; - yaw = yawByte / 255.0 * 360; - - angles[PITCH] = pitch; - angles[YAW] = yaw; - angles[ROLL] = 0; - - AngleVectors( angles, dir, NULL, NULL ); - VectorSubtract( vec3_origin, dir, dir ); - - front = DotProduct (dir, cg.refdef.viewaxis[0] ); - left = DotProduct (dir, cg.refdef.viewaxis[1] ); - up = DotProduct (dir, cg.refdef.viewaxis[2] ); - - dir[0] = front; - dir[1] = left; - dir[2] = 0; - dist = VectorLength( dir ); - if ( dist < 0.1 ) { - dist = 0.1f; - } - - cg.v_dmg_roll = kick * left; - - cg.v_dmg_pitch = -kick * front; - - if ( front <= 0.1 ) { - front = 0.1f; - } - cg.damageX = -left / front; - cg.damageY = up / dist; - } - - // clamp the position - if ( cg.damageX > 1.0 ) { - cg.damageX = 1.0; - } - if ( cg.damageX < - 1.0 ) { - cg.damageX = -1.0; - } - - if ( cg.damageY > 1.0 ) { - cg.damageY = 1.0; - } - if ( cg.damageY < - 1.0 ) { - cg.damageY = -1.0; - } - - // don't let the screen flashes vary as much - if ( kick > 10 ) { - kick = 10; - } - cg.damageValue = kick; - cg.v_dmg_time = cg.time + DAMAGE_TIME; - cg.damageTime = cg.snap->serverTime; -} - - - - -/* -================ -CG_Respawn - -A respawn happened this snapshot -================ -*/ -void CG_Respawn( void ) { - // no error decay on player movement - cg.thisFrameTeleport = qtrue; - - // display weapons available - cg.weaponSelectTime = cg.time; - - // select the weapon the server says we are using - cg.weaponSelect = cg.snap->ps.weapon; -} - -extern char *eventnames[]; - -/* -============== -CG_CheckPlayerstateEvents -============== -*/ -void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { - int i; - int event; - centity_t *cent; - - if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { - cent = &cg_entities[ ps->clientNum ]; - cent->currentState.event = ps->externalEvent; - cent->currentState.eventParm = ps->externalEventParm; - CG_EntityEvent( cent, cent->lerpOrigin ); - } - - cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; - // go through the predictable events buffer - for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { - // if we have a new predictable event - if ( i >= ops->eventSequence - // or the server told us to play another event instead of a predicted event we already issued - // or something the server told us changed our prediction causing a different event - || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { - - event = ps->events[ i & (MAX_PS_EVENTS-1) ]; - cent->currentState.event = event; - cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); - - cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; - - cg.eventSequence++; - } - } -} - -/* -================== -CG_CheckChangedPredictableEvents -================== -*/ -void CG_CheckChangedPredictableEvents( playerState_t *ps ) { - int i; - int event; - centity_t *cent; - - cent = &cg.predictedPlayerEntity; - for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { - // - if (i >= cg.eventSequence) { - continue; - } - // if this event is not further back in than the maximum predictable events we remember - if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { - // if the new playerstate event is different from a previously predicted one - if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { - - event = ps->events[ i & (MAX_PS_EVENTS-1) ]; - cent->currentState.event = event; - cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; - CG_EntityEvent( cent, cent->lerpOrigin ); - - cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; - - if ( cg_showmiss.integer ) { - CG_Printf("WARNING: changed predicted event\n"); - } - } - } - } -} - -/* -================== -pushReward -================== -*/ -static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { - if (cg.rewardStack < (MAX_REWARDSTACK-1)) { - cg.rewardStack++; - cg.rewardSound[cg.rewardStack] = sfx; - cg.rewardShader[cg.rewardStack] = shader; - cg.rewardCount[cg.rewardStack] = rewardCount; - } -} - -/* -================== -CG_CheckLocalSounds -================== -*/ -void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { - int highScore, health, armor, reward; - sfxHandle_t sfx; - - // don't play the sounds if the player just changed teams - if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { - return; - } - - // hit changes - if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { - armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; - health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; -#ifdef MISSIONPACK - if (armor > 50 ) { - trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); - } else if (armor || health > 100) { - trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); - } else { - trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); - } -#else - trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); -#endif - } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { - trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); - } - - // health changes of more than -1 should make pain sounds - if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { - if ( ps->stats[STAT_HEALTH] > 0 ) { - CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); - } - } - - - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - // reward sounds - reward = qfalse; - if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { - pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); - reward = qtrue; - //Com_Printf("capture\n"); - } - if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { -#ifdef MISSIONPACK - if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { - sfx = cgs.media.firstImpressiveSound; - } else { - sfx = cgs.media.impressiveSound; - } -#else - sfx = cgs.media.impressiveSound; -#endif - pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); - reward = qtrue; - //Com_Printf("impressive\n"); - } - if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { -#ifdef MISSIONPACK - if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { - sfx = cgs.media.firstExcellentSound; - } else { - sfx = cgs.media.excellentSound; - } -#else - sfx = cgs.media.excellentSound; -#endif - pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); - reward = qtrue; - //Com_Printf("excellent\n"); - } - if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { -#ifdef MISSIONPACK - if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { - sfx = cgs.media.firstHumiliationSound; - } else { - sfx = cgs.media.humiliationSound; - } -#else - sfx = cgs.media.humiliationSound; -#endif - pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); - reward = qtrue; - //Com_Printf("guantlet frag\n"); - } - if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { - pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); - reward = qtrue; - //Com_Printf("defend\n"); - } - if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { - pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); - reward = qtrue; - //Com_Printf("assist\n"); - } - // if any of the player event bits changed - if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { - if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { - trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); - } - else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { - trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); - } - else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != - (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { - trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); - } - reward = qtrue; - } - - // check for flag pickup - if ( cgs.gametype >= GT_TEAM ) { - if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || - (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || - (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) - { - trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); - } - } - - // lead changes - if (!reward) { - // - if ( !cg.warmup ) { - // never play lead changes during warmup - if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { - if ( cgs.gametype < GT_TEAM) { - if ( ps->persistant[PERS_RANK] == 0 ) { - CG_AddBufferedSound(cgs.media.takenLeadSound); - } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { - CG_AddBufferedSound(cgs.media.tiedLeadSound); - } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { - CG_AddBufferedSound(cgs.media.lostLeadSound); - } - } - } - } - } - - // timelimit warnings - if ( cgs.timelimit > 0 ) { - int msec; - - msec = cg.time - cgs.levelStartTime; - if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { - cg.timelimitWarnings |= 1 | 2 | 4; - trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); - } - else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { - cg.timelimitWarnings |= 1 | 2; - trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); - } - else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { - cg.timelimitWarnings |= 1; - trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); - } - } - - // fraglimit warnings - if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { - highScore = cgs.scores1; - if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { - cg.fraglimitWarnings |= 1 | 2 | 4; - CG_AddBufferedSound(cgs.media.oneFragSound); - } - else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { - cg.fraglimitWarnings |= 1 | 2; - CG_AddBufferedSound(cgs.media.twoFragSound); - } - else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { - cg.fraglimitWarnings |= 1; - CG_AddBufferedSound(cgs.media.threeFragSound); - } - } -} - -/* -=============== -CG_TransitionPlayerState - -=============== -*/ -void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { - // check for changing follow mode - if ( ps->clientNum != ops->clientNum ) { - cg.thisFrameTeleport = qtrue; - // make sure we don't get any unwanted transition effects - *ops = *ps; - } - - // damage events (player is getting wounded) - if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { - CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); - } - - // respawning - if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { - CG_Respawn(); - } - - if ( cg.mapRestart ) { - CG_Respawn(); - cg.mapRestart = qfalse; - } - - if ( cg.snap->ps.pm_type != PM_INTERMISSION - && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - CG_CheckLocalSounds( ps, ops ); - } - - // check for going low on ammo - CG_CheckAmmo(); - - // run events - CG_CheckPlayerstateEvents( ps, ops ); - - // smooth the ducking viewheight change - if ( ps->viewheight != ops->viewheight ) { - cg.duckChange = ps->viewheight - ops->viewheight; - cg.duckTime = cg.time; - } -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { + int i; + int total; + int previous; + int weapons; + + // see about how many seconds of ammo we have remaining + weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; + total = 0; + for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) { + case WP_ROCKET_LAUNCHER: + case WP_GRENADE_LAUNCHER: + case WP_RAILGUN: + case WP_SHOTGUN: +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: +#endif + total += cg.snap->ps.ammo[i] * 1000; + break; + default: + total += cg.snap->ps.ammo[i] * 200; + break; + } + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + previous = cg.lowAmmoWarning; + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } + + // play a sound on transitions + if ( cg.lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + kick = damage * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + AngleVectors( angles, dir, NULL, NULL ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg.refdef.viewaxis[0] ); + left = DotProduct (dir, cg.refdef.viewaxis[1] ); + up = DotProduct (dir, cg.refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1f; + } + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1f; + } + cg.damageX = -left / front; + cg.damageY = up / dist; + } + + // clamp the position + if ( cg.damageX > 1.0 ) { + cg.damageX = 1.0; + } + if ( cg.damageX < - 1.0 ) { + cg.damageX = -1.0; + } + + if ( cg.damageY > 1.0 ) { + cg.damageY = 1.0; + } + if ( cg.damageY < - 1.0 ) { + cg.damageY = -1.0; + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) { + kick = 10; + } + cg.damageValue = kick; + cg.v_dmg_time = cg.time + DAMAGE_TIME; + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) { + int i; + int event; + centity_t *cent; + + cent = &cg.predictedPlayerEntity; + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // + if (i >= cg.eventSequence) { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + if ( cg_showmiss.integer ) { + CG_Printf("WARNING: changed predicted event\n"); + } + } + } + } +} + +/* +================== +pushReward +================== +*/ +static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { + if (cg.rewardStack < (MAX_REWARDSTACK-1)) { + cg.rewardStack++; + cg.rewardSound[cg.rewardStack] = sfx; + cg.rewardShader[cg.rewardStack] = shader; + cg.rewardCount[cg.rewardStack] = rewardCount; + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { + int highScore, health, armor, reward; + sfxHandle_t sfx; + + // don't play the sounds if the player just changed teams + if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { + return; + } + + // hit changes + if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { + armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; + health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; +#ifdef MISSIONPACK + if (armor > 50 ) { + trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); + } else if (armor || health > 100) { + trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); + } else { + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } +#else + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); +#endif + } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { + if ( ps->stats[STAT_HEALTH] > 0 ) { + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); + } + } + + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + // reward sounds + reward = qfalse; + if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { + pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); + reward = qtrue; + //Com_Printf("capture\n"); + } + if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { + sfx = cgs.media.firstImpressiveSound; + } else { + sfx = cgs.media.impressiveSound; + } +#else + sfx = cgs.media.impressiveSound; +#endif + pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); + reward = qtrue; + //Com_Printf("impressive\n"); + } + if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { + sfx = cgs.media.firstExcellentSound; + } else { + sfx = cgs.media.excellentSound; + } +#else + sfx = cgs.media.excellentSound; +#endif + pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); + reward = qtrue; + //Com_Printf("excellent\n"); + } + if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { +#ifdef MISSIONPACK + if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { + sfx = cgs.media.firstHumiliationSound; + } else { + sfx = cgs.media.humiliationSound; + } +#else + sfx = cgs.media.humiliationSound; +#endif + pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); + reward = qtrue; + //Com_Printf("guantlet frag\n"); + } + if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { + pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); + reward = qtrue; + //Com_Printf("defend\n"); + } + if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { + pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); + reward = qtrue; + //Com_Printf("assist\n"); + } + // if any of the player event bits changed + if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { + if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { + trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { + trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { + trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); + } + reward = qtrue; + } + + // check for flag pickup + if ( cgs.gametype >= GT_TEAM ) { + if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || + (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || + (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) + { + trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); + } + } + + // lead changes + if (!reward) { + // + if ( !cg.warmup ) { + // never play lead changes during warmup + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype < GT_TEAM) { + if ( ps->persistant[PERS_RANK] == 0 ) { + CG_AddBufferedSound(cgs.media.takenLeadSound); + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + CG_AddBufferedSound(cgs.media.tiedLeadSound); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + CG_AddBufferedSound(cgs.media.lostLeadSound); + } + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 ) { + int msec; + + msec = cg.time - cgs.levelStartTime; + if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg.timelimitWarnings |= 1 | 2 | 4; + trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } + else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { + cg.timelimitWarnings |= 1 | 2; + trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); + } + else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { + cg.timelimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { + highScore = cgs.scores1; + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 1 | 2 | 4; + CG_AddBufferedSound(cgs.media.oneFragSound); + } + else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 1 | 2; + CG_AddBufferedSound(cgs.media.twoFragSound); + } + else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + CG_AddBufferedSound(cgs.media.threeFragSound); + } + } +} + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + + if ( cg.mapRestart ) { + CG_Respawn(); + cg.mapRestart = qfalse; + } + + if ( cg.snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } +} + diff --git a/code/cgame/cg_predict.c b/code/cgame/cg_predict.c index fec2cfb..893b439 100755 --- a/code/cgame/cg_predict.c +++ b/code/cgame/cg_predict.c @@ -1,628 +1,628 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_predict.c -- this file generates cg.predictedPlayerState by either -// interpolating between snapshots from the server or locally predicting -// ahead the client's movement. -// It also handles local physics interaction, like fragments bouncing off walls - -#include "cg_local.h" - -static pmove_t cg_pmove; - -static int cg_numSolidEntities; -static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; -static int cg_numTriggerEntities; -static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; - -/* -==================== -CG_BuildSolidList - -When a new cg.snap has been set, this function builds a sublist -of the entities that are actually solid, to make for more -efficient collision detection -==================== -*/ -void CG_BuildSolidList( void ) { - int i; - centity_t *cent; - snapshot_t *snap; - entityState_t *ent; - - cg_numSolidEntities = 0; - cg_numTriggerEntities = 0; - - if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { - snap = cg.nextSnap; - } else { - snap = cg.snap; - } - - for ( i = 0 ; i < snap->numEntities ; i++ ) { - cent = &cg_entities[ snap->entities[ i ].number ]; - ent = ¢->currentState; - - if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { - cg_triggerEntities[cg_numTriggerEntities] = cent; - cg_numTriggerEntities++; - continue; - } - - if ( cent->nextState.solid ) { - cg_solidEntities[cg_numSolidEntities] = cent; - cg_numSolidEntities++; - continue; - } - } -} - -/* -==================== -CG_ClipMoveToEntities - -==================== -*/ -static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask, trace_t *tr ) { - int i, x, zd, zu; - trace_t trace; - entityState_t *ent; - clipHandle_t cmodel; - vec3_t bmins, bmaxs; - vec3_t origin, angles; - centity_t *cent; - - for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { - cent = cg_solidEntities[ i ]; - ent = ¢->currentState; - - if ( ent->number == skipNumber ) { - continue; - } - - if ( ent->solid == SOLID_BMODEL ) { - // special value for bmodel - cmodel = trap_CM_InlineModel( ent->modelindex ); - VectorCopy( cent->lerpAngles, angles ); - BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); - } else { - // encoded bbox - x = (ent->solid & 255); - zd = ((ent->solid>>8) & 255); - zu = ((ent->solid>>16) & 255) - 32; - - bmins[0] = bmins[1] = -x; - bmaxs[0] = bmaxs[1] = x; - bmins[2] = -zd; - bmaxs[2] = zu; - - cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); - VectorCopy( vec3_origin, angles ); - VectorCopy( cent->lerpOrigin, origin ); - } - - - trap_CM_TransformedBoxTrace ( &trace, start, end, - mins, maxs, cmodel, mask, origin, angles); - - if (trace.allsolid || trace.fraction < tr->fraction) { - trace.entityNum = ent->number; - *tr = trace; - } else if (trace.startsolid) { - tr->startsolid = qtrue; - } - if ( tr->allsolid ) { - return; - } - } -} - -/* -================ -CG_Trace -================ -*/ -void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, - int skipNumber, int mask ) { - trace_t t; - - trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); - t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; - // check all other solid models - CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); - - *result = t; -} - -/* -================ -CG_PointContents -================ -*/ -int CG_PointContents( const vec3_t point, int passEntityNum ) { - int i; - entityState_t *ent; - centity_t *cent; - clipHandle_t cmodel; - int contents; - - contents = trap_CM_PointContents (point, 0); - - for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { - cent = cg_solidEntities[ i ]; - - ent = ¢->currentState; - - if ( ent->number == passEntityNum ) { - continue; - } - - if (ent->solid != SOLID_BMODEL) { // special value for bmodel - continue; - } - - cmodel = trap_CM_InlineModel( ent->modelindex ); - if ( !cmodel ) { - continue; - } - - contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); - } - - return contents; -} - - -/* -======================== -CG_InterpolatePlayerState - -Generates cg.predictedPlayerState by interpolating between -cg.snap->player_state and cg.nextFrame->player_state -======================== -*/ -static void CG_InterpolatePlayerState( qboolean grabAngles ) { - float f; - int i; - playerState_t *out; - snapshot_t *prev, *next; - - out = &cg.predictedPlayerState; - prev = cg.snap; - next = cg.nextSnap; - - *out = cg.snap->ps; - - // if we are still allowing local input, short circuit the view angles - if ( grabAngles ) { - usercmd_t cmd; - int cmdNum; - - cmdNum = trap_GetCurrentCmdNumber(); - trap_GetUserCmd( cmdNum, &cmd ); - - PM_UpdateViewAngles( out, &cmd ); - } - - // if the next frame is a teleport, we can't lerp to it - if ( cg.nextFrameTeleport ) { - return; - } - - if ( !next || next->serverTime <= prev->serverTime ) { - return; - } - - f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); - - i = next->ps.bobCycle; - if ( i < prev->ps.bobCycle ) { - i += 256; // handle wraparound - } - out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); - - for ( i = 0 ; i < 3 ; i++ ) { - out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); - if ( !grabAngles ) { - out->viewangles[i] = LerpAngle( - prev->ps.viewangles[i], next->ps.viewangles[i], f ); - } - out->velocity[i] = prev->ps.velocity[i] + - f * (next->ps.velocity[i] - prev->ps.velocity[i] ); - } - -} - -/* -=================== -CG_TouchItem -=================== -*/ -static void CG_TouchItem( centity_t *cent ) { - gitem_t *item; - - if ( !cg_predictItems.integer ) { - return; - } - if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { - return; - } - - // never pick an item up twice in a prediction - if ( cent->miscTime == cg.time ) { - return; - } - - if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { - return; // can't hold it - } - - item = &bg_itemlist[ cent->currentState.modelindex ]; - - // Special case for flags. - // We don't predict touching our own flag -#ifdef MISSIONPACK - if( cgs.gametype == GT_1FCTF ) { - if( item->giTag != PW_NEUTRALFLAG ) { - return; - } - } - if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { -#else - if( cgs.gametype == GT_CTF ) { -#endif - if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && - item->giTag == PW_REDFLAG) - return; - if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && - item->giTag == PW_BLUEFLAG) - return; - } - - // grab it - BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); - - // remove it from the frame so it won't be drawn - cent->currentState.eFlags |= EF_NODRAW; - - // don't touch it again this prediction - cent->miscTime = cg.time; - - // if its a weapon, give them some predicted ammo so the autoswitch will work - if ( item->giType == IT_WEAPON ) { - cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; - if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { - cg.predictedPlayerState.ammo[ item->giTag ] = 1; - } - } -} - - -/* -========================= -CG_TouchTriggerPrediction - -Predict push triggers and items -========================= -*/ -static void CG_TouchTriggerPrediction( void ) { - int i; - trace_t trace; - entityState_t *ent; - clipHandle_t cmodel; - centity_t *cent; - qboolean spectator; - - // dead clients don't activate triggers - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return; - } - - spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); - - if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { - return; - } - - for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { - cent = cg_triggerEntities[ i ]; - ent = ¢->currentState; - - if ( ent->eType == ET_ITEM && !spectator ) { - CG_TouchItem( cent ); - continue; - } - - if ( ent->solid != SOLID_BMODEL ) { - continue; - } - - cmodel = trap_CM_InlineModel( ent->modelindex ); - if ( !cmodel ) { - continue; - } - - trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, - cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); - - if ( !trace.startsolid ) { - continue; - } - - if ( ent->eType == ET_TELEPORT_TRIGGER ) { - cg.hyperspace = qtrue; - } else if ( ent->eType == ET_PUSH_TRIGGER ) { - BG_TouchJumpPad( &cg.predictedPlayerState, ent ); - } - } - - // if we didn't touch a jump pad this pmove frame - if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { - cg.predictedPlayerState.jumppad_frame = 0; - cg.predictedPlayerState.jumppad_ent = 0; - } -} - - - -/* -================= -CG_PredictPlayerState - -Generates cg.predictedPlayerState for the current cg.time -cg.predictedPlayerState is guaranteed to be valid after exiting. - -For demo playback, this will be an interpolation between two valid -playerState_t. - -For normal gameplay, it will be the result of predicted usercmd_t on -top of the most recent playerState_t received from the server. - -Each new snapshot will usually have one or more new usercmd over the last, -but we simulate all unacknowledged commands each time, not just the new ones. -This means that on an internet connection, quite a few pmoves may be issued -each frame. - -OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t -differs from the predicted one. Would require saving all intermediate -playerState_t during prediction. - -We detect prediction errors and allow them to be decayed off over several frames -to ease the jerk. -================= -*/ -void CG_PredictPlayerState( void ) { - int cmdNum, current; - playerState_t oldPlayerState; - qboolean moved; - usercmd_t oldestCmd; - usercmd_t latestCmd; - - cg.hyperspace = qfalse; // will be set if touching a trigger_teleport - - // if this is the first frame we must guarantee - // predictedPlayerState is valid even if there is some - // other error condition - if ( !cg.validPPS ) { - cg.validPPS = qtrue; - cg.predictedPlayerState = cg.snap->ps; - } - - - // demo playback just copies the moves - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { - CG_InterpolatePlayerState( qfalse ); - return; - } - - // non-predicting local movement will grab the latest angles - if ( cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_InterpolatePlayerState( qtrue ); - return; - } - - // prepare for pmove - cg_pmove.ps = &cg.predictedPlayerState; - cg_pmove.trace = CG_Trace; - cg_pmove.pointcontents = CG_PointContents; - if ( cg_pmove.ps->pm_type == PM_DEAD ) { - cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; - } - else { - cg_pmove.tracemask = MASK_PLAYERSOLID; - } - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies - } - cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; - - // save the state before the pmove so we can detect transitions - oldPlayerState = cg.predictedPlayerState; - - current = trap_GetCurrentCmdNumber(); - - // if we don't have the commands right after the snapshot, we - // can't accurately predict a current position, so just freeze at - // the last good position we had - cmdNum = current - CMD_BACKUP + 1; - trap_GetUserCmd( cmdNum, &oldestCmd ); - if ( oldestCmd.serverTime > cg.snap->ps.commandTime - && oldestCmd.serverTime < cg.time ) { // special check for map_restart - if ( cg_showmiss.integer ) { - CG_Printf ("exceeded PACKET_BACKUP on commands\n"); - } - return; - } - - // get the latest command so we can know which commands are from previous map_restarts - trap_GetUserCmd( current, &latestCmd ); - - // get the most recent information we have, even if - // the server time is beyond our current cg.time, - // because predicted player positions are going to - // be ahead of everything else anyway - if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { - cg.predictedPlayerState = cg.nextSnap->ps; - cg.physicsTime = cg.nextSnap->serverTime; - } else { - cg.predictedPlayerState = cg.snap->ps; - cg.physicsTime = cg.snap->serverTime; - } - - if ( pmove_msec.integer < 8 ) { - trap_Cvar_Set("pmove_msec", "8"); - } - else if (pmove_msec.integer > 33) { - trap_Cvar_Set("pmove_msec", "33"); - } - - cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; - cg_pmove.pmove_msec = pmove_msec.integer; - - // run cmds - moved = qfalse; - for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { - // get the command - trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); - - if ( cg_pmove.pmove_fixed ) { - PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); - } - - // don't do anything if the time is before the snapshot player time - if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { - continue; - } - - // don't do anything if the command was from a previous map_restart - if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { - continue; - } - - // check for a prediction error from last frame - // on a lan, this will often be the exact value - // from the snapshot, but on a wan we will have - // to predict several commands to get to the point - // we want to compare - if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { - vec3_t delta; - float len; - - if ( cg.thisFrameTeleport ) { - // a teleport will not cause an error decay - VectorClear( cg.predictedError ); - if ( cg_showmiss.integer ) { - CG_Printf( "PredictionTeleport\n" ); - } - cg.thisFrameTeleport = qfalse; - } else { - vec3_t adjusted; - CG_AdjustPositionForMover( cg.predictedPlayerState.origin, - cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); - - if ( cg_showmiss.integer ) { - if (!VectorCompare( oldPlayerState.origin, adjusted )) { - CG_Printf("prediction error\n"); - } - } - VectorSubtract( oldPlayerState.origin, adjusted, delta ); - len = VectorLength( delta ); - if ( len > 0.1 ) { - if ( cg_showmiss.integer ) { - CG_Printf("Prediction miss: %f\n", len); - } - if ( cg_errorDecay.integer ) { - int t; - float f; - - t = cg.time - cg.predictedErrorTime; - f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; - if ( f < 0 ) { - f = 0; - } - if ( f > 0 && cg_showmiss.integer ) { - CG_Printf("Double prediction decay: %f\n", f); - } - VectorScale( cg.predictedError, f, cg.predictedError ); - } else { - VectorClear( cg.predictedError ); - } - VectorAdd( delta, cg.predictedError, cg.predictedError ); - cg.predictedErrorTime = cg.oldTime; - } - } - } - - // don't predict gauntlet firing, which is only supposed to happen - // when it actually inflicts damage - cg_pmove.gauntletHit = qfalse; - - if ( cg_pmove.pmove_fixed ) { - cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; - } - - Pmove (&cg_pmove); - - moved = qtrue; - - // add push trigger movement effects - CG_TouchTriggerPrediction(); - - // check for predictable events that changed from previous predictions - //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); - } - - if ( cg_showmiss.integer > 1 ) { - CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); - } - - if ( !moved ) { - if ( cg_showmiss.integer ) { - CG_Printf( "not moved\n" ); - } - return; - } - - // adjust for the movement of the groundentity - CG_AdjustPositionForMover( cg.predictedPlayerState.origin, - cg.predictedPlayerState.groundEntityNum, - cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); - - if ( cg_showmiss.integer ) { - if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { - CG_Printf("WARNING: dropped event\n"); - } - } - - // fire events and other transition triggered things - CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); - - if ( cg_showmiss.integer ) { - if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { - CG_Printf("WARNING: double event\n"); - cg.eventSequence = cg.predictedPlayerState.eventSequence; - } - } -} - - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, trace_t *tr ) { + int i, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } else { + // encoded bbox + x = (ent->solid & 255); + zd = ((ent->solid>>8) & 255); + zu = ((ent->solid>>16) & 255) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + + + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles); + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + } + if ( tr->allsolid ) { + return; + } + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if (ent->solid != SOLID_BMODEL) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg.nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) { + return; + } + + if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + // Special case for flags. + // We don't predict touching our own flag +#ifdef MISSIONPACK + if( cgs.gametype == GT_1FCTF ) { + if( item->giTag != PW_NEUTRALFLAG ) { + return; + } + } + if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { +#else + if( cgs.gametype == GT_CTF ) { +#endif + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG) + return; + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BLUEFLAG) + return; + } + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg.time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) { + cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; + if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { + cg.predictedPlayerState.ammo[ item->giTag ] = 1; + } + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + + if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } + + if ( ent->eType == ET_TELEPORT_TRIGGER ) { + cg.hyperspace = qtrue; + } else if ( ent->eType == ET_PUSH_TRIGGER ) { + BG_TouchJumpPad( &cg.predictedPlayerState, ent ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { + cg.predictedPlayerState.jumppad_frame = 0; + cg.predictedPlayerState.jumppad_ent = 0; + } +} + + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) { + int cmdNum, current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + if ( cg_pmove.ps->pm_type == PM_DEAD ) { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime + && oldestCmd.serverTime < cg.time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if ( cg_pmove.pmove_fixed ) { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showmiss.integer ) { + if (!VectorCompare( oldPlayerState.origin, adjusted )) { + CG_Printf("prediction error\n"); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf("Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } else { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + cg_pmove.gauntletHit = qfalse; + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + Pmove (&cg_pmove); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + if ( cg_showmiss.integer ) { + if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { + CG_Printf("WARNING: dropped event\n"); + } + } + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + if ( cg_showmiss.integer ) { + if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { + CG_Printf("WARNING: double event\n"); + cg.eventSequence = cg.predictedPlayerState.eventSequence; + } + } +} + + diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h index 8514143..8f8ea6d 100755 --- a/code/cgame/cg_public.h +++ b/code/cgame/cg_public.h @@ -1,238 +1,238 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// - - -#define CMD_BACKUP 64 -#define CMD_MASK (CMD_BACKUP - 1) -// allow a lot of command backups for very fast systems -// multiple commands may be combined into a single packet, so this -// needs to be larger than PACKET_BACKUP - - -#define MAX_ENTITIES_IN_SNAPSHOT 256 - -// snapshots are a view of the server at a given time - -// Snapshots are generated at regular time intervals by the server, -// but they may not be sent if a client's rate level is exceeded, or -// they may be dropped by the network. -typedef struct { - int snapFlags; // SNAPFLAG_RATE_DELAYED, etc - int ping; - - int serverTime; // server time the message is valid for (in msec) - - byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits - - playerState_t ps; // complete information about the current player at this time - - int numEntities; // all of the entities that need to be presented - entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot - - int numServerCommands; // text based server commands to execute when this - int serverCommandSequence; // snapshot becomes current -} snapshot_t; - -enum { - CGAME_EVENT_NONE, - CGAME_EVENT_TEAMMENU, - CGAME_EVENT_SCOREBOARD, - CGAME_EVENT_EDITHUD -}; - - -/* -================================================================== - -functions imported from the main executable - -================================================================== -*/ - -#define CGAME_IMPORT_API_VERSION 4 - -typedef enum { - CG_PRINT, - CG_ERROR, - CG_MILLISECONDS, - CG_CVAR_REGISTER, - CG_CVAR_UPDATE, - CG_CVAR_SET, - CG_CVAR_VARIABLESTRINGBUFFER, - CG_ARGC, - CG_ARGV, - CG_ARGS, - CG_FS_FOPENFILE, - CG_FS_READ, - CG_FS_WRITE, - CG_FS_FCLOSEFILE, - CG_SENDCONSOLECOMMAND, - CG_ADDCOMMAND, - CG_SENDCLIENTCOMMAND, - CG_UPDATESCREEN, - CG_CM_LOADMAP, - CG_CM_NUMINLINEMODELS, - CG_CM_INLINEMODEL, - CG_CM_LOADMODEL, - CG_CM_TEMPBOXMODEL, - CG_CM_POINTCONTENTS, - CG_CM_TRANSFORMEDPOINTCONTENTS, - CG_CM_BOXTRACE, - CG_CM_TRANSFORMEDBOXTRACE, - CG_CM_MARKFRAGMENTS, - CG_S_STARTSOUND, - CG_S_STARTLOCALSOUND, - CG_S_CLEARLOOPINGSOUNDS, - CG_S_ADDLOOPINGSOUND, - CG_S_UPDATEENTITYPOSITION, - CG_S_RESPATIALIZE, - CG_S_REGISTERSOUND, - CG_S_STARTBACKGROUNDTRACK, - CG_R_LOADWORLDMAP, - CG_R_REGISTERMODEL, - CG_R_REGISTERSKIN, - CG_R_REGISTERSHADER, - CG_R_CLEARSCENE, - CG_R_ADDREFENTITYTOSCENE, - CG_R_ADDPOLYTOSCENE, - CG_R_ADDLIGHTTOSCENE, - CG_R_RENDERSCENE, - CG_R_SETCOLOR, - CG_R_DRAWSTRETCHPIC, - CG_R_MODELBOUNDS, - CG_R_LERPTAG, - CG_GETGLCONFIG, - CG_GETGAMESTATE, - CG_GETCURRENTSNAPSHOTNUMBER, - CG_GETSNAPSHOT, - CG_GETSERVERCOMMAND, - CG_GETCURRENTCMDNUMBER, - CG_GETUSERCMD, - CG_SETUSERCMDVALUE, - CG_R_REGISTERSHADERNOMIP, - CG_MEMORY_REMAINING, - CG_R_REGISTERFONT, - CG_KEY_ISDOWN, - CG_KEY_GETCATCHER, - CG_KEY_SETCATCHER, - CG_KEY_GETKEY, - CG_PC_ADD_GLOBAL_DEFINE, - CG_PC_LOAD_SOURCE, - CG_PC_FREE_SOURCE, - CG_PC_READ_TOKEN, - CG_PC_SOURCE_FILE_AND_LINE, - CG_S_STOPBACKGROUNDTRACK, - CG_REAL_TIME, - CG_SNAPVECTOR, - CG_REMOVECOMMAND, - CG_R_LIGHTFORPOINT, - CG_CIN_PLAYCINEMATIC, - CG_CIN_STOPCINEMATIC, - CG_CIN_RUNCINEMATIC, - CG_CIN_DRAWCINEMATIC, - CG_CIN_SETEXTENTS, - CG_R_REMAP_SHADER, - CG_S_ADDREALLOOPINGSOUND, - CG_S_STOPLOOPINGSOUND, - - CG_CM_TEMPCAPSULEMODEL, - CG_CM_CAPSULETRACE, - CG_CM_TRANSFORMEDCAPSULETRACE, - CG_R_ADDADDITIVELIGHTTOSCENE, - CG_GET_ENTITY_TOKEN, - CG_R_ADDPOLYSTOSCENE, - CG_R_INPVS, - // 1.32 - CG_FS_SEEK, - -/* - CG_LOADCAMERA, - CG_STARTCAMERA, - CG_GETCAMERAINFO, -*/ - - CG_MEMSET = 100, - CG_MEMCPY, - CG_STRNCPY, - CG_SIN, - CG_COS, - CG_ATAN2, - CG_SQRT, - CG_FLOOR, - CG_CEIL, - CG_TESTPRINTINT, - CG_TESTPRINTFLOAT, - CG_ACOS -} cgameImport_t; - - -/* -================================================================== - -functions exported to the main executable - -================================================================== -*/ - -typedef enum { - CG_INIT, -// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) - // called when the level loads or when the renderer is restarted - // all media should be registered at this time - // cgame will display loading status by calling SCR_Update, which - // will call CG_DrawInformation during the loading process - // reliableCommandSequence will be 0 on fresh loads, but higher for - // demos, tourney restarts, or vid_restarts - - CG_SHUTDOWN, -// void (*CG_Shutdown)( void ); - // oportunity to flush and close any open files - - CG_CONSOLE_COMMAND, -// qboolean (*CG_ConsoleCommand)( void ); - // a console command has been issued locally that is not recognized by the - // main game system. - // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the - // command is not known to the game - - CG_DRAW_ACTIVE_FRAME, -// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); - // Generates and draws a game scene and status information at the given time. - // If demoPlayback is set, local movement prediction will not be enabled - - CG_CROSSHAIR_PLAYER, -// int (*CG_CrosshairPlayer)( void ); - - CG_LAST_ATTACKER, -// int (*CG_LastAttacker)( void ); - - CG_KEY_EVENT, -// void (*CG_KeyEvent)( int key, qboolean down ); - - CG_MOUSE_EVENT, -// void (*CG_MouseEvent)( int dx, int dy ); - CG_EVENT_HANDLING -// void (*CG_EventHandling)(int type); -} cgameExport_t; - -//---------------------------------------------- +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + // 1.32 + CG_FS_SEEK, + +/* + CG_LOADCAMERA, + CG_STARTCAMERA, + CG_GETCAMERAINFO, +*/ + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING +// void (*CG_EventHandling)(int type); +} cgameExport_t; + +//---------------------------------------------- diff --git a/code/cgame/cg_scoreboard.c b/code/cgame/cg_scoreboard.c index 25966c9..eb9d40b 100755 --- a/code/cgame/cg_scoreboard.c +++ b/code/cgame/cg_scoreboard.c @@ -1,534 +1,534 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_scoreboard -- draw the scoreboard on top of the game screen -#include "cg_local.h" - - -#define SCOREBOARD_X (0) - -#define SB_HEADER 86 -#define SB_TOP (SB_HEADER+32) - -// Where the status bar starts, so we don't overwrite it -#define SB_STATUSBAR 420 - -#define SB_NORMAL_HEIGHT 40 -#define SB_INTER_HEIGHT 16 // interleaved height - -#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) -#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) - -// Used when interleaved - - - -#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) -#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) -#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) -#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) -// Normal -#define SB_BOTICON_X (SCOREBOARD_X+32) -#define SB_HEAD_X (SCOREBOARD_X+64) - -#define SB_SCORELINE_X 112 - -#define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 -#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 -#define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 -#define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 -#define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 -#define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 - -// The new and improved score board -// -// In cases where the number of clients is high, the score board heads are interleaved -// here's the layout - -// -// 0 32 80 112 144 240 320 400 <-- pixel position -// bot head bot head score ping time name -// -// wins/losses are drawn on bot icon now - -static qboolean localClient; // true if local client has been displayed - - - /* -================= -CG_DrawScoreboard -================= -*/ -static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { - char string[1024]; - vec3_t headAngles; - clientInfo_t *ci; - int iconx, headx; - - if ( score->client < 0 || score->client >= cgs.maxclients ) { - Com_Printf( "Bad score->client: %i\n", score->client ); - return; - } - - ci = &cgs.clientinfo[score->client]; - - iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); - headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); - - // draw the handicap or bot skill marker (unless player has flag) - if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); - } - } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); - } - } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { - if( largeFormat ) { - CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); - } - else { - CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); - } - } else { - if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { - if ( cg_drawIcons.integer ) { - if( largeFormat ) { - CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); - } - else { - CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); - } - } - } else if ( ci->handicap < 100 ) { - Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); - if ( cgs.gametype == GT_TOURNAMENT ) - CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); - else - CG_DrawSmallStringColor( iconx, y, string, color ); - } - - // draw the wins / losses - if ( cgs.gametype == GT_TOURNAMENT ) { - Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); - if( ci->handicap < 100 && !ci->botSkill ) { - CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); - } - else { - CG_DrawSmallStringColor( iconx, y, string, color ); - } - } - } - - // draw the face - VectorClear( headAngles ); - headAngles[YAW] = 180; - if( largeFormat ) { - CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, - score->client, headAngles ); - } - else { - CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); - } - -#ifdef MISSIONPACK - // draw the team task - if ( ci->teamTask != TEAMTASK_NONE ) { - if ( ci->teamTask == TEAMTASK_OFFENSE ) { - CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); - } - else if ( ci->teamTask == TEAMTASK_DEFENSE ) { - CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); - } - } -#endif - // draw the score line - if ( score->ping == -1 ) { - Com_sprintf(string, sizeof(string), - " connecting %s", ci->name); - } else if ( ci->team == TEAM_SPECTATOR ) { - Com_sprintf(string, sizeof(string), - " SPECT %3i %4i %s", score->ping, score->time, ci->name); - } else { - Com_sprintf(string, sizeof(string), - "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); - } - - // highlight your position - if ( score->client == cg.snap->ps.clientNum ) { - float hcolor[4]; - int rank; - - localClient = qtrue; - - if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR - || cgs.gametype >= GT_TEAM ) { - rank = -1; - } else { - rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; - } - if ( rank == 0 ) { - hcolor[0] = 0; - hcolor[1] = 0; - hcolor[2] = 0.7f; - } else if ( rank == 1 ) { - hcolor[0] = 0.7f; - hcolor[1] = 0; - hcolor[2] = 0; - } else if ( rank == 2 ) { - hcolor[0] = 0.7f; - hcolor[1] = 0.7f; - hcolor[2] = 0; - } else { - hcolor[0] = 0.7f; - hcolor[1] = 0.7f; - hcolor[2] = 0.7f; - } - - hcolor[3] = fade * 0.7; - CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, - 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); - } - - CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); - - // add the "ready" marker for intermission exiting - if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { - CG_DrawBigStringColor( iconx, y, "READY", color ); - } -} - -/* -================= -CG_TeamScoreboard -================= -*/ -static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { - int i; - score_t *score; - float color[4]; - int count; - clientInfo_t *ci; - - color[0] = color[1] = color[2] = 1.0; - color[3] = fade; - - count = 0; - for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { - score = &cg.scores[i]; - ci = &cgs.clientinfo[ score->client ]; - - if ( team != ci->team ) { - continue; - } - - CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); - - count++; - } - - return count; -} - -/* -================= -CG_DrawScoreboard - -Draw the normal in-game scoreboard -================= -*/ -qboolean CG_DrawOldScoreboard( void ) { - int x, y, w, i, n1, n2; - float fade; - float *fadeColor; - char *s; - int maxClients; - int lineHeight; - int topBorderSize, bottomBorderSize; - - // don't draw amuthing if the menu or console is up - if ( cg_paused.integer ) { - cg.deferredPlayerLoading = 0; - return qfalse; - } - - if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - cg.deferredPlayerLoading = 0; - return qfalse; - } - - // don't draw scoreboard during death while warmup up - if ( cg.warmup && !cg.showScores ) { - return qfalse; - } - - if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || - cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - fade = 1.0; - fadeColor = colorWhite; - } else { - fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); - - if ( !fadeColor ) { - // next time scoreboard comes up, don't print killer - cg.deferredPlayerLoading = 0; - cg.killerName[0] = 0; - return qfalse; - } - fade = *fadeColor; - } - - - // fragged by ... line - if ( cg.killerName[0] ) { - s = va("Fragged by %s", cg.killerName ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 40; - CG_DrawBigString( x, y, s, fade ); - } - - // current rank - if ( cgs.gametype < GT_TEAM) { - if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { - s = va("%s place with %i", - CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), - cg.snap->ps.persistant[PERS_SCORE] ); - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 60; - CG_DrawBigString( x, y, s, fade ); - } - } else { - if ( cg.teamScores[0] == cg.teamScores[1] ) { - s = va("Teams are tied at %i", cg.teamScores[0] ); - } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { - s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); - } else { - s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); - } - - w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - y = 60; - CG_DrawBigString( x, y, s, fade ); - } - - // scoreboard - y = SB_HEADER; - - CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); - CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); - CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); - CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); - - y = SB_TOP; - - // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores - if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { - maxClients = SB_MAXCLIENTS_INTER; - lineHeight = SB_INTER_HEIGHT; - topBorderSize = 8; - bottomBorderSize = 16; - } else { - maxClients = SB_MAXCLIENTS_NORMAL; - lineHeight = SB_NORMAL_HEIGHT; - topBorderSize = 16; - bottomBorderSize = 16; - } - - localClient = qfalse; - - if ( cgs.gametype >= GT_TEAM ) { - // - // teamplay scoreboard - // - y += lineHeight/2; - - if ( cg.teamScores[0] >= cg.teamScores[1] ) { - n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n1; - n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n2; - } else { - n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n1; - n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); - CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - maxClients -= n2; - } - n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - - } else { - // - // free for all scoreboard - // - n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); - y += (n1 * lineHeight) + BIGCHAR_HEIGHT; - n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); - y += (n2 * lineHeight) + BIGCHAR_HEIGHT; - } - - if (!localClient) { - // draw local client at the bottom - for ( i = 0 ; i < cg.numScores ; i++ ) { - if ( cg.scores[i].client == cg.snap->ps.clientNum ) { - CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); - break; - } - } - } - - // load any models that have been deferred - if ( ++cg.deferredPlayerLoading > 10 ) { - CG_LoadDeferredPlayers(); - } - - return qtrue; -} - -//================================================================================ - -/* -================ -CG_CenterGiantLine -================ -*/ -static void CG_CenterGiantLine( float y, const char *string ) { - float x; - vec4_t color; - - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); - - CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); -} - -/* -================= -CG_DrawTourneyScoreboard - -Draw the oversize scoreboard for tournements -================= -*/ -void CG_DrawOldTourneyScoreboard( void ) { - const char *s; - vec4_t color; - int min, tens, ones; - clientInfo_t *ci; - int y; - int i; - - // request more scores regularly - if ( cg.scoresRequestTime + 2000 < cg.time ) { - cg.scoresRequestTime = cg.time; - trap_SendClientCommand( "score" ); - } - - color[0] = 1; - color[1] = 1; - color[2] = 1; - color[3] = 1; - - // draw the dialog background - color[0] = color[1] = color[2] = 0; - color[3] = 1; - CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); - - // print the mesage of the day - s = CG_ConfigString( CS_MOTD ); - if ( !s[0] ) { - s = "Scoreboard"; - } - - // print optional title - CG_CenterGiantLine( 8, s ); - - // print server time - ones = cg.time / 1000; - min = ones / 60; - ones %= 60; - tens = ones / 10; - ones %= 10; - s = va("%i:%i%i", min, tens, ones ); - - CG_CenterGiantLine( 64, s ); - - - // print the two scores - - y = 160; - if ( cgs.gametype >= GT_TEAM ) { - // - // teamplay scoreboard - // - CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", cg.teamScores[0] ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - - y += 64; - - CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", cg.teamScores[1] ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - } else { - // - // free for all scoreboard - // - for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { - ci = &cgs.clientinfo[i]; - if ( !ci->infoValid ) { - continue; - } - if ( ci->team != TEAM_FREE ) { - continue; - } - - CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - s = va("%i", ci->score ); - CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); - y += 64; - } - } - - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" + + +#define SCOREBOARD_X (0) + +#define SB_HEADER 86 +#define SB_TOP (SB_HEADER+32) + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +#define SB_NORMAL_HEIGHT 40 +#define SB_INTER_HEIGHT 16 // interleaved height + +#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) +#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) + +// Used when interleaved + + + +#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) +#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) +#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) +#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) +// Normal +#define SB_BOTICON_X (SCOREBOARD_X+32) +#define SB_HEAD_X (SCOREBOARD_X+64) + +#define SB_SCORELINE_X 112 + +#define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 +#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 +#define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 +#define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 + +// The new and improved score board +// +// In cases where the number of clients is high, the score board heads are interleaved +// here's the layout + +// +// 0 32 80 112 144 240 320 400 <-- pixel position +// bot head bot head score ping time name +// +// wins/losses are drawn on bot icon now + +static qboolean localClient; // true if local client has been displayed + + + /* +================= +CG_DrawScoreboard +================= +*/ +static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { + char string[1024]; + vec3_t headAngles; + clientInfo_t *ci; + int iconx, headx; + + if ( score->client < 0 || score->client >= cgs.maxclients ) { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + ci = &cgs.clientinfo[score->client]; + + iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); + headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); + + // draw the handicap or bot skill marker (unless player has flag) + if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); + } + } else { + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { + if ( cg_drawIcons.integer ) { + if( largeFormat ) { + CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + else { + CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + } + } else if ( ci->handicap < 100 ) { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + if ( cgs.gametype == GT_TOURNAMENT ) + CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); + else + CG_DrawSmallStringColor( iconx, y, string, color ); + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + if( ci->handicap < 100 && !ci->botSkill ) { + CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); + } + else { + CG_DrawSmallStringColor( iconx, y, string, color ); + } + } + } + + // draw the face + VectorClear( headAngles ); + headAngles[YAW] = 180; + if( largeFormat ) { + CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + score->client, headAngles ); + } + else { + CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); + } + +#ifdef MISSIONPACK + // draw the team task + if ( ci->teamTask != TEAMTASK_NONE ) { + if ( ci->teamTask == TEAMTASK_OFFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); + } + else if ( ci->teamTask == TEAMTASK_DEFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); + } + } +#endif + // draw the score line + if ( score->ping == -1 ) { + Com_sprintf(string, sizeof(string), + " connecting %s", ci->name); + } else if ( ci->team == TEAM_SPECTATOR ) { + Com_sprintf(string, sizeof(string), + " SPECT %3i %4i %s", score->ping, score->time, ci->name); + } else { + Com_sprintf(string, sizeof(string), + "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); + } + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) { + float hcolor[4]; + int rank; + + localClient = qtrue; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) { + rank = -1; + } else { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7f; + } else if ( rank == 1 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( rank == 2 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0; + } else { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0.7f; + } + + hcolor[3] = fade * 0.7; + CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, + 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); + } + + CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); + + // add the "ready" marker for intermission exiting + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { + CG_DrawBigStringColor( iconx, y, "READY", color ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { + int i; + score_t *score; + float color[4]; + int count; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + + count++; + } + + return count; +} + +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawOldScoreboard( void ) { + int x, y, w, i, n1, n2; + float fade; + float *fadeColor; + char *s; + int maxClients; + int lineHeight; + int topBorderSize, bottomBorderSize; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + + // fragged by ... line + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + } + + // current rank + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i", + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + + // scoreboard + y = SB_HEADER; + + CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); + CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); + CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); + CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); + + y = SB_TOP; + + // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores + if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { + maxClients = SB_MAXCLIENTS_INTER; + lineHeight = SB_INTER_HEIGHT; + topBorderSize = 8; + bottomBorderSize = 16; + } else { + maxClients = SB_MAXCLIENTS_NORMAL; + lineHeight = SB_NORMAL_HEIGHT; + topBorderSize = 16; + bottomBorderSize = 16; + } + + localClient = qfalse; + + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + y += lineHeight/2; + + if ( cg.teamScores[0] >= cg.teamScores[1] ) { + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } else { + n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } + + if (!localClient) { + // draw local client at the bottom + for ( i = 0 ; i < cg.numScores ; i++ ) { + if ( cg.scores[i].client == cg.snap->ps.clientNum ) { + CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); + break; + } + } + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +} + +//================================================================================ + +/* +================ +CG_CenterGiantLine +================ +*/ +static void CG_CenterGiantLine( float y, const char *string ) { + float x; + vec4_t color; + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); + + CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); +} + +/* +================= +CG_DrawTourneyScoreboard + +Draw the oversize scoreboard for tournements +================= +*/ +void CG_DrawOldTourneyScoreboard( void ) { + const char *s; + vec4_t color; + int min, tens, ones; + clientInfo_t *ci; + int y; + int i; + + // request more scores regularly + if ( cg.scoresRequestTime + 2000 < cg.time ) { + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + } + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + // draw the dialog background + color[0] = color[1] = color[2] = 0; + color[3] = 1; + CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); + + // print the mesage of the day + s = CG_ConfigString( CS_MOTD ); + if ( !s[0] ) { + s = "Scoreboard"; + } + + // print optional title + CG_CenterGiantLine( 8, s ); + + // print server time + ones = cg.time / 1000; + min = ones / 60; + ones %= 60; + tens = ones / 10; + ones %= 10; + s = va("%i:%i%i", min, tens, ones ); + + CG_CenterGiantLine( 64, s ); + + + // print the two scores + + y = 160; + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[0] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + y += 64; + + CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[1] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + } else { + // + // free for all scoreboard + // + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + ci = &cgs.clientinfo[i]; + if ( !ci->infoValid ) { + continue; + } + if ( ci->team != TEAM_FREE ) { + continue; + } + + CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", ci->score ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + y += 64; + } + } + + +} + diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c index d12cd97..6558d2f 100755 --- a/code/cgame/cg_servercmds.c +++ b/code/cgame/cg_servercmds.c @@ -1,1098 +1,1098 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_servercmds.c -- reliably sequenced text commands sent by the server -// these are processed at snapshot transition time, so there will definately -// be a valid snapshot this frame - -#include "cg_local.h" -#include "../../ui/menudef.h" // bk001205 - for Q3_ui as well - -typedef struct { - const char *order; - int taskNum; -} orderTask_t; - -static const orderTask_t validOrders[] = { - { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, - { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, - { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, - { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, - { VOICECHAT_PATROL, TEAMTASK_PATROL }, - { VOICECHAT_CAMP, TEAMTASK_CAMP }, - { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, - { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, - { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } -}; - -static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); - -#ifdef MISSIONPACK // bk001204 -static int CG_ValidOrder(const char *p) { - int i; - for (i = 0; i < numValidOrders; i++) { - if (Q_stricmp(p, validOrders[i].order) == 0) { - return validOrders[i].taskNum; - } - } - return -1; -} -#endif - -/* -================= -CG_ParseScores - -================= -*/ -static void CG_ParseScores( void ) { - int i, powerups; - - cg.numScores = atoi( CG_Argv( 1 ) ); - if ( cg.numScores > MAX_CLIENTS ) { - cg.numScores = MAX_CLIENTS; - } - - cg.teamScores[0] = atoi( CG_Argv( 2 ) ); - cg.teamScores[1] = atoi( CG_Argv( 3 ) ); - - memset( cg.scores, 0, sizeof( cg.scores ) ); - for ( i = 0 ; i < cg.numScores ; i++ ) { - // - cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); - cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); - cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); - cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); - cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); - powerups = atoi( CG_Argv( i * 14 + 9 ) ); - cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); - cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); - cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); - cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); - cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); - cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); - cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); - cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); - - if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { - cg.scores[i].client = 0; - } - cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; - cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; - - cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; - } -#ifdef MISSIONPACK - CG_SetScoreSelection(NULL); -#endif - -} - -/* -================= -CG_ParseTeamInfo - -================= -*/ -static void CG_ParseTeamInfo( void ) { - int i; - int client; - - numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); - - for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { - client = atoi( CG_Argv( i * 6 + 2 ) ); - - sortedTeamPlayers[i] = client; - - cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); - cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); - cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); - cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); - cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); - } -} - - -/* -================ -CG_ParseServerinfo - -This is called explicitly when the gamestate is first received, -and whenever the server updates any serverinfo flagged cvars -================ -*/ -void CG_ParseServerinfo( void ) { - const char *info; - char *mapname; - - info = CG_ConfigString( CS_SERVERINFO ); - cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); - trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); - cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); - cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); - cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); - cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); - cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); - cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); - mapname = Info_ValueForKey( info, "mapname" ); - Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); - Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); - trap_Cvar_Set("g_redTeam", cgs.redTeam); - Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); - trap_Cvar_Set("g_blueTeam", cgs.blueTeam); -} - -/* -================== -CG_ParseWarmup -================== -*/ -static void CG_ParseWarmup( void ) { - const char *info; - int warmup; - - info = CG_ConfigString( CS_WARMUP ); - - warmup = atoi( info ); - cg.warmupCount = -1; - - if ( warmup == 0 && cg.warmup ) { - - } else if ( warmup > 0 && cg.warmup <= 0 ) { -#ifdef MISSIONPACK - if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { - trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); - } else -#endif - { - trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); - } - } - - cg.warmup = warmup; -} - -/* -================ -CG_SetConfigValues - -Called on load to set the initial values from configure strings -================ -*/ -void CG_SetConfigValues( void ) { - const char *s; - - cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); - cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); - cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); - if( cgs.gametype == GT_CTF ) { - s = CG_ConfigString( CS_FLAGSTATUS ); - cgs.redflag = s[0] - '0'; - cgs.blueflag = s[1] - '0'; - } -#ifdef MISSIONPACK - else if( cgs.gametype == GT_1FCTF ) { - s = CG_ConfigString( CS_FLAGSTATUS ); - cgs.flagStatus = s[0] - '0'; - } -#endif - cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); -} - -/* -===================== -CG_ShaderStateChanged -===================== -*/ -void CG_ShaderStateChanged(void) { - char originalShader[MAX_QPATH]; - char newShader[MAX_QPATH]; - char timeOffset[16]; - const char *o; - char *n,*t; - - o = CG_ConfigString( CS_SHADERSTATE ); - while (o && *o) { - n = strstr(o, "="); - if (n && *n) { - strncpy(originalShader, o, n-o); - originalShader[n-o] = 0; - n++; - t = strstr(n, ":"); - if (t && *t) { - strncpy(newShader, n, t-n); - newShader[t-n] = 0; - } else { - break; - } - t++; - o = strstr(t, "@"); - if (o) { - strncpy(timeOffset, t, o-t); - timeOffset[o-t] = 0; - o++; - trap_R_RemapShader( originalShader, newShader, timeOffset ); - } - } else { - break; - } - } -} - -/* -================ -CG_ConfigStringModified - -================ -*/ -static void CG_ConfigStringModified( void ) { - const char *str; - int num; - - num = atoi( CG_Argv( 1 ) ); - - // get the gamestate from the client system, which will have the - // new configstring already integrated - trap_GetGameState( &cgs.gameState ); - - // look up the individual string that was modified - str = CG_ConfigString( num ); - - // do something with it if necessary - if ( num == CS_MUSIC ) { - CG_StartMusic(); - } else if ( num == CS_SERVERINFO ) { - CG_ParseServerinfo(); - } else if ( num == CS_WARMUP ) { - CG_ParseWarmup(); - } else if ( num == CS_SCORES1 ) { - cgs.scores1 = atoi( str ); - } else if ( num == CS_SCORES2 ) { - cgs.scores2 = atoi( str ); - } else if ( num == CS_LEVEL_START_TIME ) { - cgs.levelStartTime = atoi( str ); - } else if ( num == CS_VOTE_TIME ) { - cgs.voteTime = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_YES ) { - cgs.voteYes = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_NO ) { - cgs.voteNo = atoi( str ); - cgs.voteModified = qtrue; - } else if ( num == CS_VOTE_STRING ) { - Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); -#ifdef MISSIONPACK - trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); -#endif //MISSIONPACK - } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { - cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; - } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { - cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; - } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { - cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); - cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; - } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { - Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); -#ifdef MISSIONPACK - trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); -#endif - } else if ( num == CS_INTERMISSION ) { - cg.intermissionStarted = atoi( str ); - } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { - cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); - } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { - if ( str[0] != '*' ) { // player specific sounds don't register here - cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); - } - } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { - CG_NewClientInfo( num - CS_PLAYERS ); - CG_BuildSpectatorString(); - } else if ( num == CS_FLAGSTATUS ) { - if( cgs.gametype == GT_CTF ) { - // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped - cgs.redflag = str[0] - '0'; - cgs.blueflag = str[1] - '0'; - } -#ifdef MISSIONPACK - else if( cgs.gametype == GT_1FCTF ) { - cgs.flagStatus = str[0] - '0'; - } -#endif - } - else if ( num == CS_SHADERSTATE ) { - CG_ShaderStateChanged(); - } - -} - - -/* -======================= -CG_AddToTeamChat - -======================= -*/ -static void CG_AddToTeamChat( const char *str ) { - int len; - char *p, *ls; - int lastcolor; - int chatHeight; - - if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { - chatHeight = cg_teamChatHeight.integer; - } else { - chatHeight = TEAMCHAT_HEIGHT; - } - - if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { - // team chat disabled, dump into normal chat - cgs.teamChatPos = cgs.teamLastChatPos = 0; - return; - } - - len = 0; - - p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; - *p = 0; - - lastcolor = '7'; - - ls = NULL; - while (*str) { - if (len > TEAMCHAT_WIDTH - 1) { - if (ls) { - str -= (p - ls); - str++; - p -= (p - ls); - } - *p = 0; - - cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; - - cgs.teamChatPos++; - p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; - *p = 0; - *p++ = Q_COLOR_ESCAPE; - *p++ = lastcolor; - len = 0; - ls = NULL; - } - - if ( Q_IsColorString( str ) ) { - *p++ = *str++; - lastcolor = *str; - *p++ = *str++; - continue; - } - if (*str == ' ') { - ls = p; - } - *p++ = *str++; - len++; - } - *p = 0; - - cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; - cgs.teamChatPos++; - - if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) - cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; -} - -/* -=============== -CG_MapRestart - -The server has issued a map_restart, so the next snapshot -is completely new and should not be interpolated to. - -A tournement restart will clear everything, but doesn't -require a reload of all the media -=============== -*/ -static void CG_MapRestart( void ) { - if ( cg_showmiss.integer ) { - CG_Printf( "CG_MapRestart\n" ); - } - - CG_InitLocalEntities(); - CG_InitMarkPolys(); - CG_ClearParticles (); - - // make sure the "3 frags left" warnings play again - cg.fraglimitWarnings = 0; - - cg.timelimitWarnings = 0; - - cg.intermissionStarted = qfalse; - - cgs.voteTime = 0; - - cg.mapRestart = qtrue; - - CG_StartMusic(); - - trap_S_ClearLoopingSounds(qtrue); - - // we really should clear more parts of cg here and stop sounds - - // play the "fight" sound if this is a restart without warmup - if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { - trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); - CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); - } -#ifdef MISSIONPACK - if (cg_singlePlayerActive.integer) { - trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); - if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { - trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); - } - } -#endif - trap_Cvar_Set("cg_thirdPerson", "0"); -} - -#define MAX_VOICEFILESIZE 16384 -#define MAX_VOICEFILES 8 -#define MAX_VOICECHATS 64 -#define MAX_VOICESOUNDS 64 -#define MAX_CHATSIZE 64 -#define MAX_HEADMODELS 64 - -typedef struct voiceChat_s -{ - char id[64]; - int numSounds; - sfxHandle_t sounds[MAX_VOICESOUNDS]; - char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; -} voiceChat_t; - -typedef struct voiceChatList_s -{ - char name[64]; - int gender; - int numVoiceChats; - voiceChat_t voiceChats[MAX_VOICECHATS]; -} voiceChatList_t; - -typedef struct headModelVoiceChat_s -{ - char headmodel[64]; - int voiceChatNum; -} headModelVoiceChat_t; - -voiceChatList_t voiceChatLists[MAX_VOICEFILES]; -headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; - -/* -================= -CG_ParseVoiceChats -================= -*/ -int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { - int len, i; - fileHandle_t f; - char buf[MAX_VOICEFILESIZE]; - char **p, *ptr; - char *token; - voiceChat_t *voiceChats; - qboolean compress; - sfxHandle_t sound; - - compress = qtrue; - if (cg_buildScript.integer) { - compress = qfalse; - } - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); - return qfalse; - } - if ( len >= MAX_VOICEFILESIZE ) { - trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); - trap_FS_FCloseFile( f ); - return qfalse; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - ptr = buf; - p = &ptr; - - Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); - voiceChats = voiceChatList->voiceChats; - for ( i = 0; i < maxVoiceChats; i++ ) { - voiceChats[i].id[0] = 0; - } - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - if (!Q_stricmp(token, "female")) { - voiceChatList->gender = GENDER_FEMALE; - } - else if (!Q_stricmp(token, "male")) { - voiceChatList->gender = GENDER_MALE; - } - else if (!Q_stricmp(token, "neuter")) { - voiceChatList->gender = GENDER_NEUTER; - } - else { - trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); - return qfalse; - } - - voiceChatList->numVoiceChats = 0; - while ( 1 ) { - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); - token = COM_ParseExt(p, qtrue); - if (Q_stricmp(token, "{")) { - trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); - return qfalse; - } - voiceChats[voiceChatList->numVoiceChats].numSounds = 0; - while(1) { - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - if (!Q_stricmp(token, "}")) - break; - sound = trap_S_RegisterSound( token, compress ); - voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return qtrue; - } - Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ - voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); - if (sound) - voiceChats[voiceChatList->numVoiceChats].numSounds++; - if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) - break; - } - voiceChatList->numVoiceChats++; - if (voiceChatList->numVoiceChats >= maxVoiceChats) - return qtrue; - } - return qtrue; -} - -/* -================= -CG_LoadVoiceChats -================= -*/ -void CG_LoadVoiceChats( void ) { - int size; - - size = trap_MemoryRemaining(); - CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); - CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); - CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); -} - -/* -================= -CG_HeadModelVoiceChats -================= -*/ -int CG_HeadModelVoiceChats( char *filename ) { - int len, i; - fileHandle_t f; - char buf[MAX_VOICEFILESIZE]; - char **p, *ptr; - char *token; - - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if ( !f ) { - //trap_Print( va( "voice chat file not found: %s\n", filename ) ); - return -1; - } - if ( len >= MAX_VOICEFILESIZE ) { - trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); - trap_FS_FCloseFile( f ); - return -1; - } - - trap_FS_Read( buf, len, f ); - buf[len] = 0; - trap_FS_FCloseFile( f ); - - ptr = buf; - p = &ptr; - - token = COM_ParseExt(p, qtrue); - if (!token || token[0] == 0) { - return -1; - } - - for ( i = 0; i < MAX_VOICEFILES; i++ ) { - if ( !Q_stricmp(token, voiceChatLists[i].name) ) { - return i; - } - } - - //FIXME: maybe try to load the .voice file which name is stored in token? - - return -1; -} - - -/* -================= -CG_GetVoiceChat -================= -*/ -int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { - int i, rnd; - - for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { - if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { - rnd = random() * voiceChatList->voiceChats[i].numSounds; - *snd = voiceChatList->voiceChats[i].sounds[rnd]; - *chat = voiceChatList->voiceChats[i].chats[rnd]; - return qtrue; - } - } - return qfalse; -} - -/* -================= -CG_VoiceChatListForClient -================= -*/ -voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { - clientInfo_t *ci; - int voiceChatNum, i, j, k, gender; - char filename[MAX_QPATH], headModelName[MAX_QPATH]; - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - for ( k = 0; k < 2; k++ ) { - if ( k == 0 ) { - if (ci->headModelName[0] == '*') { - Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); - } - else { - Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); - } - } - else { - if (ci->headModelName[0] == '*') { - Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); - } - else { - Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); - } - } - // find the voice file for the head model the client uses - for ( i = 0; i < MAX_HEADMODELS; i++ ) { - if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { - break; - } - } - if (i < MAX_HEADMODELS) { - return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; - } - // find a .vc file - for ( i = 0; i < MAX_HEADMODELS; i++ ) { - if (!strlen(headModelVoiceChat[i].headmodel)) { - Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); - voiceChatNum = CG_HeadModelVoiceChats(filename); - if (voiceChatNum == -1) - break; - Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), - "%s", headModelName); - headModelVoiceChat[i].voiceChatNum = voiceChatNum; - return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; - } - } - } - gender = ci->gender; - for (k = 0; k < 2; k++) { - // just pick the first with the right gender - for ( i = 0; i < MAX_VOICEFILES; i++ ) { - if (strlen(voiceChatLists[i].name)) { - if (voiceChatLists[i].gender == gender) { - // store this head model with voice chat for future reference - for ( j = 0; j < MAX_HEADMODELS; j++ ) { - if (!strlen(headModelVoiceChat[j].headmodel)) { - Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), - "%s", headModelName); - headModelVoiceChat[j].voiceChatNum = i; - break; - } - } - return &voiceChatLists[i]; - } - } - } - // fall back to male gender because we don't have neuter in the mission pack - if (gender == GENDER_MALE) - break; - gender = GENDER_MALE; - } - // store this head model with voice chat for future reference - for ( j = 0; j < MAX_HEADMODELS; j++ ) { - if (!strlen(headModelVoiceChat[j].headmodel)) { - Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), - "%s", headModelName); - headModelVoiceChat[j].voiceChatNum = 0; - break; - } - } - // just return the first voice chat list - return &voiceChatLists[0]; -} - -#define MAX_VOICECHATBUFFER 32 - -typedef struct bufferedVoiceChat_s -{ - int clientNum; - sfxHandle_t snd; - int voiceOnly; - char cmd[MAX_SAY_TEXT]; - char message[MAX_SAY_TEXT]; -} bufferedVoiceChat_t; - -bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; - -/* -================= -CG_PlayVoiceChat -================= -*/ -void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { -#ifdef MISSIONPACK - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - if ( !cg_noVoiceChats.integer ) { - trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); - if (vchat->clientNum != cg.snap->ps.clientNum) { - int orderTask = CG_ValidOrder(vchat->cmd); - if (orderTask > 0) { - cgs.acceptOrderTime = cg.time + 5000; - Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); - cgs.acceptTask = orderTask; - cgs.acceptLeader = vchat->clientNum; - } - // see if this was an order - CG_ShowResponseHead(); - } - } - if (!vchat->voiceOnly && !cg_noVoiceText.integer) { - CG_AddToTeamChat( vchat->message ); - CG_Printf( "%s\n", vchat->message ); - } - voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; -#endif -} - -/* -===================== -CG_PlayBufferedVoieChats -===================== -*/ -void CG_PlayBufferedVoiceChats( void ) { -#ifdef MISSIONPACK - if ( cg.voiceChatTime < cg.time ) { - if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { - // - CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); - // - cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; - cg.voiceChatTime = cg.time + 1000; - } - } -#endif -} - -/* -===================== -CG_AddBufferedVoiceChat -===================== -*/ -void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { -#ifdef MISSIONPACK - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); - cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; - if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { - CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); - cg.voiceChatBufferOut++; - } -#endif -} - -/* -================= -CG_VoiceChatLocal -================= -*/ -void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { -#ifdef MISSIONPACK - char *chat; - voiceChatList_t *voiceChatList; - clientInfo_t *ci; - sfxHandle_t snd; - bufferedVoiceChat_t vchat; - - // if we are going into the intermission, don't start any voices - if ( cg.intermissionStarted ) { - return; - } - - if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { - clientNum = 0; - } - ci = &cgs.clientinfo[ clientNum ]; - - cgs.currentVoiceClient = clientNum; - - voiceChatList = CG_VoiceChatListForClient( clientNum ); - - if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { - // - if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { - vchat.clientNum = clientNum; - vchat.snd = snd; - vchat.voiceOnly = voiceOnly; - Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); - if ( mode == SAY_TELL ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else if ( mode == SAY_TEAM ) { - Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - else { - Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); - } - CG_AddBufferedVoiceChat(&vchat); - } - } -#endif -} - -/* -================= -CG_VoiceChat -================= -*/ -void CG_VoiceChat( int mode ) { -#ifdef MISSIONPACK - const char *cmd; - int clientNum, color; - qboolean voiceOnly; - - voiceOnly = atoi(CG_Argv(1)); - clientNum = atoi(CG_Argv(2)); - color = atoi(CG_Argv(3)); - cmd = CG_Argv(4); - - if (cg_noTaunt.integer != 0) { - if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ - !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ - !strcmp(cmd, VOICECHAT_PRAISE)) { - return; - } - } - - CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); -#endif -} - -/* -================= -CG_RemoveChatEscapeChar -================= -*/ -static void CG_RemoveChatEscapeChar( char *text ) { - int i, l; - - l = 0; - for ( i = 0; text[i]; i++ ) { - if (text[i] == '\x19') - continue; - text[l++] = text[i]; - } - text[l] = '\0'; -} - -/* -================= -CG_ServerCommand - -The string has been tokenized and can be retrieved with -Cmd_Argc() / Cmd_Argv() -================= -*/ -static void CG_ServerCommand( void ) { - const char *cmd; - char text[MAX_SAY_TEXT]; - - cmd = CG_Argv(0); - - if ( !cmd[0] ) { - // server claimed the command - return; - } - - if ( !strcmp( cmd, "cp" ) ) { - CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); - return; - } - - if ( !strcmp( cmd, "cs" ) ) { - CG_ConfigStringModified(); - return; - } - - if ( !strcmp( cmd, "print" ) ) { - CG_Printf( "%s", CG_Argv(1) ); -#ifdef MISSIONPACK - cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about - // votes passing or failing - if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { - trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); - } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { - trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); - } -#endif - return; - } - - if ( !strcmp( cmd, "chat" ) ) { - if ( !cg_teamChatsOnly.integer ) { - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); - CG_RemoveChatEscapeChar( text ); - CG_Printf( "%s\n", text ); - } - return; - } - - if ( !strcmp( cmd, "tchat" ) ) { - trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); - Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); - CG_RemoveChatEscapeChar( text ); - CG_AddToTeamChat( text ); - CG_Printf( "%s\n", text ); - return; - } - if ( !strcmp( cmd, "vchat" ) ) { - CG_VoiceChat( SAY_ALL ); - return; - } - - if ( !strcmp( cmd, "vtchat" ) ) { - CG_VoiceChat( SAY_TEAM ); - return; - } - - if ( !strcmp( cmd, "vtell" ) ) { - CG_VoiceChat( SAY_TELL ); - return; - } - - if ( !strcmp( cmd, "scores" ) ) { - CG_ParseScores(); - return; - } - - if ( !strcmp( cmd, "tinfo" ) ) { - CG_ParseTeamInfo(); - return; - } - - if ( !strcmp( cmd, "map_restart" ) ) { - CG_MapRestart(); - return; - } - - if ( Q_stricmp (cmd, "remapShader") == 0 ) { - if (trap_Argc() == 4) { - trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); - } - } - - // loaddeferred can be both a servercmd and a consolecmd - if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo - CG_LoadDeferredPlayers(); - return; - } - - // clientLevelShot is sent before taking a special screenshot for - // the menu system during development - if ( !strcmp( cmd, "clientLevelShot" ) ) { - cg.levelShot = qtrue; - return; - } - - CG_Printf( "Unknown client game command: %s\n", cmd ); -} - - -/* -==================== -CG_ExecuteNewServerCommands - -Execute all of the server commands that were received along -with this this snapshot. -==================== -*/ -void CG_ExecuteNewServerCommands( int latestSequence ) { - while ( cgs.serverCommandSequence < latestSequence ) { - if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { - CG_ServerCommand(); - } - } -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../../ui/menudef.h" // bk001205 - for Q3_ui as well + +typedef struct { + const char *order; + int taskNum; +} orderTask_t; + +static const orderTask_t validOrders[] = { + { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, + { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, + { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, + { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, + { VOICECHAT_PATROL, TEAMTASK_PATROL }, + { VOICECHAT_CAMP, TEAMTASK_CAMP }, + { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, + { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, + { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } +}; + +static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); + +#ifdef MISSIONPACK // bk001204 +static int CG_ValidOrder(const char *p) { + int i; + for (i = 0; i < numValidOrders; i++) { + if (Q_stricmp(p, validOrders[i].order) == 0) { + return validOrders[i].taskNum; + } + } + return -1; +} +#endif + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups; + + cg.numScores = atoi( CG_Argv( 1 ) ); + if ( cg.numScores > MAX_CLIENTS ) { + cg.numScores = MAX_CLIENTS; + } + + cg.teamScores[0] = atoi( CG_Argv( 2 ) ); + cg.teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + for ( i = 0 ; i < cg.numScores ; i++ ) { + // + cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); + powerups = atoi( CG_Argv( i * 14 + 9 ) ); + cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); + cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); + cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); + cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); + cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); + cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); + cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); + cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { + cg.scores[i].client = 0; + } + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; + + cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; + } +#ifdef MISSIONPACK + CG_SetScoreSelection(NULL); +#endif + +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 6 + 2 ) ); + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); + cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); + cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); + trap_Cvar_Set("g_redTeam", cgs.redTeam); + Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); + trap_Cvar_Set("g_blueTeam", cgs.blueTeam); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + if ( warmup == 0 && cg.warmup ) { + + } else if ( warmup > 0 && cg.warmup <= 0 ) { +#ifdef MISSIONPACK + if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { + trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); + } else +#endif + { + trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); + } + } + + cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) { + const char *s; + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + if( cgs.gametype == GT_CTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.flagStatus = s[0] - '0'; + } +#endif + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged(void) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while (o && *o) { + n = strstr(o, "="); + if (n && *n) { + strncpy(originalShader, o, n-o); + originalShader[n-o] = 0; + n++; + t = strstr(n, ":"); + if (t && *t) { + strncpy(newShader, n, t-n); + newShader[t-n] = 0; + } else { + break; + } + t++; + o = strstr(t, "@"); + if (o) { + strncpy(timeOffset, t, o-t); + timeOffset[o-t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic(); + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif //MISSIONPACK + } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { + cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; + } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { + cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; + } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { + cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; + } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { + Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif + } else if ( num == CS_INTERMISSION ) { + cg.intermissionStarted = atoi( str ); + } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); + } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { + if ( str[0] != '*' ) { // player specific sounds don't register here + cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); + } + } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { + CG_NewClientInfo( num - CS_PLAYERS ); + CG_BuildSpectatorString(); + } else if ( num == CS_FLAGSTATUS ) { + if( cgs.gametype == GT_CTF ) { + // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped + cgs.redflag = str[0] - '0'; + cgs.blueflag = str[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + cgs.flagStatus = str[0] - '0'; + } +#endif + } + else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } + +} + + +/* +======================= +CG_AddToTeamChat + +======================= +*/ +static void CG_AddToTeamChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + + if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { + // team chat disabled, dump into normal chat + cgs.teamChatPos = cgs.teamLastChatPos = 0; + return; + } + + len = 0; + + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while (*str) { + if (len > TEAMCHAT_WIDTH - 1) { + if (ls) { + str -= (p - ls); + str++; + p -= (p - ls); + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + + cgs.teamChatPos++; + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if (*str == ' ') { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + cgs.teamChatPos++; + + if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) + cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; +} + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + CG_ClearParticles (); + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime = 0; + + cg.mapRestart = qtrue; + + CG_StartMusic(); + + trap_S_ClearLoopingSounds(qtrue); + + // we really should clear more parts of cg here and stop sounds + + // play the "fight" sound if this is a restart without warmup + if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); + } +#ifdef MISSIONPACK + if (cg_singlePlayerActive.integer) { + trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); + if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { + trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); + } + } +#endif + trap_Cvar_Set("cg_thirdPerson", "0"); +} + +#define MAX_VOICEFILESIZE 16384 +#define MAX_VOICEFILES 8 +#define MAX_VOICECHATS 64 +#define MAX_VOICESOUNDS 64 +#define MAX_CHATSIZE 64 +#define MAX_HEADMODELS 64 + +typedef struct voiceChat_s +{ + char id[64]; + int numSounds; + sfxHandle_t sounds[MAX_VOICESOUNDS]; + char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; +} voiceChat_t; + +typedef struct voiceChatList_s +{ + char name[64]; + int gender; + int numVoiceChats; + voiceChat_t voiceChats[MAX_VOICECHATS]; +} voiceChatList_t; + +typedef struct headModelVoiceChat_s +{ + char headmodel[64]; + int voiceChatNum; +} headModelVoiceChat_t; + +voiceChatList_t voiceChatLists[MAX_VOICEFILES]; +headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; + +/* +================= +CG_ParseVoiceChats +================= +*/ +int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + voiceChat_t *voiceChats; + qboolean compress; + sfxHandle_t sound; + + compress = qtrue; + if (cg_buildScript.integer) { + compress = qfalse; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); + return qfalse; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); + voiceChats = voiceChatList->voiceChats; + for ( i = 0; i < maxVoiceChats; i++ ) { + voiceChats[i].id[0] = 0; + } + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "female")) { + voiceChatList->gender = GENDER_FEMALE; + } + else if (!Q_stricmp(token, "male")) { + voiceChatList->gender = GENDER_MALE; + } + else if (!Q_stricmp(token, "neuter")) { + voiceChatList->gender = GENDER_NEUTER; + } + else { + trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); + return qfalse; + } + + voiceChatList->numVoiceChats = 0; + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); + token = COM_ParseExt(p, qtrue); + if (Q_stricmp(token, "{")) { + trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); + return qfalse; + } + voiceChats[voiceChatList->numVoiceChats].numSounds = 0; + while(1) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "}")) + break; + sound = trap_S_RegisterSound( token, compress ); + voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ + voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); + if (sound) + voiceChats[voiceChatList->numVoiceChats].numSounds++; + if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) + break; + } + voiceChatList->numVoiceChats++; + if (voiceChatList->numVoiceChats >= maxVoiceChats) + return qtrue; + } + return qtrue; +} + +/* +================= +CG_LoadVoiceChats +================= +*/ +void CG_LoadVoiceChats( void ) { + int size; + + size = trap_MemoryRemaining(); + CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); + CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); +} + +/* +================= +CG_HeadModelVoiceChats +================= +*/ +int CG_HeadModelVoiceChats( char *filename ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + //trap_Print( va( "voice chat file not found: %s\n", filename ) ); + return -1; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return -1; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return -1; + } + + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if ( !Q_stricmp(token, voiceChatLists[i].name) ) { + return i; + } + } + + //FIXME: maybe try to load the .voice file which name is stored in token? + + return -1; +} + + +/* +================= +CG_GetVoiceChat +================= +*/ +int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { + int i, rnd; + + for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { + if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { + rnd = random() * voiceChatList->voiceChats[i].numSounds; + *snd = voiceChatList->voiceChats[i].sounds[rnd]; + *chat = voiceChatList->voiceChats[i].chats[rnd]; + return qtrue; + } + } + return qfalse; +} + +/* +================= +CG_VoiceChatListForClient +================= +*/ +voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { + clientInfo_t *ci; + int voiceChatNum, i, j, k, gender; + char filename[MAX_QPATH], headModelName[MAX_QPATH]; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( k = 0; k < 2; k++ ) { + if ( k == 0 ) { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); + } + } + else { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); + } + } + // find the voice file for the head model the client uses + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { + break; + } + } + if (i < MAX_HEADMODELS) { + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + // find a .vc file + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!strlen(headModelVoiceChat[i].headmodel)) { + Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); + voiceChatNum = CG_HeadModelVoiceChats(filename); + if (voiceChatNum == -1) + break; + Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), + "%s", headModelName); + headModelVoiceChat[i].voiceChatNum = voiceChatNum; + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + } + } + gender = ci->gender; + for (k = 0; k < 2; k++) { + // just pick the first with the right gender + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if (strlen(voiceChatLists[i].name)) { + if (voiceChatLists[i].gender == gender) { + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = i; + break; + } + } + return &voiceChatLists[i]; + } + } + } + // fall back to male gender because we don't have neuter in the mission pack + if (gender == GENDER_MALE) + break; + gender = GENDER_MALE; + } + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = 0; + break; + } + } + // just return the first voice chat list + return &voiceChatLists[0]; +} + +#define MAX_VOICECHATBUFFER 32 + +typedef struct bufferedVoiceChat_s +{ + int clientNum; + sfxHandle_t snd; + int voiceOnly; + char cmd[MAX_SAY_TEXT]; + char message[MAX_SAY_TEXT]; +} bufferedVoiceChat_t; + +bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; + +/* +================= +CG_PlayVoiceChat +================= +*/ +void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( !cg_noVoiceChats.integer ) { + trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); + if (vchat->clientNum != cg.snap->ps.clientNum) { + int orderTask = CG_ValidOrder(vchat->cmd); + if (orderTask > 0) { + cgs.acceptOrderTime = cg.time + 5000; + Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); + cgs.acceptTask = orderTask; + cgs.acceptLeader = vchat->clientNum; + } + // see if this was an order + CG_ShowResponseHead(); + } + } + if (!vchat->voiceOnly && !cg_noVoiceText.integer) { + CG_AddToTeamChat( vchat->message ); + CG_Printf( "%s\n", vchat->message ); + } + voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; +#endif +} + +/* +===================== +CG_PlayBufferedVoieChats +===================== +*/ +void CG_PlayBufferedVoiceChats( void ) { +#ifdef MISSIONPACK + if ( cg.voiceChatTime < cg.time ) { + if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { + // + CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); + // + cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; + cg.voiceChatTime = cg.time + 1000; + } + } +#endif +} + +/* +===================== +CG_AddBufferedVoiceChat +===================== +*/ +void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); + cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; + if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + cg.voiceChatBufferOut++; + } +#endif +} + +/* +================= +CG_VoiceChatLocal +================= +*/ +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { +#ifdef MISSIONPACK + char *chat; + voiceChatList_t *voiceChatList; + clientInfo_t *ci; + sfxHandle_t snd; + bufferedVoiceChat_t vchat; + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + cgs.currentVoiceClient = clientNum; + + voiceChatList = CG_VoiceChatListForClient( clientNum ); + + if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { + // + if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.voiceOnly = voiceOnly; + Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); + if ( mode == SAY_TELL ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else if ( mode == SAY_TEAM ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else { + Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + CG_AddBufferedVoiceChat(&vchat); + } + } +#endif +} + +/* +================= +CG_VoiceChat +================= +*/ +void CG_VoiceChat( int mode ) { +#ifdef MISSIONPACK + const char *cmd; + int clientNum, color; + qboolean voiceOnly; + + voiceOnly = atoi(CG_Argv(1)); + clientNum = atoi(CG_Argv(2)); + color = atoi(CG_Argv(3)); + cmd = CG_Argv(4); + + if (cg_noTaunt.integer != 0) { + if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ + !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ + !strcmp(cmd, VOICECHAT_PRAISE)) { + return; + } + } + + CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); +#endif +} + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (text[i] == '\x19') + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + CG_Printf( "%s", CG_Argv(1) ); +#ifdef MISSIONPACK + cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about + // votes passing or failing + if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { + trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); + } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { + trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); + } +#endif + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_Printf( "%s\n", text ); + } + return; + } + + if ( !strcmp( cmd, "tchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddToTeamChat( text ); + CG_Printf( "%s\n", text ); + return; + } + if ( !strcmp( cmd, "vchat" ) ) { + CG_VoiceChat( SAY_ALL ); + return; + } + + if ( !strcmp( cmd, "vtchat" ) ) { + CG_VoiceChat( SAY_TEAM ); + return; + } + + if ( !strcmp( cmd, "vtell" ) ) { + CG_VoiceChat( SAY_TELL ); + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + + if ( Q_stricmp (cmd, "remapShader") == 0 ) { + if (trap_Argc() == 4) { + trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) { + cg.levelShot = qtrue; + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c index add49f0..f337e08 100755 --- a/code/cgame/cg_snapshot.c +++ b/code/cgame/cg_snapshot.c @@ -1,403 +1,403 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_snapshot.c -- things that happen on snapshot transition, -// not necessarily every single rendered frame - -#include "cg_local.h" - - - -/* -================== -CG_ResetEntity -================== -*/ -static void CG_ResetEntity( centity_t *cent ) { - // if the previous snapshot this entity was updated in is at least - // an event window back in time then we can reset the previous event - if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { - cent->previousEvent = 0; - } - - cent->trailTime = cg.snap->serverTime; - - VectorCopy (cent->currentState.origin, cent->lerpOrigin); - VectorCopy (cent->currentState.angles, cent->lerpAngles); - if ( cent->currentState.eType == ET_PLAYER ) { - CG_ResetPlayerEntity( cent ); - } -} - -/* -=============== -CG_TransitionEntity - -cent->nextState is moved to cent->currentState and events are fired -=============== -*/ -static void CG_TransitionEntity( centity_t *cent ) { - cent->currentState = cent->nextState; - cent->currentValid = qtrue; - - // reset if the entity wasn't in the last frame or was teleported - if ( !cent->interpolate ) { - CG_ResetEntity( cent ); - } - - // clear the next state. if will be set by the next CG_SetNextSnap - cent->interpolate = qfalse; - - // check for events - CG_CheckEvents( cent ); -} - - -/* -================== -CG_SetInitialSnapshot - -This will only happen on the very first snapshot, or -on tourney restarts. All other times will use -CG_TransitionSnapshot instead. - -FIXME: Also called by map_restart? -================== -*/ -void CG_SetInitialSnapshot( snapshot_t *snap ) { - int i; - centity_t *cent; - entityState_t *state; - - cg.snap = snap; - - BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); - - // sort out solid entities - CG_BuildSolidList(); - - CG_ExecuteNewServerCommands( snap->serverCommandSequence ); - - // set our local weapon selection pointer to - // what the server has indicated the current weapon is - CG_Respawn(); - - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - state = &cg.snap->entities[ i ]; - cent = &cg_entities[ state->number ]; - - memcpy(¢->currentState, state, sizeof(entityState_t)); - //cent->currentState = *state; - cent->interpolate = qfalse; - cent->currentValid = qtrue; - - CG_ResetEntity( cent ); - - // check for events - CG_CheckEvents( cent ); - } -} - - -/* -=================== -CG_TransitionSnapshot - -The transition point from snap to nextSnap has passed -=================== -*/ -static void CG_TransitionSnapshot( void ) { - centity_t *cent; - snapshot_t *oldFrame; - int i; - - if ( !cg.snap ) { - CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); - } - if ( !cg.nextSnap ) { - CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); - } - - // execute any server string commands before transitioning entities - CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); - - // if we had a map_restart, set everthing with initial - if ( !cg.snap ) { - } - - // clear the currentValid flag for all entities in the existing snapshot - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - cent->currentValid = qfalse; - } - - // move nextSnap to snap and do the transitions - oldFrame = cg.snap; - cg.snap = cg.nextSnap; - - BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); - cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; - - for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { - cent = &cg_entities[ cg.snap->entities[ i ].number ]; - CG_TransitionEntity( cent ); - - // remember time of snapshot this entity was last updated in - cent->snapShotTime = cg.snap->serverTime; - } - - cg.nextSnap = NULL; - - // check for playerstate transition events - if ( oldFrame ) { - playerState_t *ops, *ps; - - ops = &oldFrame->ps; - ps = &cg.snap->ps; - // teleporting checks are irrespective of prediction - if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { - cg.thisFrameTeleport = qtrue; // will be cleared by prediction code - } - - // if we are not doing client side movement prediction for any - // reason, then the client events and view changes will be issued now - if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) - || cg_nopredict.integer || cg_synchronousClients.integer ) { - CG_TransitionPlayerState( ps, ops ); - } - } - -} - - -/* -=================== -CG_SetNextSnap - -A new snapshot has just been read in from the client system. -=================== -*/ -static void CG_SetNextSnap( snapshot_t *snap ) { - int num; - entityState_t *es; - centity_t *cent; - - cg.nextSnap = snap; - - BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); - cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; - - // check for extrapolation errors - for ( num = 0 ; num < snap->numEntities ; num++ ) { - es = &snap->entities[num]; - cent = &cg_entities[ es->number ]; - - memcpy(¢->nextState, es, sizeof(entityState_t)); - //cent->nextState = *es; - - // if this frame is a teleport, or the entity wasn't in the - // previous frame, don't interpolate - if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { - cent->interpolate = qfalse; - } else { - cent->interpolate = qtrue; - } - } - - // if the next frame is a teleport for the playerstate, we - // can't interpolate during demos - if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { - cg.nextFrameTeleport = qtrue; - } else { - cg.nextFrameTeleport = qfalse; - } - - // if changing follow mode, don't interpolate - if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { - cg.nextFrameTeleport = qtrue; - } - - // if changing server restarts, don't interpolate - if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { - cg.nextFrameTeleport = qtrue; - } - - // sort out solid entities - CG_BuildSolidList(); -} - - -/* -======================== -CG_ReadNextSnapshot - -This is the only place new snapshots are requested -This may increment cgs.processedSnapshotNum multiple -times if the client system fails to return a -valid snapshot. -======================== -*/ -static snapshot_t *CG_ReadNextSnapshot( void ) { - qboolean r; - snapshot_t *dest; - - if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { - CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", - cg.latestSnapshotNum, cgs.processedSnapshotNum ); - } - - while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { - // decide which of the two slots to load it into - if ( cg.snap == &cg.activeSnapshots[0] ) { - dest = &cg.activeSnapshots[1]; - } else { - dest = &cg.activeSnapshots[0]; - } - - // try to read the snapshot from the client system - cgs.processedSnapshotNum++; - r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); - - // FIXME: why would trap_GetSnapshot return a snapshot with the same server time - if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { - //continue; - } - - // if it succeeded, return - if ( r ) { - CG_AddLagometerSnapshotInfo( dest ); - return dest; - } - - // a GetSnapshot will return failure if the snapshot - // never arrived, or is so old that its entities - // have been shoved off the end of the circular - // buffer in the client system. - - // record as a dropped packet - CG_AddLagometerSnapshotInfo( NULL ); - - // If there are additional snapshots, continue trying to - // read them. - } - - // nothing left to read - return NULL; -} - - -/* -============ -CG_ProcessSnapshots - -We are trying to set up a renderable view, so determine -what the simulated time is, and try to get snapshots -both before and after that time if available. - -If we don't have a valid cg.snap after exiting this function, -then a 3D game view cannot be rendered. This should only happen -right after the initial connection. After cg.snap has been valid -once, it will never turn invalid. - -Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot -hasn't arrived yet (it becomes an extrapolating situation instead -of an interpolating one) - -============ -*/ -void CG_ProcessSnapshots( void ) { - snapshot_t *snap; - int n; - - // see what the latest snapshot the client system has is - trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); - if ( n != cg.latestSnapshotNum ) { - if ( n < cg.latestSnapshotNum ) { - // this should never happen - CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); - } - cg.latestSnapshotNum = n; - } - - // If we have yet to receive a snapshot, check for it. - // Once we have gotten the first snapshot, cg.snap will - // always have valid data for the rest of the game - while ( !cg.snap ) { - snap = CG_ReadNextSnapshot(); - if ( !snap ) { - // we can't continue until we get a snapshot - return; - } - - // set our weapon selection to what - // the playerstate is currently using - if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { - CG_SetInitialSnapshot( snap ); - } - } - - // loop until we either have a valid nextSnap with a serverTime - // greater than cg.time to interpolate towards, or we run - // out of available snapshots - do { - // if we don't have a nextframe, try and read a new one in - if ( !cg.nextSnap ) { - snap = CG_ReadNextSnapshot(); - - // if we still don't have a nextframe, we will just have to - // extrapolate - if ( !snap ) { - break; - } - - CG_SetNextSnap( snap ); - - - // if time went backwards, we have a level restart - if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { - CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); - } - } - - // if our time is < nextFrame's, we have a nice interpolating state - if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { - break; - } - - // we have passed the transition from nextFrame to frame - CG_TransitionSnapshot(); - } while ( 1 ); - - // assert our valid conditions upon exiting - if ( cg.snap == NULL ) { - CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); - } - if ( cg.time < cg.snap->serverTime ) { - // this can happen right after a vid_restart - cg.time = cg.snap->serverTime; - } - if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { - CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); - } - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + + + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { + cent->previousEvent = 0; + } + + cent->trailTime = cg.snap->serverTime; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + if ( cent->currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( cent ); + } +} + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) { + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy(¢->currentState, state, sizeof(entityState_t)); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + } + if ( !cg.nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if ( !cg.snap ) { + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_TransitionPlayerState( ps, ops ); + } + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + memcpy(¢->nextState, es, sizeof(entityState_t)); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg.nextFrameTeleport = qtrue; + } else { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { + //continue; + } + + // if it succeeded, return + if ( r ) { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) { + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + if ( n != cg.latestSnapshotNum ) { + if ( n < cg.latestSnapshotNum ) { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while ( !cg.snap ) { + snap = CG_ReadNextSnapshot(); + if ( !snap ) { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + } + if ( cg.time < cg.snap->serverTime ) { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } + +} + diff --git a/code/cgame/cg_syscalls.asm b/code/cgame/cg_syscalls.asm index 8363fe3..0874b47 100755 --- a/code/cgame/cg_syscalls.asm +++ b/code/cgame/cg_syscalls.asm @@ -1,106 +1,106 @@ -code - -equ trap_Print -1 -equ trap_Error -2 -equ trap_Milliseconds -3 -equ trap_Cvar_Register -4 -equ trap_Cvar_Update -5 -equ trap_Cvar_Set -6 -equ trap_Cvar_VariableStringBuffer -7 -equ trap_Argc -8 -equ trap_Argv -9 -equ trap_Args -10 -equ trap_FS_FOpenFile -11 -equ trap_FS_Read -12 -equ trap_FS_Write -13 -equ trap_FS_FCloseFile -14 -equ trap_SendConsoleCommand -15 -equ trap_AddCommand -16 -equ trap_SendClientCommand -17 -equ trap_UpdateScreen -18 -equ trap_CM_LoadMap -19 -equ trap_CM_NumInlineModels -20 -equ trap_CM_InlineModel -21 -equ trap_CM_LoadModel -22 -equ trap_CM_TempBoxModel -23 -equ trap_CM_PointContents -24 -equ trap_CM_TransformedPointContents -25 -equ trap_CM_BoxTrace -26 -equ trap_CM_TransformedBoxTrace -27 -equ trap_CM_MarkFragments -28 -equ trap_S_StartSound -29 -equ trap_S_StartLocalSound -30 -equ trap_S_ClearLoopingSounds -31 -equ trap_S_AddLoopingSound -32 -equ trap_S_UpdateEntityPosition -33 -equ trap_S_Respatialize -34 -equ trap_S_RegisterSound -35 -equ trap_S_StartBackgroundTrack -36 -equ trap_R_LoadWorldMap -37 -equ trap_R_RegisterModel -38 -equ trap_R_RegisterSkin -39 -equ trap_R_RegisterShader -40 -equ trap_R_ClearScene -41 -equ trap_R_AddRefEntityToScene -42 -equ trap_R_AddPolyToScene -43 -equ trap_R_AddLightToScene -44 -equ trap_R_RenderScene -45 -equ trap_R_SetColor -46 -equ trap_R_DrawStretchPic -47 -equ trap_R_ModelBounds -48 -equ trap_R_LerpTag -49 -equ trap_GetGlconfig -50 -equ trap_GetGameState -51 -equ trap_GetCurrentSnapshotNumber -52 -equ trap_GetSnapshot -53 -equ trap_GetServerCommand -54 -equ trap_GetCurrentCmdNumber -55 -equ trap_GetUserCmd -56 -equ trap_SetUserCmdValue -57 -equ trap_R_RegisterShaderNoMip -58 -equ trap_MemoryRemaining -59 -equ trap_R_RegisterFont -60 -equ trap_Key_IsDown -61 -equ trap_Key_GetCatcher -62 -equ trap_Key_SetCatcher -63 -equ trap_Key_GetKey -64 -equ trap_PC_AddGlobalDefine -65 -equ trap_PC_LoadSource -66 -equ trap_PC_FreeSource -67 -equ trap_PC_ReadToken -68 -equ trap_PC_SourceFileAndLine -69 -equ trap_S_StopBackgroundTrack -70 -equ trap_RealTime -71 -equ trap_SnapVector -72 -equ trap_RemoveCommand -73 -equ trap_R_LightForPoint -74 -equ trap_CIN_PlayCinematic -75 -equ trap_CIN_StopCinematic -76 -equ trap_CIN_RunCinematic -77 -equ trap_CIN_DrawCinematic -78 -equ trap_CIN_SetExtents -79 -equ trap_R_RemapShader -80 -equ trap_S_AddRealLoopingSound -81 -equ trap_S_StopLoopingSound -82 -equ trap_CM_TempCapsuleModel -83 -equ trap_CM_CapsuleTrace -84 -equ trap_CM_TransformedCapsuleTrace -85 -equ trap_R_AddAdditiveLightToScene -86 -equ trap_GetEntityToken -87 -equ trap_R_AddPolysToScene -88 -equ trap_R_inPVS -89 -equ trap_FS_Seek -90 - -equ memset -101 -equ memcpy -102 -equ strncpy -103 -equ sin -104 -equ cos -105 -equ atan2 -106 -equ sqrt -107 -equ floor -108 -equ ceil -109 -equ testPrintInt -110 -equ testPrintFloat -111 -equ acos -112 - +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableStringBuffer -7 +equ trap_Argc -8 +equ trap_Argv -9 +equ trap_Args -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_AddCommand -16 +equ trap_SendClientCommand -17 +equ trap_UpdateScreen -18 +equ trap_CM_LoadMap -19 +equ trap_CM_NumInlineModels -20 +equ trap_CM_InlineModel -21 +equ trap_CM_LoadModel -22 +equ trap_CM_TempBoxModel -23 +equ trap_CM_PointContents -24 +equ trap_CM_TransformedPointContents -25 +equ trap_CM_BoxTrace -26 +equ trap_CM_TransformedBoxTrace -27 +equ trap_CM_MarkFragments -28 +equ trap_S_StartSound -29 +equ trap_S_StartLocalSound -30 +equ trap_S_ClearLoopingSounds -31 +equ trap_S_AddLoopingSound -32 +equ trap_S_UpdateEntityPosition -33 +equ trap_S_Respatialize -34 +equ trap_S_RegisterSound -35 +equ trap_S_StartBackgroundTrack -36 +equ trap_R_LoadWorldMap -37 +equ trap_R_RegisterModel -38 +equ trap_R_RegisterSkin -39 +equ trap_R_RegisterShader -40 +equ trap_R_ClearScene -41 +equ trap_R_AddRefEntityToScene -42 +equ trap_R_AddPolyToScene -43 +equ trap_R_AddLightToScene -44 +equ trap_R_RenderScene -45 +equ trap_R_SetColor -46 +equ trap_R_DrawStretchPic -47 +equ trap_R_ModelBounds -48 +equ trap_R_LerpTag -49 +equ trap_GetGlconfig -50 +equ trap_GetGameState -51 +equ trap_GetCurrentSnapshotNumber -52 +equ trap_GetSnapshot -53 +equ trap_GetServerCommand -54 +equ trap_GetCurrentCmdNumber -55 +equ trap_GetUserCmd -56 +equ trap_SetUserCmdValue -57 +equ trap_R_RegisterShaderNoMip -58 +equ trap_MemoryRemaining -59 +equ trap_R_RegisterFont -60 +equ trap_Key_IsDown -61 +equ trap_Key_GetCatcher -62 +equ trap_Key_SetCatcher -63 +equ trap_Key_GetKey -64 +equ trap_PC_AddGlobalDefine -65 +equ trap_PC_LoadSource -66 +equ trap_PC_FreeSource -67 +equ trap_PC_ReadToken -68 +equ trap_PC_SourceFileAndLine -69 +equ trap_S_StopBackgroundTrack -70 +equ trap_RealTime -71 +equ trap_SnapVector -72 +equ trap_RemoveCommand -73 +equ trap_R_LightForPoint -74 +equ trap_CIN_PlayCinematic -75 +equ trap_CIN_StopCinematic -76 +equ trap_CIN_RunCinematic -77 +equ trap_CIN_DrawCinematic -78 +equ trap_CIN_SetExtents -79 +equ trap_R_RemapShader -80 +equ trap_S_AddRealLoopingSound -81 +equ trap_S_StopLoopingSound -82 +equ trap_CM_TempCapsuleModel -83 +equ trap_CM_CapsuleTrace -84 +equ trap_CM_TransformedCapsuleTrace -85 +equ trap_R_AddAdditiveLightToScene -86 +equ trap_GetEntityToken -87 +equ trap_R_AddPolysToScene -88 +equ trap_R_inPVS -89 +equ trap_FS_Seek -90 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -108 +equ ceil -109 +equ testPrintInt -110 +equ testPrintFloat -111 +equ acos -112 + diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c index a7bf659..c7ad938 100755 --- a/code/cgame/cg_syscalls.c +++ b/code/cgame/cg_syscalls.c @@ -1,445 +1,445 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_syscalls.c -- this file is only included when building a dll -// cg_syscalls.asm is included instead when building a qvm -#ifdef Q3_VM -#error "Do not use in VM build" -#endif - -#include "cg_local.h" - -static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; - - -void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { - syscall = syscallptr; -} - - -int PASSFLOAT( float x ) { - float floatTemp; - floatTemp = x; - return *(int *)&floatTemp; -} - -void trap_Print( const char *fmt ) { - syscall( CG_PRINT, fmt ); -} - -void trap_Error( const char *fmt ) { - syscall( CG_ERROR, fmt ); -} - -int trap_Milliseconds( void ) { - return syscall( CG_MILLISECONDS ); -} - -void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { - syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); -} - -void trap_Cvar_Update( vmCvar_t *vmCvar ) { - syscall( CG_CVAR_UPDATE, vmCvar ); -} - -void trap_Cvar_Set( const char *var_name, const char *value ) { - syscall( CG_CVAR_SET, var_name, value ); -} - -void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { - syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); -} - -int trap_Argc( void ) { - return syscall( CG_ARGC ); -} - -void trap_Argv( int n, char *buffer, int bufferLength ) { - syscall( CG_ARGV, n, buffer, bufferLength ); -} - -void trap_Args( char *buffer, int bufferLength ) { - syscall( CG_ARGS, buffer, bufferLength ); -} - -int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { - return syscall( CG_FS_FOPENFILE, qpath, f, mode ); -} - -void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { - syscall( CG_FS_READ, buffer, len, f ); -} - -void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { - syscall( CG_FS_WRITE, buffer, len, f ); -} - -void trap_FS_FCloseFile( fileHandle_t f ) { - syscall( CG_FS_FCLOSEFILE, f ); -} - -int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { - return syscall( CG_FS_SEEK, f, offset, origin ); -} - -void trap_SendConsoleCommand( const char *text ) { - syscall( CG_SENDCONSOLECOMMAND, text ); -} - -void trap_AddCommand( const char *cmdName ) { - syscall( CG_ADDCOMMAND, cmdName ); -} - -void trap_RemoveCommand( const char *cmdName ) { - syscall( CG_REMOVECOMMAND, cmdName ); -} - -void trap_SendClientCommand( const char *s ) { - syscall( CG_SENDCLIENTCOMMAND, s ); -} - -void trap_UpdateScreen( void ) { - syscall( CG_UPDATESCREEN ); -} - -void trap_CM_LoadMap( const char *mapname ) { - syscall( CG_CM_LOADMAP, mapname ); -} - -int trap_CM_NumInlineModels( void ) { - return syscall( CG_CM_NUMINLINEMODELS ); -} - -clipHandle_t trap_CM_InlineModel( int index ) { - return syscall( CG_CM_INLINEMODEL, index ); -} - -clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { - return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); -} - -clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { - return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); -} - -int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { - return syscall( CG_CM_POINTCONTENTS, p, model ); -} - -int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { - return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); -} - -void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ) { - syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); -} - -void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask ) { - syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); -} - -void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ) { - syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); -} - -void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, - const vec3_t mins, const vec3_t maxs, - clipHandle_t model, int brushmask, - const vec3_t origin, const vec3_t angles ) { - syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); -} - -int trap_CM_MarkFragments( int numPoints, const vec3_t *points, - const vec3_t projection, - int maxPoints, vec3_t pointBuffer, - int maxFragments, markFragment_t *fragmentBuffer ) { - return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); -} - -void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { - syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); -} - -void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { - syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); -} - -void trap_S_ClearLoopingSounds( qboolean killall ) { - syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); -} - -void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { - syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); -} - -void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { - syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); -} - -void trap_S_StopLoopingSound( int entityNum ) { - syscall( CG_S_STOPLOOPINGSOUND, entityNum ); -} - -void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { - syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); -} - -void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { - syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); -} - -sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { - return syscall( CG_S_REGISTERSOUND, sample, compressed ); -} - -void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { - syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); -} - -void trap_R_LoadWorldMap( const char *mapname ) { - syscall( CG_R_LOADWORLDMAP, mapname ); -} - -qhandle_t trap_R_RegisterModel( const char *name ) { - return syscall( CG_R_REGISTERMODEL, name ); -} - -qhandle_t trap_R_RegisterSkin( const char *name ) { - return syscall( CG_R_REGISTERSKIN, name ); -} - -qhandle_t trap_R_RegisterShader( const char *name ) { - return syscall( CG_R_REGISTERSHADER, name ); -} - -qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { - return syscall( CG_R_REGISTERSHADERNOMIP, name ); -} - -void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { - syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); -} - -void trap_R_ClearScene( void ) { - syscall( CG_R_CLEARSCENE ); -} - -void trap_R_AddRefEntityToScene( const refEntity_t *re ) { - syscall( CG_R_ADDREFENTITYTOSCENE, re ); -} - -void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { - syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); -} - -void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { - syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); -} - -int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { - return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); -} - -void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { - syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); -} - -void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { - syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); -} - -void trap_R_RenderScene( const refdef_t *fd ) { - syscall( CG_R_RENDERSCENE, fd ); -} - -void trap_R_SetColor( const float *rgba ) { - syscall( CG_R_SETCOLOR, rgba ); -} - -void trap_R_DrawStretchPic( float x, float y, float w, float h, - float s1, float t1, float s2, float t2, qhandle_t hShader ) { - syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); -} - -void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { - syscall( CG_R_MODELBOUNDS, model, mins, maxs ); -} - -int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, - float frac, const char *tagName ) { - return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); -} - -void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { - syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); -} - -void trap_GetGlconfig( glconfig_t *glconfig ) { - syscall( CG_GETGLCONFIG, glconfig ); -} - -void trap_GetGameState( gameState_t *gamestate ) { - syscall( CG_GETGAMESTATE, gamestate ); -} - -void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { - syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); -} - -qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { - return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); -} - -qboolean trap_GetServerCommand( int serverCommandNumber ) { - return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); -} - -int trap_GetCurrentCmdNumber( void ) { - return syscall( CG_GETCURRENTCMDNUMBER ); -} - -qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { - return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); -} - -void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { - syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); -} - -void testPrintInt( char *string, int i ) { - syscall( CG_TESTPRINTINT, string, i ); -} - -void testPrintFloat( char *string, float f ) { - syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); -} - -int trap_MemoryRemaining( void ) { - return syscall( CG_MEMORY_REMAINING ); -} - -qboolean trap_Key_IsDown( int keynum ) { - return syscall( CG_KEY_ISDOWN, keynum ); -} - -int trap_Key_GetCatcher( void ) { - return syscall( CG_KEY_GETCATCHER ); -} - -void trap_Key_SetCatcher( int catcher ) { - syscall( CG_KEY_SETCATCHER, catcher ); -} - -int trap_Key_GetKey( const char *binding ) { - return syscall( CG_KEY_GETKEY, binding ); -} - -int trap_PC_AddGlobalDefine( char *define ) { - return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); -} - -int trap_PC_LoadSource( const char *filename ) { - return syscall( CG_PC_LOAD_SOURCE, filename ); -} - -int trap_PC_FreeSource( int handle ) { - return syscall( CG_PC_FREE_SOURCE, handle ); -} - -int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { - return syscall( CG_PC_READ_TOKEN, handle, pc_token ); -} - -int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { - return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); -} - -void trap_S_StopBackgroundTrack( void ) { - syscall( CG_S_STOPBACKGROUNDTRACK ); -} - -int trap_RealTime(qtime_t *qtime) { - return syscall( CG_REAL_TIME, qtime ); -} - -void trap_SnapVector( float *v ) { - syscall( CG_SNAPVECTOR, v ); -} - -// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) -int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { - return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); -} - -// stops playing the cinematic and ends it. should always return FMV_EOF -// cinematics must be stopped in reverse order of when they are started -e_status trap_CIN_StopCinematic(int handle) { - return syscall(CG_CIN_STOPCINEMATIC, handle); -} - - -// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. -e_status trap_CIN_RunCinematic (int handle) { - return syscall(CG_CIN_RUNCINEMATIC, handle); -} - - -// draws the current frame -void trap_CIN_DrawCinematic (int handle) { - syscall(CG_CIN_DRAWCINEMATIC, handle); -} - - -// allows you to resize the animation dynamically -void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { - syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); -} - -/* -qboolean trap_loadCamera( const char *name ) { - return syscall( CG_LOADCAMERA, name ); -} - -void trap_startCamera(int time) { - syscall(CG_STARTCAMERA, time); -} - -qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { - return syscall( CG_GETCAMERAINFO, time, origin, angles ); -} -*/ - -qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { - return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); -} - -qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { - return syscall( CG_R_INPVS, p1, p2 ); -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#ifdef Q3_VM +#error "Do not use in VM build" +#endif + +#include "cg_local.h" + +static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; + + +void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { + syscall = syscallptr; +} + + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { + return syscall( CG_FS_SEEK, f, offset, origin ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) { + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) { + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) { + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { + return syscall( CG_S_REGISTERSOUND, sample, compressed ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { + syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +/* +qboolean trap_loadCamera( const char *name ) { + return syscall( CG_LOADCAMERA, name ); +} + +void trap_startCamera(int time) { + syscall(CG_STARTCAMERA, time); +} + +qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { + return syscall( CG_GETCAMERAINFO, time, origin, angles ); +} +*/ + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { + return syscall( CG_R_INPVS, p1, p2 ); +} diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c index 83d5141..49c9ddd 100755 --- a/code/cgame/cg_view.c +++ b/code/cgame/cg_view.c @@ -1,876 +1,876 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_view.c -- setup all the parameters (position, angle, etc) -// for a 3D rendering -#include "cg_local.h" - - -/* -============================================================================= - - MODEL TESTING - -The viewthing and gun positioning tools from Q2 have been integrated and -enhanced into a single model testing facility. - -Model viewing can begin with either "testmodel " or "testgun ". - -The names must be the full pathname after the basedir, like -"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" - -Testmodel will create a fake entity 100 units in front of the current view -position, directly facing the viewer. It will remain immobile, so you can -move around it to view it from different angles. - -Testgun will cause the model to follow the player around and supress the real -view weapon model. The default frame 0 of most guns is completely off screen, -so you will probably have to cycle a couple frames to see it. - -"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the -frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in -q3default.cfg. - -If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let -you adjust the positioning. - -Note that none of the model testing features update while the game is paused, so -it may be convenient to test with deathmatch set to 1 so that bringing down the -console doesn't pause the game. - -============================================================================= -*/ - -/* -================= -CG_TestModel_f - -Creates an entity in front of the current position, which -can then be moved around -================= -*/ -void CG_TestModel_f (void) { - vec3_t angles; - - memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); - if ( trap_Argc() < 2 ) { - return; - } - - Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); - cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); - - if ( trap_Argc() == 3 ) { - cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); - cg.testModelEntity.frame = 1; - cg.testModelEntity.oldframe = 0; - } - if (! cg.testModelEntity.hModel ) { - CG_Printf( "Can't register model\n" ); - return; - } - - VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); - - angles[PITCH] = 0; - angles[YAW] = 180 + cg.refdefViewAngles[1]; - angles[ROLL] = 0; - - AnglesToAxis( angles, cg.testModelEntity.axis ); - cg.testGun = qfalse; -} - -/* -================= -CG_TestGun_f - -Replaces the current view weapon with the given model -================= -*/ -void CG_TestGun_f (void) { - CG_TestModel_f(); - cg.testGun = qtrue; - cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; -} - - -void CG_TestModelNextFrame_f (void) { - cg.testModelEntity.frame++; - CG_Printf( "frame %i\n", cg.testModelEntity.frame ); -} - -void CG_TestModelPrevFrame_f (void) { - cg.testModelEntity.frame--; - if ( cg.testModelEntity.frame < 0 ) { - cg.testModelEntity.frame = 0; - } - CG_Printf( "frame %i\n", cg.testModelEntity.frame ); -} - -void CG_TestModelNextSkin_f (void) { - cg.testModelEntity.skinNum++; - CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); -} - -void CG_TestModelPrevSkin_f (void) { - cg.testModelEntity.skinNum--; - if ( cg.testModelEntity.skinNum < 0 ) { - cg.testModelEntity.skinNum = 0; - } - CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); -} - -static void CG_AddTestModel (void) { - int i; - - // re-register the model, because the level may have changed - cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); - if (! cg.testModelEntity.hModel ) { - CG_Printf ("Can't register model\n"); - return; - } - - // if testing a gun, set the origin reletive to the view origin - if ( cg.testGun ) { - VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); - VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); - VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); - VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); - - // allow the position to be adjusted - for (i=0 ; i<3 ; i++) { - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; - cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; - } - } - - trap_R_AddRefEntityToScene( &cg.testModelEntity ); -} - - - -//============================================================================ - - -/* -================= -CG_CalcVrect - -Sets the coordinates of the rendered window -================= -*/ -static void CG_CalcVrect (void) { - int size; - - // the intermission should allways be full screen - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - size = 100; - } else { - // bound normal viewsize - if (cg_viewsize.integer < 30) { - trap_Cvar_Set ("cg_viewsize","30"); - size = 30; - } else if (cg_viewsize.integer > 100) { - trap_Cvar_Set ("cg_viewsize","100"); - size = 100; - } else { - size = cg_viewsize.integer; - } - - } - cg.refdef.width = cgs.glconfig.vidWidth*size/100; - cg.refdef.width &= ~1; - - cg.refdef.height = cgs.glconfig.vidHeight*size/100; - cg.refdef.height &= ~1; - - cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; - cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; -} - -//============================================================================== - - -/* -=============== -CG_OffsetThirdPersonView - -=============== -*/ -#define FOCUS_DISTANCE 512 -static void CG_OffsetThirdPersonView( void ) { - vec3_t forward, right, up; - vec3_t view; - vec3_t focusAngles; - trace_t trace; - static vec3_t mins = { -4, -4, -4 }; - static vec3_t maxs = { 4, 4, 4 }; - vec3_t focusPoint; - float focusDist; - float forwardScale, sideScale; - - cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; - - VectorCopy( cg.refdefViewAngles, focusAngles ); - - // if dead, look at killer - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; - cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; - } - - if ( focusAngles[PITCH] > 45 ) { - focusAngles[PITCH] = 45; // don't go too far overhead - } - AngleVectors( focusAngles, forward, NULL, NULL ); - - VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); - - VectorCopy( cg.refdef.vieworg, view ); - - view[2] += 8; - - cg.refdefViewAngles[PITCH] *= 0.5; - - AngleVectors( cg.refdefViewAngles, forward, right, up ); - - forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); - sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); - VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); - VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); - - // trace a ray from the origin to the viewpoint to make sure the view isn't - // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything - - if (!cg_cameraMode.integer) { - CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - - if ( trace.fraction != 1.0 ) { - VectorCopy( trace.endpos, view ); - view[2] += (1.0 - trace.fraction) * 32; - // try another trace to this position, because a tunnel may have the ceiling - // close enogh that this is poking out - - CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); - VectorCopy( trace.endpos, view ); - } - } - - - VectorCopy( view, cg.refdef.vieworg ); - - // select pitch to look at focus point from vieword - VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); - focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); - if ( focusDist < 1 ) { - focusDist = 1; // should never happen - } - cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); - cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; -} - - -// this causes a compiler bug on mac MrC compiler -static void CG_StepOffset( void ) { - int timeDelta; - - // smooth out stair climbing - timeDelta = cg.time - cg.stepTime; - if ( timeDelta < STEP_TIME ) { - cg.refdef.vieworg[2] -= cg.stepChange - * (STEP_TIME - timeDelta) / STEP_TIME; - } -} - -/* -=============== -CG_OffsetFirstPersonView - -=============== -*/ -static void CG_OffsetFirstPersonView( void ) { - float *origin; - float *angles; - float bob; - float ratio; - float delta; - float speed; - float f; - vec3_t predictedVelocity; - int timeDelta; - - if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { - return; - } - - origin = cg.refdef.vieworg; - angles = cg.refdefViewAngles; - - // if dead, fix the angle and don't add any kick - if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { - angles[ROLL] = 40; - angles[PITCH] = -15; - angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; - origin[2] += cg.predictedPlayerState.viewheight; - return; - } - - // add angles based on weapon kick - VectorAdd (angles, cg.kick_angles, angles); - - // add angles based on damage kick - if ( cg.damageTime ) { - ratio = cg.time - cg.damageTime; - if ( ratio < DAMAGE_DEFLECT_TIME ) { - ratio /= DAMAGE_DEFLECT_TIME; - angles[PITCH] += ratio * cg.v_dmg_pitch; - angles[ROLL] += ratio * cg.v_dmg_roll; - } else { - ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; - if ( ratio > 0 ) { - angles[PITCH] += ratio * cg.v_dmg_pitch; - angles[ROLL] += ratio * cg.v_dmg_roll; - } - } - } - - // add pitch based on fall kick -#if 0 - ratio = ( cg.time - cg.landTime) / FALL_TIME; - if (ratio < 0) - ratio = 0; - angles[PITCH] += ratio * cg.fall_value; -#endif - - // add angles based on velocity - VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); - - delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); - angles[PITCH] += delta * cg_runpitch.value; - - delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); - angles[ROLL] -= delta * cg_runroll.value; - - // add angles based on bob - - // make sure the bob is visible even at low speeds - speed = cg.xyspeed > 200 ? cg.xyspeed : 200; - - delta = cg.bobfracsin * cg_bobpitch.value * speed; - if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) - delta *= 3; // crouching - angles[PITCH] += delta; - delta = cg.bobfracsin * cg_bobroll.value * speed; - if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) - delta *= 3; // crouching accentuates roll - if (cg.bobcycle & 1) - delta = -delta; - angles[ROLL] += delta; - -//=================================== - - // add view height - origin[2] += cg.predictedPlayerState.viewheight; - - // smooth out duck height changes - timeDelta = cg.time - cg.duckTime; - if ( timeDelta < DUCK_TIME) { - cg.refdef.vieworg[2] -= cg.duckChange - * (DUCK_TIME - timeDelta) / DUCK_TIME; - } - - // add bob height - bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; - if (bob > 6) { - bob = 6; - } - - origin[2] += bob; - - - // add fall height - delta = cg.time - cg.landTime; - if ( delta < LAND_DEFLECT_TIME ) { - f = delta / LAND_DEFLECT_TIME; - cg.refdef.vieworg[2] += cg.landChange * f; - } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { - delta -= LAND_DEFLECT_TIME; - f = 1.0 - ( delta / LAND_RETURN_TIME ); - cg.refdef.vieworg[2] += cg.landChange * f; - } - - // add step offset - CG_StepOffset(); - - // add kick offset - - VectorAdd (origin, cg.kick_origin, origin); - - // pivot the eye based on a neck length -#if 0 - { -#define NECK_LENGTH 8 - vec3_t forward, up; - - cg.refdef.vieworg[2] -= NECK_LENGTH; - AngleVectors( cg.refdefViewAngles, forward, NULL, up ); - VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); - VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); - } -#endif -} - -//====================================================================== - -void CG_ZoomDown_f( void ) { - if ( cg.zoomed ) { - return; - } - cg.zoomed = qtrue; - cg.zoomTime = cg.time; -} - -void CG_ZoomUp_f( void ) { - if ( !cg.zoomed ) { - return; - } - cg.zoomed = qfalse; - cg.zoomTime = cg.time; -} - - -/* -==================== -CG_CalcFov - -Fixed fov at intermissions, otherwise account for fov variable and zooms. -==================== -*/ -#define WAVE_AMPLITUDE 1 -#define WAVE_FREQUENCY 0.4 - -static int CG_CalcFov( void ) { - float x; - float phase; - float v; - int contents; - float fov_x, fov_y; - float zoomFov; - float f; - int inwater; - - if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { - // if in intermission, use a fixed value - fov_x = 90; - } else { - // user selectable - if ( cgs.dmflags & DF_FIXED_FOV ) { - // dmflag to prevent wide fov for all clients - fov_x = 90; - } else { - fov_x = cg_fov.value; - if ( fov_x < 1 ) { - fov_x = 1; - } else if ( fov_x > 160 ) { - fov_x = 160; - } - } - - // account for zooms - zoomFov = cg_zoomFov.value; - if ( zoomFov < 1 ) { - zoomFov = 1; - } else if ( zoomFov > 160 ) { - zoomFov = 160; - } - - if ( cg.zoomed ) { - f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) { - fov_x = zoomFov; - } else { - fov_x = fov_x + f * ( zoomFov - fov_x ); - } - } else { - f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; - if ( f > 1.0 ) { - fov_x = fov_x; - } else { - fov_x = zoomFov + f * ( fov_x - zoomFov ); - } - } - } - - x = cg.refdef.width / tan( fov_x / 360 * M_PI ); - fov_y = atan2( cg.refdef.height, x ); - fov_y = fov_y * 360 / M_PI; - - // warp if underwater - contents = CG_PointContents( cg.refdef.vieworg, -1 ); - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ - phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; - v = WAVE_AMPLITUDE * sin( phase ); - fov_x += v; - fov_y -= v; - inwater = qtrue; - } - else { - inwater = qfalse; - } - - - // set it - cg.refdef.fov_x = fov_x; - cg.refdef.fov_y = fov_y; - - if ( !cg.zoomed ) { - cg.zoomSensitivity = 1; - } else { - cg.zoomSensitivity = cg.refdef.fov_y / 75.0; - } - - return inwater; -} - - - -/* -=============== -CG_DamageBlendBlob - -=============== -*/ -static void CG_DamageBlendBlob( void ) { - int t; - int maxTime; - refEntity_t ent; - - if ( !cg.damageValue ) { - return; - } - - //if (cg.cameraMode) { - // return; - //} - - // ragePro systems can't fade blends, so don't obscure the screen - if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { - return; - } - - maxTime = DAMAGE_TIME; - t = cg.time - cg.damageTime; - if ( t <= 0 || t >= maxTime ) { - return; - } - - - memset( &ent, 0, sizeof( ent ) ); - ent.reType = RT_SPRITE; - ent.renderfx = RF_FIRST_PERSON; - - VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); - VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); - VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); - - ent.radius = cg.damageValue * 3; - ent.customShader = cgs.media.viewBloodShader; - ent.shaderRGBA[0] = 255; - ent.shaderRGBA[1] = 255; - ent.shaderRGBA[2] = 255; - ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); - trap_R_AddRefEntityToScene( &ent ); -} - - -/* -=============== -CG_CalcViewValues - -Sets cg.refdef view values -=============== -*/ -static int CG_CalcViewValues( void ) { - playerState_t *ps; - - memset( &cg.refdef, 0, sizeof( cg.refdef ) ); - - // strings for in game rendering - // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); - // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); - - // calculate size of 3D view - CG_CalcVrect(); - - ps = &cg.predictedPlayerState; -/* - if (cg.cameraMode) { - vec3_t origin, angles; - if (trap_getCameraInfo(cg.time, &origin, &angles)) { - VectorCopy(origin, cg.refdef.vieworg); - angles[ROLL] = 0; - VectorCopy(angles, cg.refdefViewAngles); - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - return CG_CalcFov(); - } else { - cg.cameraMode = qfalse; - } - } -*/ - // intermission view - if ( ps->pm_type == PM_INTERMISSION ) { - VectorCopy( ps->origin, cg.refdef.vieworg ); - VectorCopy( ps->viewangles, cg.refdefViewAngles ); - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - return CG_CalcFov(); - } - - cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; - cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); - cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + - ps->velocity[1] * ps->velocity[1] ); - - - VectorCopy( ps->origin, cg.refdef.vieworg ); - VectorCopy( ps->viewangles, cg.refdefViewAngles ); - - if (cg_cameraOrbit.integer) { - if (cg.time > cg.nextOrbitTime) { - cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; - cg_thirdPersonAngle.value += cg_cameraOrbit.value; - } - } - // add error decay - if ( cg_errorDecay.value > 0 ) { - int t; - float f; - - t = cg.time - cg.predictedErrorTime; - f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; - if ( f > 0 && f < 1 ) { - VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); - } else { - cg.predictedErrorTime = 0; - } - } - - if ( cg.renderingThirdPerson ) { - // back away from character - CG_OffsetThirdPersonView(); - } else { - // offset for local bobbing and kicks - CG_OffsetFirstPersonView(); - } - - // position eye reletive to origin - AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); - - if ( cg.hyperspace ) { - cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; - } - - // field of view - return CG_CalcFov(); -} - - -/* -===================== -CG_PowerupTimerSounds -===================== -*/ -static void CG_PowerupTimerSounds( void ) { - int i; - int t; - - // powerup timers going away - for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { - t = cg.snap->ps.powerups[i]; - if ( t <= cg.time ) { - continue; - } - if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { - continue; - } - if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { - trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); - } - } -} - -/* -===================== -CG_AddBufferedSound -===================== -*/ -void CG_AddBufferedSound( sfxHandle_t sfx ) { - if ( !sfx ) - return; - cg.soundBuffer[cg.soundBufferIn] = sfx; - cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; - if (cg.soundBufferIn == cg.soundBufferOut) { - cg.soundBufferOut++; - } -} - -/* -===================== -CG_PlayBufferedSounds -===================== -*/ -static void CG_PlayBufferedSounds( void ) { - if ( cg.soundTime < cg.time ) { - if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { - trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); - cg.soundBuffer[cg.soundBufferOut] = 0; - cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; - cg.soundTime = cg.time + 750; - } - } -} - -//========================================================================= - -/* -================= -CG_DrawActiveFrame - -Generates and draws a game scene and status information at the given time. -================= -*/ -void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { - int inwater; - - cg.time = serverTime; - cg.demoPlayback = demoPlayback; - - // update cvars - CG_UpdateCvars(); - - // if we are only updating the screen as a loading - // pacifier, don't even try to read snapshots - if ( cg.infoScreenText[0] != 0 ) { - CG_DrawInformation(); - return; - } - - // any looped sounds will be respecified as entities - // are added to the render list - trap_S_ClearLoopingSounds(qfalse); - - // clear all the render lists - trap_R_ClearScene(); - - // set up cg.snap and possibly cg.nextSnap - CG_ProcessSnapshots(); - - // if we haven't received any snapshots yet, all - // we can draw is the information screen - if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { - CG_DrawInformation(); - return; - } - - // let the client system know what our weapon and zoom settings are - trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); - - // this counter will be bumped for every valid scene we generate - cg.clientFrame++; - - // update cg.predictedPlayerState - CG_PredictPlayerState(); - - // decide on third person view - cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); - - // build cg.refdef - inwater = CG_CalcViewValues(); - - // first person blend blobs, done after AnglesToAxis - if ( !cg.renderingThirdPerson ) { - CG_DamageBlendBlob(); - } - - // build the render lists - if ( !cg.hyperspace ) { - CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct - CG_AddMarks(); - CG_AddParticles (); - CG_AddLocalEntities(); - } - CG_AddViewWeapon( &cg.predictedPlayerState ); - - // add buffered sounds - CG_PlayBufferedSounds(); - - // play buffered voice chats - CG_PlayBufferedVoiceChats(); - - // finish up the rest of the refdef - if ( cg.testModelEntity.hModel ) { - CG_AddTestModel(); - } - cg.refdef.time = cg.time; - memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); - - // warning sounds when powerup is wearing off - CG_PowerupTimerSounds(); - - // update audio positions - trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); - - // make sure the lagometerSample and frame timing isn't done twice when in stereo - if ( stereoView != STEREO_RIGHT ) { - cg.frametime = cg.time - cg.oldTime; - if ( cg.frametime < 0 ) { - cg.frametime = 0; - } - cg.oldTime = cg.time; - CG_AddLagometerFrameInfo(); - } - if (cg_timescale.value != cg_timescaleFadeEnd.value) { - if (cg_timescale.value < cg_timescaleFadeEnd.value) { - cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; - if (cg_timescale.value > cg_timescaleFadeEnd.value) - cg_timescale.value = cg_timescaleFadeEnd.value; - } - else { - cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; - if (cg_timescale.value < cg_timescaleFadeEnd.value) - cg_timescale.value = cg_timescaleFadeEnd.value; - } - if (cg_timescaleFadeSpeed.value) { - trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); - } - } - - // actually issue the rendering calls - CG_DrawActive( stereoView ); - - if ( cg_stats.integer ) { - CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); - } - - -} - +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if (! cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg.refdef.width = cgs.glconfig.vidWidth*size/100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size/100; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + if (!cg_cameraMode.integer) { + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + } + + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if ( timeDelta < STEP_TIME ) { + cg.refdef.vieworg[2] -= cg.stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +} + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) { + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // if dead, fix the angle and don't add any kick + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + + // add angles based on weapon kick + VectorAdd (angles, cg.kick_angles, angles); + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < DUCK_TIME) { + cg.refdef.vieworg[2] -= cg.duckChange + * (DUCK_TIME - timeDelta) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; + if (bob > 6) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[2] += cg.landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[2] += cg.landChange * f; + } + + // add step offset + CG_StepOffset(); + + // add kick offset + + VectorAdd (origin, cg.kick_origin, origin); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + +//====================================================================== + +void CG_ZoomDown_f( void ) { + if ( cg.zoomed ) { + return; + } + cg.zoomed = qtrue; + cg.zoomTime = cg.time; +} + +void CG_ZoomUp_f( void ) { + if ( !cg.zoomed ) { + return; + } + cg.zoomed = qfalse; + cg.zoomTime = cg.time; +} + + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + + // account for zooms + zoomFov = cg_zoomFov.value; + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > 160 ) { + zoomFov = 160; + } + + if ( cg.zoomed ) { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + } else { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !cg.zoomed ) { + cg.zoomSensitivity = 1; + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + + +/* +=============== +CG_DamageBlendBlob + +=============== +*/ +static void CG_DamageBlendBlob( void ) { + int t; + int maxTime; + refEntity_t ent; + + if ( !cg.damageValue ) { + return; + } + + //if (cg.cameraMode) { + // return; + //} + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + maxTime = DAMAGE_TIME; + t = cg.time - cg.damageTime; + if ( t <= 0 || t >= maxTime ) { + return; + } + + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); + + ent.radius = cg.damageValue * 3; + ent.customShader = cgs.media.viewBloodShader; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) { + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); + // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg.predictedPlayerState; +/* + if (cg.cameraMode) { + vec3_t origin, angles; + if (trap_getCameraInfo(cg.time, &origin, &angles)) { + VectorCopy(origin, cg.refdef.vieworg); + angles[ROLL] = 0; + VectorCopy(angles, cg.refdefViewAngles); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } else { + cg.cameraMode = qfalse; + } + } +*/ + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + if (cg_cameraOrbit.integer) { + if (cg.time > cg.nextOrbitTime) { + cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; + cg_thirdPersonAngle.value += cg_cameraOrbit.value; + } + } + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + t = cg.snap->ps.powerups[i]; + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) { + if ( !sfx ) + return; + cg.soundBuffer[cg.soundBufferIn] = sfx; + cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; + if (cg.soundBufferIn == cg.soundBufferOut) { + cg.soundBufferOut++; + } +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) { + if ( cg.soundTime < cg.time ) { + if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { + trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); + cg.soundBuffer[cg.soundBufferOut] = 0; + cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; + cg.soundTime = cg.time + 750; + } + } +} + +//========================================================================= + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars(); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg.infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(qfalse); + + // clear all the render lists + trap_R_ClearScene(); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_DrawInformation(); + return; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view + cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) { + CG_DamageBlendBlob(); + } + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddParticles (); + CG_AddLocalEntities(); + } + CG_AddViewWeapon( &cg.predictedPlayerState ); + + // add buffered sounds + CG_PlayBufferedSounds(); + + // play buffered voice chats + CG_PlayBufferedVoiceChats(); + + // finish up the rest of the refdef + if ( cg.testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + // warning sounds when powerup is wearing off + CG_PowerupTimerSounds(); + + // update audio positions + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + if (cg_timescale.value != cg_timescaleFadeEnd.value) { + if (cg_timescale.value < cg_timescaleFadeEnd.value) { + cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value > cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + else { + cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value < cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + if (cg_timescaleFadeSpeed.value) { + trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); + } + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } + + +} + diff --git a/code/cgame/cg_weapons.c b/code/cgame/cg_weapons.c index 9ac1ca9..c4a1753 100755 --- a/code/cgame/cg_weapons.c +++ b/code/cgame/cg_weapons.c @@ -1,2277 +1,2277 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -// cg_weapons.c -- events and effects dealing with weapons -#include "cg_local.h" - -/* -========================== -CG_MachineGunEjectBrass -========================== -*/ -static void CG_MachineGunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - float waterScale = 1.0f; - vec3_t v[3]; - - if ( cg_brassTime.integer <= 0 ) { - return; - } - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 0; - velocity[1] = -50 + 40 * crandom(); - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time - (rand()&15); - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 8; - offset[1] = -4; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, re->origin ); - - VectorCopy( re->origin, le->pos.trBase ); - - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.machinegunBrassModel; - - le->bounceFactor = 0.4 * waterScale; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 2; - le->angles.trDelta[1] = 1; - le->angles.trDelta[2] = 0; - - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; -} - -/* -========================== -CG_ShotgunEjectBrass -========================== -*/ -static void CG_ShotgunEjectBrass( centity_t *cent ) { - localEntity_t *le; - refEntity_t *re; - vec3_t velocity, xvelocity; - vec3_t offset, xoffset; - vec3_t v[3]; - int i; - - if ( cg_brassTime.integer <= 0 ) { - return; - } - - for ( i = 0; i < 2; i++ ) { - float waterScale = 1.0f; - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 60 + 60 * crandom(); - if ( i == 0 ) { - velocity[1] = 40 + 10 * crandom(); - } else { - velocity[1] = -40 + 10 * crandom(); - } - velocity[2] = 100 + 50 * crandom(); - - le->leType = LE_FRAGMENT; - le->startTime = cg.time; - le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 8; - offset[1] = 0; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, re->origin ); - VectorCopy( re->origin, le->pos.trBase ); - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->hModel = cgs.media.shotgunBrassModel; - le->bounceFactor = 0.3f; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 1; - le->angles.trDelta[1] = 0.5; - le->angles.trDelta[2] = 0; - - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_BRASS; - le->leMarkType = LEMT_NONE; - } -} - - -#ifdef MISSIONPACK -/* -========================== -CG_NailgunEjectBrass -========================== -*/ -static void CG_NailgunEjectBrass( centity_t *cent ) { - localEntity_t *smoke; - vec3_t origin; - vec3_t v[3]; - vec3_t offset; - vec3_t xoffset; - vec3_t up; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 0; - offset[1] = -12; - offset[2] = 24; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - VectorAdd( cent->lerpOrigin, xoffset, origin ); - - VectorSet( up, 0, 0, 64 ); - - smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; -} -#endif - - -/* -========================== -CG_RailTrail -========================== -*/ -void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { - vec3_t axis[36], move, move2, next_move, vec, temp; - float len; - int i, j, skip; - - localEntity_t *le; - refEntity_t *re; - -#define RADIUS 4 -#define ROTATION 1 -#define SPACING 5 - - start[2] -= 4; - VectorCopy (start, move); - VectorSubtract (end, start, vec); - len = VectorNormalize (vec); - PerpendicularVector(temp, vec); - for (i = 0 ; i < 36; i++) { - RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 - } - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - le->leType = LE_FADE_RGB; - le->startTime = cg.time; - le->endTime = cg.time + cg_railTrailTime.value; - le->lifeRate = 1.0 / (le->endTime - le->startTime); - - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_RAIL_CORE; - re->customShader = cgs.media.railCoreShader; - - VectorCopy(start, re->origin); - VectorCopy(end, re->oldorigin); - - re->shaderRGBA[0] = ci->color1[0] * 255; - re->shaderRGBA[1] = ci->color1[1] * 255; - re->shaderRGBA[2] = ci->color1[2] * 255; - re->shaderRGBA[3] = 255; - - le->color[0] = ci->color1[0] * 0.75; - le->color[1] = ci->color1[1] * 0.75; - le->color[2] = ci->color1[2] * 0.75; - le->color[3] = 1.0f; - - AxisClear( re->axis ); - - VectorMA(move, 20, vec, move); - VectorCopy(move, next_move); - VectorScale (vec, SPACING, vec); - - if (cg_oldRail.integer != 0) { - // nudge down a bit so it isn't exactly in center - re->origin[2] -= 8; - re->oldorigin[2] -= 8; - return; - } - skip = -1; - - j = 18; - for (i = 0; i < len; i += SPACING) { - if (i != skip) { - skip = i + SPACING; - le = CG_AllocLocalEntity(); - re = &le->refEntity; - le->leFlags = LEF_PUFF_DONT_SCALE; - le->leType = LE_MOVE_SCALE_FADE; - le->startTime = cg.time; - le->endTime = cg.time + (i>>1) + 600; - le->lifeRate = 1.0 / (le->endTime - le->startTime); - - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_SPRITE; - re->radius = 1.1f; - re->customShader = cgs.media.railRingsShader; - - re->shaderRGBA[0] = ci->color2[0] * 255; - re->shaderRGBA[1] = ci->color2[1] * 255; - re->shaderRGBA[2] = ci->color2[2] * 255; - re->shaderRGBA[3] = 255; - - le->color[0] = ci->color2[0] * 0.75; - le->color[1] = ci->color2[1] * 0.75; - le->color[2] = ci->color2[2] * 0.75; - le->color[3] = 1.0f; - - le->pos.trType = TR_LINEAR; - le->pos.trTime = cg.time; - - VectorCopy( move, move2); - VectorMA(move2, RADIUS , axis[j], move2); - VectorCopy(move2, le->pos.trBase); - - le->pos.trDelta[0] = axis[j][0]*6; - le->pos.trDelta[1] = axis[j][1]*6; - le->pos.trDelta[2] = axis[j][2]*6; - } - - VectorAdd (move, vec, move); - - j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; - } -} - -/* -========================== -CG_RocketTrail -========================== -*/ -static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { - int step; - vec3_t origin, lastPos; - int t; - int startTime, contents; - int lastContents; - entityState_t *es; - vec3_t up; - localEntity_t *smoke; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - up[0] = 0; - up[1] = 0; - up[2] = 0; - - step = 50; - - es = &ent->currentState; - startTime = ent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - contents = CG_PointContents( origin, -1 ); - - // if object (e.g. grenade) is stationary, don't toss up smoke - if ( es->pos.trType == TR_STATIONARY ) { - ent->trailTime = cg.time; - return; - } - - BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); - lastContents = CG_PointContents( lastPos, -1 ); - - ent->trailTime = cg.time; - - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - if ( contents & lastContents & CONTENTS_WATER ) { - CG_BubbleTrail( lastPos, origin, 8 ); - } - return; - } - - for ( ; t <= ent->trailTime ; t += step ) { - BG_EvaluateTrajectory( &es->pos, t, lastPos ); - - smoke = CG_SmokePuff( lastPos, up, - wi->trailRadius, - 1, 1, 1, 0.33f, - wi->wiTrailTime, - t, - 0, - 0, - cgs.media.smokePuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; - } - -} - -#ifdef MISSIONPACK -/* -========================== -CG_NailTrail -========================== -*/ -static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { - int step; - vec3_t origin, lastPos; - int t; - int startTime, contents; - int lastContents; - entityState_t *es; - vec3_t up; - localEntity_t *smoke; - - if ( cg_noProjectileTrail.integer ) { - return; - } - - up[0] = 0; - up[1] = 0; - up[2] = 0; - - step = 50; - - es = &ent->currentState; - startTime = ent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - contents = CG_PointContents( origin, -1 ); - - // if object (e.g. grenade) is stationary, don't toss up smoke - if ( es->pos.trType == TR_STATIONARY ) { - ent->trailTime = cg.time; - return; - } - - BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); - lastContents = CG_PointContents( lastPos, -1 ); - - ent->trailTime = cg.time; - - if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { - if ( contents & lastContents & CONTENTS_WATER ) { - CG_BubbleTrail( lastPos, origin, 8 ); - } - return; - } - - for ( ; t <= ent->trailTime ; t += step ) { - BG_EvaluateTrajectory( &es->pos, t, lastPos ); - - smoke = CG_SmokePuff( lastPos, up, - wi->trailRadius, - 1, 1, 1, 0.33f, - wi->wiTrailTime, - t, - 0, - 0, - cgs.media.nailPuffShader ); - // use the optimized local entity add - smoke->leType = LE_SCALE_FADE; - } - -} -#endif - -/* -========================== -CG_NailTrail -========================== -*/ -static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { - localEntity_t *le; - refEntity_t *re; - entityState_t *es; - vec3_t velocity, xvelocity, origin; - vec3_t offset, xoffset; - vec3_t v[3]; - int t, startTime, step; - - float waterScale = 1.0f; - - if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { - return; - } - - step = 50; - - es = ¢->currentState; - startTime = cent->trailTime; - t = step * ( (startTime + step) / step ); - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - - le = CG_AllocLocalEntity(); - re = &le->refEntity; - - velocity[0] = 60 - 120 * crandom(); - velocity[1] = 40 - 80 * crandom(); - velocity[2] = 100 - 200 * crandom(); - - le->leType = LE_MOVE_SCALE_FADE; - le->leFlags = LEF_TUMBLE; - le->leBounceSoundType = LEBS_NONE; - le->leMarkType = LEMT_NONE; - - le->startTime = cg.time; - le->endTime = le->startTime + 600; - - le->pos.trType = TR_GRAVITY; - le->pos.trTime = cg.time; - - AnglesToAxis( cent->lerpAngles, v ); - - offset[0] = 2; - offset[1] = 2; - offset[2] = 2; - - xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; - xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; - xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; - - VectorAdd( origin, xoffset, re->origin ); - VectorCopy( re->origin, le->pos.trBase ); - - if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { - waterScale = 0.10f; - } - - xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; - xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; - xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; - VectorScale( xvelocity, waterScale, le->pos.trDelta ); - - AxisCopy( axisDefault, re->axis ); - re->shaderTime = cg.time / 1000.0f; - re->reType = RT_SPRITE; - re->radius = 0.25f; - re->customShader = cgs.media.railRingsShader; - le->bounceFactor = 0.3f; - - re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; - re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; - re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; - re->shaderRGBA[3] = 63; - - le->color[0] = wi->flashDlightColor[0] * 0.2; - le->color[1] = wi->flashDlightColor[1] * 0.2; - le->color[2] = wi->flashDlightColor[2] * 0.2; - le->color[3] = 0.25f; - - le->angles.trType = TR_LINEAR; - le->angles.trTime = cg.time; - le->angles.trBase[0] = rand()&31; - le->angles.trBase[1] = rand()&31; - le->angles.trBase[2] = rand()&31; - le->angles.trDelta[0] = 1; - le->angles.trDelta[1] = 0.5; - le->angles.trDelta[2] = 0; - -} -/* -========================== -CG_GrappleTrail -========================== -*/ -void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { - vec3_t origin; - entityState_t *es; - vec3_t forward, up; - refEntity_t beam; - - es = &ent->currentState; - - BG_EvaluateTrajectory( &es->pos, cg.time, origin ); - ent->trailTime = cg.time; - - memset( &beam, 0, sizeof( beam ) ); - //FIXME adjust for muzzle position - VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); - beam.origin[2] += 26; - AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); - VectorMA( beam.origin, -6, up, beam.origin ); - VectorCopy( origin, beam.oldorigin ); - - if (Distance( beam.origin, beam.oldorigin ) < 64 ) - return; // Don't draw if close - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - - AxisClear( beam.axis ); - beam.shaderRGBA[0] = 0xff; - beam.shaderRGBA[1] = 0xff; - beam.shaderRGBA[2] = 0xff; - beam.shaderRGBA[3] = 0xff; - trap_R_AddRefEntityToScene( &beam ); -} - -/* -========================== -CG_GrenadeTrail -========================== -*/ -static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { - CG_RocketTrail( ent, wi ); -} - - -/* -================= -CG_RegisterWeapon - -The server says this item is used on this level -================= -*/ -void CG_RegisterWeapon( int weaponNum ) { - weaponInfo_t *weaponInfo; - gitem_t *item, *ammo; - char path[MAX_QPATH]; - vec3_t mins, maxs; - int i; - - weaponInfo = &cg_weapons[weaponNum]; - - if ( weaponNum == 0 ) { - return; - } - - if ( weaponInfo->registered ) { - return; - } - - memset( weaponInfo, 0, sizeof( *weaponInfo ) ); - weaponInfo->registered = qtrue; - - for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { - if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { - weaponInfo->item = item; - break; - } - } - if ( !item->classname ) { - CG_Error( "Couldn't find weapon %i", weaponNum ); - } - CG_RegisterItemVisuals( item - bg_itemlist ); - - // load cmodel before model so filecache works - weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); - - // calc midpoint for rotation - trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); - for ( i = 0 ; i < 3 ; i++ ) { - weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); - } - - weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); - weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); - - for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { - if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { - break; - } - } - if ( ammo->classname && ammo->world_model[0] ) { - weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); - } - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_flash.md3" ); - weaponInfo->flashModel = trap_R_RegisterModel( path ); - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_barrel.md3" ); - weaponInfo->barrelModel = trap_R_RegisterModel( path ); - - strcpy( path, item->world_model[0] ); - COM_StripExtension( path, path ); - strcat( path, "_hand.md3" ); - weaponInfo->handsModel = trap_R_RegisterModel( path ); - - if ( !weaponInfo->handsModel ) { - weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); - } - - weaponInfo->loopFireSound = qfalse; - - switch ( weaponNum ) { - case WP_GAUNTLET: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); - break; - - case WP_LIGHTNING: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); - - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); - cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); - cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); - cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); - cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); - cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); - - break; - - case WP_GRAPPLING_HOOK: - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); - weaponInfo->missileTrailFunc = CG_GrappleTrail; - weaponInfo->missileDlight = 200; - weaponInfo->wiTrailTime = 2000; - weaponInfo->trailRadius = 64; - MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); - break; - -#ifdef MISSIONPACK - case WP_CHAINGUN: - weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); - weaponInfo->loopFireSound = qtrue; - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); - weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); - weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); - weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; - cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); - break; -#endif - - case WP_MACHINEGUN: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); - weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); - weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); - weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; - cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); - break; - - case WP_SHOTGUN: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); - weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; - break; - - case WP_ROCKET_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); - weaponInfo->missileTrailFunc = CG_RocketTrail; - weaponInfo->missileDlight = 200; - weaponInfo->wiTrailTime = 2000; - weaponInfo->trailRadius = 64; - - MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); - - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); - cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); - break; - -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); - weaponInfo->missileTrailFunc = CG_GrenadeTrail; - weaponInfo->wiTrailTime = 700; - weaponInfo->trailRadius = 32; - MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); - cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); - break; -#endif - - case WP_GRENADE_LAUNCHER: - weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); - weaponInfo->missileTrailFunc = CG_GrenadeTrail; - weaponInfo->wiTrailTime = 700; - weaponInfo->trailRadius = 32; - MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); - cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); - break; - -#ifdef MISSIONPACK - case WP_NAILGUN: - weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; - weaponInfo->missileTrailFunc = CG_NailTrail; -// weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); - weaponInfo->trailRadius = 16; - weaponInfo->wiTrailTime = 250; - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); - break; -#endif - - case WP_PLASMAGUN: -// weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; - weaponInfo->missileTrailFunc = CG_PlasmaTrail; - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); - cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); - cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); - break; - - case WP_RAILGUN: - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); - cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); - cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); - cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); - break; - - case WP_BFG: - weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); - MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); - cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); - weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); - weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); - break; - - default: - MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); - weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); - break; - } -} - -/* -================= -CG_RegisterItemVisuals - -The server says this item is used on this level -================= -*/ -void CG_RegisterItemVisuals( int itemNum ) { - itemInfo_t *itemInfo; - gitem_t *item; - - if ( itemNum < 0 || itemNum >= bg_numItems ) { - CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); - } - - itemInfo = &cg_items[ itemNum ]; - if ( itemInfo->registered ) { - return; - } - - item = &bg_itemlist[ itemNum ]; - - memset( itemInfo, 0, sizeof( &itemInfo ) ); - itemInfo->registered = qtrue; - - itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); - - itemInfo->icon = trap_R_RegisterShader( item->icon ); - - if ( item->giType == IT_WEAPON ) { - CG_RegisterWeapon( item->giTag ); - } - - // - // powerups have an accompanying ring or sphere - // - if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || - item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { - if ( item->world_model[1] ) { - itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); - } - } -} - - -/* -======================================================================================== - -VIEW WEAPON - -======================================================================================== -*/ - -/* -================= -CG_MapTorsoToWeaponFrame - -================= -*/ -static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { - - // change weapon - if ( frame >= ci->animations[TORSO_DROP].firstFrame - && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { - return frame - ci->animations[TORSO_DROP].firstFrame + 6; - } - - // stand attack - if ( frame >= ci->animations[TORSO_ATTACK].firstFrame - && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { - return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; - } - - // stand attack 2 - if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame - && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { - return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; - } - - return 0; -} - - -/* -============== -CG_CalculateWeaponPosition -============== -*/ -static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { - float scale; - int delta; - float fracsin; - - VectorCopy( cg.refdef.vieworg, origin ); - VectorCopy( cg.refdefViewAngles, angles ); - - // on odd legs, invert some angles - if ( cg.bobcycle & 1 ) { - scale = -cg.xyspeed; - } else { - scale = cg.xyspeed; - } - - // gun angles from bobbing - angles[ROLL] += scale * cg.bobfracsin * 0.005; - angles[YAW] += scale * cg.bobfracsin * 0.01; - angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; - - // drop the weapon when landing - delta = cg.time - cg.landTime; - if ( delta < LAND_DEFLECT_TIME ) { - origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; - } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { - origin[2] += cg.landChange*0.25 * - (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; - } - -#if 0 - // drop the weapon when stair climbing - delta = cg.time - cg.stepTime; - if ( delta < STEP_TIME/2 ) { - origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); - } else if ( delta < STEP_TIME ) { - origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); - } -#endif - - // idle drift - scale = cg.xyspeed + 40; - fracsin = sin( cg.time * 0.001 ); - angles[ROLL] += scale * fracsin * 0.01; - angles[YAW] += scale * fracsin * 0.01; - angles[PITCH] += scale * fracsin * 0.01; -} - - -/* -=============== -CG_LightningBolt - -Origin will be the exact tag point, which is slightly -different than the muzzle point used for determining hits. -The cent should be the non-predicted cent if it is from the player, -so the endpoint will reflect the simulated strike (lagging the predicted -angle) -=============== -*/ -static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { - trace_t trace; - refEntity_t beam; - vec3_t forward; - vec3_t muzzlePoint, endPoint; - - if (cent->currentState.weapon != WP_LIGHTNING) { - return; - } - - memset( &beam, 0, sizeof( beam ) ); - - // CPMA "true" lightning - if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { - vec3_t angle; - int i; - - for (i = 0; i < 3; i++) { - float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; - if (a > 180) { - a -= 360; - } - if (a < -180) { - a += 360; - } - - angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); - if (angle[i] < 0) { - angle[i] += 360; - } - if (angle[i] > 360) { - angle[i] -= 360; - } - } - - AngleVectors(angle, forward, NULL, NULL ); - VectorCopy(cent->lerpOrigin, muzzlePoint ); -// VectorCopy(cg.refdef.vieworg, muzzlePoint ); - } else { - // !CPMA - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - VectorCopy(cent->lerpOrigin, muzzlePoint ); - } - - // FIXME: crouch - muzzlePoint[2] += DEFAULT_VIEWHEIGHT; - - VectorMA( muzzlePoint, 14, forward, muzzlePoint ); - - // project forward by the lightning range - VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); - - // see if it hit a wall - CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, - cent->currentState.number, MASK_SHOT ); - - // this is the endpoint - VectorCopy( trace.endpos, beam.oldorigin ); - - // use the provided origin, even though it may be slightly - // different than the muzzle origin - VectorCopy( origin, beam.origin ); - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - trap_R_AddRefEntityToScene( &beam ); - - // add the impact flare if it hit something - if ( trace.fraction < 1.0 ) { - vec3_t angles; - vec3_t dir; - - VectorSubtract( beam.oldorigin, beam.origin, dir ); - VectorNormalize( dir ); - - memset( &beam, 0, sizeof( beam ) ); - beam.hModel = cgs.media.lightningExplosionModel; - - VectorMA( trace.endpos, -16, dir, beam.origin ); - - // make a random orientation - angles[0] = rand() % 360; - angles[1] = rand() % 360; - angles[2] = rand() % 360; - AnglesToAxis( angles, beam.axis ); - trap_R_AddRefEntityToScene( &beam ); - } -} -/* - -static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { - trace_t trace; - refEntity_t beam; - vec3_t forward; - vec3_t muzzlePoint, endPoint; - - if ( cent->currentState.weapon != WP_LIGHTNING ) { - return; - } - - memset( &beam, 0, sizeof( beam ) ); - - // find muzzle point for this frame - VectorCopy( cent->lerpOrigin, muzzlePoint ); - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - - // FIXME: crouch - muzzlePoint[2] += DEFAULT_VIEWHEIGHT; - - VectorMA( muzzlePoint, 14, forward, muzzlePoint ); - - // project forward by the lightning range - VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); - - // see if it hit a wall - CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, - cent->currentState.number, MASK_SHOT ); - - // this is the endpoint - VectorCopy( trace.endpos, beam.oldorigin ); - - // use the provided origin, even though it may be slightly - // different than the muzzle origin - VectorCopy( origin, beam.origin ); - - beam.reType = RT_LIGHTNING; - beam.customShader = cgs.media.lightningShader; - trap_R_AddRefEntityToScene( &beam ); - - // add the impact flare if it hit something - if ( trace.fraction < 1.0 ) { - vec3_t angles; - vec3_t dir; - - VectorSubtract( beam.oldorigin, beam.origin, dir ); - VectorNormalize( dir ); - - memset( &beam, 0, sizeof( beam ) ); - beam.hModel = cgs.media.lightningExplosionModel; - - VectorMA( trace.endpos, -16, dir, beam.origin ); - - // make a random orientation - angles[0] = rand() % 360; - angles[1] = rand() % 360; - angles[2] = rand() % 360; - AnglesToAxis( angles, beam.axis ); - trap_R_AddRefEntityToScene( &beam ); - } -} -*/ - -/* -=============== -CG_SpawnRailTrail - -Origin will be the exact tag point, which is slightly -different than the muzzle point used for determining hits. -=============== -*/ -static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { - clientInfo_t *ci; - - if ( cent->currentState.weapon != WP_RAILGUN ) { - return; - } - if ( !cent->pe.railgunFlash ) { - return; - } - cent->pe.railgunFlash = qtrue; - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - CG_RailTrail( ci, origin, cent->pe.railgunImpact ); -} - - -/* -====================== -CG_MachinegunSpinAngle -====================== -*/ -#define SPIN_SPEED 0.9 -#define COAST_TIME 1000 -static float CG_MachinegunSpinAngle( centity_t *cent ) { - int delta; - float angle; - float speed; - - delta = cg.time - cent->pe.barrelTime; - if ( cent->pe.barrelSpinning ) { - angle = cent->pe.barrelAngle + delta * SPIN_SPEED; - } else { - if ( delta > COAST_TIME ) { - delta = COAST_TIME; - } - - speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); - angle = cent->pe.barrelAngle + delta * speed; - } - - if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { - cent->pe.barrelTime = cg.time; - cent->pe.barrelAngle = AngleMod( angle ); - cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); -#ifdef MISSIONPACK - if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { - trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); - } -#endif - } - - return angle; -} - - -/* -======================== -CG_AddWeaponWithPowerups -======================== -*/ -static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { - // add powerup effects - if ( powerups & ( 1 << PW_INVIS ) ) { - gun->customShader = cgs.media.invisShader; - trap_R_AddRefEntityToScene( gun ); - } else { - trap_R_AddRefEntityToScene( gun ); - - if ( powerups & ( 1 << PW_BATTLESUIT ) ) { - gun->customShader = cgs.media.battleWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - if ( powerups & ( 1 << PW_QUAD ) ) { - gun->customShader = cgs.media.quadWeaponShader; - trap_R_AddRefEntityToScene( gun ); - } - } -} - - -/* -============= -CG_AddPlayerWeapon - -Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) -The main player will have this called for BOTH cases, so effects like light and -sound should only be done on the world model case. -============= -*/ -void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { - refEntity_t gun; - refEntity_t barrel; - refEntity_t flash; - vec3_t angles; - weapon_t weaponNum; - weaponInfo_t *weapon; - centity_t *nonPredictedCent; -// int col; - - weaponNum = cent->currentState.weapon; - - CG_RegisterWeapon( weaponNum ); - weapon = &cg_weapons[weaponNum]; - - // add the weapon - memset( &gun, 0, sizeof( gun ) ); - VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); - gun.shadowPlane = parent->shadowPlane; - gun.renderfx = parent->renderfx; - - // set custom shading for railgun refire rate - if ( ps ) { - if ( cg.predictedPlayerState.weapon == WP_RAILGUN - && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { - float f; - - f = (float)cg.predictedPlayerState.weaponTime / 1500; - gun.shaderRGBA[1] = 0; - gun.shaderRGBA[0] = - gun.shaderRGBA[2] = 255 * ( 1.0 - f ); - } else { - gun.shaderRGBA[0] = 255; - gun.shaderRGBA[1] = 255; - gun.shaderRGBA[2] = 255; - gun.shaderRGBA[3] = 255; - } - } - - gun.hModel = weapon->weaponModel; - if (!gun.hModel) { - return; - } - - if ( !ps ) { - // add weapon ready sound - cent->pe.lightningFiring = qfalse; - if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { - // lightning gun and guantlet make a different sound when fire is held down - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); - cent->pe.lightningFiring = qtrue; - } else if ( weapon->readySound ) { - trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); - } - } - - CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); - - CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); - - // add the spinning barrel - if ( weapon->barrelModel ) { - memset( &barrel, 0, sizeof( barrel ) ); - VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); - barrel.shadowPlane = parent->shadowPlane; - barrel.renderfx = parent->renderfx; - - barrel.hModel = weapon->barrelModel; - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = CG_MachinegunSpinAngle( cent ); - AnglesToAxis( angles, barrel.axis ); - - CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); - - CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); - } - - // make sure we aren't looking at cg.predictedPlayerEntity for LG - nonPredictedCent = &cg_entities[cent->currentState.clientNum]; - - // if the index of the nonPredictedCent is not the same as the clientNum - // then this is a fake player (like on teh single player podiums), so - // go ahead and use the cent - if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { - nonPredictedCent = cent; - } - - // add the flash - if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) - && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) - { - // continuous flash - } else { - // impulse flash - if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { - return; - } - } - - memset( &flash, 0, sizeof( flash ) ); - VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); - flash.shadowPlane = parent->shadowPlane; - flash.renderfx = parent->renderfx; - - flash.hModel = weapon->flashModel; - if (!flash.hModel) { - return; - } - angles[YAW] = 0; - angles[PITCH] = 0; - angles[ROLL] = crandom() * 10; - AnglesToAxis( angles, flash.axis ); - - // colorize the railgun blast - if ( weaponNum == WP_RAILGUN ) { - clientInfo_t *ci; - - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - flash.shaderRGBA[0] = 255 * ci->color1[0]; - flash.shaderRGBA[1] = 255 * ci->color1[1]; - flash.shaderRGBA[2] = 255 * ci->color1[2]; - } - - CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); - trap_R_AddRefEntityToScene( &flash ); - - if ( ps || cg.renderingThirdPerson || - cent->currentState.number != cg.predictedPlayerState.clientNum ) { - // add lightning bolt - CG_LightningBolt( nonPredictedCent, flash.origin ); - - // add rail trail - CG_SpawnRailTrail( cent, flash.origin ); - - if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { - trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], - weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); - } - } -} - -/* -============== -CG_AddViewWeapon - -Add the weapon, and flash for the player's view -============== -*/ -void CG_AddViewWeapon( playerState_t *ps ) { - refEntity_t hand; - centity_t *cent; - clientInfo_t *ci; - float fovOffset; - vec3_t angles; - weaponInfo_t *weapon; - - if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { - return; - } - - if ( ps->pm_type == PM_INTERMISSION ) { - return; - } - - // no gun if in third person view or a camera is active - //if ( cg.renderingThirdPerson || cg.cameraMode) { - if ( cg.renderingThirdPerson ) { - return; - } - - - // allow the gun to be completely removed - if ( !cg_drawGun.integer ) { - vec3_t origin; - - if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { - // special hack for lightning gun... - VectorCopy( cg.refdef.vieworg, origin ); - VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); - CG_LightningBolt( &cg_entities[ps->clientNum], origin ); - } - return; - } - - // don't draw if testing a gun model - if ( cg.testGun ) { - return; - } - - // drop gun lower at higher fov - if ( cg_fov.integer > 90 ) { - fovOffset = -0.2 * ( cg_fov.integer - 90 ); - } else { - fovOffset = 0; - } - - cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; - CG_RegisterWeapon( ps->weapon ); - weapon = &cg_weapons[ ps->weapon ]; - - memset (&hand, 0, sizeof(hand)); - - // set up gun position - CG_CalculateWeaponPosition( hand.origin, angles ); - - VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); - VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); - VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); - - AnglesToAxis( angles, hand.axis ); - - // map torso animations to weapon animations - if ( cg_gun_frame.integer ) { - // development tool - hand.frame = hand.oldframe = cg_gun_frame.integer; - hand.backlerp = 0; - } else { - // get clientinfo for animation map - ci = &cgs.clientinfo[ cent->currentState.clientNum ]; - hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); - hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); - hand.backlerp = cent->pe.torso.backlerp; - } - - hand.hModel = weapon->handsModel; - hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; - - // add everything onto the hand - CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); -} - -/* -============================================================================== - -WEAPON SELECTION - -============================================================================== -*/ - -/* -=================== -CG_DrawWeaponSelect -=================== -*/ -void CG_DrawWeaponSelect( void ) { - int i; - int bits; - int count; - int x, y, w; - char *name; - float *color; - - // don't display if dead - if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { - return; - } - - color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); - if ( !color ) { - return; - } - trap_R_SetColor( color ); - - // showing weapon select clears pickup item display, but not the blend blob - cg.itemPickupTime = 0; - - // count the number of weapons owned - bits = cg.snap->ps.stats[ STAT_WEAPONS ]; - count = 0; - for ( i = 1 ; i < 16 ; i++ ) { - if ( bits & ( 1 << i ) ) { - count++; - } - } - - x = 320 - count * 20; - y = 380; - - for ( i = 1 ; i < 16 ; i++ ) { - if ( !( bits & ( 1 << i ) ) ) { - continue; - } - - CG_RegisterWeapon( i ); - - // draw weapon icon - CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); - - // draw selection marker - if ( i == cg.weaponSelect ) { - CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); - } - - // no ammo cross on top - if ( !cg.snap->ps.ammo[ i ] ) { - CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); - } - - x += 40; - } - - // draw the selected name - if ( cg_weapons[ cg.weaponSelect ].item ) { - name = cg_weapons[ cg.weaponSelect ].item->pickup_name; - if ( name ) { - w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; - x = ( SCREEN_WIDTH - w ) / 2; - CG_DrawBigStringColor(x, y - 22, name, color); - } - } - - trap_R_SetColor( NULL ); -} - - -/* -=============== -CG_WeaponSelectable -=============== -*/ -static qboolean CG_WeaponSelectable( int i ) { - if ( !cg.snap->ps.ammo[i] ) { - return qfalse; - } - if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { - return qfalse; - } - - return qtrue; -} - -/* -=============== -CG_NextWeapon_f -=============== -*/ -void CG_NextWeapon_f( void ) { - int i; - int original; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - cg.weaponSelectTime = cg.time; - original = cg.weaponSelect; - - for ( i = 0 ; i < 16 ; i++ ) { - cg.weaponSelect++; - if ( cg.weaponSelect == 16 ) { - cg.weaponSelect = 0; - } - if ( cg.weaponSelect == WP_GAUNTLET ) { - continue; // never cycle to gauntlet - } - if ( CG_WeaponSelectable( cg.weaponSelect ) ) { - break; - } - } - if ( i == 16 ) { - cg.weaponSelect = original; - } -} - -/* -=============== -CG_PrevWeapon_f -=============== -*/ -void CG_PrevWeapon_f( void ) { - int i; - int original; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - cg.weaponSelectTime = cg.time; - original = cg.weaponSelect; - - for ( i = 0 ; i < 16 ; i++ ) { - cg.weaponSelect--; - if ( cg.weaponSelect == -1 ) { - cg.weaponSelect = 15; - } - if ( cg.weaponSelect == WP_GAUNTLET ) { - continue; // never cycle to gauntlet - } - if ( CG_WeaponSelectable( cg.weaponSelect ) ) { - break; - } - } - if ( i == 16 ) { - cg.weaponSelect = original; - } -} - -/* -=============== -CG_Weapon_f -=============== -*/ -void CG_Weapon_f( void ) { - int num; - - if ( !cg.snap ) { - return; - } - if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { - return; - } - - num = atoi( CG_Argv( 1 ) ); - - if ( num < 1 || num > 15 ) { - return; - } - - cg.weaponSelectTime = cg.time; - - if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { - return; // don't have the weapon - } - - cg.weaponSelect = num; -} - -/* -=================== -CG_OutOfAmmoChange - -The current weapon has just run out of ammo -=================== -*/ -void CG_OutOfAmmoChange( void ) { - int i; - - cg.weaponSelectTime = cg.time; - - for ( i = 15 ; i > 0 ; i-- ) { - if ( CG_WeaponSelectable( i ) ) { - cg.weaponSelect = i; - break; - } - } -} - - - -/* -=================================================================================================== - -WEAPON EVENTS - -=================================================================================================== -*/ - -/* -================ -CG_FireWeapon - -Caused by an EV_FIRE_WEAPON event -================ -*/ -void CG_FireWeapon( centity_t *cent ) { - entityState_t *ent; - int c; - weaponInfo_t *weap; - - ent = ¢->currentState; - if ( ent->weapon == WP_NONE ) { - return; - } - if ( ent->weapon >= WP_NUM_WEAPONS ) { - CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); - return; - } - weap = &cg_weapons[ ent->weapon ]; - - // mark the entity as muzzle flashing, so when it is added it will - // append the flash to the weapon model - cent->muzzleFlashTime = cg.time; - - // lightning gun only does this this on initial press - if ( ent->weapon == WP_LIGHTNING ) { - if ( cent->pe.lightningFiring ) { - return; - } - } - - // play quad sound if needed - if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { - trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); - } - - // play a sound - for ( c = 0 ; c < 4 ; c++ ) { - if ( !weap->flashSound[c] ) { - break; - } - } - if ( c > 0 ) { - c = rand() % c; - if ( weap->flashSound[c] ) - { - trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); - } - } - - // do brass ejection - if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { - weap->ejectBrassFunc( cent ); - } -} - - -/* -================= -CG_MissileHitWall - -Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing -================= -*/ -void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { - qhandle_t mod; - qhandle_t mark; - qhandle_t shader; - sfxHandle_t sfx; - float radius; - float light; - vec3_t lightColor; - localEntity_t *le; - int r; - qboolean alphaFade; - qboolean isSprite; - int duration; - vec3_t sprOrg; - vec3_t sprVel; - - mark = 0; - radius = 32; - sfx = 0; - mod = 0; - shader = 0; - light = 0; - lightColor[0] = 1; - lightColor[1] = 1; - lightColor[2] = 0; - - // set defaults - isSprite = qfalse; - duration = 600; - - switch ( weapon ) { - default: -#ifdef MISSIONPACK - case WP_NAILGUN: - if( soundType == IMPACTSOUND_FLESH ) { - sfx = cgs.media.sfx_nghitflesh; - } else if( soundType == IMPACTSOUND_METAL ) { - sfx = cgs.media.sfx_nghitmetal; - } else { - sfx = cgs.media.sfx_nghit; - } - mark = cgs.media.holeMarkShader; - radius = 12; - break; -#endif - case WP_LIGHTNING: - // no explosion at LG impact, it is added with the beam - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.sfx_lghit2; - } else if ( r == 2 ) { - sfx = cgs.media.sfx_lghit1; - } else { - sfx = cgs.media.sfx_lghit3; - } - mark = cgs.media.holeMarkShader; - radius = 12; - break; -#ifdef MISSIONPACK - case WP_PROX_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.grenadeExplosionShader; - sfx = cgs.media.sfx_proxexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - break; -#endif - case WP_GRENADE_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.grenadeExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - break; - case WP_ROCKET_LAUNCHER: - mod = cgs.media.dishFlashModel; - shader = cgs.media.rocketExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 64; - light = 300; - isSprite = qtrue; - duration = 1000; - lightColor[0] = 1; - lightColor[1] = 0.75; - lightColor[2] = 0.0; - if (cg_oldRocket.integer == 0) { - // explosion sprite animation - VectorMA( origin, 24, dir, sprOrg ); - VectorScale( dir, 64, sprVel ); - - CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); - } - break; - case WP_RAILGUN: - mod = cgs.media.ringFlashModel; - shader = cgs.media.railExplosionShader; - sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 24; - break; - case WP_PLASMAGUN: - mod = cgs.media.ringFlashModel; - shader = cgs.media.plasmaExplosionShader; - sfx = cgs.media.sfx_plasmaexp; - mark = cgs.media.energyMarkShader; - radius = 16; - break; - case WP_BFG: - mod = cgs.media.dishFlashModel; - shader = cgs.media.bfgExplosionShader; - sfx = cgs.media.sfx_rockexp; - mark = cgs.media.burnMarkShader; - radius = 32; - isSprite = qtrue; - break; - case WP_SHOTGUN: - mod = cgs.media.bulletFlashModel; - shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; - sfx = 0; - radius = 4; - break; - -#ifdef MISSIONPACK - case WP_CHAINGUN: - mod = cgs.media.bulletFlashModel; - if( soundType == IMPACTSOUND_FLESH ) { - sfx = cgs.media.sfx_chghitflesh; - } else if( soundType == IMPACTSOUND_METAL ) { - sfx = cgs.media.sfx_chghitmetal; - } else { - sfx = cgs.media.sfx_chghit; - } - mark = cgs.media.bulletMarkShader; - - r = rand() & 3; - if ( r < 2 ) { - sfx = cgs.media.sfx_ric1; - } else if ( r == 2 ) { - sfx = cgs.media.sfx_ric2; - } else { - sfx = cgs.media.sfx_ric3; - } - - radius = 8; - break; -#endif - - case WP_MACHINEGUN: - mod = cgs.media.bulletFlashModel; - shader = cgs.media.bulletExplosionShader; - mark = cgs.media.bulletMarkShader; - - r = rand() & 3; - if ( r == 0 ) { - sfx = cgs.media.sfx_ric1; - } else if ( r == 1 ) { - sfx = cgs.media.sfx_ric2; - } else { - sfx = cgs.media.sfx_ric3; - } - - radius = 8; - break; - } - - if ( sfx ) { - trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); - } - - // - // create the explosion - // - if ( mod ) { - le = CG_MakeExplosion( origin, dir, - mod, shader, - duration, isSprite ); - le->light = light; - VectorCopy( lightColor, le->lightColor ); - if ( weapon == WP_RAILGUN ) { - // colorize with client color - VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); - } - } - - // - // impact mark - // - alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color - if ( weapon == WP_RAILGUN ) { - float *color; - - // colorize with client color - color = cgs.clientinfo[clientNum].color2; - CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); - } else { - CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); - } -} - - -/* -================= -CG_MissileHitPlayer -================= -*/ -void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { - CG_Bleed( origin, entityNum ); - - // some weapons will make an explosion with the blood, while - // others will just make the blood - switch ( weapon ) { - case WP_GRENADE_LAUNCHER: - case WP_ROCKET_LAUNCHER: -#ifdef MISSIONPACK - case WP_NAILGUN: - case WP_CHAINGUN: - case WP_PROX_LAUNCHER: -#endif - CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); - break; - default: - break; - } -} - - - -/* -============================================================================ - -SHOTGUN TRACING - -============================================================================ -*/ - -/* -================ -CG_ShotgunPellet -================ -*/ -static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { - trace_t tr; - int sourceContentType, destContentType; - - CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); - - sourceContentType = trap_CM_PointContents( start, 0 ); - destContentType = trap_CM_PointContents( tr.endpos, 0 ); - - // FIXME: should probably move this cruft into CG_BubbleTrail - if ( sourceContentType == destContentType ) { - if ( sourceContentType & CONTENTS_WATER ) { - CG_BubbleTrail( start, tr.endpos, 32 ); - } - } else if ( sourceContentType & CONTENTS_WATER ) { - trace_t trace; - - trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( start, trace.endpos, 32 ); - } else if ( destContentType & CONTENTS_WATER ) { - trace_t trace; - - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); - } - - if ( tr.surfaceFlags & SURF_NOIMPACT ) { - return; - } - - if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { - CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); - } else { - if ( tr.surfaceFlags & SURF_NOIMPACT ) { - // SURF_NOIMPACT will not make a flame puff or a mark - return; - } - if ( tr.surfaceFlags & SURF_METALSTEPS ) { - CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); - } else { - CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); - } - } -} - -/* -================ -CG_ShotgunPattern - -Perform the same traces the server did to locate the -hit splashes -================ -*/ -static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { - int i; - float r, u; - vec3_t end; - vec3_t forward, right, up; - - // derive the right and up vectors from the forward vector, because - // the client won't have any other information - VectorNormalize2( origin2, forward ); - PerpendicularVector( right, forward ); - CrossProduct( forward, right, up ); - - // generate the "random" spread pattern - for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { - r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; - u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; - VectorMA( origin, 8192 * 16, forward, end); - VectorMA (end, r, right, end); - VectorMA (end, u, up, end); - - CG_ShotgunPellet( origin, end, otherEntNum ); - } -} - -/* -============== -CG_ShotgunFire -============== -*/ -void CG_ShotgunFire( entityState_t *es ) { - vec3_t v; - int contents; - - VectorSubtract( es->origin2, es->pos.trBase, v ); - VectorNormalize( v ); - VectorScale( v, 32, v ); - VectorAdd( es->pos.trBase, v, v ); - if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { - // ragepro can't alpha fade, so don't even bother with smoke - vec3_t up; - - contents = trap_CM_PointContents( es->pos.trBase, 0 ); - if ( !( contents & CONTENTS_WATER ) ) { - VectorSet( up, 0, 0, 8 ); - CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); - } - } - CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); -} - -/* -============================================================================ - -BULLETS - -============================================================================ -*/ - - -/* -=============== -CG_Tracer -=============== -*/ -void CG_Tracer( vec3_t source, vec3_t dest ) { - vec3_t forward, right; - polyVert_t verts[4]; - vec3_t line; - float len, begin, end; - vec3_t start, finish; - vec3_t midpoint; - - // tracer - VectorSubtract( dest, source, forward ); - len = VectorNormalize( forward ); - - // start at least a little ways from the muzzle - if ( len < 100 ) { - return; - } - begin = 50 + random() * (len - 60); - end = begin + cg_tracerLength.value; - if ( end > len ) { - end = len; - } - VectorMA( source, begin, forward, start ); - VectorMA( source, end, forward, finish ); - - line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); - line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); - - VectorScale( cg.refdef.viewaxis[1], line[1], right ); - VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); - VectorNormalize( right ); - - VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); - verts[0].st[0] = 0; - verts[0].st[1] = 1; - verts[0].modulate[0] = 255; - verts[0].modulate[1] = 255; - verts[0].modulate[2] = 255; - verts[0].modulate[3] = 255; - - VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); - verts[1].st[0] = 1; - verts[1].st[1] = 0; - verts[1].modulate[0] = 255; - verts[1].modulate[1] = 255; - verts[1].modulate[2] = 255; - verts[1].modulate[3] = 255; - - VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); - verts[2].st[0] = 1; - verts[2].st[1] = 1; - verts[2].modulate[0] = 255; - verts[2].modulate[1] = 255; - verts[2].modulate[2] = 255; - verts[2].modulate[3] = 255; - - VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); - verts[3].st[0] = 0; - verts[3].st[1] = 0; - verts[3].modulate[0] = 255; - verts[3].modulate[1] = 255; - verts[3].modulate[2] = 255; - verts[3].modulate[3] = 255; - - trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); - - midpoint[0] = ( start[0] + finish[0] ) * 0.5; - midpoint[1] = ( start[1] + finish[1] ) * 0.5; - midpoint[2] = ( start[2] + finish[2] ) * 0.5; - - // add the tracer sound - trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); - -} - - -/* -====================== -CG_CalcMuzzlePoint -====================== -*/ -static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { - vec3_t forward; - centity_t *cent; - int anim; - - if ( entityNum == cg.snap->ps.clientNum ) { - VectorCopy( cg.snap->ps.origin, muzzle ); - muzzle[2] += cg.snap->ps.viewheight; - AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); - VectorMA( muzzle, 14, forward, muzzle ); - return qtrue; - } - - cent = &cg_entities[entityNum]; - if ( !cent->currentValid ) { - return qfalse; - } - - VectorCopy( cent->currentState.pos.trBase, muzzle ); - - AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); - anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; - if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { - muzzle[2] += CROUCH_VIEWHEIGHT; - } else { - muzzle[2] += DEFAULT_VIEWHEIGHT; - } - - VectorMA( muzzle, 14, forward, muzzle ); - - return qtrue; - -} - -/* -====================== -CG_Bullet - -Renders bullet effects. -====================== -*/ -void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { - trace_t trace; - int sourceContentType, destContentType; - vec3_t start; - - // if the shooter is currently valid, calc a source point and possibly - // do trail effects - if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { - if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { - sourceContentType = trap_CM_PointContents( start, 0 ); - destContentType = trap_CM_PointContents( end, 0 ); - - // do a complete bubble trail if necessary - if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { - CG_BubbleTrail( start, end, 32 ); - } - // bubble trail from water into air - else if ( ( sourceContentType & CONTENTS_WATER ) ) { - trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( start, trace.endpos, 32 ); - } - // bubble trail from air into water - else if ( ( destContentType & CONTENTS_WATER ) ) { - trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); - CG_BubbleTrail( trace.endpos, end, 32 ); - } - - // draw a tracer - if ( random() < cg_tracerChance.value ) { - CG_Tracer( start, end ); - } - } - } - - // impact splash and mark - if ( flesh ) { - CG_Bleed( end, fleshEntityNum ); - } else { - CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); - } - -} +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" + +/* +========================== +CG_MachineGunEjectBrass +========================== +*/ +static void CG_MachineGunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + float waterScale = 1.0f; + vec3_t v[3]; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 0; + velocity[1] = -50 + 40 * crandom(); + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - (rand()&15); + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = -4; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.machinegunBrassModel; + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +/* +========================== +CG_ShotgunEjectBrass +========================== +*/ +static void CG_ShotgunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + vec3_t v[3]; + int i; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + for ( i = 0; i < 2; i++ ) { + float waterScale = 1.0f; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 + 60 * crandom(); + if ( i == 0 ) { + velocity[1] = 40 + 10 * crandom(); + } else { + velocity[1] = -40 + 10 * crandom(); + } + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = 0; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.shotgunBrassModel; + le->bounceFactor = 0.3f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; + } +} + + +#ifdef MISSIONPACK +/* +========================== +CG_NailgunEjectBrass +========================== +*/ +static void CG_NailgunEjectBrass( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + vec3_t v[3]; + vec3_t offset; + vec3_t xoffset; + vec3_t up; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 0; + offset[1] = -12; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, origin ); + + VectorSet( up, 0, 0, 64 ); + + smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} +#endif + + +/* +========================== +CG_RailTrail +========================== +*/ +void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { + vec3_t axis[36], move, move2, next_move, vec, temp; + float len; + int i, j, skip; + + localEntity_t *le; + refEntity_t *re; + +#define RADIUS 4 +#define ROTATION 1 +#define SPACING 5 + + start[2] -= 4; + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + PerpendicularVector(temp, vec); + for (i = 0 ; i < 36; i++) { + RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + cg_railTrailTime.value; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_RAIL_CORE; + re->customShader = cgs.media.railCoreShader; + + VectorCopy(start, re->origin); + VectorCopy(end, re->oldorigin); + + re->shaderRGBA[0] = ci->color1[0] * 255; + re->shaderRGBA[1] = ci->color1[1] * 255; + re->shaderRGBA[2] = ci->color1[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color1[0] * 0.75; + le->color[1] = ci->color1[1] * 0.75; + le->color[2] = ci->color1[2] * 0.75; + le->color[3] = 1.0f; + + AxisClear( re->axis ); + + VectorMA(move, 20, vec, move); + VectorCopy(move, next_move); + VectorScale (vec, SPACING, vec); + + if (cg_oldRail.integer != 0) { + // nudge down a bit so it isn't exactly in center + re->origin[2] -= 8; + re->oldorigin[2] -= 8; + return; + } + skip = -1; + + j = 18; + for (i = 0; i < len; i += SPACING) { + if (i != skip) { + skip = i + SPACING; + le = CG_AllocLocalEntity(); + re = &le->refEntity; + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + (i>>1) + 600; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 1.1f; + re->customShader = cgs.media.railRingsShader; + + re->shaderRGBA[0] = ci->color2[0] * 255; + re->shaderRGBA[1] = ci->color2[1] * 255; + re->shaderRGBA[2] = ci->color2[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color2[0] * 0.75; + le->color[1] = ci->color2[1] * 0.75; + le->color[2] = ci->color2[2] * 0.75; + le->color[3] = 1.0f; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + + VectorCopy( move, move2); + VectorMA(move2, RADIUS , axis[j], move2); + VectorCopy(move2, le->pos.trBase); + + le->pos.trDelta[0] = axis[j][0]*6; + le->pos.trDelta[1] = axis[j][1]*6; + le->pos.trDelta[2] = axis[j][2]*6; + } + + VectorAdd (move, vec, move); + + j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; + } +} + +/* +========================== +CG_RocketTrail +========================== +*/ +static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} + +#ifdef MISSIONPACK +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.nailPuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} +#endif + +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { + localEntity_t *le; + refEntity_t *re; + entityState_t *es; + vec3_t velocity, xvelocity, origin; + vec3_t offset, xoffset; + vec3_t v[3]; + int t, startTime, step; + + float waterScale = 1.0f; + + if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { + return; + } + + step = 50; + + es = ¢->currentState; + startTime = cent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 - 120 * crandom(); + velocity[1] = 40 - 80 * crandom(); + velocity[2] = 100 - 200 * crandom(); + + le->leType = LE_MOVE_SCALE_FADE; + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_NONE; + le->leMarkType = LEMT_NONE; + + le->startTime = cg.time; + le->endTime = le->startTime + 600; + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 2; + offset[1] = 2; + offset[2] = 2; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + + VectorAdd( origin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 0.25f; + re->customShader = cgs.media.railRingsShader; + le->bounceFactor = 0.3f; + + re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; + re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; + re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; + re->shaderRGBA[3] = 63; + + le->color[0] = wi->flashDlightColor[0] * 0.2; + le->color[1] = wi->flashDlightColor[1] * 0.2; + le->color[2] = wi->flashDlightColor[2] * 0.2; + le->color[3] = 0.25f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + +} +/* +========================== +CG_GrappleTrail +========================== +*/ +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { + vec3_t origin; + entityState_t *es; + vec3_t forward, up; + refEntity_t beam; + + es = &ent->currentState; + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + ent->trailTime = cg.time; + + memset( &beam, 0, sizeof( beam ) ); + //FIXME adjust for muzzle position + VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); + beam.origin[2] += 26; + AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); + VectorMA( beam.origin, -6, up, beam.origin ); + VectorCopy( origin, beam.oldorigin ); + + if (Distance( beam.origin, beam.oldorigin ) < 64 ) + return; // Don't draw if close + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + trap_R_AddRefEntityToScene( &beam ); +} + +/* +========================== +CG_GrenadeTrail +========================== +*/ +static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { + CG_RocketTrail( ent, wi ); +} + + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH]; + vec3_t mins, maxs; + int i; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + CG_RegisterItemVisuals( item - bg_itemlist ); + + // load cmodel before model so filecache works + weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); + weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { + break; + } + } + if ( ammo->classname && ammo->world_model[0] ) { + weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); + } + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + weaponInfo->flashModel = trap_R_RegisterModel( path ); + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel = trap_R_RegisterModel( path ); + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + + if ( !weaponInfo->handsModel ) { + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + } + + weaponInfo->loopFireSound = qfalse; + + switch ( weaponNum ) { + case WP_GAUNTLET: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); + break; + + case WP_LIGHTNING: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); + cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); + cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); + cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); + cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); + cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); + + break; + + case WP_GRAPPLING_HOOK: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileTrailFunc = CG_GrappleTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); + weaponInfo->loopFireSound = qtrue; + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; +#endif + + case WP_MACHINEGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; + + case WP_SHOTGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; + break; + + case WP_ROCKET_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + weaponInfo->missileTrailFunc = CG_RocketTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + + MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; +#endif + + case WP_GRENADE_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_NAILGUN: + weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; + weaponInfo->missileTrailFunc = CG_NailTrail; +// weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); + weaponInfo->trailRadius = 16; + weaponInfo->wiTrailTime = 250; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); + break; +#endif + + case WP_PLASMAGUN: +// weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; + weaponInfo->missileTrailFunc = CG_PlasmaTrail; + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); + cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + break; + + case WP_RAILGUN: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); + cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); + break; + + case WP_BFG: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); + cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + break; + } +} + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + + if ( itemNum < 0 || itemNum >= bg_numItems ) { + CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); + } + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + + itemInfo->icon = trap_R_RegisterShader( item->icon ); + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + // + // powerups have an accompanying ring or sphere + // + if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || + item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { + if ( item->world_model[1] ) { + itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); + } + } +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { + + // change weapon + if ( frame >= ci->animations[TORSO_DROP].firstFrame + && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { + return frame - ci->animations[TORSO_DROP].firstFrame + 6; + } + + // stand attack + if ( frame >= ci->animations[TORSO_ATTACK].firstFrame + && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; + } + + // stand attack 2 + if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame + && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; + } + + return 0; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // on odd legs, invert some angles + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // gun angles from bobbing + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg.landChange*0.25 * + (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME/2 ) { + origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); + } +#endif + + // idle drift + scale = cg.xyspeed + 40; + fracsin = sin( cg.time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; +} + + +/* +=============== +CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { + trace_t trace; + refEntity_t beam; + vec3_t forward; + vec3_t muzzlePoint, endPoint; + + if (cent->currentState.weapon != WP_LIGHTNING) { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // CPMA "true" lightning + if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { + vec3_t angle; + int i; + + for (i = 0; i < 3; i++) { + float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; + if (a > 180) { + a -= 360; + } + if (a < -180) { + a += 360; + } + + angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); + if (angle[i] < 0) { + angle[i] += 360; + } + if (angle[i] > 360) { + angle[i] -= 360; + } + } + + AngleVectors(angle, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); +// VectorCopy(cg.refdef.vieworg, muzzlePoint ); + } else { + // !CPMA + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); + } + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } +} +/* + +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { + trace_t trace; + refEntity_t beam; + vec3_t forward; + vec3_t muzzlePoint, endPoint; + + if ( cent->currentState.weapon != WP_LIGHTNING ) { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // find muzzle point for this frame + VectorCopy( cent->lerpOrigin, muzzlePoint ); + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } +} +*/ + +/* +=============== +CG_SpawnRailTrail + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +=============== +*/ +static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { + clientInfo_t *ci; + + if ( cent->currentState.weapon != WP_RAILGUN ) { + return; + } + if ( !cent->pe.railgunFlash ) { + return; + } + cent->pe.railgunFlash = qtrue; + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RailTrail( ci, origin, cent->pe.railgunImpact ); +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 +static float CG_MachinegunSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); + } +#endif + } + + return angle; +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { + // add powerup effects + if ( powerups & ( 1 << PW_INVIS ) ) { + gun->customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( gun ); + } else { + trap_R_AddRefEntityToScene( gun ); + + if ( powerups & ( 1 << PW_BATTLESUIT ) ) { + gun->customShader = cgs.media.battleWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + if ( powerups & ( 1 << PW_QUAD ) ) { + gun->customShader = cgs.media.quadWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + } +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; +// int col; + + weaponNum = cent->currentState.weapon; + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; + + // add the weapon + memset( &gun, 0, sizeof( gun ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + // set custom shading for railgun refire rate + if ( ps ) { + if ( cg.predictedPlayerState.weapon == WP_RAILGUN + && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { + float f; + + f = (float)cg.predictedPlayerState.weaponTime / 1500; + gun.shaderRGBA[1] = 0; + gun.shaderRGBA[0] = + gun.shaderRGBA[2] = 255 * ( 1.0 - f ); + } else { + gun.shaderRGBA[0] = 255; + gun.shaderRGBA[1] = 255; + gun.shaderRGBA[2] = 255; + gun.shaderRGBA[3] = 255; + } + } + + gun.hModel = weapon->weaponModel; + if (!gun.hModel) { + return; + } + + if ( !ps ) { + // add weapon ready sound + cent->pe.lightningFiring = qfalse; + if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // lightning gun and guantlet make a different sound when fire is held down + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); + cent->pe.lightningFiring = qtrue; + } else if ( weapon->readySound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + } + + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); + + // add the spinning barrel + if ( weapon->barrelModel ) { + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + barrel.hModel = weapon->barrelModel; + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = CG_MachinegunSpinAngle( cent ); + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + } + + // make sure we aren't looking at cg.predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on teh single player podiums), so + // go ahead and use the cent + if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + // add the flash + if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } else { + // impulse flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { + return; + } + } + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + flash.hModel = weapon->flashModel; + if (!flash.hModel) { + return; + } + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = crandom() * 10; + AnglesToAxis( angles, flash.axis ); + + // colorize the railgun blast + if ( weaponNum == WP_RAILGUN ) { + clientInfo_t *ci; + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + flash.shaderRGBA[0] = 255 * ci->color1[0]; + flash.shaderRGBA[1] = 255 * ci->color1[1]; + flash.shaderRGBA[2] = 255 * ci->color1[2]; + } + + CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); + trap_R_AddRefEntityToScene( &flash ); + + if ( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) { + // add lightning bolt + CG_LightningBolt( nonPredictedCent, flash.origin ); + + // add rail trail + CG_SpawnRailTrail( cent, flash.origin ); + + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *weapon; + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view or a camera is active + //if ( cg.renderingThirdPerson || cg.cameraMode) { + if ( cg.renderingThirdPerson ) { + return; + } + + + // allow the gun to be completely removed + if ( !cg_drawGun.integer ) { + vec3_t origin; + + if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { + // special hack for lightning gun... + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); + CG_LightningBolt( &cg_entities[ps->clientNum], origin ); + } + return; + } + + // don't draw if testing a gun model + if ( cg.testGun ) { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 90 ) { + fovOffset = -0.2 * ( cg_fov.integer - 90 ); + } else { + fovOffset = 0; + } + + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset (&hand, 0, sizeof(hand)); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if ( cg_gun_frame.integer ) { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { + // get clientinfo for animation map + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); + hand.backlerp = cent->pe.torso.backlerp; + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +void CG_DrawWeaponSelect( void ) { + int i; + int bits; + int count; + int x, y, w; + char *name; + float *color; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if ( !color ) { + return; + } + trap_R_SetColor( color ); + + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // count the number of weapons owned + bits = cg.snap->ps.stats[ STAT_WEAPONS ]; + count = 0; + for ( i = 1 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + count++; + } + } + + x = 320 - count * 20; + y = 380; + + for ( i = 1 ; i < 16 ; i++ ) { + if ( !( bits & ( 1 << i ) ) ) { + continue; + } + + CG_RegisterWeapon( i ); + + // draw weapon icon + CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); + + // draw selection marker + if ( i == cg.weaponSelect ) { + CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); + } + + // no ammo cross on top + if ( !cg.snap->ps.ammo[ i ] ) { + CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); + } + + x += 40; + } + + // draw the selected name + if ( cg_weapons[ cg.weaponSelect ].item ) { + name = cg_weapons[ cg.weaponSelect ].item->pickup_name; + if ( name ) { + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + CG_DrawBigStringColor(x, y - 22, name, color); + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + if ( !cg.snap->ps.ammo[i] ) { + return qfalse; + } + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect++; + if ( cg.weaponSelect == 16 ) { + cg.weaponSelect = 0; + } + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < 16 ; i++ ) { + cg.weaponSelect--; + if ( cg.weaponSelect == -1 ) { + cg.weaponSelect = 15; + } + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == 16 ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > 15 ) { + return; + } + + cg.weaponSelectTime = cg.time; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { + return; // don't have the weapon + } + + cg.weaponSelect = num; +} + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( void ) { + int i; + + cg.weaponSelectTime = cg.time; + + for ( i = 15 ; i > 0 ; i-- ) { + if ( CG_WeaponSelectable( i ) ) { + cg.weaponSelect = i; + break; + } + } +} + + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + + ent = ¢->currentState; + if ( ent->weapon == WP_NONE ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + cent->muzzleFlashTime = cg.time; + + // lightning gun only does this this on initial press + if ( ent->weapon == WP_LIGHTNING ) { + if ( cent->pe.lightningFiring ) { + return; + } + } + + // play quad sound if needed + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + } + + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->flashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->flashSound[c] ) + { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); + } + } + + // do brass ejection + if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { + weap->ejectBrassFunc( cent ); + } +} + + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; + int r; + qboolean alphaFade; + qboolean isSprite; + int duration; + vec3_t sprOrg; + vec3_t sprVel; + + mark = 0; + radius = 32; + sfx = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + + switch ( weapon ) { + default: +#ifdef MISSIONPACK + case WP_NAILGUN: + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_nghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_nghitmetal; + } else { + sfx = cgs.media.sfx_nghit; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#endif + case WP_LIGHTNING: + // no explosion at LG impact, it is added with the beam + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_lghit2; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_lghit1; + } else { + sfx = cgs.media.sfx_lghit3; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_proxexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; +#endif + case WP_GRENADE_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; + case WP_ROCKET_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.rocketExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 1; + lightColor[1] = 0.75; + lightColor[2] = 0.0; + if (cg_oldRocket.integer == 0) { + // explosion sprite animation + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); + } + break; + case WP_RAILGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.railExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 24; + break; + case WP_PLASMAGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.plasmaExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 16; + break; + case WP_BFG: + mod = cgs.media.dishFlashModel; + shader = cgs.media.bfgExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 32; + isSprite = qtrue; + break; + case WP_SHOTGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + sfx = 0; + radius = 4; + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + mod = cgs.media.bulletFlashModel; + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_chghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_chghitmetal; + } else { + sfx = cgs.media.sfx_chghit; + } + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; +#endif + + case WP_MACHINEGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r == 0 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 1 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; + } + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, isSprite ); + le->light = light; + VectorCopy( lightColor, le->lightColor ); + if ( weapon == WP_RAILGUN ) { + // colorize with client color + VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); + } + } + + // + // impact mark + // + alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color + if ( weapon == WP_RAILGUN ) { + float *color; + + // colorize with client color + color = cgs.clientinfo[clientNum].color2; + CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); + } else { + CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { + CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { + case WP_GRENADE_LAUNCHER: + case WP_ROCKET_LAUNCHER: +#ifdef MISSIONPACK + case WP_NAILGUN: + case WP_CHAINGUN: + case WP_PROX_LAUNCHER: +#endif + CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); + break; + default: + break; + } +} + + + +/* +============================================================================ + +SHOTGUN TRACING + +============================================================================ +*/ + +/* +================ +CG_ShotgunPellet +================ +*/ +static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { + trace_t tr; + int sourceContentType, destContentType; + + CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); + + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( tr.endpos, 0 ); + + // FIXME: should probably move this cruft into CG_BubbleTrail + if ( sourceContentType == destContentType ) { + if ( sourceContentType & CONTENTS_WATER ) { + CG_BubbleTrail( start, tr.endpos, 32 ); + } + } else if ( sourceContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } else if ( destContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { + CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); + } else { + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + // SURF_NOIMPACT will not make a flame puff or a mark + return; + } + if ( tr.surfaceFlags & SURF_METALSTEPS ) { + CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); + } else { + CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); + } + } +} + +/* +================ +CG_ShotgunPattern + +Perform the same traces the server did to locate the +hit splashes +================ +*/ +static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + + // generate the "random" spread pattern + for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { + r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; + u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; + VectorMA( origin, 8192 * 16, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + CG_ShotgunPellet( origin, end, otherEntNum ); + } +} + +/* +============== +CG_ShotgunFire +============== +*/ +void CG_ShotgunFire( entityState_t *es ) { + vec3_t v; + int contents; + + VectorSubtract( es->origin2, es->pos.trBase, v ); + VectorNormalize( v ); + VectorScale( v, 32, v ); + VectorAdd( es->pos.trBase, v, v ); + if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { + // ragepro can't alpha fade, so don't even bother with smoke + vec3_t up; + + contents = trap_CM_PointContents( es->pos.trBase, 0 ); + if ( !( contents & CONTENTS_WATER ) ) { + VectorSet( up, 0, 0, 8 ); + CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + } + } + CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); +} + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +void CG_Tracer( vec3_t source, vec3_t dest ) { + vec3_t forward, right; + polyVert_t verts[4]; + vec3_t line; + float len, begin, end; + vec3_t start, finish; + vec3_t midpoint; + + // tracer + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + + // start at least a little ways from the muzzle + if ( len < 100 ) { + return; + } + begin = 50 + random() * (len - 60); + end = begin + cg_tracerLength.value; + if ( end > len ) { + end = len; + } + VectorMA( source, begin, forward, start ); + VectorMA( source, end, forward, finish ); + + line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); + line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); + + VectorScale( cg.refdef.viewaxis[1], line[1], right ); + VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); + VectorNormalize( right ); + + VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 1; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); + verts[1].st[0] = 1; + verts[1].st[1] = 0; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); + verts[3].st[0] = 0; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); + + midpoint[0] = ( start[0] + finish[0] ) * 0.5; + midpoint[1] = ( start[1] + finish[1] ) * 0.5; + midpoint[2] = ( start[2] + finish[2] ) * 0.5; + + // add the tracer sound + trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); + +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward; + centity_t *cent; + int anim; + + if ( entityNum == cg.snap->ps.clientNum ) { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[2] += cg.snap->ps.viewheight; + AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + } + + cent = &cg_entities[entityNum]; + if ( !cent->currentValid ) { + return qfalse; + } + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { + trace_t trace; + int sourceContentType, destContentType; + vec3_t start; + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( end, 0 ); + + // do a complete bubble trail if necessary + if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { + CG_BubbleTrail( start, end, 32 ); + } + // bubble trail from water into air + else if ( ( sourceContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } + // bubble trail from air into water + else if ( ( destContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( trace.endpos, end, 32 ); + } + + // draw a tracer + if ( random() < cg_tracerChance.value ) { + CG_Tracer( start, end ); + } + } + } + + // impact splash and mark + if ( flesh ) { + CG_Bleed( end, fleshEntityNum ); + } else { + CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); + } + +} diff --git a/code/cgame/cgame.bat b/code/cgame/cgame.bat index 6e13523..a59381e 100755 --- a/code/cgame/cgame.bat +++ b/code/cgame/cgame.bat @@ -1,63 +1,63 @@ -rem make sure we have a safe environement -set LIBRARY= -set INCLUDE= - -mkdir vm -cd vm -set cc=lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 - -%cc% ../../game/bg_misc.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_pmove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_slidemove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_lib.c -@if errorlevel 1 goto quit -%cc% ../../game/q_math.c -@if errorlevel 1 goto quit -%cc% ../../game/q_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_consolecmds.c -@if errorlevel 1 goto quit -%cc% ../cg_draw.c -@if errorlevel 1 goto quit -%cc% ../cg_drawtools.c -@if errorlevel 1 goto quit -%cc% ../cg_effects.c -@if errorlevel 1 goto quit -%cc% ../cg_ents.c -@if errorlevel 1 goto quit -%cc% ../cg_event.c -@if errorlevel 1 goto quit -%cc% ../cg_info.c -@if errorlevel 1 goto quit -%cc% ../cg_localents.c -@if errorlevel 1 goto quit -%cc% ../cg_main.c -@if errorlevel 1 goto quit -%cc% ../cg_marks.c -@if errorlevel 1 goto quit -%cc% ../cg_players.c -@if errorlevel 1 goto quit -%cc% ../cg_playerstate.c -@if errorlevel 1 goto quit -%cc% ../cg_predict.c -@if errorlevel 1 goto quit -%cc% ../cg_scoreboard.c -@if errorlevel 1 goto quit -%cc% ../cg_servercmds.c -@if errorlevel 1 goto quit -%cc% ../cg_snapshot.c -@if errorlevel 1 goto quit -%cc% ../cg_view.c -@if errorlevel 1 goto quit -%cc% ../cg_weapons.c -@if errorlevel 1 goto quit - - - - -q3asm -f ../cgame -:quit -cd .. +rem make sure we have a safe environement +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm +set cc=lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_marks.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit + + + + +q3asm -f ../cgame +:quit +cd .. diff --git a/code/cgame/cgame.def b/code/cgame/cgame.def index 01861ba..2ee748e 100755 --- a/code/cgame/cgame.def +++ b/code/cgame/cgame.def @@ -1,3 +1,3 @@ -EXPORTS - vmMain - dllEntry +EXPORTS + vmMain + dllEntry diff --git a/code/cgame/cgame.plg b/code/cgame/cgame.plg index b7c80ee..781b1eb 100755 --- a/code/cgame/cgame.plg +++ b/code/cgame/cgame.plg @@ -1,124 +1,124 @@ - - -
-

Build Log

-

---------------------Configuration: cgame - Win32 Release-------------------- -

-

Command Lines

-Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" with contents -[ -/nologo /G6 /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c -"D:\quake3\MissionPack\code\game\bg_misc.c" -"D:\quake3\MissionPack\code\game\bg_pmove.c" -"D:\quake3\MissionPack\code\game\bg_slidemove.c" -"D:\quake3\MissionPack\code\cgame\cg_consolecmds.c" -"D:\quake3\MissionPack\code\cgame\cg_draw.c" -"D:\quake3\MissionPack\code\cgame\cg_drawtools.c" -"D:\quake3\MissionPack\code\cgame\cg_effects.c" -"D:\quake3\MissionPack\code\cgame\cg_ents.c" -"D:\quake3\MissionPack\code\cgame\cg_event.c" -"D:\quake3\MissionPack\code\cgame\cg_info.c" -"D:\quake3\MissionPack\code\cgame\cg_localents.c" -"D:\quake3\MissionPack\code\cgame\cg_main.c" -"D:\quake3\MissionPack\code\cgame\cg_marks.c" -"D:\quake3\MissionPack\code\cgame\cg_players.c" -"D:\quake3\MissionPack\code\cgame\cg_playerstate.c" -"D:\quake3\MissionPack\code\cgame\cg_predict.c" -"D:\quake3\MissionPack\code\cgame\cg_rankings.c" -"D:\quake3\MissionPack\code\cgame\cg_scoreboard.c" -"D:\quake3\MissionPack\code\cgame\cg_servercmds.c" -"D:\quake3\MissionPack\code\cgame\cg_snapshot.c" -"D:\quake3\MissionPack\code\cgame\cg_syscalls.c" -"D:\quake3\MissionPack\code\cgame\cg_view.c" -"D:\quake3\MissionPack\code\cgame\cg_weapons.c" -"D:\quake3\MissionPack\code\game\q_math.c" -"D:\quake3\MissionPack\code\game\q_shared.c" -"D:\quake3\MissionPack\code\ui\ui_shared.c" -] -Creating command line "cl.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" -Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" with contents -[ -/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:no /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /machine:I386 /def:".\cgame.def" /out:"../Release/cgamex86.dll" /implib:"Release/cgamex86.lib" -.\Release\bg_misc.obj -.\Release\bg_pmove.obj -.\Release\bg_slidemove.obj -.\Release\cg_consolecmds.obj -.\Release\cg_draw.obj -.\Release\cg_drawtools.obj -.\Release\cg_effects.obj -.\Release\cg_ents.obj -.\Release\cg_event.obj -.\Release\cg_info.obj -.\Release\cg_localents.obj -.\Release\cg_main.obj -.\Release\cg_marks.obj -.\Release\cg_players.obj -.\Release\cg_playerstate.obj -.\Release\cg_predict.obj -.\Release\cg_rankings.obj -.\Release\cg_scoreboard.obj -.\Release\cg_servercmds.obj -.\Release\cg_snapshot.obj -.\Release\cg_syscalls.obj -.\Release\cg_view.obj -.\Release\cg_weapons.obj -.\Release\q_math.obj -.\Release\q_shared.obj -.\Release\ui_shared.obj -] -Creating command line "link.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" -

Output Window

-Compiling... -bg_misc.c -bg_pmove.c -D:\quake3\MissionPack\code\game\bg_pmove.c(987) : warning C4189: 'shit' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\game\bg_pmove.c(2001) : warning C4505: 'PM_InvulnerabilityMove' : unreferenced local function has been removed - D:\quake3\MissionPack\code\game\bg_pmove.c(519) : see declaration of 'PM_InvulnerabilityMove' -bg_slidemove.c -cg_consolecmds.c -cg_draw.c -cg_drawtools.c -cg_effects.c -cg_ents.c -cg_event.c -cg_info.c -cg_localents.c -cg_main.c -D:\quake3\MissionPack\code\cgame\cg_main.c(1819) : warning C4505: 'CG_Cvar_Get' : unreferenced local function has been removed - D:\quake3\MissionPack\code\cgame\cg_main.c(1513) : see declaration of 'CG_Cvar_Get' -cg_marks.c -cg_players.c -D:\quake3\MissionPack\code\cgame\cg_players.c(2209) : warning C4505: 'CG_PlayerTokens' : unreferenced local function has been removed - D:\quake3\MissionPack\code\cgame\cg_players.c(1371) : see declaration of 'CG_PlayerTokens' -cg_playerstate.c -cg_predict.c -cg_rankings.c -cg_scoreboard.c -cg_servercmds.c -cg_snapshot.c -cg_syscalls.c -cg_view.c -cg_weapons.c -q_math.c -q_shared.c -ui_shared.c -D:\quake3\MissionPack\code\ui\ui_shared.c(2223) : warning C4189: 'parent' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\ui\ui_shared.c(3501) : warning C4189: 'collision' : local variable is initialized but not referenced -D:\quake3\MissionPack\code\ui\ui_shared.c(4622) : warning C4505: 'Controls_SetDefaults' : unreferenced local function has been removed - D:\quake3\MissionPack\code\ui\ui_shared.c(2595) : see declaration of 'Controls_SetDefaults' -D:\quake3\MissionPack\code\ui\ui_shared.c(1540) : warning C4701: local variable 'value' may be used without having been initialized -D:\quake3\MissionPack\code\ui\ui_shared.c(1566) : warning C4701: local variable 'value' may be used without having been initialized -D:\quake3\MissionPack\code\ui\ui_shared.c(1912) : warning C4702: unreachable code -D:\quake3\MissionPack\code\ui\ui_shared.c(4013) : warning C4702: unreachable code -D:\quake3\MissionPack\code\ui\ui_shared.c(4056) : warning C4702: unreachable code -Linking... - Creating library Release/cgamex86.lib and object Release/cgamex86.exp - - - -

Results

-cgamex86.dll - 0 error(s), 12 warning(s) -
- - + + +
+

Build Log

+

+--------------------Configuration: cgame - Win32 Release-------------------- +

+

Command Lines

+Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" with contents +[ +/nologo /G6 /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c +"D:\quake3\MissionPack\code\game\bg_misc.c" +"D:\quake3\MissionPack\code\game\bg_pmove.c" +"D:\quake3\MissionPack\code\game\bg_slidemove.c" +"D:\quake3\MissionPack\code\cgame\cg_consolecmds.c" +"D:\quake3\MissionPack\code\cgame\cg_draw.c" +"D:\quake3\MissionPack\code\cgame\cg_drawtools.c" +"D:\quake3\MissionPack\code\cgame\cg_effects.c" +"D:\quake3\MissionPack\code\cgame\cg_ents.c" +"D:\quake3\MissionPack\code\cgame\cg_event.c" +"D:\quake3\MissionPack\code\cgame\cg_info.c" +"D:\quake3\MissionPack\code\cgame\cg_localents.c" +"D:\quake3\MissionPack\code\cgame\cg_main.c" +"D:\quake3\MissionPack\code\cgame\cg_marks.c" +"D:\quake3\MissionPack\code\cgame\cg_players.c" +"D:\quake3\MissionPack\code\cgame\cg_playerstate.c" +"D:\quake3\MissionPack\code\cgame\cg_predict.c" +"D:\quake3\MissionPack\code\cgame\cg_rankings.c" +"D:\quake3\MissionPack\code\cgame\cg_scoreboard.c" +"D:\quake3\MissionPack\code\cgame\cg_servercmds.c" +"D:\quake3\MissionPack\code\cgame\cg_snapshot.c" +"D:\quake3\MissionPack\code\cgame\cg_syscalls.c" +"D:\quake3\MissionPack\code\cgame\cg_view.c" +"D:\quake3\MissionPack\code\cgame\cg_weapons.c" +"D:\quake3\MissionPack\code\game\q_math.c" +"D:\quake3\MissionPack\code\game\q_shared.c" +"D:\quake3\MissionPack\code\ui\ui_shared.c" +] +Creating command line "cl.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" +Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" with contents +[ +/nologo /base:"0x30000000" /subsystem:windows /dll /incremental:no /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /machine:I386 /def:".\cgame.def" /out:"../Release/cgamex86.dll" /implib:"Release/cgamex86.lib" +.\Release\bg_misc.obj +.\Release\bg_pmove.obj +.\Release\bg_slidemove.obj +.\Release\cg_consolecmds.obj +.\Release\cg_draw.obj +.\Release\cg_drawtools.obj +.\Release\cg_effects.obj +.\Release\cg_ents.obj +.\Release\cg_event.obj +.\Release\cg_info.obj +.\Release\cg_localents.obj +.\Release\cg_main.obj +.\Release\cg_marks.obj +.\Release\cg_players.obj +.\Release\cg_playerstate.obj +.\Release\cg_predict.obj +.\Release\cg_rankings.obj +.\Release\cg_scoreboard.obj +.\Release\cg_servercmds.obj +.\Release\cg_snapshot.obj +.\Release\cg_syscalls.obj +.\Release\cg_view.obj +.\Release\cg_weapons.obj +.\Release\q_math.obj +.\Release\q_shared.obj +.\Release\ui_shared.obj +] +Creating command line "link.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" +

Output Window

+Compiling... +bg_misc.c +bg_pmove.c +D:\quake3\MissionPack\code\game\bg_pmove.c(987) : warning C4189: 'shit' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\game\bg_pmove.c(2001) : warning C4505: 'PM_InvulnerabilityMove' : unreferenced local function has been removed + D:\quake3\MissionPack\code\game\bg_pmove.c(519) : see declaration of 'PM_InvulnerabilityMove' +bg_slidemove.c +cg_consolecmds.c +cg_draw.c +cg_drawtools.c +cg_effects.c +cg_ents.c +cg_event.c +cg_info.c +cg_localents.c +cg_main.c +D:\quake3\MissionPack\code\cgame\cg_main.c(1819) : warning C4505: 'CG_Cvar_Get' : unreferenced local function has been removed + D:\quake3\MissionPack\code\cgame\cg_main.c(1513) : see declaration of 'CG_Cvar_Get' +cg_marks.c +cg_players.c +D:\quake3\MissionPack\code\cgame\cg_players.c(2209) : warning C4505: 'CG_PlayerTokens' : unreferenced local function has been removed + D:\quake3\MissionPack\code\cgame\cg_players.c(1371) : see declaration of 'CG_PlayerTokens' +cg_playerstate.c +cg_predict.c +cg_rankings.c +cg_scoreboard.c +cg_servercmds.c +cg_snapshot.c +cg_syscalls.c +cg_view.c +cg_weapons.c +q_math.c +q_shared.c +ui_shared.c +D:\quake3\MissionPack\code\ui\ui_shared.c(2223) : warning C4189: 'parent' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\ui\ui_shared.c(3501) : warning C4189: 'collision' : local variable is initialized but not referenced +D:\quake3\MissionPack\code\ui\ui_shared.c(4622) : warning C4505: 'Controls_SetDefaults' : unreferenced local function has been removed + D:\quake3\MissionPack\code\ui\ui_shared.c(2595) : see declaration of 'Controls_SetDefaults' +D:\quake3\MissionPack\code\ui\ui_shared.c(1540) : warning C4701: local variable 'value' may be used without having been initialized +D:\quake3\MissionPack\code\ui\ui_shared.c(1566) : warning C4701: local variable 'value' may be used without having been initialized +D:\quake3\MissionPack\code\ui\ui_shared.c(1912) : warning C4702: unreachable code +D:\quake3\MissionPack\code\ui\ui_shared.c(4013) : warning C4702: unreachable code +D:\quake3\MissionPack\code\ui\ui_shared.c(4056) : warning C4702: unreachable code +Linking... + Creating library Release/cgamex86.lib and object Release/cgamex86.exp + + + +

Results

+cgamex86.dll - 0 error(s), 12 warning(s) +
+ + diff --git a/code/cgame/cgame.q3asm b/code/cgame/cgame.q3asm index 7923f73..8bd1d4a 100755 --- a/code/cgame/cgame.q3asm +++ b/code/cgame/cgame.q3asm @@ -1,26 +1,26 @@ --o "\quake3\baseq3\vm\cgame" -cg_main -..\cg_syscalls -cg_consolecmds -cg_draw -cg_drawtools -cg_effects -cg_ents -cg_event -cg_info -cg_localents -cg_marks -cg_players -cg_playerstate -cg_predict -cg_scoreboard -cg_servercmds -cg_snapshot -cg_view -cg_weapons -bg_slidemove -bg_pmove -bg_lib -bg_misc -q_math -q_shared +-o "\quake3\baseq3\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared diff --git a/code/cgame/cgame.sh b/code/cgame/cgame.sh index 62f2570..cdba268 100755 --- a/code/cgame/cgame.sh +++ b/code/cgame/cgame.sh @@ -1,36 +1,36 @@ -#!/bin/sh - -mkdir -p vm -cd vm - -CC="q3lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../q3_ui" - -$CC ../cg_syscalls.c -$CC ../../game/bg_misc.c -$CC ../../game/bg_pmove.c -$CC ../../game/bg_slidemove.c -$CC ../../game/bg_lib.c -$CC ../../game/q_math.c -$CC ../../game/q_shared.c -$CC ../cg_consolecmds.c -$CC ../cg_draw.c -$CC ../cg_drawtools.c -$CC ../cg_effects.c -$CC ../cg_ents.c -$CC ../cg_event.c -$CC ../cg_info.c -$CC ../cg_localents.c -$CC ../cg_main.c -$CC ../cg_marks.c -$CC ../cg_players.c -$CC ../cg_playerstate.c -$CC ../cg_predict.c -$CC ../cg_scoreboard.c -$CC ../cg_servercmds.c -$CC ../cg_snapshot.c -$CC ../cg_view.c -$CC ../cg_weapons.c - -q3asm -f ../cgame - -cd .. +#!/bin/sh + +mkdir -p vm +cd vm + +CC="q3lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../q3_ui" + +$CC ../cg_syscalls.c +$CC ../../game/bg_misc.c +$CC ../../game/bg_pmove.c +$CC ../../game/bg_slidemove.c +$CC ../../game/bg_lib.c +$CC ../../game/q_math.c +$CC ../../game/q_shared.c +$CC ../cg_consolecmds.c +$CC ../cg_draw.c +$CC ../cg_drawtools.c +$CC ../cg_effects.c +$CC ../cg_ents.c +$CC ../cg_event.c +$CC ../cg_info.c +$CC ../cg_localents.c +$CC ../cg_main.c +$CC ../cg_marks.c +$CC ../cg_players.c +$CC ../cg_playerstate.c +$CC ../cg_predict.c +$CC ../cg_scoreboard.c +$CC ../cg_servercmds.c +$CC ../cg_snapshot.c +$CC ../cg_view.c +$CC ../cg_weapons.c + +q3asm -f ../cgame + +cd .. diff --git a/code/cgame/cgame.vcproj b/code/cgame/cgame.vcproj index 4dbdfd0..fe2a212 100755 --- a/code/cgame/cgame.vcproj +++ b/code/cgame/cgame.vcproj @@ -1,1221 +1,1221 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/cgame/cgame_ta.bat b/code/cgame/cgame_ta.bat index 266e017..098be0e 100755 --- a/code/cgame/cgame_ta.bat +++ b/code/cgame/cgame_ta.bat @@ -1,65 +1,65 @@ -rem make sure we have a safe environement -set LIBRARY= -set INCLUDE= - -mkdir vm -cd vm -set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 - -%cc% ../../game/bg_misc.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_pmove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_slidemove.c -@if errorlevel 1 goto quit -%cc% ../../game/bg_lib.c -@if errorlevel 1 goto quit -%cc% ../../game/q_math.c -@if errorlevel 1 goto quit -%cc% ../../game/q_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_consolecmds.c -@if errorlevel 1 goto quit -%cc% ../cg_draw.c -@if errorlevel 1 goto quit -%cc% ../cg_drawtools.c -@if errorlevel 1 goto quit -%cc% ../cg_effects.c -@if errorlevel 1 goto quit -%cc% ../cg_ents.c -@if errorlevel 1 goto quit -%cc% ../cg_event.c -@if errorlevel 1 goto quit -%cc% ../cg_info.c -@if errorlevel 1 goto quit -%cc% ../cg_localents.c -@if errorlevel 1 goto quit -%cc% ../cg_main.c -@if errorlevel 1 goto quit -%cc% ../cg_marks.c -@if errorlevel 1 goto quit -%cc% ../cg_players.c -@if errorlevel 1 goto quit -%cc% ../cg_playerstate.c -@if errorlevel 1 goto quit -%cc% ../cg_predict.c -@if errorlevel 1 goto quit -%cc% ../cg_scoreboard.c -@if errorlevel 1 goto quit -%cc% ../cg_servercmds.c -@if errorlevel 1 goto quit -%cc% ../cg_snapshot.c -@if errorlevel 1 goto quit -%cc% ../cg_view.c -@if errorlevel 1 goto quit -%cc% ../cg_weapons.c -@if errorlevel 1 goto quit -%cc% ../../ui/ui_shared.c -@if errorlevel 1 goto quit -%cc% ../cg_newdraw.c -@if errorlevel 1 goto quit - - -q3asm -f ../cgame_ta -:quit -cd .. +rem make sure we have a safe environement +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm +set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_marks.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit +%cc% ../../ui/ui_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_newdraw.c +@if errorlevel 1 goto quit + + +q3asm -f ../cgame_ta +:quit +cd .. diff --git a/code/cgame/cgame_ta.q3asm b/code/cgame/cgame_ta.q3asm index 8b14ed9..629a778 100755 --- a/code/cgame/cgame_ta.q3asm +++ b/code/cgame/cgame_ta.q3asm @@ -1,28 +1,28 @@ --o "\quake3\missionpack\vm\cgame" -cg_main -..\cg_syscalls -cg_consolecmds -cg_draw -cg_drawtools -cg_effects -cg_ents -cg_event -cg_info -cg_localents -cg_marks -cg_players -cg_playerstate -cg_predict -cg_scoreboard -cg_servercmds -cg_snapshot -cg_view -cg_weapons -bg_slidemove -bg_pmove -bg_lib -bg_misc -q_math -q_shared -ui_shared -cg_newdraw +-o "\quake3\missionpack\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared +ui_shared +cg_newdraw diff --git a/code/cgame/cgame_ta.sh b/code/cgame/cgame_ta.sh index 5f87eb5..ce22b30 100755 --- a/code/cgame/cgame_ta.sh +++ b/code/cgame/cgame_ta.sh @@ -1,38 +1,38 @@ -#!/bin/sh - -mkdir -p vm -cd vm - -CC="q3lcc -DQ3_VM -DCGAME -DMISSIONPACK -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../ui" - -$CC ../cg_syscalls.c -$CC ../../game/bg_misc.c -$CC ../../game/bg_pmove.c -$CC ../../game/bg_slidemove.c -$CC ../../game/bg_lib.c -$CC ../../game/q_math.c -$CC ../../game/q_shared.c -$CC ../cg_consolecmds.c -$CC ../cg_draw.c -$CC ../cg_drawtools.c -$CC ../cg_effects.c -$CC ../cg_ents.c -$CC ../cg_event.c -$CC ../cg_info.c -$CC ../cg_localents.c -$CC ../cg_main.c -$CC ../cg_marks.c -$CC ../cg_players.c -$CC ../cg_playerstate.c -$CC ../cg_predict.c -$CC ../cg_scoreboard.c -$CC ../cg_servercmds.c -$CC ../cg_snapshot.c -$CC ../cg_view.c -$CC ../cg_weapons.c -$CC ../../ui/ui_shared.c -$CC ../cg_newdraw.c - -q3asm -f ../cgame_ta - -cd .. +#!/bin/sh + +mkdir -p vm +cd vm + +CC="q3lcc -DQ3_VM -DCGAME -DMISSIONPACK -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../ui" + +$CC ../cg_syscalls.c +$CC ../../game/bg_misc.c +$CC ../../game/bg_pmove.c +$CC ../../game/bg_slidemove.c +$CC ../../game/bg_lib.c +$CC ../../game/q_math.c +$CC ../../game/q_shared.c +$CC ../cg_consolecmds.c +$CC ../cg_draw.c +$CC ../cg_drawtools.c +$CC ../cg_effects.c +$CC ../cg_ents.c +$CC ../cg_event.c +$CC ../cg_info.c +$CC ../cg_localents.c +$CC ../cg_main.c +$CC ../cg_marks.c +$CC ../cg_players.c +$CC ../cg_playerstate.c +$CC ../cg_predict.c +$CC ../cg_scoreboard.c +$CC ../cg_servercmds.c +$CC ../cg_snapshot.c +$CC ../cg_view.c +$CC ../cg_weapons.c +$CC ../../ui/ui_shared.c +$CC ../cg_newdraw.c + +q3asm -f ../cgame_ta + +cd .. diff --git a/code/cgame/tr_types.h b/code/cgame/tr_types.h index ec2e054..d68a174 100755 --- a/code/cgame/tr_types.h +++ b/code/cgame/tr_types.h @@ -1,229 +1,229 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. - -This file is part of Quake III Arena source code. - -Quake III Arena source code 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. - -Quake III Arena source code 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 Foobar; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ -// -#ifndef __TR_TYPES_H -#define __TR_TYPES_H - - -#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces -#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing - -// renderfx flags -#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) -#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) -#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) -#define RF_DEPTHHACK 8 // for view weapon Z crunching -#define RF_NOSHADOW 64 // don't add stencil shadows - -#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin - // for lighting. This allows entities to sink into the floor - // with their origin going solid, and allows all parts of a - // player to get the same lighting -#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane -#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous - // animation without needing to know the frame count - -// refdef flags -#define RDF_NOWORLDMODEL 1 // used for player configuration screen -#define RDF_HYPERSPACE 4 // teleportation effect - -typedef struct { - vec3_t xyz; - float st[2]; - byte modulate[4]; -} polyVert_t; - -typedef struct poly_s { - qhandle_t hShader; - int numVerts; - polyVert_t *verts; -} poly_t; - -typedef enum { - RT_MODEL, - RT_POLY, - RT_SPRITE, - RT_BEAM, - RT_RAIL_CORE, - RT_RAIL_RINGS, - RT_LIGHTNING, - RT_PORTALSURFACE, // doesn't draw anything, just info for portals - - RT_MAX_REF_ENTITY_TYPE -} refEntityType_t; - -typedef struct { - refEntityType_t reType; - int renderfx; - - qhandle_t hModel; // opaque type outside refresh - - // most recent data - vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) - float shadowPlane; // projection shadows go here, stencils go slightly lower - - vec3_t axis[3]; // rotation vectors - qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale - float origin[3]; // also used as MODEL_BEAM's "from" - int frame; // also used as MODEL_BEAM's diameter - - // previous data for frame interpolation - float oldorigin[3]; // also used as MODEL_BEAM's "to" - int oldframe; - float backlerp; // 0.0 = current, 1.0 = old - - // texturing - int skinNum; // inline skin index - qhandle_t customSkin; // NULL for default skin - qhandle_t customShader; // use one image for the entire thing - - // misc - byte shaderRGBA[4]; // colors used by rgbgen entity shaders - float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers - float shaderTime; // subtracted from refdef time to control effect start times - - // extra sprite information - float radius; - float rotation; -} refEntity_t; - - -#define MAX_RENDER_STRINGS 8 -#define MAX_RENDER_STRING_LENGTH 32 - -typedef struct { - int x, y, width, height; - float fov_x, fov_y; - vec3_t vieworg; - vec3_t viewaxis[3]; // transformation matrix - - // time in milliseconds for shader effects and other time dependent rendering issues - int time; - - int rdflags; // RDF_NOWORLDMODEL, etc - - // 1 bits will prevent the associated area from rendering at all - byte areamask[MAX_MAP_AREA_BYTES]; - - // text messages for deform text shaders - char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; -} refdef_t; - - -typedef enum { - STEREO_CENTER, - STEREO_LEFT, - STEREO_RIGHT -} stereoFrame_t; - - -/* -** glconfig_t -** -** Contains variables specific to the OpenGL configuration -** being run right now. These are constant once the OpenGL -** subsystem is initialized. -*/ -typedef enum { - TC_NONE, - TC_S3TC -} textureCompression_t; - -typedef enum { - GLDRV_ICD, // driver is integrated with window system - // WARNING: there are tests that check for - // > GLDRV_ICD for minidriverness, so this - // should always be the lowest value in this - // enum set - GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver - GLDRV_VOODOO // driver is a 3Dfx standalone driver -} glDriverType_t; - -typedef enum { - GLHW_GENERIC, // where everthing works the way it should - GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is - // the hardware type then there can NOT exist a secondary - // display adapter - GLHW_RIVA128, // where you can't interpolate alpha - GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures - GLHW_PERMEDIA2 // where you don't have src*dst -} glHardwareType_t; - -typedef struct { - char renderer_string[MAX_STRING_CHARS]; - char vendor_string[MAX_STRING_CHARS]; - char version_string[MAX_STRING_CHARS]; - char extensions_string[BIG_INFO_STRING]; - - int maxTextureSize; // queried from GL - int maxActiveTextures; // multitexture ability - - int colorBits, depthBits, stencilBits; - - glDriverType_t driverType; - glHardwareType_t hardwareType; - - qboolean deviceSupportsGamma; - textureCompression_t textureCompression; - qboolean textureEnvAddAvailable; - - int vidWidth, vidHeight; - // aspect is the screen's physical width / height, which may be different - // than scrWidth / scrHeight if the pixels are non-square - // normal screens should be 4/3, but wide aspect monitors may be 16/9 - float windowAspect; - - int displayFrequency; - - // synonymous with "does rendering consume the entire screen?", therefore - // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that - // used CDS. - qboolean isFullscreen; - qboolean stereoEnabled; - qboolean smpActive; // dual processor -} glconfig_t; - -// FIXME: VM should be OS agnostic .. in theory - -/* -#ifdef Q3_VM - -#define _3DFX_DRIVER_NAME "Voodoo" -#define OPENGL_DRIVER_NAME "Default" - -#elif defined(_WIN32) -*/ - -#if defined(Q3_VM) || defined(_WIN32) - -#define _3DFX_DRIVER_NAME "3dfxvgl" -#define OPENGL_DRIVER_NAME "opengl32" - -#else - -#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 -#define OPENGL_DRIVER_NAME "libGL.so.1" - -#endif // !defined _WIN32 - -#endif // __TR_TYPES_H +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code 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. + +Quake III Arena source code 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 Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + +// renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 8 // for view weapon Z crunching +#define RF_NOSHADOW 64 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean smpActive; // dual processor +} glconfig_t; + +// FIXME: VM should be OS agnostic .. in theory + +/* +#ifdef Q3_VM + +#define _3DFX_DRIVER_NAME "Voodoo" +#define OPENGL_DRIVER_NAME "Default" + +#elif defined(_WIN32) +*/ + +#if defined(Q3_VM) || defined(_WIN32) + +#define _3DFX_DRIVER_NAME "3dfxvgl" +#define OPENGL_DRIVER_NAME "opengl32" + +#else + +#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME "libGL.so.1" + +#endif // !defined _WIN32 + +#endif // __TR_TYPES_H -- cgit v1.2.3