From b585e51a813ea9e895bf273b0dc37370eb54b512 Mon Sep 17 00:00:00 2001 From: Literally A Penguin Date: Wed, 25 Sep 2024 15:37:26 -0500 Subject: [PATCH] Quack --- baseq2/config.cfg | 150 ++ baseq2/save/save0/game.ssv | Bin 0 -> 32012 bytes baseq2/save/save0/server.ssv | Bin 0 -> 2720 bytes client/adivtab.h | 1058 +++++++++ client/anorms.h | 181 ++ client/asm_i386.h | 81 + client/block16.h | 123 ++ client/block8.h | 124 ++ client/cdaudio.h | 26 + client/cl_cin.c | 650 ++++++ client/cl_ents.c | 1500 +++++++++++++ client/cl_fx.c | 2298 +++++++++++++++++++ client/cl_input.c | 542 +++++ client/cl_inv.c | 142 ++ client/cl_main.c | 1844 ++++++++++++++++ client/cl_newfx.c | 1323 +++++++++++ client/cl_parse.c | 806 +++++++ client/cl_pred.c | 278 +++ client/cl_scrn.c | 1401 ++++++++++++ client/cl_tent.c | 1745 +++++++++++++++ client/cl_view.c | 584 +++++ client/client.h | 584 +++++ client/console.c | 682 ++++++ client/console.h | 62 + client/input.h | 34 + client/keys.c | 943 ++++++++ client/keys.h | 146 ++ client/menu.c | 4016 ++++++++++++++++++++++++++++++++++ client/qmenu.c | 674 ++++++ client/qmenu.h | 140 ++ client/ref.h | 224 ++ client/screen.h | 62 + client/snd_dma.c | 1214 ++++++++++ client/snd_loc.h | 164 ++ client/snd_mem.c | 359 +++ client/snd_mix.c | 497 +++++ client/sound.h | 45 + client/vid.h | 42 + client/x86.c | 95 + ctf/2do.txt | 16 + ctf/Makefile.Linux.i386 | 159 ++ ctf/ctf.001 | 1009 +++++++++ ctf/ctf.def | 2 + ctf/ctf.dsp | 1007 +++++++++ ctf/ctf.plg | 17 + ctf/docs/admin.gif | Bin 0 -> 11206 bytes ctf/docs/adminset.gif | Bin 0 -> 12303 bytes ctf/docs/automac.gif | Bin 0 -> 16924 bytes ctf/docs/ghost.jpg | Bin 0 -> 11088 bytes ctf/docs/grapple.jpg | Bin 0 -> 17631 bytes ctf/docs/layout.jpg | Bin 0 -> 36882 bytes ctf/docs/mainctf_back.jpg | Bin 0 -> 15335 bytes ctf/docs/menu.gif | Bin 0 -> 12559 bytes ctf/docs/q2ctf.html | 1243 +++++++++++ ctf/docs/say_team.gif | Bin 0 -> 6717 bytes ctf/docs/stats.jpg | Bin 0 -> 5558 bytes ctf/docs/tech1.gif | Bin 0 -> 1375 bytes ctf/docs/tech2.gif | Bin 0 -> 1344 bytes ctf/docs/tech3.gif | Bin 0 -> 1595 bytes ctf/docs/tech4.gif | Bin 0 -> 1434 bytes ctf/g_ai.c | 1117 ++++++++++ ctf/g_chase.c | 157 ++ ctf/g_cmds.c | 1066 +++++++++ ctf/g_combat.c | 596 +++++ ctf/g_ctf.c | 4016 ++++++++++++++++++++++++++++++++++ ctf/g_ctf.h | 185 ++ ctf/g_func.c | 2047 +++++++++++++++++ ctf/g_items.c | 2446 +++++++++++++++++++++ ctf/g_local.h | 1145 ++++++++++ ctf/g_main.c | 427 ++++ ctf/g_misc.c | 1909 ++++++++++++++++ ctf/g_monster.c | 740 +++++++ ctf/g_phys.c | 959 ++++++++ ctf/g_save.c | 743 +++++++ ctf/g_spawn.c | 998 +++++++++ ctf/g_svcmds.c | 48 + ctf/g_target.c | 809 +++++++ ctf/g_trigger.c | 598 +++++ ctf/g_utils.c | 570 +++++ ctf/g_weapon.c | 919 ++++++++ ctf/game.h | 242 ++ ctf/layout.txt | 12 + ctf/m_move.c | 556 +++++ ctf/m_player.h | 225 ++ ctf/p_client.c | 1726 +++++++++++++++ ctf/p_hud.c | 544 +++++ ctf/p_menu.c | 256 +++ ctf/p_menu.h | 49 + ctf/p_trail.c | 146 ++ game/g_ai.c | 1117 ++++++++++ game/g_chase.c | 175 ++ game/g_cmds.c | 992 +++++++++ game/g_combat.c | 576 +++++ game/g_func.c | 2048 +++++++++++++++++ game/g_items.c | 2216 +++++++++++++++++++ game/g_local.h | 1113 ++++++++++ game/g_main.c | 411 ++++ game/g_misc.c | 1876 ++++++++++++++++ game/g_monster.c | 740 +++++++ game/g_phys.c | 961 ++++++++ 100 files changed, 65768 insertions(+) create mode 100644 baseq2/config.cfg create mode 100644 baseq2/save/save0/game.ssv create mode 100644 baseq2/save/save0/server.ssv create mode 100644 client/adivtab.h create mode 100644 client/anorms.h create mode 100644 client/asm_i386.h create mode 100644 client/block16.h create mode 100644 client/block8.h create mode 100644 client/cdaudio.h create mode 100644 client/cl_cin.c create mode 100644 client/cl_ents.c create mode 100644 client/cl_fx.c create mode 100644 client/cl_input.c create mode 100644 client/cl_inv.c create mode 100644 client/cl_main.c create mode 100644 client/cl_newfx.c create mode 100644 client/cl_parse.c create mode 100644 client/cl_pred.c create mode 100644 client/cl_scrn.c create mode 100644 client/cl_tent.c create mode 100644 client/cl_view.c create mode 100644 client/client.h create mode 100644 client/console.c create mode 100644 client/console.h create mode 100644 client/input.h create mode 100644 client/keys.c create mode 100644 client/keys.h create mode 100644 client/menu.c create mode 100644 client/qmenu.c create mode 100644 client/qmenu.h create mode 100644 client/ref.h create mode 100644 client/screen.h create mode 100644 client/snd_dma.c create mode 100644 client/snd_loc.h create mode 100644 client/snd_mem.c create mode 100644 client/snd_mix.c create mode 100644 client/sound.h create mode 100644 client/vid.h create mode 100644 client/x86.c create mode 100644 ctf/2do.txt create mode 100644 ctf/Makefile.Linux.i386 create mode 100644 ctf/ctf.001 create mode 100644 ctf/ctf.def create mode 100644 ctf/ctf.dsp create mode 100644 ctf/ctf.plg create mode 100644 ctf/docs/admin.gif create mode 100644 ctf/docs/adminset.gif create mode 100644 ctf/docs/automac.gif create mode 100644 ctf/docs/ghost.jpg create mode 100644 ctf/docs/grapple.jpg create mode 100644 ctf/docs/layout.jpg create mode 100644 ctf/docs/mainctf_back.jpg create mode 100644 ctf/docs/menu.gif create mode 100644 ctf/docs/q2ctf.html create mode 100644 ctf/docs/say_team.gif create mode 100644 ctf/docs/stats.jpg create mode 100644 ctf/docs/tech1.gif create mode 100644 ctf/docs/tech2.gif create mode 100644 ctf/docs/tech3.gif create mode 100644 ctf/docs/tech4.gif create mode 100644 ctf/g_ai.c create mode 100644 ctf/g_chase.c create mode 100644 ctf/g_cmds.c create mode 100644 ctf/g_combat.c create mode 100644 ctf/g_ctf.c create mode 100644 ctf/g_ctf.h create mode 100644 ctf/g_func.c create mode 100644 ctf/g_items.c create mode 100644 ctf/g_local.h create mode 100644 ctf/g_main.c create mode 100644 ctf/g_misc.c create mode 100644 ctf/g_monster.c create mode 100644 ctf/g_phys.c create mode 100644 ctf/g_save.c create mode 100644 ctf/g_spawn.c create mode 100644 ctf/g_svcmds.c create mode 100644 ctf/g_target.c create mode 100644 ctf/g_trigger.c create mode 100644 ctf/g_utils.c create mode 100644 ctf/g_weapon.c create mode 100644 ctf/game.h create mode 100644 ctf/layout.txt create mode 100644 ctf/m_move.c create mode 100644 ctf/m_player.h create mode 100644 ctf/p_client.c create mode 100644 ctf/p_hud.c create mode 100644 ctf/p_menu.c create mode 100644 ctf/p_menu.h create mode 100644 ctf/p_trail.c create mode 100644 game/g_ai.c create mode 100644 game/g_chase.c create mode 100644 game/g_cmds.c create mode 100644 game/g_combat.c create mode 100644 game/g_func.c create mode 100644 game/g_items.c create mode 100644 game/g_local.h create mode 100644 game/g_main.c create mode 100644 game/g_misc.c create mode 100644 game/g_monster.c create mode 100644 game/g_phys.c diff --git a/baseq2/config.cfg b/baseq2/config.cfg new file mode 100644 index 000000000..3e718debb --- /dev/null +++ b/baseq2/config.cfg @@ -0,0 +1,150 @@ +// generated by quake, do not modify +bind TAB "inven" +bind ENTER "invuse" +bind ESCAPE "togglemenu" +bind SPACE "+moveup" +bind ' "inven_drop" +bind + "sizeup" +bind , "+moveleft" +bind - "sizedown" +bind . "+moveright" +bind / "weapnext" +bind 0 "use BFG10K" +bind 1 "use Blaster" +bind 2 "use Shotgun" +bind 3 "use Super Shotgun" +bind 4 "use Machinegun" +bind 5 "use Chaingun" +bind 6 "use Grenade Launcher" +bind 7 "use Rocket Launcher" +bind 8 "use HyperBlaster" +bind 9 "use Railgun" +bind = "sizeup" +bind [ "invprev" +bind \ "+mlook" +bind ] "invnext" +bind ` "toggleconsole" +bind a "+moveleft" +bind b "use rebreather" +bind c "+movedown" +bind d "+moveright" +bind e "weapnext" +bind g "use grenades" +bind h "wave 0" +bind i "use invulnerability" +bind j "wave 1" +bind k "wave 2" +bind l "wave 3" +bind p "use shield" +bind q "invprev" +bind r "invuse" +bind s "+back" +bind t "messagemode" +bind u "wave 4" +bind w "+forward" +bind x "centerview" +bind z "+movedown" +bind ~ "toggleconsole" +bind BACKSPACE "invdrop" +bind UPARROW "+forward" +bind DOWNARROW "+back" +bind LEFTARROW "+left" +bind RIGHTARROW "+right" +bind ALT "+strafe" +bind CTRL "+attack" +bind SHIFT "+speed" +bind F1 "cmd help" +bind F2 "menu_savegame" +bind F3 "menu_loadgame" +bind F4 "give ammo" +bind F5 "give weapons" +bind F6 "r_speeds 0" +bind F7 "r_speeds 1" +bind F8 "notarget" +bind F9 "noclip" +bind F10 "god" +bind F11 "screenshot" +bind F12 "quit" +bind INS "+klook" +bind DEL "+lookdown" +bind PGDN "+lookup" +bind PGUP "+lookup" +bind END "centerview" +bind MOUSE1 "+attack" +bind MOUSE2 "+strafe" +bind MOUSE3 "+mlook" +bind PAUSE "pause" +set gl_3dlabs_broken "1" +set gl_swapinterval "1" +set gl_ext_compiled_vertex_array "1" +set gl_ext_pointparameters "1" +set gl_ext_multitexture "1" +set gl_ext_palettedtexture "1" +set gl_ext_swapinterval "1" +set gl_vertex_arrays "0" +set gl_texturesolidmode "default" +set gl_texturealphamode "default" +set gl_texturemode "GL_LINEAR_MIPMAP_NEAREST" +set gl_driver "opengl32" +set gl_finish "0" +set gl_shadows "0" +set gl_mode "3" +set gl_modulate "1" +set gl_particle_att_c "0.01" +set gl_particle_att_b "0.0" +set gl_particle_att_a "0.01" +set gl_particle_size "40" +set gl_particle_max_size "40" +set gl_particle_min_size "2" +set g_select_empty "0" +set in_joystick "0" +set in_mouse "1" +set cl_vwep "1" +set gender_auto "1" +set gender "male" +set fov "90" +set msg "1" +set rate "25000" +set freelook "0" +set cl_stereo_separation "0.4" +set adr8 "" +set adr7 "" +set adr6 "" +set adr5 "" +set adr4 "" +set adr3 "" +set adr2 "" +set adr1 "" +set adr0 "" +set cd_nocd "0" +set s_primary "0" +set s_mixahead "0.2" +set s_loadas8bit "1" +set s_khz "11" +set s_volume "0.7" +set sw_mode "0" +set sw_stipplealpha "0" +set sw_allow_modex "1" +set vid_gamma "1" +set vid_ypos "32" +set vid_xpos "115" +set vid_ref "gl" +set sv_reconnect_limit "3" +set allow_download_maps "1" +set allow_download_sounds "1" +set allow_download_models "1" +set allow_download_players "0" +set allow_download "0" +set hostname "noname" +set skin "male/grunt" +set name "hook" +set lookstrafe "0" +set lookspring "1" +set m_pitch "-0.022000" +set hand "2" +set cl_run "0" +set crosshair "1" +set sensitivity "9.000000" +set win_noalttab "0" +set vid_fullscreen "0" +set viewsize "100" diff --git a/baseq2/save/save0/game.ssv b/baseq2/save/save0/game.ssv new file mode 100644 index 0000000000000000000000000000000000000000..f1569a306de9e7d6a58627d24f4f4d70755b25fa GIT binary patch literal 32012 zcmeI#Jr06E5C%|9EIbB@ot;!}sg*bJ1fE|Z*hsaB{0zH~0z$Icoq0n-zT4~Bw5hqI zbltfK5Fqe4-S-sg7C83p9|nVS_wYds7VZQH5Fk*O!25gE)wOO35FkLHG=ctiR$5Q` zAwYlt0RjZ_1!UWND=P^QAV46lfNUFgHYy`PfB*pkO9;reODH>o009C7<`$4`=N7g< z0RjXF5Qr@x+s2-c>Ie`ZKp;;*w#{?0iU0uu1PGKWAlsHYS$z>8K%g!G*|x5E>y`ik z0t5(*1!UW?h<5@62oTt!fNZ0D*r6WZPfC4_^~wDF6Tf literal 0 HcmV?d00001 diff --git a/baseq2/save/save0/server.ssv b/baseq2/save/save0/server.ssv new file mode 100644 index 0000000000000000000000000000000000000000..ba337c70bcfb000e186138b3163baf051e6e1acf GIT binary patch literal 2720 zcmc(gOA5j;5Qck{9HA{P%u=CBA-!k8h$v;|1~uLWbFV^Uo_)eW}jJ z`XEk=<5}#)2dAB8o0$B?6?wUVfPc`IdTz-57bRgpyQLMS6or)A zgwz!k^bh{7p0rlz|3{gY8Pn|ZN_r-x*zgSKgFFrfVEg%3M*9_j8UGB^^=qP4$Ar=z sqCoEcz*plqg%@?u{onZVcRe}PVKRWOzrAZ?Fw#F%wEpn_0p-KdZdata; + + if (pcx->manufacturer != 0x0a + || pcx->version != 5 + || pcx->encoding != 1 + || pcx->bits_per_pixel != 8 + || pcx->xmax >= 640 + || pcx->ymax >= 480) + { + Com_Printf ("Bad pcx file %s\n", filename); + return; + } + + out = Z_Malloc ( (pcx->ymax+1) * (pcx->xmax+1) ); + + *pic = out; + + pix = out; + + if (palette) + { + *palette = Z_Malloc(768); + memcpy (*palette, (byte *)pcx + len - 768, 768); + } + + if (width) + *width = pcx->xmax+1; + if (height) + *height = pcx->ymax+1; + + for (y=0 ; y<=pcx->ymax ; y++, pix += pcx->xmax+1) + { + for (x=0 ; x<=pcx->xmax ; ) + { + dataByte = *raw++; + + if((dataByte & 0xC0) == 0xC0) + { + runLength = dataByte & 0x3F; + dataByte = *raw++; + } + else + runLength = 1; + + while(runLength-- > 0) + pix[x++] = dataByte; + } + + } + + if ( raw - (byte *)pcx > len) + { + Com_Printf ("PCX file %s was malformed", filename); + Z_Free (*pic); + *pic = NULL; + } + + FS_FreeFile (pcx); +} + +//============================================================= + +/* +================== +SCR_StopCinematic +================== +*/ +void SCR_StopCinematic (void) +{ + cl.cinematictime = 0; // done + if (cin.pic) + { + Z_Free (cin.pic); + cin.pic = NULL; + } + if (cin.pic_pending) + { + Z_Free (cin.pic_pending); + cin.pic_pending = NULL; + } + if (cl.cinematicpalette_active) + { + re.CinematicSetPalette(NULL); + cl.cinematicpalette_active = false; + } + if (cl.cinematic_file) + { + fclose (cl.cinematic_file); + cl.cinematic_file = NULL; + } + if (cin.hnodes1) + { + Z_Free (cin.hnodes1); + cin.hnodes1 = NULL; + } + + // switch back down to 11 khz sound if necessary + if (cin.restart_sound) + { + cin.restart_sound = false; + CL_Snd_Restart_f (); + } + +} + +/* +==================== +SCR_FinishCinematic + +Called when either the cinematic completes, or it is aborted +==================== +*/ +void SCR_FinishCinematic (void) +{ + // tell the server to advance to the next map / cinematic + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + SZ_Print (&cls.netchan.message, va("nextserver %i\n", cl.servercount)); +} + +//========================================================================== + +/* +================== +SmallestNode1 +================== +*/ +int SmallestNode1 (int numhnodes) +{ + int i; + int best, bestnode; + + best = 99999999; + bestnode = -1; + for (i=0 ; i>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + //----------- + if (nodenum < 256) + { + hnodes = hnodesbase + (nodenum<<9); + *out_p++ = nodenum; + if (!--count) + break; + nodenum = cin.numhnodes1[nodenum]; + } + nodenum = hnodes[nodenum*2 + (inbyte&1)]; + inbyte >>=1; + } + + if (input - in.data != in.count && input - in.data != in.count+1) + { + Com_Printf ("Decompression overread by %i", (input - in.data) - in.count); + } + out.count = out_p - out.data; + + return out; +} + +/* +================== +SCR_ReadNextFrame +================== +*/ +byte *SCR_ReadNextFrame (void) +{ + int r; + int command; + byte samples[22050/14*4]; + byte compressed[0x20000]; + int size; + byte *pic; + cblock_t in, huf1; + int start, end, count; + + // read the next frame + r = fread (&command, 4, 1, cl.cinematic_file); + if (r == 0) // we'll give it one more chance + r = fread (&command, 4, 1, cl.cinematic_file); + + if (r != 1) + return NULL; + command = LittleLong(command); + if (command == 2) + return NULL; // last frame marker + + if (command == 1) + { // read palette + FS_Read (cl.cinematicpalette, sizeof(cl.cinematicpalette), cl.cinematic_file); + cl.cinematicpalette_active=0; // dubious.... exposes an edge case + } + + // decompress the next frame + FS_Read (&size, 4, cl.cinematic_file); + size = LittleLong(size); + if (size > sizeof(compressed) || size < 1) + Com_Error (ERR_DROP, "Bad compressed frame size"); + FS_Read (compressed, size, cl.cinematic_file); + + // read sound + start = cl.cinematicframe*cin.s_rate/14; + end = (cl.cinematicframe+1)*cin.s_rate/14; + count = end - start; + + FS_Read (samples, count*cin.s_width*cin.s_channels, cl.cinematic_file); + + S_RawSamples (count, cin.s_rate, cin.s_width, cin.s_channels, samples); + + in.data = compressed; + in.count = size; + + huf1 = Huff1Decompress (in); + + pic = huf1.data; + + cl.cinematicframe++; + + return pic; +} + + +/* +================== +SCR_RunCinematic + +================== +*/ +void SCR_RunCinematic (void) +{ + int frame; + + if (cl.cinematictime <= 0) + { + SCR_StopCinematic (); + return; + } + + if (cl.cinematicframe == -1) + return; // static image + + if (cls.key_dest != key_game) + { // pause if menu or console is up + cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14; + return; + } + + frame = (cls.realtime - cl.cinematictime)*14.0/1000; + if (frame <= cl.cinematicframe) + return; + if (frame > cl.cinematicframe+1) + { + Com_Printf ("Dropped frame: %i > %i\n", frame, cl.cinematicframe+1); + cl.cinematictime = cls.realtime - cl.cinematicframe*1000/14; + } + if (cin.pic) + Z_Free (cin.pic); + cin.pic = cin.pic_pending; + cin.pic_pending = NULL; + cin.pic_pending = SCR_ReadNextFrame (); + if (!cin.pic_pending) + { + SCR_StopCinematic (); + SCR_FinishCinematic (); + cl.cinematictime = 1; // hack to get the black screen behind loading + SCR_BeginLoadingPlaque (); + cl.cinematictime = 0; + return; + } +} + +/* +================== +SCR_DrawCinematic + +Returns true if a cinematic is active, meaning the view rendering +should be skipped +================== +*/ +qboolean SCR_DrawCinematic (void) +{ + if (cl.cinematictime <= 0) + { + return false; + } + + if (cls.key_dest == key_menu) + { // blank screen and pause if menu is up + re.CinematicSetPalette(NULL); + cl.cinematicpalette_active = false; + return true; + } + + if (!cl.cinematicpalette_active) + { + re.CinematicSetPalette(cl.cinematicpalette); + cl.cinematicpalette_active = true; + } + + if (!cin.pic) + return true; + + re.DrawStretchRaw (0, 0, viddef.width, viddef.height, + cin.width, cin.height, cin.pic); + + return true; +} + +/* +================== +SCR_PlayCinematic + +================== +*/ +void SCR_PlayCinematic (char *arg) +{ + int width, height; + byte *palette; + char name[MAX_OSPATH], *dot; + int old_khz; + + // make sure CD isn't playing music + CDAudio_Stop(); + + cl.cinematicframe = 0; + dot = strstr (arg, "."); + if (dot && !strcmp (dot, ".pcx")) + { // static pcx image + Com_sprintf (name, sizeof(name), "pics/%s", arg); + SCR_LoadPCX (name, &cin.pic, &palette, &cin.width, &cin.height); + cl.cinematicframe = -1; + cl.cinematictime = 1; + SCR_EndLoadingPlaque (); + cls.state = ca_active; + if (!cin.pic) + { + Com_Printf ("%s not found.\n", name); + cl.cinematictime = 0; + } + else + { + memcpy (cl.cinematicpalette, palette, sizeof(cl.cinematicpalette)); + Z_Free (palette); + } + return; + } + + Com_sprintf (name, sizeof(name), "video/%s", arg); + FS_FOpenFile (name, &cl.cinematic_file); + if (!cl.cinematic_file) + { +// Com_Error (ERR_DROP, "Cinematic %s not found.\n", name); + SCR_FinishCinematic (); + cl.cinematictime = 0; // done + return; + } + + SCR_EndLoadingPlaque (); + + cls.state = ca_active; + + FS_Read (&width, 4, cl.cinematic_file); + FS_Read (&height, 4, cl.cinematic_file); + cin.width = LittleLong(width); + cin.height = LittleLong(height); + + FS_Read (&cin.s_rate, 4, cl.cinematic_file); + cin.s_rate = LittleLong(cin.s_rate); + FS_Read (&cin.s_width, 4, cl.cinematic_file); + cin.s_width = LittleLong(cin.s_width); + FS_Read (&cin.s_channels, 4, cl.cinematic_file); + cin.s_channels = LittleLong(cin.s_channels); + + Huff1TableInit (); + + // switch up to 22 khz sound if necessary + old_khz = Cvar_VariableValue ("s_khz"); + if (old_khz != cin.s_rate/1000) + { + cin.restart_sound = true; + Cvar_SetValue ("s_khz", cin.s_rate/1000); + CL_Snd_Restart_f (); + Cvar_SetValue ("s_khz", old_khz); + } + + cl.cinematicframe = 0; + cin.pic = SCR_ReadNextFrame (); + cl.cinematictime = Sys_Milliseconds (); +} diff --git a/client/cl_ents.c b/client/cl_ents.c new file mode 100644 index 000000000..5f2f16bcb --- /dev/null +++ b/client/cl_ents.c @@ -0,0 +1,1500 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_ents.c -- entity parsing and management + +#include "client.h" + + +extern struct model_s *cl_mod_powerscreen; + +//PGM +int vidref_val; +//PGM + +/* +========================================================================= + +FRAME PARSING + +========================================================================= +*/ + +#if 0 + +typedef struct +{ + int modelindex; + int num; // entity number + int effects; + vec3_t origin; + vec3_t oldorigin; + vec3_t angles; + qboolean present; +} projectile_t; + +#define MAX_PROJECTILES 64 +projectile_t cl_projectiles[MAX_PROJECTILES]; + +void CL_ClearProjectiles (void) +{ + int i; + + for (i = 0; i < MAX_PROJECTILES; i++) { +// if (cl_projectiles[i].present) +// Com_DPrintf("PROJ: %d CLEARED\n", cl_projectiles[i].num); + cl_projectiles[i].present = false; + } +} + +/* +===================== +CL_ParseProjectiles + +Flechettes are passed as efficient temporary entities +===================== +*/ +void CL_ParseProjectiles (void) +{ + int i, c, j; + byte bits[8]; + byte b; + projectile_t pr; + int lastempty = -1; + qboolean old = false; + + c = MSG_ReadByte (&net_message); + for (i=0 ; i>4) + (bits[2]<<4) ) <<1) - 4096; + pr.origin[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; + VectorCopy(pr.origin, pr.oldorigin); + + if (bits[4] & 64) + pr.effects = EF_BLASTER; + else + pr.effects = 0; + + if (bits[4] & 128) { + old = true; + bits[0] = MSG_ReadByte (&net_message); + bits[1] = MSG_ReadByte (&net_message); + bits[2] = MSG_ReadByte (&net_message); + bits[3] = MSG_ReadByte (&net_message); + bits[4] = MSG_ReadByte (&net_message); + pr.oldorigin[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096; + pr.oldorigin[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096; + pr.oldorigin[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; + } + + bits[0] = MSG_ReadByte (&net_message); + bits[1] = MSG_ReadByte (&net_message); + bits[2] = MSG_ReadByte (&net_message); + + pr.angles[0] = 360*bits[0]/256; + pr.angles[1] = 360*bits[1]/256; + pr.modelindex = bits[2]; + + b = MSG_ReadByte (&net_message); + pr.num = (b & 0x7f); + if (b & 128) // extra entity number byte + pr.num |= (MSG_ReadByte (&net_message) << 7); + + pr.present = true; + + // find if this projectile already exists from previous frame + for (j = 0; j < MAX_PROJECTILES; j++) { + if (cl_projectiles[j].modelindex) { + if (cl_projectiles[j].num == pr.num) { + // already present, set up oldorigin for interpolation + if (!old) + VectorCopy(cl_projectiles[j].origin, pr.oldorigin); + cl_projectiles[j] = pr; + break; + } + } else + lastempty = j; + } + + // not present previous frame, add it + if (j == MAX_PROJECTILES) { + if (lastempty != -1) { + cl_projectiles[lastempty] = pr; + } + } + } +} + +/* +============= +CL_LinkProjectiles + +============= +*/ +void CL_AddProjectiles (void) +{ + int i, j; + projectile_t *pr; + entity_t ent; + + memset (&ent, 0, sizeof(ent)); + + for (i=0, pr=cl_projectiles ; i < MAX_PROJECTILES ; i++, pr++) + { + // grab an entity to fill in + if (pr->modelindex < 1) + continue; + if (!pr->present) { + pr->modelindex = 0; + continue; // not present this frame (it was in the previous frame) + } + + ent.model = cl.model_draw[pr->modelindex]; + + // interpolate origin + for (j=0 ; j<3 ; j++) + { + ent.origin[j] = ent.oldorigin[j] = pr->oldorigin[j] + cl.lerpfrac * + (pr->origin[j] - pr->oldorigin[j]); + + } + + if (pr->effects & EF_BLASTER) + CL_BlasterTrail (pr->oldorigin, ent.origin); + V_AddLight (pr->origin, 200, 1, 1, 0); + + VectorCopy (pr->angles, ent.angles); + V_AddEntity (&ent); + } +} +#endif + +/* +================= +CL_ParseEntityBits + +Returns the entity number and the header bits +================= +*/ +int bitcounts[32]; /// just for protocol profiling +int CL_ParseEntityBits (unsigned *bits) +{ + unsigned b, total; + int i; + int number; + + total = MSG_ReadByte (&net_message); + if (total & U_MOREBITS1) + { + b = MSG_ReadByte (&net_message); + total |= b<<8; + } + if (total & U_MOREBITS2) + { + b = MSG_ReadByte (&net_message); + total |= b<<16; + } + if (total & U_MOREBITS3) + { + b = MSG_ReadByte (&net_message); + total |= b<<24; + } + + // count the bits for net profiling + for (i=0 ; i<32 ; i++) + if (total&(1<origin, to->old_origin); + to->number = number; + + if (bits & U_MODEL) + to->modelindex = MSG_ReadByte (&net_message); + if (bits & U_MODEL2) + to->modelindex2 = MSG_ReadByte (&net_message); + if (bits & U_MODEL3) + to->modelindex3 = MSG_ReadByte (&net_message); + if (bits & U_MODEL4) + to->modelindex4 = MSG_ReadByte (&net_message); + + if (bits & U_FRAME8) + to->frame = MSG_ReadByte (&net_message); + if (bits & U_FRAME16) + to->frame = MSG_ReadShort (&net_message); + + if ((bits & U_SKIN8) && (bits & U_SKIN16)) //used for laser colors + to->skinnum = MSG_ReadLong(&net_message); + else if (bits & U_SKIN8) + to->skinnum = MSG_ReadByte(&net_message); + else if (bits & U_SKIN16) + to->skinnum = MSG_ReadShort(&net_message); + + if ( (bits & (U_EFFECTS8|U_EFFECTS16)) == (U_EFFECTS8|U_EFFECTS16) ) + to->effects = MSG_ReadLong(&net_message); + else if (bits & U_EFFECTS8) + to->effects = MSG_ReadByte(&net_message); + else if (bits & U_EFFECTS16) + to->effects = MSG_ReadShort(&net_message); + + if ( (bits & (U_RENDERFX8|U_RENDERFX16)) == (U_RENDERFX8|U_RENDERFX16) ) + to->renderfx = MSG_ReadLong(&net_message); + else if (bits & U_RENDERFX8) + to->renderfx = MSG_ReadByte(&net_message); + else if (bits & U_RENDERFX16) + to->renderfx = MSG_ReadShort(&net_message); + + if (bits & U_ORIGIN1) + to->origin[0] = MSG_ReadCoord (&net_message); + if (bits & U_ORIGIN2) + to->origin[1] = MSG_ReadCoord (&net_message); + if (bits & U_ORIGIN3) + to->origin[2] = MSG_ReadCoord (&net_message); + + if (bits & U_ANGLE1) + to->angles[0] = MSG_ReadAngle(&net_message); + if (bits & U_ANGLE2) + to->angles[1] = MSG_ReadAngle(&net_message); + if (bits & U_ANGLE3) + to->angles[2] = MSG_ReadAngle(&net_message); + + if (bits & U_OLDORIGIN) + MSG_ReadPos (&net_message, to->old_origin); + + if (bits & U_SOUND) + to->sound = MSG_ReadByte (&net_message); + + if (bits & U_EVENT) + to->event = MSG_ReadByte (&net_message); + else + to->event = 0; + + if (bits & U_SOLID) + to->solid = MSG_ReadShort (&net_message); +} + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity (frame_t *frame, int newnum, entity_state_t *old, int bits) +{ + centity_t *ent; + entity_state_t *state; + + ent = &cl_entities[newnum]; + + state = &cl_parse_entities[cl.parse_entities & (MAX_PARSE_ENTITIES-1)]; + cl.parse_entities++; + frame->num_entities++; + + CL_ParseDelta (old, state, newnum, bits); + + // some data changes will force no lerping + if (state->modelindex != ent->current.modelindex + || state->modelindex2 != ent->current.modelindex2 + || state->modelindex3 != ent->current.modelindex3 + || state->modelindex4 != ent->current.modelindex4 + || abs(state->origin[0] - ent->current.origin[0]) > 512 + || abs(state->origin[1] - ent->current.origin[1]) > 512 + || abs(state->origin[2] - ent->current.origin[2]) > 512 + || state->event == EV_PLAYER_TELEPORT + || state->event == EV_OTHER_TELEPORT + ) + { + ent->serverframe = -99; + } + + if (ent->serverframe != cl.frame.serverframe - 1) + { // wasn't in last update, so initialize some things + ent->trailcount = 1024; // for diminishing rocket / grenade trails + // duplicate the current state so lerping doesn't hurt anything + ent->prev = *state; + if (state->event == EV_OTHER_TELEPORT) + { + VectorCopy (state->origin, ent->prev.origin); + VectorCopy (state->origin, ent->lerp_origin); + } + else + { + VectorCopy (state->old_origin, ent->prev.origin); + VectorCopy (state->old_origin, ent->lerp_origin); + } + } + else + { // shuffle the last state to previous + ent->prev = ent->current; + } + + ent->serverframe = cl.frame.serverframe; + ent->current = *state; +} + +/* +================== +CL_ParsePacketEntities + +An svc_packetentities has just been parsed, deal with the +rest of the data stream. +================== +*/ +void CL_ParsePacketEntities (frame_t *oldframe, frame_t *newframe) +{ + int newnum; + int bits; + entity_state_t *oldstate; + int oldindex, oldnum; + + newframe->parse_entities = cl.parse_entities; + newframe->num_entities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + if (!oldframe) + oldnum = 99999; + else + { + if (oldindex >= oldframe->num_entities) + oldnum = 99999; + else + { + oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while (1) + { + newnum = CL_ParseEntityBits (&bits); + if (newnum >= MAX_EDICTS) + Com_Error (ERR_DROP,"CL_ParsePacketEntities: bad number:%i", newnum); + + if (net_message.readcount > net_message.cursize) + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + + if (!newnum) + break; + + while (oldnum < newnum) + { // one or more entities from the old packet are unchanged + if (cl_shownet->value == 3) + Com_Printf (" unchanged: %i\n", oldnum); + CL_DeltaEntity (newframe, oldnum, oldstate, 0); + + oldindex++; + + if (oldindex >= oldframe->num_entities) + oldnum = 99999; + else + { + oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + if (bits & U_REMOVE) + { // the entity present in oldframe is not in the current frame + if (cl_shownet->value == 3) + Com_Printf (" remove: %i\n", newnum); + if (oldnum != newnum) + Com_Printf ("U_REMOVE: oldnum != newnum\n"); + + oldindex++; + + if (oldindex >= oldframe->num_entities) + oldnum = 99999; + else + { + oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if (oldnum == newnum) + { // delta from previous state + if (cl_shownet->value == 3) + Com_Printf (" delta: %i\n", newnum); + CL_DeltaEntity (newframe, newnum, oldstate, bits); + + oldindex++; + + if (oldindex >= oldframe->num_entities) + oldnum = 99999; + else + { + oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if (oldnum > newnum) + { // delta from baseline + if (cl_shownet->value == 3) + Com_Printf (" baseline: %i\n", newnum); + CL_DeltaEntity (newframe, newnum, &cl_entities[newnum].baseline, bits); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while (oldnum != 99999) + { // one or more entities from the old packet are unchanged + if (cl_shownet->value == 3) + Com_Printf (" unchanged: %i\n", oldnum); + CL_DeltaEntity (newframe, oldnum, oldstate, 0); + + oldindex++; + + if (oldindex >= oldframe->num_entities) + oldnum = 99999; + else + { + oldstate = &cl_parse_entities[(oldframe->parse_entities+oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + + +/* +=================== +CL_ParsePlayerstate +=================== +*/ +void CL_ParsePlayerstate (frame_t *oldframe, frame_t *newframe) +{ + int flags; + player_state_t *state; + int i; + int statbits; + + state = &newframe->playerstate; + + // clear to old value before delta parsing + if (oldframe) + *state = oldframe->playerstate; + else + memset (state, 0, sizeof(*state)); + + flags = MSG_ReadShort (&net_message); + + // + // parse the pmove_state_t + // + if (flags & PS_M_TYPE) + state->pmove.pm_type = MSG_ReadByte (&net_message); + + if (flags & PS_M_ORIGIN) + { + state->pmove.origin[0] = MSG_ReadShort (&net_message); + state->pmove.origin[1] = MSG_ReadShort (&net_message); + state->pmove.origin[2] = MSG_ReadShort (&net_message); + } + + if (flags & PS_M_VELOCITY) + { + state->pmove.velocity[0] = MSG_ReadShort (&net_message); + state->pmove.velocity[1] = MSG_ReadShort (&net_message); + state->pmove.velocity[2] = MSG_ReadShort (&net_message); + } + + if (flags & PS_M_TIME) + state->pmove.pm_time = MSG_ReadByte (&net_message); + + if (flags & PS_M_FLAGS) + state->pmove.pm_flags = MSG_ReadByte (&net_message); + + if (flags & PS_M_GRAVITY) + state->pmove.gravity = MSG_ReadShort (&net_message); + + if (flags & PS_M_DELTA_ANGLES) + { + state->pmove.delta_angles[0] = MSG_ReadShort (&net_message); + state->pmove.delta_angles[1] = MSG_ReadShort (&net_message); + state->pmove.delta_angles[2] = MSG_ReadShort (&net_message); + } + + if (cl.attractloop) + state->pmove.pm_type = PM_FREEZE; // demo playback + + // + // parse the rest of the player_state_t + // + if (flags & PS_VIEWOFFSET) + { + state->viewoffset[0] = MSG_ReadChar (&net_message) * 0.25; + state->viewoffset[1] = MSG_ReadChar (&net_message) * 0.25; + state->viewoffset[2] = MSG_ReadChar (&net_message) * 0.25; + } + + if (flags & PS_VIEWANGLES) + { + state->viewangles[0] = MSG_ReadAngle16 (&net_message); + state->viewangles[1] = MSG_ReadAngle16 (&net_message); + state->viewangles[2] = MSG_ReadAngle16 (&net_message); + } + + if (flags & PS_KICKANGLES) + { + state->kick_angles[0] = MSG_ReadChar (&net_message) * 0.25; + state->kick_angles[1] = MSG_ReadChar (&net_message) * 0.25; + state->kick_angles[2] = MSG_ReadChar (&net_message) * 0.25; + } + + if (flags & PS_WEAPONINDEX) + { + state->gunindex = MSG_ReadByte (&net_message); + } + + if (flags & PS_WEAPONFRAME) + { + state->gunframe = MSG_ReadByte (&net_message); + state->gunoffset[0] = MSG_ReadChar (&net_message)*0.25; + state->gunoffset[1] = MSG_ReadChar (&net_message)*0.25; + state->gunoffset[2] = MSG_ReadChar (&net_message)*0.25; + state->gunangles[0] = MSG_ReadChar (&net_message)*0.25; + state->gunangles[1] = MSG_ReadChar (&net_message)*0.25; + state->gunangles[2] = MSG_ReadChar (&net_message)*0.25; + } + + if (flags & PS_BLEND) + { + state->blend[0] = MSG_ReadByte (&net_message)/255.0; + state->blend[1] = MSG_ReadByte (&net_message)/255.0; + state->blend[2] = MSG_ReadByte (&net_message)/255.0; + state->blend[3] = MSG_ReadByte (&net_message)/255.0; + } + + if (flags & PS_FOV) + state->fov = MSG_ReadByte (&net_message); + + if (flags & PS_RDFLAGS) + state->rdflags = MSG_ReadByte (&net_message); + + // parse stats + statbits = MSG_ReadLong (&net_message); + for (i=0 ; istats[i] = MSG_ReadShort(&net_message); +} + + +/* +================== +CL_FireEntityEvents + +================== +*/ +void CL_FireEntityEvents (frame_t *frame) +{ + entity_state_t *s1; + int pnum, num; + + for (pnum = 0 ; pnumnum_entities ; pnum++) + { + num = (frame->parse_entities + pnum)&(MAX_PARSE_ENTITIES-1); + s1 = &cl_parse_entities[num]; + if (s1->event) + CL_EntityEvent (s1); + + // EF_TELEPORTER acts like an event, but is not cleared each frame + if (s1->effects & EF_TELEPORTER) + CL_TeleporterParticles (s1); + } +} + + +/* +================ +CL_ParseFrame +================ +*/ +void CL_ParseFrame (void) +{ + int cmd; + int len; + frame_t *old; + + memset (&cl.frame, 0, sizeof(cl.frame)); + +#if 0 + CL_ClearProjectiles(); // clear projectiles for new frame +#endif + + cl.frame.serverframe = MSG_ReadLong (&net_message); + cl.frame.deltaframe = MSG_ReadLong (&net_message); + cl.frame.servertime = cl.frame.serverframe*100; + + // BIG HACK to let old demos continue to work + if (cls.serverProtocol != 26) + cl.surpressCount = MSG_ReadByte (&net_message); + + if (cl_shownet->value == 3) + Com_Printf (" frame:%i delta:%i\n", cl.frame.serverframe, + cl.frame.deltaframe); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if (cl.frame.deltaframe <= 0) + { + cl.frame.valid = true; // uncompressed frame + old = NULL; + cls.demowaiting = false; // we can start recording now + } + else + { + old = &cl.frames[cl.frame.deltaframe & UPDATE_MASK]; + if (!old->valid) + { // should never happen + Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); + } + if (old->serverframe != cl.frame.deltaframe) + { // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_Printf ("Delta frame too old.\n"); + } + else if (cl.parse_entities - old->parse_entities > MAX_PARSE_ENTITIES-128) + { + Com_Printf ("Delta parse_entities too old.\n"); + } + else + cl.frame.valid = true; // valid delta parse + } + + // clamp time + if (cl.time > cl.frame.servertime) + cl.time = cl.frame.servertime; + else if (cl.time < cl.frame.servertime - 100) + cl.time = cl.frame.servertime - 100; + + // read areabits + len = MSG_ReadByte (&net_message); + MSG_ReadData (&net_message, &cl.frame.areabits, len); + + // read playerinfo + cmd = MSG_ReadByte (&net_message); + SHOWNET(svc_strings[cmd]); + if (cmd != svc_playerinfo) + Com_Error (ERR_DROP, "CL_ParseFrame: not playerinfo"); + CL_ParsePlayerstate (old, &cl.frame); + + // read packet entities + cmd = MSG_ReadByte (&net_message); + SHOWNET(svc_strings[cmd]); + if (cmd != svc_packetentities) + Com_Error (ERR_DROP, "CL_ParseFrame: not packetentities"); + CL_ParsePacketEntities (old, &cl.frame); + +#if 0 + if (cmd == svc_packetentities2) + CL_ParseProjectiles(); +#endif + + // save the frame off in the backup array for later delta comparisons + cl.frames[cl.frame.serverframe & UPDATE_MASK] = cl.frame; + + if (cl.frame.valid) + { + // getting a valid frame message ends the connection process + if (cls.state != ca_active) + { + cls.state = ca_active; + cl.force_refdef = true; + cl.predicted_origin[0] = cl.frame.playerstate.pmove.origin[0]*0.125; + cl.predicted_origin[1] = cl.frame.playerstate.pmove.origin[1]*0.125; + cl.predicted_origin[2] = cl.frame.playerstate.pmove.origin[2]*0.125; + VectorCopy (cl.frame.playerstate.viewangles, cl.predicted_angles); + if (cls.disable_servercount != cl.servercount + && cl.refresh_prepped) + SCR_EndLoadingPlaque (); // get rid of loading plaque + } + cl.sound_prepped = true; // can start mixing ambient sounds + + // fire entity events + CL_FireEntityEvents (&cl.frame); + CL_CheckPredictionError (); + } +} + +/* +========================================================================== + +INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS + +========================================================================== +*/ + +struct model_s *S_RegisterSexedModel (entity_state_t *ent, char *base) +{ + int n; + char *p; + struct model_s *mdl; + char model[MAX_QPATH]; + char buffer[MAX_QPATH]; + + // determine what model the client is using + model[0] = 0; + n = CS_PLAYERSKINS + ent->number - 1; + if (cl.configstrings[n][0]) + { + p = strchr(cl.configstrings[n], '\\'); + if (p) + { + p += 1; + strcpy(model, p); + p = strchr(model, '/'); + if (p) + *p = 0; + } + } + // if we can't figure it out, they're male + if (!model[0]) + strcpy(model, "male"); + + Com_sprintf (buffer, sizeof(buffer), "players/%s/%s", model, base+1); + mdl = re.RegisterModel(buffer); + if (!mdl) { + // not found, try default weapon model + Com_sprintf (buffer, sizeof(buffer), "players/%s/weapon.md2", model); + mdl = re.RegisterModel(buffer); + if (!mdl) { + // no, revert to the male model + Com_sprintf (buffer, sizeof(buffer), "players/%s/%s", "male", base+1); + mdl = re.RegisterModel(buffer); + if (!mdl) { + // last try, default male weapon.md2 + Com_sprintf (buffer, sizeof(buffer), "players/male/weapon.md2"); + mdl = re.RegisterModel(buffer); + } + } + } + + return mdl; +} + +/* +=============== +CL_AddPacketEntities + +=============== +*/ +void CL_AddPacketEntities (frame_t *frame) +{ + entity_t ent; + entity_state_t *s1; + float autorotate; + int i; + int pnum; + centity_t *cent; + int autoanim; + clientinfo_t *ci; + unsigned int effects, renderfx; + + // bonus items rotate at a fixed rate + autorotate = anglemod(cl.time/10); + + // brush models can auto animate their frames + autoanim = 2*cl.time/1000; + + memset (&ent, 0, sizeof(ent)); + + for (pnum = 0 ; pnumnum_entities ; pnum++) + { + s1 = &cl_parse_entities[(frame->parse_entities+pnum)&(MAX_PARSE_ENTITIES-1)]; + + cent = &cl_entities[s1->number]; + + effects = s1->effects; + renderfx = s1->renderfx; + + // set frame + if (effects & EF_ANIM01) + ent.frame = autoanim & 1; + else if (effects & EF_ANIM23) + ent.frame = 2 + (autoanim & 1); + else if (effects & EF_ANIM_ALL) + ent.frame = autoanim; + else if (effects & EF_ANIM_ALLFAST) + ent.frame = cl.time / 100; + else + ent.frame = s1->frame; + + // quad and pent can do different things on client + if (effects & EF_PENT) + { + effects &= ~EF_PENT; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_RED; + } + + if (effects & EF_QUAD) + { + effects &= ~EF_QUAD; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_BLUE; + } +//====== +// PMM + if (effects & EF_DOUBLE) + { + effects &= ~EF_DOUBLE; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_DOUBLE; + } + + if (effects & EF_HALF_DAMAGE) + { + effects &= ~EF_HALF_DAMAGE; + effects |= EF_COLOR_SHELL; + renderfx |= RF_SHELL_HALF_DAM; + } +// pmm +//====== + ent.oldframe = cent->prev.frame; + ent.backlerp = 1.0 - cl.lerpfrac; + + if (renderfx & (RF_FRAMELERP|RF_BEAM)) + { // step origin discretely, because the frames + // do the animation properly + VectorCopy (cent->current.origin, ent.origin); + VectorCopy (cent->current.old_origin, ent.oldorigin); + } + else + { // interpolate origin + for (i=0 ; i<3 ; i++) + { + ent.origin[i] = ent.oldorigin[i] = cent->prev.origin[i] + cl.lerpfrac * + (cent->current.origin[i] - cent->prev.origin[i]); + } + } + + // create a new entity + + // tweak the color of beams + if ( renderfx & RF_BEAM ) + { // the four beam colors are encoded in 32 bits of skinnum (hack) + ent.alpha = 0.30; + ent.skinnum = (s1->skinnum >> ((rand() % 4)*8)) & 0xff; + ent.model = NULL; + } + else + { + // set skin + if (s1->modelindex == 255) + { // use custom player skin + ent.skinnum = 0; + ci = &cl.clientinfo[s1->skinnum & 0xff]; + ent.skin = ci->skin; + ent.model = ci->model; + if (!ent.skin || !ent.model) + { + ent.skin = cl.baseclientinfo.skin; + ent.model = cl.baseclientinfo.model; + } + +//============ +//PGM + if (renderfx & RF_USE_DISGUISE) + { + if(!strncmp((char *)ent.skin, "players/male", 12)) + { + ent.skin = re.RegisterSkin ("players/male/disguise.pcx"); + ent.model = re.RegisterModel ("players/male/tris.md2"); + } + else if(!strncmp((char *)ent.skin, "players/female", 14)) + { + ent.skin = re.RegisterSkin ("players/female/disguise.pcx"); + ent.model = re.RegisterModel ("players/female/tris.md2"); + } + else if(!strncmp((char *)ent.skin, "players/cyborg", 14)) + { + ent.skin = re.RegisterSkin ("players/cyborg/disguise.pcx"); + ent.model = re.RegisterModel ("players/cyborg/tris.md2"); + } + } +//PGM +//============ + } + else + { + ent.skinnum = s1->skinnum; + ent.skin = NULL; + ent.model = cl.model_draw[s1->modelindex]; + } + } + + // only used for black hole model right now, FIXME: do better + if (renderfx == RF_TRANSLUCENT) + ent.alpha = 0.70; + + // render effects (fullbright, translucent, etc) + if ((effects & EF_COLOR_SHELL)) + ent.flags = 0; // renderfx go on color shell entity + else + ent.flags = renderfx; + + // calculate angles + if (effects & EF_ROTATE) + { // some bonus items auto-rotate + ent.angles[0] = 0; + ent.angles[1] = autorotate; + ent.angles[2] = 0; + } + // RAFAEL + else if (effects & EF_SPINNINGLIGHTS) + { + ent.angles[0] = 0; + ent.angles[1] = anglemod(cl.time/2) + s1->angles[1]; + ent.angles[2] = 180; + { + vec3_t forward; + vec3_t start; + + AngleVectors (ent.angles, forward, NULL, NULL); + VectorMA (ent.origin, 64, forward, start); + V_AddLight (start, 100, 1, 0, 0); + } + } + else + { // interpolate angles + float a1, a2; + + for (i=0 ; i<3 ; i++) + { + a1 = cent->current.angles[i]; + a2 = cent->prev.angles[i]; + ent.angles[i] = LerpAngle (a2, a1, cl.lerpfrac); + } + } + + if (s1->number == cl.playernum+1) + { + ent.flags |= RF_VIEWERMODEL; // only draw from mirrors + // FIXME: still pass to refresh + + if (effects & EF_FLAG1) + V_AddLight (ent.origin, 225, 1.0, 0.1, 0.1); + else if (effects & EF_FLAG2) + V_AddLight (ent.origin, 225, 0.1, 0.1, 1.0); + else if (effects & EF_TAGTRAIL) //PGM + V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0); //PGM + else if (effects & EF_TRACKERTRAIL) //PGM + V_AddLight (ent.origin, 225, -1.0, -1.0, -1.0); //PGM + + continue; + } + + // if set to invisible, skip + if (!s1->modelindex) + continue; + + if (effects & EF_BFG) + { + ent.flags |= RF_TRANSLUCENT; + ent.alpha = 0.30; + } + + // RAFAEL + if (effects & EF_PLASMA) + { + ent.flags |= RF_TRANSLUCENT; + ent.alpha = 0.6; + } + + if (effects & EF_SPHERETRANS) + { + ent.flags |= RF_TRANSLUCENT; + // PMM - *sigh* yet more EF overloading + if (effects & EF_TRACKERTRAIL) + ent.alpha = 0.6; + else + ent.alpha = 0.3; + } +//pmm + + // add to refresh list + V_AddEntity (&ent); + + // color shells generate a seperate entity for the main model + if (effects & EF_COLOR_SHELL) + { + ent.flags = renderfx | RF_TRANSLUCENT; + ent.alpha = 0.30; + V_AddEntity (&ent); + } + + ent.skin = NULL; // never use a custom skin on others + ent.skinnum = 0; + ent.flags = 0; + ent.alpha = 0; + + // duplicate for linked models + if (s1->modelindex2) + { + if (s1->modelindex2 == 255) + { // custom weapon + ci = &cl.clientinfo[s1->skinnum & 0xff]; + i = (s1->skinnum >> 8); // 0 is default weapon model + if (!cl_vwep->value || i > MAX_CLIENTWEAPONMODELS - 1) + i = 0; + ent.model = ci->weaponmodel[i]; + if (!ent.model) { + if (i != 0) + ent.model = ci->weaponmodel[0]; + if (!ent.model) + ent.model = cl.baseclientinfo.weaponmodel[0]; + } + } + //PGM - hack to allow translucent linked models (defender sphere's shell) + // set the high bit 0x80 on modelindex2 to enable translucency + else if(s1->modelindex2 & 0x80) + { + ent.model = cl.model_draw[s1->modelindex2 & 0x7F]; + ent.alpha = 0.32; + ent.flags = RF_TRANSLUCENT; + } + //PGM + else + ent.model = cl.model_draw[s1->modelindex2]; + V_AddEntity (&ent); + + //PGM - make sure these get reset. + ent.flags = 0; + ent.alpha = 0; + //PGM + } + if (s1->modelindex3) + { + ent.model = cl.model_draw[s1->modelindex3]; + V_AddEntity (&ent); + } + if (s1->modelindex4) + { + ent.model = cl.model_draw[s1->modelindex4]; + V_AddEntity (&ent); + } + + if ( effects & EF_POWERSCREEN ) + { + ent.model = cl_mod_powerscreen; + ent.oldframe = 0; + ent.frame = 0; + ent.flags |= (RF_TRANSLUCENT | RF_SHELL_GREEN); + ent.alpha = 0.30; + V_AddEntity (&ent); + } + + // add automatic particle trails + if ( (effects&~EF_ROTATE) ) + { + if (effects & EF_ROCKET) + { + CL_RocketTrail (cent->lerp_origin, ent.origin, cent); + V_AddLight (ent.origin, 200, 1, 1, 0); + } + // PGM - Do not reorder EF_BLASTER and EF_HYPERBLASTER. + // EF_BLASTER | EF_TRACKER is a special case for EF_BLASTER2... Cheese! + else if (effects & EF_BLASTER) + { +// CL_BlasterTrail (cent->lerp_origin, ent.origin); +//PGM + if (effects & EF_TRACKER) // lame... problematic? + { + CL_BlasterTrail2 (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 200, 0, 1, 0); + } + else + { + CL_BlasterTrail (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 200, 1, 1, 0); + } +//PGM + } + else if (effects & EF_HYPERBLASTER) + { + if (effects & EF_TRACKER) // PGM overloaded for blaster2. + V_AddLight (ent.origin, 200, 0, 1, 0); // PGM + else // PGM + V_AddLight (ent.origin, 200, 1, 1, 0); + } + else if (effects & EF_GIB) + { + CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects); + } + else if (effects & EF_GRENADE) + { + CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects); + } + else if (effects & EF_FLIES) + { + CL_FlyEffect (cent, ent.origin); + } + else if (effects & EF_BFG) + { + static int bfg_lightramp[6] = {300, 400, 600, 300, 150, 75}; + + if (effects & EF_ANIM_ALLFAST) + { + CL_BfgParticles (&ent); + i = 200; + } + else + { + i = bfg_lightramp[s1->frame]; + } + V_AddLight (ent.origin, i, 0, 1, 0); + } + // RAFAEL + else if (effects & EF_TRAP) + { + ent.origin[2] += 32; + CL_TrapParticles (&ent); + i = (rand()%100) + 100; + V_AddLight (ent.origin, i, 1, 0.8, 0.1); + } + else if (effects & EF_FLAG1) + { + CL_FlagTrail (cent->lerp_origin, ent.origin, 242); + V_AddLight (ent.origin, 225, 1, 0.1, 0.1); + } + else if (effects & EF_FLAG2) + { + CL_FlagTrail (cent->lerp_origin, ent.origin, 115); + V_AddLight (ent.origin, 225, 0.1, 0.1, 1); + } +//====== +//ROGUE + else if (effects & EF_TAGTRAIL) + { + CL_TagTrail (cent->lerp_origin, ent.origin, 220); + V_AddLight (ent.origin, 225, 1.0, 1.0, 0.0); + } + else if (effects & EF_TRACKERTRAIL) + { + if (effects & EF_TRACKER) + { + float intensity; + + intensity = 50 + (500 * (sin(cl.time/500.0) + 1.0)); + // FIXME - check out this effect in rendition + if(vidref_val == VIDREF_GL) + V_AddLight (ent.origin, intensity, -1.0, -1.0, -1.0); + else + V_AddLight (ent.origin, -1.0 * intensity, 1.0, 1.0, 1.0); + } + else + { + CL_Tracker_Shell (cent->lerp_origin); + V_AddLight (ent.origin, 155, -1.0, -1.0, -1.0); + } + } + else if (effects & EF_TRACKER) + { + CL_TrackerTrail (cent->lerp_origin, ent.origin, 0); + // FIXME - check out this effect in rendition + if(vidref_val == VIDREF_GL) + V_AddLight (ent.origin, 200, -1, -1, -1); + else + V_AddLight (ent.origin, -200, 1, 1, 1); + } +//ROGUE +//====== + // RAFAEL + else if (effects & EF_GREENGIB) + { + CL_DiminishingTrail (cent->lerp_origin, ent.origin, cent, effects); + } + // RAFAEL + else if (effects & EF_IONRIPPER) + { + CL_IonripperTrail (cent->lerp_origin, ent.origin); + V_AddLight (ent.origin, 100, 1, 0.5, 0.5); + } + // RAFAEL + else if (effects & EF_BLUEHYPERBLASTER) + { + V_AddLight (ent.origin, 200, 0, 0, 1); + } + // RAFAEL + else if (effects & EF_PLASMA) + { + if (effects & EF_ANIM_ALLFAST) + { + CL_BlasterTrail (cent->lerp_origin, ent.origin); + } + V_AddLight (ent.origin, 130, 1, 0.5, 0.5); + } + } + + VectorCopy (ent.origin, cent->lerp_origin); + } +} + + + +/* +============== +CL_AddViewWeapon +============== +*/ +void CL_AddViewWeapon (player_state_t *ps, player_state_t *ops) +{ + entity_t gun; // view model + int i; + + // allow the gun to be completely removed + if (!cl_gun->value) + return; + + // don't draw gun if in wide angle view + if (ps->fov > 90) + return; + + memset (&gun, 0, sizeof(gun)); + + if (gun_model) + gun.model = gun_model; // development tool + else + gun.model = cl.model_draw[ps->gunindex]; + if (!gun.model) + return; + + // set up gun position + for (i=0 ; i<3 ; i++) + { + gun.origin[i] = cl.refdef.vieworg[i] + ops->gunoffset[i] + + cl.lerpfrac * (ps->gunoffset[i] - ops->gunoffset[i]); + gun.angles[i] = cl.refdef.viewangles[i] + LerpAngle (ops->gunangles[i], + ps->gunangles[i], cl.lerpfrac); + } + + if (gun_frame) + { + gun.frame = gun_frame; // development tool + gun.oldframe = gun_frame; // development tool + } + else + { + gun.frame = ps->gunframe; + if (gun.frame == 0) + gun.oldframe = 0; // just changed weapons, don't lerp from old + else + gun.oldframe = ops->gunframe; + } + + gun.flags = RF_MINLIGHT | RF_DEPTHHACK | RF_WEAPONMODEL; + gun.backlerp = 1.0 - cl.lerpfrac; + VectorCopy (gun.origin, gun.oldorigin); // don't lerp at all + V_AddEntity (&gun); +} + + +/* +=============== +CL_CalcViewValues + +Sets cl.refdef view values +=============== +*/ +void CL_CalcViewValues (void) +{ + int i; + float lerp, backlerp; + centity_t *ent; + frame_t *oldframe; + player_state_t *ps, *ops; + + // find the previous frame to interpolate from + ps = &cl.frame.playerstate; + i = (cl.frame.serverframe - 1) & UPDATE_MASK; + oldframe = &cl.frames[i]; + if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid) + oldframe = &cl.frame; // previous frame was dropped or involid + ops = &oldframe->playerstate; + + // see if the player entity was teleported this frame + if ( fabs(ops->pmove.origin[0] - ps->pmove.origin[0]) > 256*8 + || abs(ops->pmove.origin[1] - ps->pmove.origin[1]) > 256*8 + || abs(ops->pmove.origin[2] - ps->pmove.origin[2]) > 256*8) + ops = ps; // don't interpolate + + ent = &cl_entities[cl.playernum+1]; + lerp = cl.lerpfrac; + + // calculate the origin + if ((cl_predict->value) && !(cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION)) + { // use predicted values + unsigned delta; + + backlerp = 1.0 - lerp; + for (i=0 ; i<3 ; i++) + { + cl.refdef.vieworg[i] = cl.predicted_origin[i] + ops->viewoffset[i] + + cl.lerpfrac * (ps->viewoffset[i] - ops->viewoffset[i]) + - backlerp * cl.prediction_error[i]; + } + + // smooth out stair climbing + delta = cls.realtime - cl.predicted_step_time; + if (delta < 100) + cl.refdef.vieworg[2] -= cl.predicted_step * (100 - delta) * 0.01; + } + else + { // just use interpolated values + for (i=0 ; i<3 ; i++) + cl.refdef.vieworg[i] = ops->pmove.origin[i]*0.125 + ops->viewoffset[i] + + lerp * (ps->pmove.origin[i]*0.125 + ps->viewoffset[i] + - (ops->pmove.origin[i]*0.125 + ops->viewoffset[i]) ); + } + + // if not running a demo or on a locked frame, add the local angle movement + if ( cl.frame.playerstate.pmove.pm_type < PM_DEAD ) + { // use predicted values + for (i=0 ; i<3 ; i++) + cl.refdef.viewangles[i] = cl.predicted_angles[i]; + } + else + { // just use interpolated values + for (i=0 ; i<3 ; i++) + cl.refdef.viewangles[i] = LerpAngle (ops->viewangles[i], ps->viewangles[i], lerp); + } + + for (i=0 ; i<3 ; i++) + cl.refdef.viewangles[i] += LerpAngle (ops->kick_angles[i], ps->kick_angles[i], lerp); + + AngleVectors (cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up); + + // interpolate field of view + cl.refdef.fov_x = ops->fov + lerp * (ps->fov - ops->fov); + + // don't interpolate blend color + for (i=0 ; i<4 ; i++) + cl.refdef.blend[i] = ps->blend[i]; + + // add the weapon + CL_AddViewWeapon (ps, ops); +} + +/* +=============== +CL_AddEntities + +Emits all entities, particles, and lights to the refresh +=============== +*/ +void CL_AddEntities (void) +{ + if (cls.state != ca_active) + return; + + if (cl.time > cl.frame.servertime) + { + if (cl_showclamp->value) + Com_Printf ("high clamp %i\n", cl.time - cl.frame.servertime); + cl.time = cl.frame.servertime; + cl.lerpfrac = 1.0; + } + else if (cl.time < cl.frame.servertime - 100) + { + if (cl_showclamp->value) + Com_Printf ("low clamp %i\n", cl.frame.servertime-100 - cl.time); + cl.time = cl.frame.servertime - 100; + cl.lerpfrac = 0; + } + else + cl.lerpfrac = 1.0 - (cl.frame.servertime - cl.time) * 0.01; + + if (cl_timedemo->value) + cl.lerpfrac = 1.0; + +// CL_AddPacketEntities (&cl.frame); +// CL_AddTEnts (); +// CL_AddParticles (); +// CL_AddDLights (); +// CL_AddLightStyles (); + + CL_CalcViewValues (); + // PMM - moved this here so the heat beam has the right values for the vieworg, and can lock the beam to the gun + CL_AddPacketEntities (&cl.frame); +#if 0 + CL_AddProjectiles (); +#endif + CL_AddTEnts (); + CL_AddParticles (); + CL_AddDLights (); + CL_AddLightStyles (); +} + + + +/* +=============== +CL_GetEntitySoundOrigin + +Called to get the sound spatialization origin +=============== +*/ +void CL_GetEntitySoundOrigin (int ent, vec3_t org) +{ + centity_t *old; + + if (ent < 0 || ent >= MAX_EDICTS) + Com_Error (ERR_DROP, "CL_GetEntitySoundOrigin: bad ent"); + old = &cl_entities[ent]; + VectorCopy (old->lerp_origin, org); + + // FIXME: bmodel issues... +} diff --git a/client/cl_fx.c b/client/cl_fx.c new file mode 100644 index 000000000..32c7dec69 --- /dev/null +++ b/client/cl_fx.c @@ -0,0 +1,2298 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_fx.c -- entity effects parsing and management + +#include "client.h" + +void CL_LogoutEffect (vec3_t org, int type); +void CL_ItemRespawnParticles (vec3_t org); + +static vec3_t avelocities [NUMVERTEXNORMALS]; + +extern struct model_s *cl_mod_smoke; +extern struct model_s *cl_mod_flash; + +/* +============================================================== + +LIGHT STYLE MANAGEMENT + +============================================================== +*/ + +typedef struct +{ + int length; + float value[3]; + float map[MAX_QPATH]; +} clightstyle_t; + +clightstyle_t cl_lightstyle[MAX_LIGHTSTYLES]; +int lastofs; + +/* +================ +CL_ClearLightStyles +================ +*/ +void CL_ClearLightStyles (void) +{ + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + lastofs = -1; +} + +/* +================ +CL_RunLightStyles +================ +*/ +void CL_RunLightStyles (void) +{ + int ofs; + int i; + clightstyle_t *ls; + + ofs = cl.time / 100; + if (ofs == lastofs) + return; + lastofs = ofs; + + for (i=0,ls=cl_lightstyle ; ilength) + { + ls->value[0] = ls->value[1] = ls->value[2] = 1.0; + continue; + } + if (ls->length == 1) + ls->value[0] = ls->value[1] = ls->value[2] = ls->map[0]; + else + ls->value[0] = ls->value[1] = ls->value[2] = ls->map[ofs%ls->length]; + } +} + + +void CL_SetLightstyle (int i) +{ + char *s; + int j, k; + + s = cl.configstrings[i+CS_LIGHTS]; + + j = strlen (s); + if (j >= MAX_QPATH) + Com_Error (ERR_DROP, "svc_lightstyle length=%i", j); + + cl_lightstyle[i].length = j; + + for (k=0 ; kvalue[0], ls->value[1], ls->value[2]); +} + +/* +============================================================== + +DLIGHT MANAGEMENT + +============================================================== +*/ + +cdlight_t cl_dlights[MAX_DLIGHTS]; + +/* +================ +CL_ClearDlights +================ +*/ +void CL_ClearDlights (void) +{ + memset (cl_dlights, 0, sizeof(cl_dlights)); +} + +/* +=============== +CL_AllocDlight + +=============== +*/ +cdlight_t *CL_AllocDlight (int key) +{ + int i; + cdlight_t *dl; + +// first look for an exact key match + if (key) + { + dl = cl_dlights; + for (i=0 ; ikey == key) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + return dl; + } + } + } + +// then look for anything else + dl = cl_dlights; + for (i=0 ; idie < cl.time) + { + memset (dl, 0, sizeof(*dl)); + dl->key = key; + return dl; + } + } + + dl = &cl_dlights[0]; + memset (dl, 0, sizeof(*dl)); + dl->key = key; + return dl; +} + +/* +=============== +CL_NewDlight +=============== +*/ +void CL_NewDlight (int key, float x, float y, float z, float radius, float time) +{ + cdlight_t *dl; + + dl = CL_AllocDlight (key); + dl->origin[0] = x; + dl->origin[1] = y; + dl->origin[2] = z; + dl->radius = radius; + dl->die = cl.time + time; +} + + +/* +=============== +CL_RunDLights + +=============== +*/ +void CL_RunDLights (void) +{ + int i; + cdlight_t *dl; + + dl = cl_dlights; + for (i=0 ; iradius) + continue; + + if (dl->die < cl.time) + { + dl->radius = 0; + return; + } + dl->radius -= cls.frametime*dl->decay; + if (dl->radius < 0) + dl->radius = 0; + } +} + +/* +============== +CL_ParseMuzzleFlash +============== +*/ +void CL_ParseMuzzleFlash (void) +{ + vec3_t fv, rv; + cdlight_t *dl; + int i, weapon; + centity_t *pl; + int silenced; + float volume; + char soundname[64]; + + i = MSG_ReadShort (&net_message); + if (i < 1 || i >= MAX_EDICTS) + Com_Error (ERR_DROP, "CL_ParseMuzzleFlash: bad entity"); + + weapon = MSG_ReadByte (&net_message); + silenced = weapon & MZ_SILENCED; + weapon &= ~MZ_SILENCED; + + pl = &cl_entities[i]; + + dl = CL_AllocDlight (i); + VectorCopy (pl->current.origin, dl->origin); + AngleVectors (pl->current.angles, fv, rv, NULL); + VectorMA (dl->origin, 18, fv, dl->origin); + VectorMA (dl->origin, 16, rv, dl->origin); + if (silenced) + dl->radius = 100 + (rand()&31); + else + dl->radius = 200 + (rand()&31); + dl->minlight = 32; + dl->die = cl.time; // + 0.1; + + if (silenced) + volume = 0.2; + else + volume = 1; + + switch (weapon) + { + case MZ_BLASTER: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/blastf1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_BLUEHYPERBLASTER: + dl->color[0] = 0;dl->color[1] = 0;dl->color[2] = 1; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/hyprbf1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_HYPERBLASTER: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/hyprbf1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_MACHINEGUN: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0); + break; + case MZ_SHOTGUN: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/shotgf1b.wav"), volume, ATTN_NORM, 0); + S_StartSound (NULL, i, CHAN_AUTO, S_RegisterSound("weapons/shotgr1b.wav"), volume, ATTN_NORM, 0.1); + break; + case MZ_SSHOTGUN: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/sshotf1b.wav"), volume, ATTN_NORM, 0); + break; + case MZ_CHAINGUN1: + dl->radius = 200 + (rand()&31); + dl->color[0] = 1;dl->color[1] = 0.25;dl->color[2] = 0; + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0); + break; + case MZ_CHAINGUN2: + dl->radius = 225 + (rand()&31); + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0; + dl->die = cl.time + 0.1; // long delay + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0); + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0.05); + break; + case MZ_CHAINGUN3: + dl->radius = 250 + (rand()&31); + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + dl->die = cl.time + 0.1; // long delay + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0); + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0.033); + Com_sprintf(soundname, sizeof(soundname), "weapons/machgf%ib.wav", (rand() % 5) + 1); + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound(soundname), volume, ATTN_NORM, 0.066); + break; + case MZ_RAILGUN: + dl->color[0] = 0.5;dl->color[1] = 0.5;dl->color[2] = 1.0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/railgf1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_ROCKET: + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/rocklf1a.wav"), volume, ATTN_NORM, 0); + S_StartSound (NULL, i, CHAN_AUTO, S_RegisterSound("weapons/rocklr1b.wav"), volume, ATTN_NORM, 0.1); + break; + case MZ_GRENADE: + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/grenlf1a.wav"), volume, ATTN_NORM, 0); + S_StartSound (NULL, i, CHAN_AUTO, S_RegisterSound("weapons/grenlr1b.wav"), volume, ATTN_NORM, 0.1); + break; + case MZ_BFG: + dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/bfg__f1y.wav"), volume, ATTN_NORM, 0); + break; + + case MZ_LOGIN: + dl->color[0] = 0;dl->color[1] = 1; dl->color[2] = 0; + dl->die = cl.time + 1.0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/grenlf1a.wav"), 1, ATTN_NORM, 0); + CL_LogoutEffect (pl->current.origin, weapon); + break; + case MZ_LOGOUT: + dl->color[0] = 1;dl->color[1] = 0; dl->color[2] = 0; + dl->die = cl.time + 1.0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/grenlf1a.wav"), 1, ATTN_NORM, 0); + CL_LogoutEffect (pl->current.origin, weapon); + break; + case MZ_RESPAWN: + dl->color[0] = 1;dl->color[1] = 1; dl->color[2] = 0; + dl->die = cl.time + 1.0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/grenlf1a.wav"), 1, ATTN_NORM, 0); + CL_LogoutEffect (pl->current.origin, weapon); + break; + // RAFAEL + case MZ_PHALANX: + dl->color[0] = 1;dl->color[1] = 0.5; dl->color[2] = 0.5; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/plasshot.wav"), volume, ATTN_NORM, 0); + break; + // RAFAEL + case MZ_IONRIPPER: + dl->color[0] = 1;dl->color[1] = 0.5; dl->color[2] = 0.5; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/rippfire.wav"), volume, ATTN_NORM, 0); + break; + +// ====================== +// PGM + case MZ_ETF_RIFLE: + dl->color[0] = 0.9;dl->color[1] = 0.7;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/nail1.wav"), volume, ATTN_NORM, 0); + break; + case MZ_SHOTGUN2: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/shotg2.wav"), volume, ATTN_NORM, 0); + break; + case MZ_HEATBEAM: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + dl->die = cl.time + 100; +// S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/bfg__l1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_BLASTER2: + dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0; + // FIXME - different sound for blaster2 ?? + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/blastf1a.wav"), volume, ATTN_NORM, 0); + break; + case MZ_TRACKER: + // negative flashes handled the same in gl/soft until CL_AddDLights + dl->color[0] = -1;dl->color[1] = -1;dl->color[2] = -1; + S_StartSound (NULL, i, CHAN_WEAPON, S_RegisterSound("weapons/disint2.wav"), volume, ATTN_NORM, 0); + break; + case MZ_NUKE1: + dl->color[0] = 1;dl->color[1] = 0;dl->color[2] = 0; + dl->die = cl.time + 100; + break; + case MZ_NUKE2: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + dl->die = cl.time + 100; + break; + case MZ_NUKE4: + dl->color[0] = 0;dl->color[1] = 0;dl->color[2] = 1; + dl->die = cl.time + 100; + break; + case MZ_NUKE8: + dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 1; + dl->die = cl.time + 100; + break; +// PGM +// ====================== + } +} + + +/* +============== +CL_ParseMuzzleFlash2 +============== +*/ +void CL_ParseMuzzleFlash2 (void) +{ + int ent; + vec3_t origin; + int flash_number; + cdlight_t *dl; + vec3_t forward, right; + char soundname[64]; + + ent = MSG_ReadShort (&net_message); + if (ent < 1 || ent >= MAX_EDICTS) + Com_Error (ERR_DROP, "CL_ParseMuzzleFlash2: bad entity"); + + flash_number = MSG_ReadByte (&net_message); + + // locate the origin + AngleVectors (cl_entities[ent].current.angles, forward, right, NULL); + origin[0] = cl_entities[ent].current.origin[0] + forward[0] * monster_flash_offset[flash_number][0] + right[0] * monster_flash_offset[flash_number][1]; + origin[1] = cl_entities[ent].current.origin[1] + forward[1] * monster_flash_offset[flash_number][0] + right[1] * monster_flash_offset[flash_number][1]; + origin[2] = cl_entities[ent].current.origin[2] + forward[2] * monster_flash_offset[flash_number][0] + right[2] * monster_flash_offset[flash_number][1] + monster_flash_offset[flash_number][2]; + + dl = CL_AllocDlight (ent); + VectorCopy (origin, dl->origin); + dl->radius = 200 + (rand()&31); + dl->minlight = 32; + dl->die = cl.time; // + 0.1; + + switch (flash_number) + { + case MZ2_INFANTRY_MACHINEGUN_1: + case MZ2_INFANTRY_MACHINEGUN_2: + case MZ2_INFANTRY_MACHINEGUN_3: + case MZ2_INFANTRY_MACHINEGUN_4: + case MZ2_INFANTRY_MACHINEGUN_5: + case MZ2_INFANTRY_MACHINEGUN_6: + case MZ2_INFANTRY_MACHINEGUN_7: + case MZ2_INFANTRY_MACHINEGUN_8: + case MZ2_INFANTRY_MACHINEGUN_9: + case MZ2_INFANTRY_MACHINEGUN_10: + case MZ2_INFANTRY_MACHINEGUN_11: + case MZ2_INFANTRY_MACHINEGUN_12: + case MZ2_INFANTRY_MACHINEGUN_13: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("infantry/infatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_SOLDIER_MACHINEGUN_1: + case MZ2_SOLDIER_MACHINEGUN_2: + case MZ2_SOLDIER_MACHINEGUN_3: + case MZ2_SOLDIER_MACHINEGUN_4: + case MZ2_SOLDIER_MACHINEGUN_5: + case MZ2_SOLDIER_MACHINEGUN_6: + case MZ2_SOLDIER_MACHINEGUN_7: + case MZ2_SOLDIER_MACHINEGUN_8: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("soldier/solatck3.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_GUNNER_MACHINEGUN_1: + case MZ2_GUNNER_MACHINEGUN_2: + case MZ2_GUNNER_MACHINEGUN_3: + case MZ2_GUNNER_MACHINEGUN_4: + case MZ2_GUNNER_MACHINEGUN_5: + case MZ2_GUNNER_MACHINEGUN_6: + case MZ2_GUNNER_MACHINEGUN_7: + case MZ2_GUNNER_MACHINEGUN_8: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("gunner/gunatck2.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_ACTOR_MACHINEGUN_1: + case MZ2_SUPERTANK_MACHINEGUN_1: + case MZ2_SUPERTANK_MACHINEGUN_2: + case MZ2_SUPERTANK_MACHINEGUN_3: + case MZ2_SUPERTANK_MACHINEGUN_4: + case MZ2_SUPERTANK_MACHINEGUN_5: + case MZ2_SUPERTANK_MACHINEGUN_6: + case MZ2_TURRET_MACHINEGUN: // PGM + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("infantry/infatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_BOSS2_MACHINEGUN_L1: + case MZ2_BOSS2_MACHINEGUN_L2: + case MZ2_BOSS2_MACHINEGUN_L3: + case MZ2_BOSS2_MACHINEGUN_L4: + case MZ2_BOSS2_MACHINEGUN_L5: + case MZ2_CARRIER_MACHINEGUN_L1: // PMM + case MZ2_CARRIER_MACHINEGUN_L2: // PMM + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("infantry/infatck1.wav"), 1, ATTN_NONE, 0); + break; + + case MZ2_SOLDIER_BLASTER_1: + case MZ2_SOLDIER_BLASTER_2: + case MZ2_SOLDIER_BLASTER_3: + case MZ2_SOLDIER_BLASTER_4: + case MZ2_SOLDIER_BLASTER_5: + case MZ2_SOLDIER_BLASTER_6: + case MZ2_SOLDIER_BLASTER_7: + case MZ2_SOLDIER_BLASTER_8: + case MZ2_TURRET_BLASTER: // PGM + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("soldier/solatck2.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_FLYER_BLASTER_1: + case MZ2_FLYER_BLASTER_2: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("flyer/flyatck3.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_MEDIC_BLASTER_1: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("medic/medatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_HOVER_BLASTER_1: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("hover/hovatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_FLOAT_BLASTER_1: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("floater/fltatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_SOLDIER_SHOTGUN_1: + case MZ2_SOLDIER_SHOTGUN_2: + case MZ2_SOLDIER_SHOTGUN_3: + case MZ2_SOLDIER_SHOTGUN_4: + case MZ2_SOLDIER_SHOTGUN_5: + case MZ2_SOLDIER_SHOTGUN_6: + case MZ2_SOLDIER_SHOTGUN_7: + case MZ2_SOLDIER_SHOTGUN_8: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("soldier/solatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_TANK_BLASTER_1: + case MZ2_TANK_BLASTER_2: + case MZ2_TANK_BLASTER_3: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("tank/tnkatck3.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_TANK_MACHINEGUN_1: + case MZ2_TANK_MACHINEGUN_2: + case MZ2_TANK_MACHINEGUN_3: + case MZ2_TANK_MACHINEGUN_4: + case MZ2_TANK_MACHINEGUN_5: + case MZ2_TANK_MACHINEGUN_6: + case MZ2_TANK_MACHINEGUN_7: + case MZ2_TANK_MACHINEGUN_8: + case MZ2_TANK_MACHINEGUN_9: + case MZ2_TANK_MACHINEGUN_10: + case MZ2_TANK_MACHINEGUN_11: + case MZ2_TANK_MACHINEGUN_12: + case MZ2_TANK_MACHINEGUN_13: + case MZ2_TANK_MACHINEGUN_14: + case MZ2_TANK_MACHINEGUN_15: + case MZ2_TANK_MACHINEGUN_16: + case MZ2_TANK_MACHINEGUN_17: + case MZ2_TANK_MACHINEGUN_18: + case MZ2_TANK_MACHINEGUN_19: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + Com_sprintf(soundname, sizeof(soundname), "tank/tnkatk2%c.wav", 'a' + rand() % 5); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound(soundname), 1, ATTN_NORM, 0); + break; + + case MZ2_CHICK_ROCKET_1: + case MZ2_TURRET_ROCKET: // PGM + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("chick/chkatck2.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_TANK_ROCKET_1: + case MZ2_TANK_ROCKET_2: + case MZ2_TANK_ROCKET_3: + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("tank/tnkatck1.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_SUPERTANK_ROCKET_1: + case MZ2_SUPERTANK_ROCKET_2: + case MZ2_SUPERTANK_ROCKET_3: + case MZ2_BOSS2_ROCKET_1: + case MZ2_BOSS2_ROCKET_2: + case MZ2_BOSS2_ROCKET_3: + case MZ2_BOSS2_ROCKET_4: + case MZ2_CARRIER_ROCKET_1: +// case MZ2_CARRIER_ROCKET_2: +// case MZ2_CARRIER_ROCKET_3: +// case MZ2_CARRIER_ROCKET_4: + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0.2; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("tank/rocket.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_GUNNER_GRENADE_1: + case MZ2_GUNNER_GRENADE_2: + case MZ2_GUNNER_GRENADE_3: + case MZ2_GUNNER_GRENADE_4: + dl->color[0] = 1;dl->color[1] = 0.5;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("gunner/gunatck3.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_GLADIATOR_RAILGUN_1: + // PMM + case MZ2_CARRIER_RAILGUN: + case MZ2_WIDOW_RAIL: + // pmm + dl->color[0] = 0.5;dl->color[1] = 0.5;dl->color[2] = 1.0; + break; + +// --- Xian's shit starts --- + case MZ2_MAKRON_BFG: + dl->color[0] = 0.5;dl->color[1] = 1 ;dl->color[2] = 0.5; + //S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("makron/bfg_fire.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_MAKRON_BLASTER_1: + case MZ2_MAKRON_BLASTER_2: + case MZ2_MAKRON_BLASTER_3: + case MZ2_MAKRON_BLASTER_4: + case MZ2_MAKRON_BLASTER_5: + case MZ2_MAKRON_BLASTER_6: + case MZ2_MAKRON_BLASTER_7: + case MZ2_MAKRON_BLASTER_8: + case MZ2_MAKRON_BLASTER_9: + case MZ2_MAKRON_BLASTER_10: + case MZ2_MAKRON_BLASTER_11: + case MZ2_MAKRON_BLASTER_12: + case MZ2_MAKRON_BLASTER_13: + case MZ2_MAKRON_BLASTER_14: + case MZ2_MAKRON_BLASTER_15: + case MZ2_MAKRON_BLASTER_16: + case MZ2_MAKRON_BLASTER_17: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("makron/blaster.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_JORG_MACHINEGUN_L1: + case MZ2_JORG_MACHINEGUN_L2: + case MZ2_JORG_MACHINEGUN_L3: + case MZ2_JORG_MACHINEGUN_L4: + case MZ2_JORG_MACHINEGUN_L5: + case MZ2_JORG_MACHINEGUN_L6: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("boss3/xfire.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_JORG_MACHINEGUN_R1: + case MZ2_JORG_MACHINEGUN_R2: + case MZ2_JORG_MACHINEGUN_R3: + case MZ2_JORG_MACHINEGUN_R4: + case MZ2_JORG_MACHINEGUN_R5: + case MZ2_JORG_MACHINEGUN_R6: + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + break; + + case MZ2_JORG_BFG_1: + dl->color[0] = 0.5;dl->color[1] = 1 ;dl->color[2] = 0.5; + break; + + case MZ2_BOSS2_MACHINEGUN_R1: + case MZ2_BOSS2_MACHINEGUN_R2: + case MZ2_BOSS2_MACHINEGUN_R3: + case MZ2_BOSS2_MACHINEGUN_R4: + case MZ2_BOSS2_MACHINEGUN_R5: + case MZ2_CARRIER_MACHINEGUN_R1: // PMM + case MZ2_CARRIER_MACHINEGUN_R2: // PMM + + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + + CL_ParticleEffect (origin, vec3_origin, 0, 40); + CL_SmokeAndFlash(origin); + break; + +// ====== +// ROGUE + case MZ2_STALKER_BLASTER: + case MZ2_DAEDALUS_BLASTER: + case MZ2_MEDIC_BLASTER_2: + case MZ2_WIDOW_BLASTER: + case MZ2_WIDOW_BLASTER_SWEEP1: + case MZ2_WIDOW_BLASTER_SWEEP2: + case MZ2_WIDOW_BLASTER_SWEEP3: + case MZ2_WIDOW_BLASTER_SWEEP4: + case MZ2_WIDOW_BLASTER_SWEEP5: + case MZ2_WIDOW_BLASTER_SWEEP6: + case MZ2_WIDOW_BLASTER_SWEEP7: + case MZ2_WIDOW_BLASTER_SWEEP8: + case MZ2_WIDOW_BLASTER_SWEEP9: + case MZ2_WIDOW_BLASTER_100: + case MZ2_WIDOW_BLASTER_90: + case MZ2_WIDOW_BLASTER_80: + case MZ2_WIDOW_BLASTER_70: + case MZ2_WIDOW_BLASTER_60: + case MZ2_WIDOW_BLASTER_50: + case MZ2_WIDOW_BLASTER_40: + case MZ2_WIDOW_BLASTER_30: + case MZ2_WIDOW_BLASTER_20: + case MZ2_WIDOW_BLASTER_10: + case MZ2_WIDOW_BLASTER_0: + case MZ2_WIDOW_BLASTER_10L: + case MZ2_WIDOW_BLASTER_20L: + case MZ2_WIDOW_BLASTER_30L: + case MZ2_WIDOW_BLASTER_40L: + case MZ2_WIDOW_BLASTER_50L: + case MZ2_WIDOW_BLASTER_60L: + case MZ2_WIDOW_BLASTER_70L: + case MZ2_WIDOW_RUN_1: + case MZ2_WIDOW_RUN_2: + case MZ2_WIDOW_RUN_3: + case MZ2_WIDOW_RUN_4: + case MZ2_WIDOW_RUN_5: + case MZ2_WIDOW_RUN_6: + case MZ2_WIDOW_RUN_7: + case MZ2_WIDOW_RUN_8: + dl->color[0] = 0;dl->color[1] = 1;dl->color[2] = 0; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("tank/tnkatck3.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_WIDOW_DISRUPTOR: + dl->color[0] = -1;dl->color[1] = -1;dl->color[2] = -1; + S_StartSound (NULL, ent, CHAN_WEAPON, S_RegisterSound("weapons/disint2.wav"), 1, ATTN_NORM, 0); + break; + + case MZ2_WIDOW_PLASMABEAM: + case MZ2_WIDOW2_BEAMER_1: + case MZ2_WIDOW2_BEAMER_2: + case MZ2_WIDOW2_BEAMER_3: + case MZ2_WIDOW2_BEAMER_4: + case MZ2_WIDOW2_BEAMER_5: + case MZ2_WIDOW2_BEAM_SWEEP_1: + case MZ2_WIDOW2_BEAM_SWEEP_2: + case MZ2_WIDOW2_BEAM_SWEEP_3: + case MZ2_WIDOW2_BEAM_SWEEP_4: + case MZ2_WIDOW2_BEAM_SWEEP_5: + case MZ2_WIDOW2_BEAM_SWEEP_6: + case MZ2_WIDOW2_BEAM_SWEEP_7: + case MZ2_WIDOW2_BEAM_SWEEP_8: + case MZ2_WIDOW2_BEAM_SWEEP_9: + case MZ2_WIDOW2_BEAM_SWEEP_10: + case MZ2_WIDOW2_BEAM_SWEEP_11: + dl->radius = 300 + (rand()&100); + dl->color[0] = 1;dl->color[1] = 1;dl->color[2] = 0; + dl->die = cl.time + 200; + break; +// ROGUE +// ====== + +// --- Xian's shit ends --- + + } +} + + +/* +=============== +CL_AddDLights + +=============== +*/ +void CL_AddDLights (void) +{ + int i; + cdlight_t *dl; + + dl = cl_dlights; + +//===== +//PGM + if(vidref_val == VIDREF_GL) + { + for (i=0 ; iradius) + continue; + V_AddLight (dl->origin, dl->radius, + dl->color[0], dl->color[1], dl->color[2]); + } + } + else + { + for (i=0 ; iradius) + continue; + + // negative light in software. only black allowed + if ((dl->color[0] < 0) || (dl->color[1] < 0) || (dl->color[2] < 0)) + { + dl->radius = -(dl->radius); + dl->color[0] = 1; + dl->color[1] = 1; + dl->color[2] = 1; + } + V_AddLight (dl->origin, dl->radius, + dl->color[0], dl->color[1], dl->color[2]); + } + } +//PGM +//===== +} + + + +/* +============================================================== + +PARTICLE MANAGEMENT + +============================================================== +*/ + +/* +// THIS HAS BEEN RELOCATED TO CLIENT.H +typedef struct particle_s +{ + struct particle_s *next; + + float time; + + vec3_t org; + vec3_t vel; + vec3_t accel; + float color; + float colorvel; + float alpha; + float alphavel; +} cparticle_t; + + +#define PARTICLE_GRAVITY 40 +*/ + +cparticle_t *active_particles, *free_particles; + +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + + +/* +=============== +CL_ClearParticles +=============== +*/ +void CL_ClearParticles (void) +{ + int i; + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color + (rand()&7); + + d = rand()&31; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = crand()*20; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + + +/* +=============== +CL_ParticleEffect2 +=============== +*/ +void CL_ParticleEffect2 (vec3_t org, vec3_t dir, int color, int count) +{ + int i, j; + cparticle_t *p; + float d; + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color; + + d = rand()&7; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = crand()*20; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + + +// RAFAEL +/* +=============== +CL_ParticleEffect3 +=============== +*/ +void CL_ParticleEffect3 (vec3_t org, vec3_t dir, int color, int count) +{ + int i, j; + cparticle_t *p; + float d; + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color; + + d = rand()&7; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = crand()*20; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + +/* +=============== +CL_TeleporterParticles +=============== +*/ +void CL_TeleporterParticles (entity_state_t *ent) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<8 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 0xdb; + + for (j=0 ; j<2 ; j++) + { + p->org[j] = ent->origin[j] - 16 + (rand()&31); + p->vel[j] = crand()*14; + } + + p->org[2] = ent->origin[2] - 8 + (rand()&7); + p->vel[2] = 80 + (rand()&7); + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -0.5; + } +} + + +/* +=============== +CL_LogoutEffect + +=============== +*/ +void CL_LogoutEffect (vec3_t org, int type) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<500 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + + if (type == MZ_LOGIN) + p->color = 0xd0 + (rand()&7); // green + else if (type == MZ_LOGOUT) + p->color = 0x40 + (rand()&7); // red + else + p->color = 0xe0 + (rand()&7); // yellow + + p->org[0] = org[0] - 16 + frand()*32; + p->org[1] = org[1] - 16 + frand()*32; + p->org[2] = org[2] - 24 + frand()*56; + + for (j=0 ; j<3 ; j++) + p->vel[j] = crand()*20; + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (1.0 + frand()*0.3); + } +} + + +/* +=============== +CL_ItemRespawnParticles + +=============== +*/ +void CL_ItemRespawnParticles (vec3_t org) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<64 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + + p->color = 0xd4 + (rand()&3); // green + + p->org[0] = org[0] + crand()*8; + p->org[1] = org[1] + crand()*8; + p->org[2] = org[2] + crand()*8; + + for (j=0 ; j<3 ; j++) + p->vel[j] = crand()*8; + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY*0.2; + p->alpha = 1.0; + + p->alphavel = -1.0 / (1.0 + frand()*0.3); + } +} + + +/* +=============== +CL_ExplosionParticles +=============== +*/ +void CL_ExplosionParticles (vec3_t org) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<256 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 0xe0 + (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%384)-192; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -0.8 / (0.5 + frand()*0.3); + } +} + + +/* +=============== +CL_BigTeleportParticles +=============== +*/ +void CL_BigTeleportParticles (vec3_t org) +{ + int i; + cparticle_t *p; + float angle, dist; + static int colortable[4] = {2*8,13*8,21*8,18*8}; + + for (i=0 ; i<4096 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + + p->color = colortable[rand()&3]; + + angle = M_PI*2*(rand()&1023)/1023.0; + dist = rand()&31; + p->org[0] = org[0] + cos(angle)*dist; + p->vel[0] = cos(angle)*(70+(rand()&63)); + p->accel[0] = -cos(angle)*100; + + p->org[1] = org[1] + sin(angle)*dist; + p->vel[1] = sin(angle)*(70+(rand()&63)); + p->accel[1] = -sin(angle)*100; + + p->org[2] = org[2] + 8 + (rand()%90); + p->vel[2] = -100 + (rand()&31); + p->accel[2] = PARTICLE_GRAVITY*4; + p->alpha = 1.0; + + p->alphavel = -0.3 / (0.5 + frand()*0.3); + } +} + + +/* +=============== +CL_BlasterParticles + +Wall impact puffs +=============== +*/ +void CL_BlasterParticles (vec3_t org, vec3_t dir) +{ + int i, j; + cparticle_t *p; + float d; + int count; + + count = 40; + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 0xe0 + (rand()&7); + + d = rand()&15; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = dir[j] * 30 + crand()*40; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + + +/* +=============== +CL_BlasterTrail + +=============== +*/ +void CL_BlasterTrail (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.3+frand()*0.2); + p->color = 0xe0; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand(); + p->vel[j] = crand()*5; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} + +/* +=============== +CL_QuadTrail + +=============== +*/ +void CL_QuadTrail (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.8+frand()*0.2); + p->color = 115; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*16; + p->vel[j] = crand()*5; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} + +/* +=============== +CL_FlagTrail + +=============== +*/ +void CL_FlagTrail (vec3_t start, vec3_t end, float color) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.8+frand()*0.2); + p->color = color; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*16; + p->vel[j] = crand()*5; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} + +/* +=============== +CL_DiminishingTrail + +=============== +*/ +void CL_DiminishingTrail (vec3_t start, vec3_t end, centity_t *old, int flags) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + float dec; + float orgscale; + float velscale; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 0.5; + VectorScale (vec, dec, vec); + + if (old->trailcount > 900) + { + orgscale = 4; + velscale = 15; + } + else if (old->trailcount > 800) + { + orgscale = 2; + velscale = 10; + } + else + { + orgscale = 1; + velscale = 5; + } + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + + // drop less particles as it flies + if ((rand()&1023) < old->trailcount) + { + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + if (flags & EF_GIB) + { + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.4); + p->color = 0xe8 + (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*orgscale; + p->vel[j] = crand()*velscale; + p->accel[j] = 0; + } + p->vel[2] -= PARTICLE_GRAVITY; + } + else if (flags & EF_GREENGIB) + { + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.4); + p->color = 0xdb + (rand()&7); + for (j=0; j< 3; j++) + { + p->org[j] = move[j] + crand()*orgscale; + p->vel[j] = crand()*velscale; + p->accel[j] = 0; + } + p->vel[2] -= PARTICLE_GRAVITY; + } + else + { + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.2); + p->color = 4 + (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*orgscale; + p->vel[j] = crand()*velscale; + } + p->accel[2] = 20; + } + } + + old->trailcount -= 5; + if (old->trailcount < 100) + old->trailcount = 100; + VectorAdd (move, vec, move); + } +} + +void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up) +{ + float d; + + // this rotate and negat guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right); + CrossProduct (right, forward, up); +} + +/* +=============== +CL_RocketTrail + +=============== +*/ +void CL_RocketTrail (vec3_t start, vec3_t end, centity_t *old) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + float dec; + + // smoke + CL_DiminishingTrail (start, end, old, EF_ROCKET); + + // fire + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 1; + VectorScale (vec, dec, vec); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + + if ( (rand()&7) == 0) + { + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + VectorClear (p->accel); + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.2); + p->color = 0xdc + (rand()&3); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*5; + p->vel[j] = crand()*20; + } + p->accel[2] = -PARTICLE_GRAVITY; + } + VectorAdd (move, vec, move); + } +} + +/* +=============== +CL_RailTrail + +=============== +*/ +void CL_RailTrail (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + float dec; + vec3_t right, up; + int i; + float d, c, s; + vec3_t dir; + byte clr = 0x74; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + MakeNormalVectors (vec, right, up); + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); + + d = i * 0.1; + c = cos(d); + s = sin(d); + + VectorScale (right, c, dir); + VectorMA (dir, s, up, dir); + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.2); + p->color = clr + (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + dir[j]*3; + p->vel[j] = dir[j]*6; + } + + VectorAdd (move, vec, move); + } + + dec = 0.75; + VectorScale (vec, dec, vec); + VectorCopy (start, move); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.6+frand()*0.2); + p->color = 0x0 + rand()&15; + + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*3; + p->vel[j] = crand()*3; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} + +// RAFAEL +/* +=============== +CL_IonripperTrail +=============== +*/ +void CL_IonripperTrail (vec3_t start, vec3_t ent) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + int left = 0; + + VectorCopy (start, move); + VectorSubtract (ent, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + p->alpha = 0.5; + p->alphavel = -1.0 / (0.3 + frand() * 0.2); + p->color = 0xe4 + (rand()&3); + + for (j=0; j<3; j++) + { + p->org[j] = move[j]; + p->accel[j] = 0; + } + if (left) + { + left = 0; + p->vel[0] = 10; + } + else + { + left = 1; + p->vel[0] = -10; + } + + p->vel[1] = 0; + p->vel[2] = 0; + + VectorAdd (move, vec, move); + } +} + + +/* +=============== +CL_BubbleTrail + +=============== +*/ +void CL_BubbleTrail (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int i, j; + cparticle_t *p; + float dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 32; + VectorScale (vec, dec, vec); + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + VectorClear (p->accel); + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.2); + p->color = 4 + (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*2; + p->vel[j] = crand()*5; + } + p->vel[2] += 6; + + VectorAdd (move, vec, move); + } +} + + +/* +=============== +CL_FlyParticles +=============== +*/ + +#define BEAMLENGTH 16 +void CL_FlyParticles (vec3_t origin, int count) +{ + int i; + cparticle_t *p; + float angle; + float sr, sp, sy, cr, cp, cy; + vec3_t forward; + float dist = 64; + float ltime; + + + if (count > NUMVERTEXNORMALS) + count = NUMVERTEXNORMALS; + + if (!avelocities[0][0]) + { + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + + dist = sin(ltime + i)*64; + p->org[0] = origin[0] + bytedirs[i][0]*dist + forward[0]*BEAMLENGTH; + p->org[1] = origin[1] + bytedirs[i][1]*dist + forward[1]*BEAMLENGTH; + p->org[2] = origin[2] + bytedirs[i][2]*dist + forward[2]*BEAMLENGTH; + + VectorClear (p->vel); + VectorClear (p->accel); + + p->color = 0; + p->colorvel = 0; + + p->alpha = 1; + p->alphavel = -100; + } +} + +void CL_FlyEffect (centity_t *ent, vec3_t origin) +{ + int n; + int count; + int starttime; + + if (ent->fly_stoptime < cl.time) + { + starttime = cl.time; + ent->fly_stoptime = cl.time + 60000; + } + else + { + starttime = ent->fly_stoptime - 60000; + } + + n = cl.time - starttime; + if (n < 20000) + count = n * 162 / 20000.0; + else + { + n = ent->fly_stoptime - cl.time; + if (n < 20000) + count = n * 162 / 20000.0; + else + count = 162; + } + + CL_FlyParticles (origin, count); +} + + +/* +=============== +CL_BfgParticles +=============== +*/ + +#define BEAMLENGTH 16 +void CL_BfgParticles (entity_t *ent) +{ + int i; + cparticle_t *p; + float angle; + float sr, sp, sy, cr, cp, cy; + vec3_t forward; + float dist = 64; + vec3_t v; + float ltime; + + if (!avelocities[0][0]) + { + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + + dist = sin(ltime + i)*64; + p->org[0] = ent->origin[0] + bytedirs[i][0]*dist + forward[0]*BEAMLENGTH; + p->org[1] = ent->origin[1] + bytedirs[i][1]*dist + forward[1]*BEAMLENGTH; + p->org[2] = ent->origin[2] + bytedirs[i][2]*dist + forward[2]*BEAMLENGTH; + + VectorClear (p->vel); + VectorClear (p->accel); + + VectorSubtract (p->org, ent->origin, v); + dist = VectorLength(v) / 90.0; + p->color = floor (0xd0 + dist * 7); + p->colorvel = 0; + + p->alpha = 1.0 - dist; + p->alphavel = -100; + } +} + + +/* +=============== +CL_TrapParticles +=============== +*/ +// RAFAEL +void CL_TrapParticles (entity_t *ent) +{ + vec3_t move; + vec3_t vec; + vec3_t start, end; + float len; + int j; + cparticle_t *p; + int dec; + + ent->origin[2]-=14; + VectorCopy (ent->origin, start); + VectorCopy (ent->origin, end); + end[2]+=64; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.3+frand()*0.2); + p->color = 0xe0; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand(); + p->vel[j] = crand()*15; + p->accel[j] = 0; + } + p->accel[2] = PARTICLE_GRAVITY; + + VectorAdd (move, vec, move); + } + + { + + + int i, j, k; + cparticle_t *p; + float vel; + vec3_t dir; + vec3_t org; + + + ent->origin[2]+=14; + VectorCopy (ent->origin, org); + + + for (i=-2 ; i<=2 ; i+=4) + for (j=-2 ; j<=2 ; j+=4) + for (k=-2 ; k<=4 ; k+=4) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 0xe0 + (rand()&3); + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.3 + (rand()&7) * 0.02); + + p->org[0] = org[0] + i + ((rand()&23) * crand()); + p->org[1] = org[1] + j + ((rand()&23) * crand()); + p->org[2] = org[2] + k + ((rand()&23) * crand()); + + dir[0] = j * 8; + dir[1] = i * 8; + dir[2] = k * 8; + + VectorNormalize (dir); + vel = 50 + rand()&63; + VectorScale (dir, vel, p->vel); + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + } + } +} + + +/* +=============== +CL_BFGExplosionParticles +=============== +*/ +//FIXME combined with CL_ExplosionParticles +void CL_BFGExplosionParticles (vec3_t org) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<256 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 0xd0 + (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%384)-192; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -0.8 / (0.5 + frand()*0.3); + } +} + + +/* +=============== +CL_TeleportParticles + +=============== +*/ +void CL_TeleportParticles (vec3_t org) +{ + int i, j, k; + cparticle_t *p; + float vel; + vec3_t dir; + + for (i=-16 ; i<=16 ; i+=4) + for (j=-16 ; j<=16 ; j+=4) + for (k=-16 ; k<=32 ; k+=4) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = 7 + (rand()&7); + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.3 + (rand()&7) * 0.02); + + p->org[0] = org[0] + i + (rand()&3); + p->org[1] = org[1] + j + (rand()&3); + p->org[2] = org[2] + k + (rand()&3); + + dir[0] = j*8; + dir[1] = i*8; + dir[2] = k*8; + + VectorNormalize (dir); + vel = 50 + (rand()&63); + VectorScale (dir, vel, p->vel); + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + } +} + + +/* +=============== +CL_AddParticles +=============== +*/ +void CL_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + // PMM - added INSTANT_PARTICLE handling for heat beam + if (p->alphavel != INSTANT_PARTICLE) + { + time = (cl.time - p->time)*0.001; + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + continue; + } + } + else + { + alpha = p->alpha; + } + + 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; + + V_AddParticle (org, color, alpha); + // PMM + if (p->alphavel == INSTANT_PARTICLE) + { + p->alphavel = 0.0; + p->alpha = 0.0; + } + } + + active_particles = active; +} + + +/* +============== +CL_EntityEvent + +An entity has just been parsed that has an event value + +the female events are there for backwards compatability +============== +*/ +extern struct sfx_s *cl_sfx_footsteps[4]; + +void CL_EntityEvent (entity_state_t *ent) +{ + switch (ent->event) + { + case EV_ITEM_RESPAWN: + S_StartSound (NULL, ent->number, CHAN_WEAPON, S_RegisterSound("items/respawn1.wav"), 1, ATTN_IDLE, 0); + CL_ItemRespawnParticles (ent->origin); + break; + case EV_PLAYER_TELEPORT: + S_StartSound (NULL, ent->number, CHAN_WEAPON, S_RegisterSound("misc/tele1.wav"), 1, ATTN_IDLE, 0); + CL_TeleportParticles (ent->origin); + break; + case EV_FOOTSTEP: + if (cl_footsteps->value) + S_StartSound (NULL, ent->number, CHAN_BODY, cl_sfx_footsteps[rand()&3], 1, ATTN_NORM, 0); + break; + case EV_FALLSHORT: + S_StartSound (NULL, ent->number, CHAN_AUTO, S_RegisterSound ("player/land1.wav"), 1, ATTN_NORM, 0); + break; + case EV_FALL: + S_StartSound (NULL, ent->number, CHAN_AUTO, S_RegisterSound ("*fall2.wav"), 1, ATTN_NORM, 0); + break; + case EV_FALLFAR: + S_StartSound (NULL, ent->number, CHAN_AUTO, S_RegisterSound ("*fall1.wav"), 1, ATTN_NORM, 0); + break; + } +} + + +/* +============== +CL_ClearEffects + +============== +*/ +void CL_ClearEffects (void) +{ + CL_ClearParticles (); + CL_ClearDlights (); + CL_ClearLightStyles (); +} diff --git a/client/cl_input.c b/client/cl_input.c new file mode 100644 index 000000000..800d5a1bc --- /dev/null +++ b/client/cl_input.c @@ -0,0 +1,542 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +cvar_t *cl_nodelta; + +extern unsigned sys_frame_time; +unsigned frame_msec; +unsigned old_sys_frame_time; + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + + +Key_Event (int key, qboolean down, unsigned time); + + +mlook src time + +=============================================================================== +*/ + + +kbutton_t in_klook; +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed, in_use, in_attack; +kbutton_t in_up, in_down; + +int in_impulse; + + +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + Com_Printf ("Three keys down for a button!\n"); + return; + } + + if (b->state & 1) + return; // still down + + // save timestamp + c = Cmd_Argv(2); + b->downtime = atoi(c); + if (!b->downtime) + b->downtime = sys_frame_time - 100; + + b->state |= 1 + 2; // down + impulse down +} + +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + return; // some other key is still holding it down + + if (!(b->state & 1)) + return; // still up (this should not happen) + + // save timestamp + c = Cmd_Argv(2); + uptime = atoi(c); + if (uptime) + b->msec += uptime - b->downtime; + else + b->msec += 10; + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} +void IN_ForwardDown(void) {KeyDown(&in_forward);} +void IN_ForwardUp(void) {KeyUp(&in_forward);} +void IN_BackDown(void) {KeyDown(&in_back);} +void IN_BackUp(void) {KeyUp(&in_back);} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {KeyDown(&in_moveright);} +void IN_MoverightUp(void) {KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +void IN_AttackDown(void) {KeyDown(&in_attack);} +void IN_AttackUp(void) {KeyUp(&in_attack);} + +void IN_UseDown (void) {KeyDown(&in_use);} +void IN_UseUp (void) {KeyUp(&in_use);} + +void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));} + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val; + int msec; + + key->state &= 1; // clear impulses + + msec = key->msec; + key->msec = 0; + + if (key->state) + { // still down + msec += sys_frame_time - key->downtime; + key->downtime = sys_frame_time; + } + +#if 0 + if (msec) + { + Com_Printf ("%i ", msec); + } +#endif + + val = (float)msec / frame_msec; + if (val < 0) + val = 0; + if (val > 1) + val = 1; + + return val; +} + + + + +//========================================================================== + +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_sidespeed; + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles (void) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + speed = cls.frametime * cl_anglespeedkey->value; + else + speed = cls.frametime; + + if (!(in_strafe.state & 1)) + { + cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + if (in_klook.state & 1) + { + cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + cl.viewangles[PITCH] += speed*cl_pitchspeed->value * down; +} + +/* +================ +CL_BaseMove + +Send the intended movement message to the server +================ +*/ +void CL_BaseMove (usercmd_t *cmd) +{ + CL_AdjustAngles (); + + memset (cmd, 0, sizeof(*cmd)); + + VectorCopy (cl.viewangles, cmd->angles); + if (in_strafe.state & 1) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if (! (in_klook.state & 1) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState (&in_back); + } + +// +// adjust for speed key / running +// + if ( (in_speed.state & 1) ^ (int)(cl_run->value) ) + { + cmd->forwardmove *= 2; + cmd->sidemove *= 2; + cmd->upmove *= 2; + } +} + +void CL_ClampPitch (void) +{ + float pitch; + + pitch = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]); + if (pitch > 180) + pitch -= 360; + if (cl.viewangles[PITCH] + pitch > 89) + cl.viewangles[PITCH] = 89 - pitch; + if (cl.viewangles[PITCH] + pitch < -89) + cl.viewangles[PITCH] = -89 - pitch; +} + +/* +============== +CL_FinishMove +============== +*/ +void CL_FinishMove (usercmd_t *cmd) +{ + int ms; + int i; + +// +// figure button bits +// + if ( in_attack.state & 3 ) + cmd->buttons |= BUTTON_ATTACK; + in_attack.state &= ~2; + + if (in_use.state & 3) + cmd->buttons |= BUTTON_USE; + in_use.state &= ~2; + + if (anykeydown && cls.key_dest == key_game) + cmd->buttons |= BUTTON_ANY; + + // send milliseconds of time to apply the move + ms = cls.frametime * 1000; + if (ms > 250) + ms = 100; // time was unreasonable + cmd->msec = ms; + + CL_ClampPitch (); + for (i=0 ; i<3 ; i++) + cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); + + cmd->impulse = in_impulse; + in_impulse = 0; + +// send the ambient light level at the player's current position + cmd->lightlevel = (byte)cl_lightlevel->value; +} + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd (void) +{ + usercmd_t cmd; + + frame_msec = sys_frame_time - old_sys_frame_time; + if (frame_msec < 1) + frame_msec = 1; + if (frame_msec > 200) + frame_msec = 200; + + // get basic movement from keyboard + CL_BaseMove (&cmd); + + // allow mice or other external controllers to add to the move + IN_Move (&cmd); + + CL_FinishMove (&cmd); + + old_sys_frame_time = sys_frame_time; + +//cmd.impulse = cls.framecount; + + return cmd; +} + + +void IN_CenterView (void) +{ + cl.viewangles[PITCH] = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]); +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput (void) +{ + Cmd_AddCommand ("centerview",IN_CenterView); + + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_AttackDown); + Cmd_AddCommand ("-attack", IN_AttackUp); + Cmd_AddCommand ("+use", IN_UseDown); + Cmd_AddCommand ("-use", IN_UseUp); + Cmd_AddCommand ("impulse", IN_Impulse); + Cmd_AddCommand ("+klook", IN_KLookDown); + Cmd_AddCommand ("-klook", IN_KLookUp); + + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); +} + + + +/* +================= +CL_SendCmd +================= +*/ +void CL_SendCmd (void) +{ + sizebuf_t buf; + byte data[128]; + int i; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int checksumIndex; + + // build a command even if not connected + + // save this command off for prediction + i = cls.netchan.outgoing_sequence & (CMD_BACKUP-1); + cmd = &cl.cmds[i]; + cl.cmd_time[i] = cls.realtime; // for netgraph ping calculation + + *cmd = CL_CreateCmd (); + + cl.cmd = *cmd; + + if (cls.state == ca_disconnected || cls.state == ca_connecting) + return; + + if ( cls.state == ca_connected) + { + if (cls.netchan.message.cursize || curtime - cls.netchan.last_sent > 1000 ) + Netchan_Transmit (&cls.netchan, 0, buf.data); + return; + } + + // send a userinfo update if needed + if (userinfo_modified) + { + CL_FixUpGender(); + userinfo_modified = false; + MSG_WriteByte (&cls.netchan.message, clc_userinfo); + MSG_WriteString (&cls.netchan.message, Cvar_Userinfo() ); + } + + SZ_Init (&buf, data, sizeof(data)); + + if (cmd->buttons && cl.cinematictime > 0 && !cl.attractloop + && cls.realtime - cl.cinematictime > 1000) + { // skip the rest of the cinematic + SCR_FinishCinematic (); + } + + // begin a client move command + MSG_WriteByte (&buf, clc_move); + + // save the position for a checksum byte + checksumIndex = buf.cursize; + MSG_WriteByte (&buf, 0); + + // let the server know what the last frame we + // got was, so the next message can be delta compressed + if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting) + MSG_WriteLong (&buf, -1); // no compression + else + MSG_WriteLong (&buf, cl.frame.serverframe); + + // send this and the previous cmds in the message, so + // if the last packet was dropped, it can be recovered + i = (cls.netchan.outgoing_sequence-2) & (CMD_BACKUP-1); + cmd = &cl.cmds[i]; + memset (&nullcmd, 0, sizeof(nullcmd)); + MSG_WriteDeltaUsercmd (&buf, &nullcmd, cmd); + oldcmd = cmd; + + i = (cls.netchan.outgoing_sequence-1) & (CMD_BACKUP-1); + cmd = &cl.cmds[i]; + MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); + oldcmd = cmd; + + i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP-1); + cmd = &cl.cmds[i]; + MSG_WriteDeltaUsercmd (&buf, oldcmd, cmd); + + // calculate a checksum over the move commands + buf.data[checksumIndex] = COM_BlockSequenceCRCByte( + buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1, + cls.netchan.outgoing_sequence); + + // + // deliver the message + // + Netchan_Transmit (&cls.netchan, buf.cursize, buf.data); +} + + diff --git a/client/cl_inv.c b/client/cl_inv.c new file mode 100644 index 000000000..8a2d20f1a --- /dev/null +++ b/client/cl_inv.c @@ -0,0 +1,142 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_inv.c -- client inventory screen + +#include "client.h" + +/* +================ +CL_ParseInventory +================ +*/ +void CL_ParseInventory (void) +{ + int i; + + for (i=0 ; i + +Begins recording a demo from the current position +==================== +*/ +void CL_Record_f (void) +{ + char name[MAX_OSPATH]; + char buf_data[MAX_MSGLEN]; + sizebuf_t buf; + int i; + int len; + entity_state_t *ent; + entity_state_t nullstate; + + if (Cmd_Argc() != 2) + { + Com_Printf ("record \n"); + return; + } + + if (cls.demorecording) + { + Com_Printf ("Already recording.\n"); + return; + } + + if (cls.state != ca_active) + { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // + // open the demo file + // + Com_sprintf (name, sizeof(name), "%s/demos/%s.dm2", FS_Gamedir(), Cmd_Argv(1)); + + Com_Printf ("recording to %s.\n", name); + FS_CreatePath (name); + cls.demofile = fopen (name, "wb"); + if (!cls.demofile) + { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + cls.demorecording = true; + + // don't start saving messages until a non-delta compressed message is received + cls.demowaiting = true; + + // + // write out messages to hold the startup information + // + SZ_Init (&buf, buf_data, sizeof(buf_data)); + + // send the serverdata + MSG_WriteByte (&buf, svc_serverdata); + MSG_WriteLong (&buf, PROTOCOL_VERSION); + MSG_WriteLong (&buf, 0x10000 + cl.servercount); + MSG_WriteByte (&buf, 1); // demos are always attract loops + MSG_WriteString (&buf, cl.gamedir); + MSG_WriteShort (&buf, cl.playernum); + + MSG_WriteString (&buf, cl.configstrings[CS_NAME]); + + // configstrings + for (i=0 ; i buf.maxsize) + { // write it out + len = LittleLong (buf.cursize); + fwrite (&len, 4, 1, cls.demofile); + fwrite (buf.data, buf.cursize, 1, cls.demofile); + buf.cursize = 0; + } + + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteString (&buf, cl.configstrings[i]); + } + + } + + // baselines + memset (&nullstate, 0, sizeof(nullstate)); + for (i=0; imodelindex) + continue; + + if (buf.cursize + 64 > buf.maxsize) + { // write it out + len = LittleLong (buf.cursize); + fwrite (&len, 4, 1, cls.demofile); + fwrite (buf.data, buf.cursize, 1, cls.demofile); + buf.cursize = 0; + } + + MSG_WriteByte (&buf, svc_spawnbaseline); + MSG_WriteDeltaEntity (&nullstate, &cl_entities[i].baseline, &buf, true, true); + } + + MSG_WriteByte (&buf, svc_stufftext); + MSG_WriteString (&buf, "precache\n"); + + // write it to the demo file + + len = LittleLong (buf.cursize); + fwrite (&len, 4, 1, cls.demofile); + fwrite (buf.data, buf.cursize, 1, cls.demofile); + + // the rest of the demo file will be individual frames +} + +//====================================================================== + +/* +=================== +Cmd_ForwardToServer + +adds the current command line as a clc_stringcmd to the client message. +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void Cmd_ForwardToServer (void) +{ + char *cmd; + + cmd = Cmd_Argv(0); + if (cls.state <= ca_connected || *cmd == '-' || *cmd == '+') + { + Com_Printf ("Unknown command \"%s\"\n", cmd); + return; + } + + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + SZ_Print (&cls.netchan.message, cmd); + if (Cmd_Argc() > 1) + { + SZ_Print (&cls.netchan.message, " "); + SZ_Print (&cls.netchan.message, Cmd_Args()); + } +} + +void CL_Setenv_f( void ) +{ + int argc = Cmd_Argc(); + + if ( argc > 2 ) + { + char buffer[1000]; + int i; + + strcpy( buffer, Cmd_Argv(1) ); + strcat( buffer, "=" ); + + for ( i = 2; i < argc; i++ ) + { + strcat( buffer, Cmd_Argv( i ) ); + strcat( buffer, " " ); + } + + putenv( buffer ); + } + else if ( argc == 2 ) + { + char *env = getenv( Cmd_Argv(1) ); + + if ( env ) + { + Com_Printf( "%s=%s\n", Cmd_Argv(1), env ); + } + else + { + Com_Printf( "%s undefined\n", Cmd_Argv(1), env ); + } + } +} + + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f (void) +{ + if (cls.state != ca_connected && cls.state != ca_active) + { + Com_Printf ("Can't \"%s\", not connected\n", Cmd_Argv(0)); + return; + } + + // don't forward the first argument + if (Cmd_Argc() > 1) + { + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + SZ_Print (&cls.netchan.message, Cmd_Args()); + } +} + + +/* +================== +CL_Pause_f +================== +*/ +void CL_Pause_f (void) +{ + // never pause in multiplayer + if (Cvar_VariableValue ("maxclients") > 1 || !Com_ServerState ()) + { + Cvar_SetValue ("paused", 0); + return; + } + + Cvar_SetValue ("paused", !cl_paused->value); +} + +/* +================== +CL_Quit_f +================== +*/ +void CL_Quit_f (void) +{ + CL_Disconnect (); + Com_Quit (); +} + +/* +================ +CL_Drop + +Called after an ERR_DROP was thrown +================ +*/ +void CL_Drop (void) +{ + if (cls.state == ca_uninitialized) + return; + if (cls.state == ca_disconnected) + return; + + CL_Disconnect (); + + // drop loading plaque unless this is the initial game start + if (cls.disable_servercount != -1) + SCR_EndLoadingPlaque (); // get rid of loading plaque +} + + +/* +======================= +CL_SendConnectPacket + +We have gotten a challenge from the server, so try and +connect. +====================== +*/ +void CL_SendConnectPacket (void) +{ + netadr_t adr; + int port; + + if (!NET_StringToAdr (cls.servername, &adr)) + { + Com_Printf ("Bad server address\n"); + cls.connect_time = 0; + return; + } + if (adr.port == 0) + adr.port = BigShort (PORT_SERVER); + + port = Cvar_VariableValue ("qport"); + userinfo_modified = false; + + Netchan_OutOfBandPrint (NS_CLIENT, adr, "connect %i %i %i \"%s\"\n", + PROTOCOL_VERSION, port, cls.challenge, Cvar_Userinfo() ); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend (void) +{ + netadr_t adr; + + // if the local server is running and we aren't + // then connect + if (cls.state == ca_disconnected && Com_ServerState() ) + { + cls.state = ca_connecting; + strncpy (cls.servername, "localhost", sizeof(cls.servername)-1); + // we don't need a challenge on the localhost + CL_SendConnectPacket (); + return; +// cls.connect_time = -99999; // CL_CheckForResend() will fire immediately + } + + // resend if we haven't gotten a reply yet + if (cls.state != ca_connecting) + return; + + if (cls.realtime - cls.connect_time < 3000) + return; + + if (!NET_StringToAdr (cls.servername, &adr)) + { + Com_Printf ("Bad server address\n"); + cls.state = ca_disconnected; + return; + } + if (adr.port == 0) + adr.port = BigShort (PORT_SERVER); + + cls.connect_time = cls.realtime; // for retransmit requests + + Com_Printf ("Connecting to %s...\n", cls.servername); + + Netchan_OutOfBandPrint (NS_CLIENT, adr, "getchallenge\n"); +} + + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f (void) +{ + char *server; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: connect \n"); + return; + } + + if (Com_ServerState ()) + { // if running a local server, kill it and reissue + SV_Shutdown (va("Server quit\n", msg), false); + } + else + { + CL_Disconnect (); + } + + server = Cmd_Argv (1); + + NET_Config (true); // allow remote + + CL_Disconnect (); + + cls.state = ca_connecting; + strncpy (cls.servername, server, sizeof(cls.servername)-1); + cls.connect_time = -99999; // CL_CheckForResend() will fire immediately +} + + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f (void) +{ + char message[1024]; + int i; + netadr_t to; + + if (!rcon_client_password->string) + { + Com_Printf ("You must set 'rcon_password' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = (char)255; + message[1] = (char)255; + message[2] = (char)255; + message[3] = (char)255; + message[4] = 0; + + NET_Config (true); // allow remote + + strcat (message, "rcon "); + + strcat (message, rcon_client_password->string); + strcat (message, " "); + + for (i=1 ; i= ca_connected) + to = cls.netchan.remote_address; + else + { + if (!strlen(rcon_address->string)) + { + Com_Printf ("You must either be connected,\n" + "or set the 'rcon_address' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rcon_address->string, &to); + if (to.port == 0) + to.port = BigShort (PORT_SERVER); + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState (void) +{ + S_StopAllSounds (); + CL_ClearEffects (); + CL_ClearTEnts (); + +// wipe the entire cl structure + memset (&cl, 0, sizeof(cl)); + memset (&cl_entities, 0, sizeof(cl_entities)); + + SZ_Clear (&cls.netchan.message); + +} + +/* +===================== +CL_Disconnect + +Goes from a connected state to full screen console state +Sends a disconnect message to the server +This is also called on Com_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect (void) +{ + byte final[32]; + + if (cls.state == ca_disconnected) + return; + + if (cl_timedemo && cl_timedemo->value) + { + int time; + + time = Sys_Milliseconds () - cl.timedemo_start; + if (time > 0) + Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", cl.timedemo_frames, + time/1000.0, cl.timedemo_frames*1000.0 / time); + } + + VectorClear (cl.refdef.blend); + re.CinematicSetPalette(NULL); + + M_ForceMenuOff (); + + cls.connect_time = 0; + + SCR_StopCinematic (); + + if (cls.demorecording) + CL_Stop_f (); + + // send a disconnect message to the server + final[0] = clc_stringcmd; + strcpy ((char *)final+1, "disconnect"); + Netchan_Transmit (&cls.netchan, strlen(final), final); + Netchan_Transmit (&cls.netchan, strlen(final), final); + Netchan_Transmit (&cls.netchan, strlen(final), final); + + CL_ClearState (); + + // stop download + if (cls.download) { + fclose(cls.download); + cls.download = NULL; + } + + cls.state = ca_disconnected; +} + +void CL_Disconnect_f (void) +{ + Com_Error (ERR_DROP, "Disconnected from server"); +} + + +/* +==================== +CL_Packet_f + +packet + +Contents allows \n escape character +==================== +*/ +void CL_Packet_f (void) +{ + char send[2048]; + int i, l; + char *in, *out; + netadr_t adr; + + if (Cmd_Argc() != 3) + { + Com_Printf ("packet \n"); + return; + } + + NET_Config (true); // allow remote + + if (!NET_StringToAdr (Cmd_Argv(1), &adr)) + { + Com_Printf ("Bad address\n"); + return; + } + if (!adr.port) + adr.port = BigShort (PORT_SERVER); + + in = Cmd_Argv(2); + out = send+4; + send[0] = send[1] = send[2] = send[3] = (char)0xff; + + l = strlen (in); + for (i=0 ; i= ca_connected) { + CL_Disconnect(); + cls.connect_time = cls.realtime - 1500; + } else + cls.connect_time = -99999; // fire immediately + + cls.state = ca_connecting; + Com_Printf ("reconnecting...\n"); + } +} + +/* +================= +CL_ParseStatusMessage + +Handle a reply from a ping +================= +*/ +void CL_ParseStatusMessage (void) +{ + char *s; + + s = MSG_ReadString(&net_message); + + Com_Printf ("%s\n", s); + M_AddToServerList (net_from, s); +} + + +/* +================= +CL_PingServers_f +================= +*/ +void CL_PingServers_f (void) +{ + int i; + netadr_t adr; + char name[32]; + char *adrstring; + cvar_t *noudp; + cvar_t *noipx; + + NET_Config (true); // allow remote + + // send a broadcast packet + Com_Printf ("pinging broadcast...\n"); + + noudp = Cvar_Get ("noudp", "0", CVAR_NOSET); + if (!noudp->value) + { + adr.type = NA_BROADCAST; + adr.port = BigShort(PORT_SERVER); + Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); + } + + noipx = Cvar_Get ("noipx", "0", CVAR_NOSET); + if (!noipx->value) + { + adr.type = NA_BROADCAST_IPX; + adr.port = BigShort(PORT_SERVER); + Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); + } + + // send a packet to each address book entry + for (i=0 ; i<16 ; i++) + { + Com_sprintf (name, sizeof(name), "adr%i", i); + adrstring = Cvar_VariableString (name); + if (!adrstring || !adrstring[0]) + continue; + + Com_Printf ("pinging %s...\n", adrstring); + if (!NET_StringToAdr (adrstring, &adr)) + { + Com_Printf ("Bad address: %s\n", adrstring); + continue; + } + if (!adr.port) + adr.port = BigShort(PORT_SERVER); + Netchan_OutOfBandPrint (NS_CLIENT, adr, va("info %i", PROTOCOL_VERSION)); + } +} + + +/* +================= +CL_Skins_f + +Load or download any custom player skins and models +================= +*/ +void CL_Skins_f (void) +{ + int i; + + for (i=0 ; i= ca_connected + && cls.realtime - cls.netchan.last_received > cl_timeout->value*1000) + { + if (++cl.timeoutcount > 5) // timeoutcount saves debugger + { + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect (); + return; + } + } + else + cl.timeoutcount = 0; + +} + + +//============================================================================= + +/* +============== +CL_FixUpGender_f +============== +*/ +void CL_FixUpGender(void) +{ + char *p; + char sk[80]; + + if (gender_auto->value) { + + if (gender->modified) { + // was set directly, don't override the user + gender->modified = false; + return; + } + + strncpy(sk, skin->string, sizeof(sk) - 1); + if ((p = strchr(sk, '/')) != NULL) + *p = 0; + if (Q_stricmp(sk, "male") == 0 || Q_stricmp(sk, "cyborg") == 0) + Cvar_Set ("gender", "male"); + else if (Q_stricmp(sk, "female") == 0 || Q_stricmp(sk, "crackhor") == 0) + Cvar_Set ("gender", "female"); + else + Cvar_Set ("gender", "none"); + gender->modified = false; + } +} + +/* +============== +CL_Userinfo_f +============== +*/ +void CL_Userinfo_f (void) +{ + Com_Printf ("User info settings:\n"); + Info_Print (Cvar_Userinfo()); +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem so it can pick up +new parameters and flush all sounds +================= +*/ +void CL_Snd_Restart_f (void) +{ + S_Shutdown (); + S_Init (); + CL_RegisterSounds (); +} + +int precache_check; // for autodownload of precache items +int precache_spawncount; +int precache_tex; +int precache_model_skin; + +byte *precache_model; // used for skin checking in alias models + +#define PLAYER_MULT 5 + +// ENV_CNT is map load, ENV_CNT+1 is first env map +#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) +#define TEXTURE_CNT (ENV_CNT+13) + +static const char *env_suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"}; + +void CL_RequestNextDownload (void) +{ + unsigned map_checksum; // for detecting cheater maps + char fn[MAX_OSPATH]; + dmdl_t *pheader; + + if (cls.state != ca_connected) + return; + + if (!allow_download->value && precache_check < ENV_CNT) + precache_check = ENV_CNT; + +//ZOID + if (precache_check == CS_MODELS) { // confirm map + precache_check = CS_MODELS+2; // 0 isn't used + if (allow_download_maps->value) + if (!CL_CheckOrDownloadFile(cl.configstrings[CS_MODELS+1])) + return; // started a download + } + if (precache_check >= CS_MODELS && precache_check < CS_MODELS+MAX_MODELS) { + if (allow_download_models->value) { + while (precache_check < CS_MODELS+MAX_MODELS && + cl.configstrings[precache_check][0]) { + if (cl.configstrings[precache_check][0] == '*' || + cl.configstrings[precache_check][0] == '#') { + precache_check++; + continue; + } + if (precache_model_skin == 0) { + if (!CL_CheckOrDownloadFile(cl.configstrings[precache_check])) { + precache_model_skin = 1; + return; // started a download + } + precache_model_skin = 1; + } + + // checking for skins in the model + if (!precache_model) { + + FS_LoadFile (cl.configstrings[precache_check], (void **)&precache_model); + if (!precache_model) { + precache_model_skin = 0; + precache_check++; + continue; // couldn't load it + } + if (LittleLong(*(unsigned *)precache_model) != IDALIASHEADER) { + // not an alias model + FS_FreeFile(precache_model); + precache_model = 0; + precache_model_skin = 0; + precache_check++; + continue; + } + pheader = (dmdl_t *)precache_model; + if (LittleLong (pheader->version) != ALIAS_VERSION) { + precache_check++; + precache_model_skin = 0; + continue; // couldn't load it + } + } + + pheader = (dmdl_t *)precache_model; + + while (precache_model_skin - 1 < LittleLong(pheader->num_skins)) { + if (!CL_CheckOrDownloadFile((char *)precache_model + + LittleLong(pheader->ofs_skins) + + (precache_model_skin - 1)*MAX_SKINNAME)) { + precache_model_skin++; + return; // started a download + } + precache_model_skin++; + } + if (precache_model) { + FS_FreeFile(precache_model); + precache_model = 0; + } + precache_model_skin = 0; + precache_check++; + } + } + precache_check = CS_SOUNDS; + } + if (precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS+MAX_SOUNDS) { + if (allow_download_sounds->value) { + if (precache_check == CS_SOUNDS) + precache_check++; // zero is blank + while (precache_check < CS_SOUNDS+MAX_SOUNDS && + cl.configstrings[precache_check][0]) { + if (cl.configstrings[precache_check][0] == '*') { + precache_check++; + continue; + } + Com_sprintf(fn, sizeof(fn), "sound/%s", cl.configstrings[precache_check++]); + if (!CL_CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = CS_IMAGES; + } + if (precache_check >= CS_IMAGES && precache_check < CS_IMAGES+MAX_IMAGES) { + if (precache_check == CS_IMAGES) + precache_check++; // zero is blank + while (precache_check < CS_IMAGES+MAX_IMAGES && + cl.configstrings[precache_check][0]) { + Com_sprintf(fn, sizeof(fn), "pics/%s.pcx", cl.configstrings[precache_check++]); + if (!CL_CheckOrDownloadFile(fn)) + return; // started a download + } + precache_check = CS_PLAYERSKINS; + } + // skins are special, since a player has three things to download: + // model, weapon model and skin + // so precache_check is now *3 + if (precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) { + if (allow_download_players->value) { + while (precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT) { + int i, n; + char model[MAX_QPATH], skin[MAX_QPATH], *p; + + i = (precache_check - CS_PLAYERSKINS)/PLAYER_MULT; + n = (precache_check - CS_PLAYERSKINS)%PLAYER_MULT; + + if (!cl.configstrings[CS_PLAYERSKINS+i][0]) { + precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT; + continue; + } + + if ((p = strchr(cl.configstrings[CS_PLAYERSKINS+i], '\\')) != NULL) + p++; + else + p = cl.configstrings[CS_PLAYERSKINS+i]; + strcpy(model, p); + p = strchr(model, '/'); + if (!p) + p = strchr(model, '\\'); + if (p) { + *p++ = 0; + strcpy(skin, p); + } else + *skin = 0; + + switch (n) { + case 0: // model + Com_sprintf(fn, sizeof(fn), "players/%s/tris.md2", model); + if (!CL_CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 1: // weapon model + Com_sprintf(fn, sizeof(fn), "players/%s/weapon.md2", model); + if (!CL_CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 2: // weapon skin + Com_sprintf(fn, sizeof(fn), "players/%s/weapon.pcx", model); + if (!CL_CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 3: // skin + Com_sprintf(fn, sizeof(fn), "players/%s/%s.pcx", model, skin); + if (!CL_CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4; + return; // started a download + } + n++; + /*FALL THROUGH*/ + + case 4: // skin_i + Com_sprintf(fn, sizeof(fn), "players/%s/%s_i.pcx", model, skin); + if (!CL_CheckOrDownloadFile(fn)) { + precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5; + return; // started a download + } + // move on to next model + precache_check = CS_PLAYERSKINS + (i + 1) * PLAYER_MULT; + } + } + } + // precache phase completed + precache_check = ENV_CNT; + } + + if (precache_check == ENV_CNT) { + precache_check = ENV_CNT + 1; + + CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum); + + if (map_checksum != atoi(cl.configstrings[CS_MAPCHECKSUM])) { + Com_Error (ERR_DROP, "Local map version differs from server: %i != '%s'\n", + map_checksum, cl.configstrings[CS_MAPCHECKSUM]); + return; + } + } + + if (precache_check > ENV_CNT && precache_check < TEXTURE_CNT) { + if (allow_download->value && allow_download_maps->value) { + while (precache_check < TEXTURE_CNT) { + int n = precache_check++ - ENV_CNT - 1; + + if (n & 1) + Com_sprintf(fn, sizeof(fn), "env/%s%s.pcx", + cl.configstrings[CS_SKY], env_suf[n/2]); + else + Com_sprintf(fn, sizeof(fn), "env/%s%s.tga", + cl.configstrings[CS_SKY], env_suf[n/2]); + if (!CL_CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = TEXTURE_CNT; + } + + if (precache_check == TEXTURE_CNT) { + precache_check = TEXTURE_CNT+1; + precache_tex = 0; + } + + // confirm existance of textures, download any that don't exist + if (precache_check == TEXTURE_CNT+1) { + // from qcommon/cmodel.c + extern int numtexinfo; + extern mapsurface_t map_surfaces[]; + + if (allow_download->value && allow_download_maps->value) { + while (precache_tex < numtexinfo) { + char fn[MAX_OSPATH]; + + sprintf(fn, "textures/%s.wal", map_surfaces[precache_tex++].rname); + if (!CL_CheckOrDownloadFile(fn)) + return; // started a download + } + } + precache_check = TEXTURE_CNT+999; + } + +//ZOID + CL_RegisterSounds (); + CL_PrepRefresh (); + + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + MSG_WriteString (&cls.netchan.message, va("begin %i\n", precache_spawncount) ); +} + +/* +================= +CL_Precache_f + +The server will send this command right +before allowing the client into the server +================= +*/ +void CL_Precache_f (void) +{ + //Yet another hack to let old demos work + //the old precache sequence + if (Cmd_Argc() < 2) { + unsigned map_checksum; // for detecting cheater maps + + CM_LoadMap (cl.configstrings[CS_MODELS+1], true, &map_checksum); + CL_RegisterSounds (); + CL_PrepRefresh (); + return; + } + + precache_check = CS_MODELS; + precache_spawncount = atoi(Cmd_Argv(1)); + precache_model = 0; + precache_model_skin = 0; + + CL_RequestNextDownload(); +} + + +/* +================= +CL_InitLocal +================= +*/ +void CL_InitLocal (void) +{ + cls.state = ca_disconnected; + cls.realtime = Sys_Milliseconds (); + + CL_InitInput (); + + adr0 = Cvar_Get( "adr0", "", CVAR_ARCHIVE ); + adr1 = Cvar_Get( "adr1", "", CVAR_ARCHIVE ); + adr2 = Cvar_Get( "adr2", "", CVAR_ARCHIVE ); + adr3 = Cvar_Get( "adr3", "", CVAR_ARCHIVE ); + adr4 = Cvar_Get( "adr4", "", CVAR_ARCHIVE ); + adr5 = Cvar_Get( "adr5", "", CVAR_ARCHIVE ); + adr6 = Cvar_Get( "adr6", "", CVAR_ARCHIVE ); + adr7 = Cvar_Get( "adr7", "", CVAR_ARCHIVE ); + adr8 = Cvar_Get( "adr8", "", CVAR_ARCHIVE ); + +// +// register our variables +// + cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE ); + cl_stereo = Cvar_Get( "cl_stereo", "0", 0 ); + + cl_add_blend = Cvar_Get ("cl_blend", "1", 0); + cl_add_lights = Cvar_Get ("cl_lights", "1", 0); + cl_add_particles = Cvar_Get ("cl_particles", "1", 0); + cl_add_entities = Cvar_Get ("cl_entities", "1", 0); + cl_gun = Cvar_Get ("cl_gun", "1", 0); + cl_footsteps = Cvar_Get ("cl_footsteps", "1", 0); + cl_noskins = Cvar_Get ("cl_noskins", "0", 0); + cl_autoskins = Cvar_Get ("cl_autoskins", "0", 0); + cl_predict = Cvar_Get ("cl_predict", "1", 0); +// cl_minfps = Cvar_Get ("cl_minfps", "5", 0); + cl_maxfps = Cvar_Get ("cl_maxfps", "90", 0); + + cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0); + cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0); + cl_sidespeed = Cvar_Get ("cl_sidespeed", "200", 0); + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", 0); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "150", 0); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_run = Cvar_Get ("cl_run", "0", CVAR_ARCHIVE); + freelook = Cvar_Get( "freelook", "0", CVAR_ARCHIVE ); + lookspring = Cvar_Get ("lookspring", "0", CVAR_ARCHIVE); + lookstrafe = Cvar_Get ("lookstrafe", "0", CVAR_ARCHIVE); + sensitivity = Cvar_Get ("sensitivity", "3", CVAR_ARCHIVE); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", 0); + m_forward = Cvar_Get ("m_forward", "1", 0); + m_side = Cvar_Get ("m_side", "1", 0); + + cl_shownet = Cvar_Get ("cl_shownet", "0", 0); + cl_showmiss = Cvar_Get ("cl_showmiss", "0", 0); + cl_showclamp = Cvar_Get ("showclamp", "0", 0); + cl_timeout = Cvar_Get ("cl_timeout", "120", 0); + cl_paused = Cvar_Get ("paused", "0", 0); + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + + rcon_client_password = Cvar_Get ("rcon_password", "", 0); + rcon_address = Cvar_Get ("rcon_address", "", 0); + + cl_lightlevel = Cvar_Get ("r_lightlevel", "0", 0); + + // + // userinfo + // + info_password = Cvar_Get ("password", "", CVAR_USERINFO); + info_spectator = Cvar_Get ("spectator", "0", CVAR_USERINFO); + name = Cvar_Get ("name", "unnamed", CVAR_USERINFO | CVAR_ARCHIVE); + skin = Cvar_Get ("skin", "male/grunt", CVAR_USERINFO | CVAR_ARCHIVE); + rate = Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE); // FIXME + msg = Cvar_Get ("msg", "1", CVAR_USERINFO | CVAR_ARCHIVE); + hand = Cvar_Get ("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE); + fov = Cvar_Get ("fov", "90", CVAR_USERINFO | CVAR_ARCHIVE); + gender = Cvar_Get ("gender", "male", CVAR_USERINFO | CVAR_ARCHIVE); + gender_auto = Cvar_Get ("gender_auto", "1", CVAR_ARCHIVE); + gender->modified = false; // clear this so we know when user sets it manually + + cl_vwep = Cvar_Get ("cl_vwep", "1", CVAR_ARCHIVE); + + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("pause", CL_Pause_f); + Cmd_AddCommand ("pingservers", CL_PingServers_f); + Cmd_AddCommand ("skins", CL_Skins_f); + + Cmd_AddCommand ("userinfo", CL_Userinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + + Cmd_AddCommand ("changing", CL_Changing_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("stop", CL_Stop_f); + + Cmd_AddCommand ("quit", CL_Quit_f); + + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + + Cmd_AddCommand ("rcon", CL_Rcon_f); + +// Cmd_AddCommand ("packet", CL_Packet_f); // this is dangerous to leave in + + Cmd_AddCommand ("setenv", CL_Setenv_f ); + + Cmd_AddCommand ("precache", CL_Precache_f); + + Cmd_AddCommand ("download", CL_Download_f); + + // + // forward to server commands + // + // the only thing this does is allow command completion + // to work -- all unknown commands are automatically + // forwarded to the server + Cmd_AddCommand ("wave", NULL); + Cmd_AddCommand ("inven", NULL); + Cmd_AddCommand ("kill", NULL); + Cmd_AddCommand ("use", NULL); + Cmd_AddCommand ("drop", NULL); + Cmd_AddCommand ("say", NULL); + Cmd_AddCommand ("say_team", NULL); + Cmd_AddCommand ("info", NULL); + Cmd_AddCommand ("prog", NULL); + Cmd_AddCommand ("give", NULL); + Cmd_AddCommand ("god", NULL); + Cmd_AddCommand ("notarget", NULL); + Cmd_AddCommand ("noclip", NULL); + Cmd_AddCommand ("invuse", NULL); + Cmd_AddCommand ("invprev", NULL); + Cmd_AddCommand ("invnext", NULL); + Cmd_AddCommand ("invdrop", NULL); + Cmd_AddCommand ("weapnext", NULL); + Cmd_AddCommand ("weapprev", NULL); +} + + + +/* +=============== +CL_WriteConfiguration + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +void CL_WriteConfiguration (void) +{ + FILE *f; + char path[MAX_QPATH]; + + if (cls.state == ca_uninitialized) + return; + + Com_sprintf (path, sizeof(path),"%s/config.cfg",FS_Gamedir()); + f = fopen (path, "w"); + if (!f) + { + Com_Printf ("Couldn't write config.cfg.\n"); + return; + } + + fprintf (f, "// generated by quake, do not modify\n"); + Key_WriteBindings (f); + fclose (f); + + Cvar_WriteVariables (path); +} + + +/* +================== +CL_FixCvarCheats + +================== +*/ + +typedef struct +{ + char *name; + char *value; + cvar_t *var; +} cheatvar_t; + +cheatvar_t cheatvars[] = { + {"timescale", "1"}, + {"timedemo", "0"}, + {"r_drawworld", "1"}, + {"cl_testlights", "0"}, + {"r_fullbright", "0"}, + {"r_drawflat", "0"}, + {"paused", "0"}, + {"fixedtime", "0"}, + {"sw_draworder", "0"}, + {"gl_lightmap", "0"}, + {"gl_saturatelighting", "0"}, + {NULL, NULL} +}; + +int numcheatvars; + +void CL_FixCvarCheats (void) +{ + int i; + cheatvar_t *var; + + if ( !strcmp(cl.configstrings[CS_MAXCLIENTS], "1") + || !cl.configstrings[CS_MAXCLIENTS][0] ) + return; // single player can cheat + + // find all the cvars if we haven't done it yet + if (!numcheatvars) + { + while (cheatvars[numcheatvars].name) + { + cheatvars[numcheatvars].var = Cvar_Get (cheatvars[numcheatvars].name, + cheatvars[numcheatvars].value, 0); + numcheatvars++; + } + } + + // make sure they are all set to the proper values + for (i=0, var = cheatvars ; ivar->string, var->value) ) + { + Cvar_Set (var->name, var->value); + } + } +} + +//============================================================================ + +/* +================== +CL_SendCommand + +================== +*/ +void CL_SendCommand (void) +{ + // get new key events + Sys_SendKeyEvents (); + + // allow mice or other external controllers to add commands + IN_Commands (); + + // process console commands + Cbuf_Execute (); + + // fix any cheating cvars + CL_FixCvarCheats (); + + // send intentions now + CL_SendCmd (); + + // resend a connection request if necessary + CL_CheckForResend (); +} + + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame (int msec) +{ + static int extratime; + static int lasttimecalled; + + if (dedicated->value) + return; + + extratime += msec; + + if (!cl_timedemo->value) + { + if (cls.state == ca_connected && extratime < 100) + return; // don't flood packets out while connecting + if (extratime < 1000/cl_maxfps->value) + return; // framerate is too high + } + + // let the mouse activate or deactivate + IN_Frame (); + + // decide the simulation time + cls.frametime = extratime/1000.0; + cl.time += extratime; + cls.realtime = curtime; + + extratime = 0; +#if 0 + if (cls.frametime > (1.0 / cl_minfps->value)) + cls.frametime = (1.0 / cl_minfps->value); +#else + if (cls.frametime > (1.0 / 5)) + cls.frametime = (1.0 / 5); +#endif + + // if in the debugger last frame, don't timeout + if (msec > 5000) + cls.netchan.last_received = Sys_Milliseconds (); + + // fetch results from server + CL_ReadPackets (); + + // send a new command message to the server + CL_SendCommand (); + + // predict all unacknowledged movements + CL_PredictMovement (); + + // allow rendering DLL change + VID_CheckChanges (); + if (!cl.refresh_prepped && cls.state == ca_active) + CL_PrepRefresh (); + + // update the screen + if (host_speeds->value) + time_before_ref = Sys_Milliseconds (); + SCR_UpdateScreen (); + if (host_speeds->value) + time_after_ref = Sys_Milliseconds (); + + // update audio + S_Update (cl.refdef.vieworg, cl.v_forward, cl.v_right, cl.v_up); + + CDAudio_Update(); + + // advance local effects for next frame + CL_RunDLights (); + CL_RunLightStyles (); + SCR_RunCinematic (); + SCR_RunConsole (); + + cls.framecount++; + + if ( log_stats->value ) + { + if ( cls.state == ca_active ) + { + if ( !lasttimecalled ) + { + lasttimecalled = Sys_Milliseconds(); + if ( log_stats_file ) + fprintf( log_stats_file, "0\n" ); + } + else + { + int now = Sys_Milliseconds(); + + if ( log_stats_file ) + fprintf( log_stats_file, "%d\n", now - lasttimecalled ); + lasttimecalled = now; + } + } + } +} + + +//============================================================================ + +/* +==================== +CL_Init +==================== +*/ +void CL_Init (void) +{ + if (dedicated->value) + return; // nothing running on the client + + // all archived variables will now be loaded + + Con_Init (); +#if defined __linux__ || defined __sgi + S_Init (); + VID_Init (); +#else + VID_Init (); + S_Init (); // sound must be initialized after window is created +#endif + + V_Init (); + + net_message.data = net_message_buffer; + net_message.maxsize = sizeof(net_message_buffer); + + M_Init (); + + SCR_Init (); + cls.disable_screen = true; // don't draw yet + + CDAudio_Init (); + CL_InitLocal (); + IN_Init (); + +// Cbuf_AddText ("exec autoexec.cfg\n"); + FS_ExecAutoexec (); + Cbuf_Execute (); + +} + + +/* +=============== +CL_Shutdown + +FIXME: this is a callback from Sys_Quit and Com_Error. It would be better +to run quit through here before the final handoff to the sys code. +=============== +*/ +void CL_Shutdown(void) +{ + static qboolean isdown = false; + + if (isdown) + { + printf ("recursive shutdown\n"); + return; + } + isdown = true; + + CL_WriteConfiguration (); + + CDAudio_Shutdown (); + S_Shutdown(); + IN_Shutdown (); + VID_Shutdown(); +} + + diff --git a/client/cl_newfx.c b/client/cl_newfx.c new file mode 100644 index 000000000..5ad92b6d7 --- /dev/null +++ b/client/cl_newfx.c @@ -0,0 +1,1323 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_newfx.c -- MORE entity effects parsing and management + +#include "client.h" + +extern cparticle_t *active_particles, *free_particles; +extern cparticle_t particles[MAX_PARTICLES]; +extern int cl_numparticles; +extern cvar_t *vid_ref; + +extern void MakeNormalVectors (vec3_t forward, vec3_t right, vec3_t up); + + +/* +====== +vectoangles2 - this is duplicated in the game DLL, but I need it here. +====== +*/ +void vectoangles2 (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + // PMM - fixed to correct for pitch of 0 + if (value1[0]) + yaw = (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = 270; + + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +//============= +//============= +void CL_Flashlight (int ent, vec3_t pos) +{ + cdlight_t *dl; + + dl = CL_AllocDlight (ent); + VectorCopy (pos, dl->origin); + dl->radius = 400; + dl->minlight = 250; + dl->die = cl.time + 100; + dl->color[0] = 1; + dl->color[1] = 1; + dl->color[2] = 1; +} + +/* +====== +CL_ColorFlash - flash of light +====== +*/ +void CL_ColorFlash (vec3_t pos, int ent, int intensity, float r, float g, float b) +{ + cdlight_t *dl; + + if((vidref_val == VIDREF_SOFT) && ((r < 0) || (g<0) || (b<0))) + { + intensity = -intensity; + r = -r; + g = -g; + b = -b; + } + + dl = CL_AllocDlight (ent); + VectorCopy (pos, dl->origin); + dl->radius = intensity; + dl->minlight = 250; + dl->die = cl.time + 100; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; +} + + +/* +====== +CL_DebugTrail +====== +*/ +void CL_DebugTrail (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; +// int j; + cparticle_t *p; + float dec; + vec3_t right, up; +// int i; +// float d, c, s; +// vec3_t dir; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + MakeNormalVectors (vec, right, up); + +// VectorScale(vec, RT2_SKIP, vec); + +// dec = 1.0; +// dec = 0.75; + dec = 3; + VectorScale (vec, dec, vec); + VectorCopy (start, move); + + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); + VectorClear (p->vel); + p->alpha = 1.0; + p->alphavel = -0.1; +// p->alphavel = 0; + p->color = 0x74 + (rand()&7); + VectorCopy (move, p->org); +/* + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*2; + p->vel[j] = crand()*3; + p->accel[j] = 0; + } +*/ + VectorAdd (move, vec, move); + } + +} + +/* +=============== +CL_SmokeTrail +=============== +*/ +void CL_SmokeTrail (vec3_t start, vec3_t end, int colorStart, int colorRun, int spacing) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + VectorScale (vec, spacing, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= spacing; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.5); + p->color = colorStart + (rand() % colorRun); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*3; + p->accel[j] = 0; + } + p->vel[2] = 20 + crand()*5; + + VectorAdd (move, vec, move); + } +} + +void CL_ForceWall (vec3_t start, vec3_t end, int color) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + VectorScale (vec, 4, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= 4; + + if (!free_particles) + return; + + if (frand() > 0.3) + { + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (3.0+frand()*0.5); + p->color = color; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*3; + p->accel[j] = 0; + } + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -40 - (crand()*10); + } + + VectorAdd (move, vec, move); + } +} + +void CL_FlameEffects (centity_t *ent, vec3_t origin) +{ + int n, count; + int j; + cparticle_t *p; + + count = rand() & 0xF; + + for(n=0;nnext; + p->next = active_particles; + active_particles = p; + + VectorClear (p->accel); + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.2); + p->color = 226 + (rand() % 4); + for (j=0 ; j<3 ; j++) + { + p->org[j] = origin[j] + crand()*5; + p->vel[j] = crand()*5; + } + p->vel[2] = crand() * -10; + p->accel[2] = -PARTICLE_GRAVITY; + } + + count = rand() & 0x7; + + for(n=0;nnext; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.5); + p->color = 0 + (rand() % 4); + for (j=0 ; j<3 ; j++) + { + p->org[j] = origin[j] + crand()*3; + } + p->vel[2] = 20 + crand()*5; + } + +} + + +/* +=============== +CL_GenericParticleEffect +=============== +*/ +void CL_GenericParticleEffect (vec3_t org, vec3_t dir, int color, int count, int numcolors, int dirspread, float alphavel) +{ + int i, j; + cparticle_t *p; + float d; + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + if (numcolors > 1) + p->color = color + (rand() & numcolors); + else + p->color = color; + + d = rand() & dirspread; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = crand()*20; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; +// VectorCopy (accel, p->accel); + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*alphavel); +// p->alphavel = alphavel; + } +} + +/* +=============== +CL_BubbleTrail2 (lets you control the # of bubbles by setting the distance between the spawns) + +=============== +*/ +void CL_BubbleTrail2 (vec3_t start, vec3_t end, int dist) +{ + vec3_t move; + vec3_t vec; + float len; + int i, j; + cparticle_t *p; + float dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = dist; + VectorScale (vec, dec, vec); + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + VectorClear (p->accel); + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (1+frand()*0.1); + p->color = 4 + (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*2; + p->vel[j] = crand()*10; + } + p->org[2] -= 4; +// p->vel[2] += 6; + p->vel[2] += 20; + + VectorAdd (move, vec, move); + } +} + +//#define CORKSCREW 1 +//#define DOUBLE_SCREW 1 +#define RINGS 1 +//#define SPRAY 1 + +#ifdef CORKSCREW +void CL_Heatbeam (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j,k; + cparticle_t *p; + vec3_t right, up; + int i; + float d, c, s; + vec3_t dir; + float ltime; + float step = 5.0; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + +// MakeNormalVectors (vec, right, up); + VectorCopy (cl.v_right, right); + VectorCopy (cl.v_up, up); + VectorMA (move, -1, right, move); + VectorMA (move, -1, up, move); + + VectorScale (vec, step, vec); + ltime = (float) cl.time/1000.0; + +// for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); + + p->alpha = 0.5; + // p->alphavel = -1.0 / (1+frand()*0.2); + // only last one frame! + p->alphavel = INSTANT_PARTICLE; + // p->color = 0x74 + (rand()&7); +// p->color = 223 - (rand()&7); + p->color = 223; +// p->color = 240; + + // trim it so it looks like it's starting at the origin + if (i < 10) + { + VectorScale (right, c*(i/10.0)*k, dir); + VectorMA (dir, s*(i/10.0)*k, up, dir); + } + else + { + VectorScale (right, c*k, dir); + VectorMA (dir, s*k, up, dir); + } + + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + dir[j]*3; + // p->vel[j] = dir[j]*6; + p->vel[j] = 0; + } +#ifdef DOUBLE_SCREW + } +#endif + VectorAdd (move, vec, move); + } +} +#endif +#ifdef RINGS +//void CL_Heatbeam (vec3_t start, vec3_t end) +void CL_Heatbeam (vec3_t start, vec3_t forward) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + vec3_t right, up; + int i; + float c, s; + vec3_t dir; + float ltime; + float step = 32.0, rstep; + float start_pt; + float rot; + float variance; + vec3_t end; + + VectorMA (start, 4096, forward, end); + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // FIXME - pmm - these might end up using old values? +// MakeNormalVectors (vec, right, up); + VectorCopy (cl.v_right, right); + VectorCopy (cl.v_up, up); + if (vidref_val == VIDREF_GL) + { // GL mode + VectorMA (move, -0.5, right, move); + VectorMA (move, -0.5, up, move); + } + // otherwise assume SOFT + + ltime = (float) cl.time/1000.0; + start_pt = fmod(ltime*96.0,step); + VectorMA (move, start_pt, vec, move); + + VectorScale (vec, step, vec); + +// Com_Printf ("%f\n", ltime); + rstep = M_PI/10.0; + for (i=start_pt ; istep*5) // don't bother after the 5th ring + break; + + for (rot = 0; rot < M_PI*2; rot += rstep) + { + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); +// rot+= fmod(ltime, 12.0)*M_PI; +// c = cos(rot)/2.0; +// s = sin(rot)/2.0; +// variance = 0.4 + ((float)rand()/(float)RAND_MAX) *0.2; + variance = 0.5; + c = cos(rot)*variance; + s = sin(rot)*variance; + + // trim it so it looks like it's starting at the origin + if (i < 10) + { + VectorScale (right, c*(i/10.0), dir); + VectorMA (dir, s*(i/10.0), up, dir); + } + else + { + VectorScale (right, c, dir); + VectorMA (dir, s, up, dir); + } + + p->alpha = 0.5; + // p->alphavel = -1.0 / (1+frand()*0.2); + p->alphavel = -1000.0; + // p->color = 0x74 + (rand()&7); + p->color = 223 - (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + dir[j]*3; + // p->vel[j] = dir[j]*6; + p->vel[j] = 0; + } + } + VectorAdd (move, vec, move); + } +} +#endif +#ifdef SPRAY +void CL_Heatbeam (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + vec3_t forward, right, up; + int i; + float d, c, s; + vec3_t dir; + float ltime; + float step = 32.0, rstep; + float start_pt; + float rot; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + +// MakeNormalVectors (vec, right, up); + VectorCopy (cl.v_forward, forward); + VectorCopy (cl.v_right, right); + VectorCopy (cl.v_up, up); + VectorMA (move, -0.5, right, move); + VectorMA (move, -0.5, up, move); + + for (i=0; i<8; i++) + { + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); + + d = crand()*M_PI; + c = cos(d)*30; + s = sin(d)*30; + + p->alpha = 1.0; + p->alphavel = -5.0 / (1+frand()); + p->color = 223 - (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j]; + } + VectorScale (vec, 450, p->vel); + VectorMA (p->vel, c, right, p->vel); + VectorMA (p->vel, s, up, p->vel); + } +/* + + ltime = (float) cl.time/1000.0; + start_pt = fmod(ltime*16.0,step); + VectorMA (move, start_pt, vec, move); + + VectorScale (vec, step, vec); + +// Com_Printf ("%f\n", ltime); + rstep = M_PI/12.0; + for (i=start_pt ; istep*5) // don't bother after the 5th ring + break; + + for (rot = 0; rot < M_PI*2; rot += rstep) + { + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + VectorClear (p->accel); +// rot+= fmod(ltime, 12.0)*M_PI; +// c = cos(rot)/2.0; +// s = sin(rot)/2.0; + c = cos(rot)/1.5; + s = sin(rot)/1.5; + + // trim it so it looks like it's starting at the origin + if (i < 10) + { + VectorScale (right, c*(i/10.0), dir); + VectorMA (dir, s*(i/10.0), up, dir); + } + else + { + VectorScale (right, c, dir); + VectorMA (dir, s, up, dir); + } + + p->alpha = 0.5; + // p->alphavel = -1.0 / (1+frand()*0.2); + p->alphavel = -1000.0; + // p->color = 0x74 + (rand()&7); + p->color = 223 - (rand()&7); + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + dir[j]*3; + // p->vel[j] = dir[j]*6; + p->vel[j] = 0; + } + } + VectorAdd (move, vec, move); + } +*/ +} +#endif + +/* +=============== +CL_ParticleSteamEffect + +Puffs with velocity along direction, with some randomness thrown in +=============== +*/ +void CL_ParticleSteamEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude) +{ + int i, j; + cparticle_t *p; + float d; + vec3_t r, u; + +// vectoangles2 (dir, angle_dir); +// AngleVectors (angle_dir, f, r, u); + + MakeNormalVectors (dir, r, u); + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color + (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + magnitude*0.1*crand(); +// p->vel[j] = dir[j]*magnitude; + } + VectorScale (dir, magnitude, p->vel); + d = crand()*magnitude/3; + VectorMA (p->vel, d, r, p->vel); + d = crand()*magnitude/3; + VectorMA (p->vel, d, u, p->vel); + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY/2; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + +void CL_ParticleSteamEffect2 (cl_sustain_t *self) +//vec3_t org, vec3_t dir, int color, int count, int magnitude) +{ + int i, j; + cparticle_t *p; + float d; + vec3_t r, u; + vec3_t dir; + +// vectoangles2 (dir, angle_dir); +// AngleVectors (angle_dir, f, r, u); + + VectorCopy (self->dir, dir); + MakeNormalVectors (dir, r, u); + + for (i=0 ; icount ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = self->color + (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = self->org[j] + self->magnitude*0.1*crand(); +// p->vel[j] = dir[j]*magnitude; + } + VectorScale (dir, self->magnitude, p->vel); + d = crand()*self->magnitude/3; + VectorMA (p->vel, d, r, p->vel); + d = crand()*self->magnitude/3; + VectorMA (p->vel, d, u, p->vel); + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY/2; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } + self->nextthink += self->thinkinterval; +} + +/* +=============== +CL_TrackerTrail +=============== +*/ +void CL_TrackerTrail (vec3_t start, vec3_t end, int particleColor) +{ + vec3_t move; + vec3_t vec; + vec3_t forward,right,up,angle_dir; + float len; + int j; + cparticle_t *p; + int dec; + float dist; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + VectorCopy(vec, forward); + vectoangles2 (forward, angle_dir); + AngleVectors (angle_dir, forward, right, up); + + dec = 3; + VectorScale (vec, 3, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -2.0; + p->color = particleColor; + dist = DotProduct(move, forward); + VectorMA(move, 8 * cos(dist), up, p->org); + for (j=0 ; j<3 ; j++) + { +// p->org[j] = move[j] + crand(); + p->vel[j] = 0; + p->accel[j] = 0; + } + p->vel[2] = 5; + + VectorAdd (move, vec, move); + } +} + +void CL_Tracker_Shell(vec3_t origin) +{ + vec3_t dir; + int i; + cparticle_t *p; + + for(i=0;i<300;i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = INSTANT_PARTICLE; + p->color = 0; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + + VectorMA(origin, 40, dir, p->org); + } +} + +void CL_MonsterPlasma_Shell(vec3_t origin) +{ + vec3_t dir; + int i; + cparticle_t *p; + + for(i=0;i<40;i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = INSTANT_PARTICLE; + p->color = 0xe0; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + + VectorMA(origin, 10, dir, p->org); +// VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org); + } +} + +void CL_Widowbeamout (cl_sustain_t *self) +{ + vec3_t dir; + int i; + cparticle_t *p; + static int colortable[4] = {2*8,13*8,21*8,18*8}; + float ratio; + + ratio = 1.0 - (((float)self->endtime - (float)cl.time)/2100.0); + + for(i=0;i<300;i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = INSTANT_PARTICLE; + p->color = colortable[rand()&3]; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + + VectorMA(self->org, (45.0 * ratio), dir, p->org); +// VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org); + } +} + +void CL_Nukeblast (cl_sustain_t *self) +{ + vec3_t dir; + int i; + cparticle_t *p; + static int colortable[4] = {110, 112, 114, 116}; + float ratio; + + ratio = 1.0 - (((float)self->endtime - (float)cl.time)/1000.0); + + for(i=0;i<700;i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = INSTANT_PARTICLE; + p->color = colortable[rand()&3]; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + + VectorMA(self->org, (200.0 * ratio), dir, p->org); +// VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))), dir, p->org); + } +} + +void CL_WidowSplash (vec3_t org) +{ + static int colortable[4] = {2*8,13*8,21*8,18*8}; + int i; + cparticle_t *p; + vec3_t dir; + + for (i=0 ; i<256 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = colortable[rand()&3]; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + VectorMA(org, 45.0, dir, p->org); + VectorMA(vec3_origin, 40.0, dir, p->vel); + + p->accel[0] = p->accel[1] = 0; + p->alpha = 1.0; + + p->alphavel = -0.8 / (0.5 + frand()*0.3); + } + +} + +void CL_Tracker_Explode(vec3_t origin) +{ + vec3_t dir, backdir; + int i; + cparticle_t *p; + + for(i=0;i<300;i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0; + p->color = 0; + + dir[0] = crand(); + dir[1] = crand(); + dir[2] = crand(); + VectorNormalize(dir); + VectorScale(dir, -1, backdir); + + VectorMA(origin, 64, dir, p->org); + VectorScale(backdir, 64, p->vel); + } + +} + +/* +=============== +CL_TagTrail + +=============== +*/ +void CL_TagTrail (vec3_t start, vec3_t end, float color) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + while (len >= 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.8+frand()*0.2); + p->color = color; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand()*16; + p->vel[j] = crand()*5; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} + +/* +=============== +CL_ColorExplosionParticles +=============== +*/ +void CL_ColorExplosionParticles (vec3_t org, int color, int run) +{ + int i, j; + cparticle_t *p; + + for (i=0 ; i<128 ; i++) + { + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color + (rand() % run); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()%32)-16); + p->vel[j] = (rand()%256)-128; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -0.4 / (0.6 + frand()*0.2); + } +} + +/* +=============== +CL_ParticleSmokeEffect - like the steam effect, but unaffected by gravity +=============== +*/ +void CL_ParticleSmokeEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude) +{ + int i, j; + cparticle_t *p; + float d; + vec3_t r, u; + + MakeNormalVectors (dir, r, u); + + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color + (rand()&7); + + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + magnitude*0.1*crand(); +// p->vel[j] = dir[j]*magnitude; + } + VectorScale (dir, magnitude, p->vel); + d = crand()*magnitude/3; + VectorMA (p->vel, d, r, p->vel); + d = crand()*magnitude/3; + VectorMA (p->vel, d, u, p->vel); + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + +/* +=============== +CL_BlasterParticles2 + +Wall impact puffs (Green) +=============== +*/ +void CL_BlasterParticles2 (vec3_t org, vec3_t dir, unsigned int color) +{ + int i, j; + cparticle_t *p; + float d; + int count; + + count = 40; + for (i=0 ; inext; + p->next = active_particles; + active_particles = p; + + p->time = cl.time; + p->color = color + (rand()&7); + + d = rand()&15; + for (j=0 ; j<3 ; j++) + { + p->org[j] = org[j] + ((rand()&7)-4) + d*dir[j]; + p->vel[j] = dir[j] * 30 + crand()*40; + } + + p->accel[0] = p->accel[1] = 0; + p->accel[2] = -PARTICLE_GRAVITY; + p->alpha = 1.0; + + p->alphavel = -1.0 / (0.5 + frand()*0.3); + } +} + +/* +=============== +CL_BlasterTrail2 + +Green! +=============== +*/ +void CL_BlasterTrail2 (vec3_t start, vec3_t end) +{ + vec3_t move; + vec3_t vec; + float len; + int j; + cparticle_t *p; + int dec; + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + dec = 5; + VectorScale (vec, 5, vec); + + // FIXME: this is a really silly way to have a loop + while (len > 0) + { + len -= dec; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + VectorClear (p->accel); + + p->time = cl.time; + + p->alpha = 1.0; + p->alphavel = -1.0 / (0.3+frand()*0.2); + p->color = 0xd0; + for (j=0 ; j<3 ; j++) + { + p->org[j] = move[j] + crand(); + p->vel[j] = crand()*5; + p->accel[j] = 0; + } + + VectorAdd (move, vec, move); + } +} diff --git a/client/cl_parse.c b/client/cl_parse.c new file mode 100644 index 000000000..5468c2e78 --- /dev/null +++ b/client/cl_parse.c @@ -0,0 +1,806 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +char *svc_strings[256] = +{ + "svc_bad", + + "svc_muzzleflash", + "svc_muzzlflash2", + "svc_temp_entity", + "svc_layout", + "svc_inventory", + + "svc_nop", + "svc_disconnect", + "svc_reconnect", + "svc_sound", + "svc_print", + "svc_stufftext", + "svc_serverdata", + "svc_configstring", + "svc_spawnbaseline", + "svc_centerprint", + "svc_download", + "svc_playerinfo", + "svc_packetentities", + "svc_deltapacketentities", + "svc_frame" +}; + +//============================================================================= + +void CL_DownloadFileName(char *dest, int destlen, char *fn) +{ + if (strncmp(fn, "players", 7) == 0) + Com_sprintf (dest, destlen, "%s/%s", BASEDIRNAME, fn); + else + Com_sprintf (dest, destlen, "%s/%s", FS_Gamedir(), fn); +} + +/* +=============== +CL_CheckOrDownloadFile + +Returns true if the file exists, otherwise it attempts +to start a download from the server. +=============== +*/ +qboolean CL_CheckOrDownloadFile (char *filename) +{ + FILE *fp; + char name[MAX_OSPATH]; + + if (strstr (filename, "..")) + { + Com_Printf ("Refusing to download a path with ..\n"); + return true; + } + + if (FS_LoadFile (filename, NULL) != -1) + { // it exists, no need to download + return true; + } + + strcpy (cls.downloadname, filename); + + // download to a temp name, and only rename + // to the real name when done, so if interrupted + // a runt file wont be left + COM_StripExtension (cls.downloadname, cls.downloadtempname); + strcat (cls.downloadtempname, ".tmp"); + +//ZOID + // check to see if we already have a tmp for this file, if so, try to resume + // open the file if not opened yet + CL_DownloadFileName(name, sizeof(name), cls.downloadtempname); + +// FS_CreatePath (name); + + fp = fopen (name, "r+b"); + if (fp) { // it exists + int len; + fseek(fp, 0, SEEK_END); + len = ftell(fp); + + cls.download = fp; + + // give the server an offset to start the download + Com_Printf ("Resuming %s\n", cls.downloadname); + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + MSG_WriteString (&cls.netchan.message, + va("download %s %i", cls.downloadname, len)); + } else { + Com_Printf ("Downloading %s\n", cls.downloadname); + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + MSG_WriteString (&cls.netchan.message, + va("download %s", cls.downloadname)); + } + + cls.downloadnumber++; + + return false; +} + +/* +=============== +CL_Download_f + +Request a download from the server +=============== +*/ +void CL_Download_f (void) +{ + char filename[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf("Usage: download \n"); + return; + } + + Com_sprintf(filename, sizeof(filename), "%s", Cmd_Argv(1)); + + if (strstr (filename, "..")) + { + Com_Printf ("Refusing to download a path with ..\n"); + return; + } + + if (FS_LoadFile (filename, NULL) != -1) + { // it exists, no need to download + Com_Printf("File already exists.\n"); + return; + } + + strcpy (cls.downloadname, filename); + Com_Printf ("Downloading %s\n", cls.downloadname); + + // download to a temp name, and only rename + // to the real name when done, so if interrupted + // a runt file wont be left + COM_StripExtension (cls.downloadname, cls.downloadtempname); + strcat (cls.downloadtempname, ".tmp"); + + MSG_WriteByte (&cls.netchan.message, clc_stringcmd); + MSG_WriteString (&cls.netchan.message, + va("download %s", cls.downloadname)); + + cls.downloadnumber++; +} + +/* +====================== +CL_RegisterSounds +====================== +*/ +void CL_RegisterSounds (void) +{ + int i; + + S_BeginRegistration (); + CL_RegisterTEntSounds (); + for (i=1 ; istring || !*fs_gamedirvar->string || strcmp(fs_gamedirvar->string, str))) || (!*str && (fs_gamedirvar->string || *fs_gamedirvar->string))) + Cvar_Set("game", str); + + // parse player entity number + cl.playernum = MSG_ReadShort (&net_message); + + // get the full level name + str = MSG_ReadString (&net_message); + + if (cl.playernum == -1) + { // playing a cinematic or showing a pic, not a level + SCR_PlayCinematic (str); + } + else + { + // seperate the printfs so the server message can have a color + Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); + Com_Printf ("%c%s\n", 2, str); + + // need to prep refresh at next oportunity + cl.refresh_prepped = false; + } +} + +/* +================== +CL_ParseBaseline +================== +*/ +void CL_ParseBaseline (void) +{ + entity_state_t *es; + int bits; + int newnum; + entity_state_t nullstate; + + memset (&nullstate, 0, sizeof(nullstate)); + + newnum = CL_ParseEntityBits (&bits); + es = &cl_entities[newnum].baseline; + CL_ParseDelta (&nullstate, es, newnum, bits); +} + + +/* +================ +CL_LoadClientinfo + +================ +*/ +void CL_LoadClientinfo (clientinfo_t *ci, char *s) +{ + int i; + char *t; + char model_name[MAX_QPATH]; + char skin_name[MAX_QPATH]; + char model_filename[MAX_QPATH]; + char skin_filename[MAX_QPATH]; + char weapon_filename[MAX_QPATH]; + + strncpy(ci->cinfo, s, sizeof(ci->cinfo)); + ci->cinfo[sizeof(ci->cinfo)-1] = 0; + + // isolate the player's name + strncpy(ci->name, s, sizeof(ci->name)); + ci->name[sizeof(ci->name)-1] = 0; + t = strstr (s, "\\"); + if (t) + { + ci->name[t-s] = 0; + s = t+1; + } + + if (cl_noskins->value || *s == 0) + { + Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); + Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/weapon.md2"); + Com_sprintf (skin_filename, sizeof(skin_filename), "players/male/grunt.pcx"); + Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/male/grunt_i.pcx"); + ci->model = re.RegisterModel (model_filename); + memset(ci->weaponmodel, 0, sizeof(ci->weaponmodel)); + ci->weaponmodel[0] = re.RegisterModel (weapon_filename); + ci->skin = re.RegisterSkin (skin_filename); + ci->icon = re.RegisterPic (ci->iconname); + } + else + { + // isolate the model name + strcpy (model_name, s); + t = strstr(model_name, "/"); + if (!t) + t = strstr(model_name, "\\"); + if (!t) + t = model_name; + *t = 0; + + // isolate the skin name + strcpy (skin_name, s + strlen(model_name) + 1); + + // model file + Com_sprintf (model_filename, sizeof(model_filename), "players/%s/tris.md2", model_name); + ci->model = re.RegisterModel (model_filename); + if (!ci->model) + { + strcpy(model_name, "male"); + Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); + ci->model = re.RegisterModel (model_filename); + } + + // skin file + Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name); + ci->skin = re.RegisterSkin (skin_filename); + + // if we don't have the skin and the model wasn't male, + // see if the male has it (this is for CTF's skins) + if (!ci->skin && Q_stricmp(model_name, "male")) + { + // change model to male + strcpy(model_name, "male"); + Com_sprintf (model_filename, sizeof(model_filename), "players/male/tris.md2"); + ci->model = re.RegisterModel (model_filename); + + // see if the skin exists for the male model + Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/%s.pcx", model_name, skin_name); + ci->skin = re.RegisterSkin (skin_filename); + } + + // if we still don't have a skin, it means that the male model didn't have + // it, so default to grunt + if (!ci->skin) { + // see if the skin exists for the male model + Com_sprintf (skin_filename, sizeof(skin_filename), "players/%s/grunt.pcx", model_name, skin_name); + ci->skin = re.RegisterSkin (skin_filename); + } + + // weapon file + for (i = 0; i < num_cl_weaponmodels; i++) { + Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/%s/%s", model_name, cl_weaponmodels[i]); + ci->weaponmodel[i] = re.RegisterModel(weapon_filename); + if (!ci->weaponmodel[i] && strcmp(model_name, "cyborg") == 0) { + // try male + Com_sprintf (weapon_filename, sizeof(weapon_filename), "players/male/%s", cl_weaponmodels[i]); + ci->weaponmodel[i] = re.RegisterModel(weapon_filename); + } + if (!cl_vwep->value) + break; // only one when vwep is off + } + + // icon file + Com_sprintf (ci->iconname, sizeof(ci->iconname), "/players/%s/%s_i.pcx", model_name, skin_name); + ci->icon = re.RegisterPic (ci->iconname); + } + + // must have loaded all data types to be valud + if (!ci->skin || !ci->icon || !ci->model || !ci->weaponmodel[0]) + { + ci->skin = NULL; + ci->icon = NULL; + ci->model = NULL; + ci->weaponmodel[0] = NULL; + return; + } +} + +/* +================ +CL_ParseClientinfo + +Load the skin, icon, and model for a client +================ +*/ +void CL_ParseClientinfo (int player) +{ + char *s; + clientinfo_t *ci; + + s = cl.configstrings[player+CS_PLAYERSKINS]; + + ci = &cl.clientinfo[player]; + + CL_LoadClientinfo (ci, s); +} + + +/* +================ +CL_ParseConfigString +================ +*/ +void CL_ParseConfigString (void) +{ + int i; + char *s; + + i = MSG_ReadShort (&net_message); + if (i < 0 || i >= MAX_CONFIGSTRINGS) + Com_Error (ERR_DROP, "configstring > MAX_CONFIGSTRINGS"); + s = MSG_ReadString(&net_message); + strcpy (cl.configstrings[i], s); + + // do something apropriate + + if (i >= CS_LIGHTS && i < CS_LIGHTS+MAX_LIGHTSTYLES) + CL_SetLightstyle (i - CS_LIGHTS); + else if (i == CS_CDTRACK) + { + if (cl.refresh_prepped) + CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true); + } + else if (i >= CS_MODELS && i < CS_MODELS+MAX_MODELS) + { + if (cl.refresh_prepped) + { + cl.model_draw[i-CS_MODELS] = re.RegisterModel (cl.configstrings[i]); + if (cl.configstrings[i][0] == '*') + cl.model_clip[i-CS_MODELS] = CM_InlineModel (cl.configstrings[i]); + else + cl.model_clip[i-CS_MODELS] = NULL; + } + } + else if (i >= CS_SOUNDS && i < CS_SOUNDS+MAX_MODELS) + { + if (cl.refresh_prepped) + cl.sound_precache[i-CS_SOUNDS] = S_RegisterSound (cl.configstrings[i]); + } + else if (i >= CS_IMAGES && i < CS_IMAGES+MAX_MODELS) + { + if (cl.refresh_prepped) + cl.image_precache[i-CS_IMAGES] = re.RegisterPic (cl.configstrings[i]); + } + else if (i >= CS_PLAYERSKINS && i < CS_PLAYERSKINS+MAX_CLIENTS) + { + if (cl.refresh_prepped) + CL_ParseClientinfo (i-CS_PLAYERSKINS); + } +} + + +/* +===================================================================== + +ACTION MESSAGES + +===================================================================== +*/ + +/* +================== +CL_ParseStartSoundPacket +================== +*/ +void CL_ParseStartSoundPacket(void) +{ + vec3_t pos_v; + float *pos; + int channel, ent; + int sound_num; + float volume; + float attenuation; + int flags; + float ofs; + + flags = MSG_ReadByte (&net_message); + sound_num = MSG_ReadByte (&net_message); + + if (flags & SND_VOLUME) + volume = MSG_ReadByte (&net_message) / 255.0; + else + volume = DEFAULT_SOUND_PACKET_VOLUME; + + if (flags & SND_ATTENUATION) + attenuation = MSG_ReadByte (&net_message) / 64.0; + else + attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; + + if (flags & SND_OFFSET) + ofs = MSG_ReadByte (&net_message) / 1000.0; + else + ofs = 0; + + if (flags & SND_ENT) + { // entity reletive + channel = MSG_ReadShort(&net_message); + ent = channel>>3; + if (ent > MAX_EDICTS) + Com_Error (ERR_DROP,"CL_ParseStartSoundPacket: ent = %i", ent); + + channel &= 7; + } + else + { + ent = 0; + channel = 0; + } + + if (flags & SND_POS) + { // positioned in space + MSG_ReadPos (&net_message, pos_v); + + pos = pos_v; + } + else // use entity number + pos = NULL; + + if (!cl.sound_precache[sound_num]) + return; + + S_StartSound (pos, ent, channel, cl.sound_precache[sound_num], volume, attenuation, ofs); +} + + +void SHOWNET(char *s) +{ + if (cl_shownet->value>=2) + Com_Printf ("%3i:%s\n", net_message.readcount-1, s); +} + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage (void) +{ + int cmd; + char *s; + int i; + +// +// if recording demos, copy the message out +// + if (cl_shownet->value == 1) + Com_Printf ("%i ",net_message.cursize); + else if (cl_shownet->value >= 2) + Com_Printf ("------------------\n"); + + +// +// parse the message +// + while (1) + { + if (net_message.readcount > net_message.cursize) + { + Com_Error (ERR_DROP,"CL_ParseServerMessage: Bad server message"); + break; + } + + cmd = MSG_ReadByte (&net_message); + + if (cmd == -1) + { + SHOWNET("END OF MESSAGE"); + break; + } + + if (cl_shownet->value>=2) + { + if (!svc_strings[cmd]) + Com_Printf ("%3i:BAD CMD %i\n", net_message.readcount-1,cmd); + else + SHOWNET(svc_strings[cmd]); + } + + // other commands + switch (cmd) + { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); + break; + + case svc_nop: +// Com_Printf ("svc_nop\n"); + break; + + case svc_disconnect: + Com_Error (ERR_DISCONNECT,"Server disconnected\n"); + break; + + case svc_reconnect: + Com_Printf ("Server disconnected, reconnecting\n"); + if (cls.download) { + //ZOID, close download + fclose (cls.download); + cls.download = NULL; + } + cls.state = ca_connecting; + cls.connect_time = -99999; // CL_CheckForResend() will fire immediately + break; + + case svc_print: + i = MSG_ReadByte (&net_message); + if (i == PRINT_CHAT) + { + S_StartLocalSound ("misc/talk.wav"); + con.ormask = 128; + } + Com_Printf ("%s", MSG_ReadString (&net_message)); + con.ormask = 0; + break; + + case svc_centerprint: + SCR_CenterPrint (MSG_ReadString (&net_message)); + break; + + case svc_stufftext: + s = MSG_ReadString (&net_message); + Com_DPrintf ("stufftext: %s\n", s); + Cbuf_AddText (s); + break; + + case svc_serverdata: + Cbuf_Execute (); // make sure any stuffed commands are done + CL_ParseServerData (); + break; + + case svc_configstring: + CL_ParseConfigString (); + break; + + case svc_sound: + CL_ParseStartSoundPacket(); + break; + + case svc_spawnbaseline: + CL_ParseBaseline (); + break; + + case svc_temp_entity: + CL_ParseTEnt (); + break; + + case svc_muzzleflash: + CL_ParseMuzzleFlash (); + break; + + case svc_muzzleflash2: + CL_ParseMuzzleFlash2 (); + break; + + case svc_download: + CL_ParseDownload (); + break; + + case svc_frame: + CL_ParseFrame (); + break; + + case svc_inventory: + CL_ParseInventory (); + break; + + case svc_layout: + s = MSG_ReadString (&net_message); + strncpy (cl.layout, s, sizeof(cl.layout)-1); + break; + + case svc_playerinfo: + case svc_packetentities: + case svc_deltapacketentities: + Com_Error (ERR_DROP, "Out of place frame data"); + break; + } + } + + CL_AddNetgraph (); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if (cls.demorecording && !cls.demowaiting) + CL_WriteDemoMessage (); + +} + + diff --git a/client/cl_pred.c b/client/cl_pred.c new file mode 100644 index 000000000..638642c34 --- /dev/null +++ b/client/cl_pred.c @@ -0,0 +1,278 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "client.h" + + +/* +=================== +CL_CheckPredictionError +=================== +*/ +void CL_CheckPredictionError (void) +{ + int frame; + int delta[3]; + int i; + int len; + + if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION)) + return; + + // calculate the last usercmd_t we sent that the server has processed + frame = cls.netchan.incoming_acknowledged; + frame &= (CMD_BACKUP-1); + + // compare what the server returned with what we had predicted it to be + VectorSubtract (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame], delta); + + // save the prediction error for interpolation + len = abs(delta[0]) + abs(delta[1]) + abs(delta[2]); + if (len > 640) // 80 world units + { // a teleport or something + VectorClear (cl.prediction_error); + } + else + { + if (cl_showmiss->value && (delta[0] || delta[1] || delta[2]) ) + Com_Printf ("prediction miss on %i: %i\n", cl.frame.serverframe, + delta[0] + delta[1] + delta[2]); + + VectorCopy (cl.frame.playerstate.pmove.origin, cl.predicted_origins[frame]); + + // save for error itnerpolation + for (i=0 ; i<3 ; i++) + cl.prediction_error[i] = delta[i]*0.125; + } +} + + +/* +==================== +CL_ClipMoveToEntities + +==================== +*/ +void CL_ClipMoveToEntities ( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, trace_t *tr ) +{ + int i, x, zd, zu; + trace_t trace; + int headnode; + float *angles; + entity_state_t *ent; + int num; + cmodel_t *cmodel; + vec3_t bmins, bmaxs; + + for (i=0 ; isolid) + continue; + + if (ent->number == cl.playernum+1) + continue; + + if (ent->solid == 31) + { // special value for bmodel + cmodel = cl.model_clip[ent->modelindex]; + if (!cmodel) + continue; + headnode = cmodel->headnode; + angles = ent->angles; + } + else + { // encoded bbox + x = 8*(ent->solid & 31); + zd = 8*((ent->solid>>5) & 31); + zu = 8*((ent->solid>>10) & 63) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + headnode = CM_HeadnodeForBox (bmins, bmaxs); + angles = vec3_origin; // boxes don't rotate + } + + if (tr->allsolid) + return; + + trace = CM_TransformedBoxTrace (start, end, + mins, maxs, headnode, MASK_PLAYERSOLID, + ent->origin, angles); + + if (trace.allsolid || trace.startsolid || + trace.fraction < tr->fraction) + { + trace.ent = (struct edict_s *)ent; + if (tr->startsolid) + { + *tr = trace; + tr->startsolid = true; + } + else + *tr = trace; + } + else if (trace.startsolid) + tr->startsolid = true; + } +} + + +/* +================ +CL_PMTrace +================ +*/ +trace_t CL_PMTrace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + trace_t t; + + // check against world + t = CM_BoxTrace (start, end, mins, maxs, 0, MASK_PLAYERSOLID); + if (t.fraction < 1.0) + t.ent = (struct edict_s *)1; + + // check all other solid models + CL_ClipMoveToEntities (start, mins, maxs, end, &t); + + return t; +} + +int CL_PMpointcontents (vec3_t point) +{ + int i; + entity_state_t *ent; + int num; + cmodel_t *cmodel; + int contents; + + contents = CM_PointContents (point, 0); + + for (i=0 ; isolid != 31) // special value for bmodel + continue; + + cmodel = cl.model_clip[ent->modelindex]; + if (!cmodel) + continue; + + contents |= CM_TransformedPointContents (point, cmodel->headnode, ent->origin, ent->angles); + } + + return contents; +} + + +/* +================= +CL_PredictMovement + +Sets cl.predicted_origin and cl.predicted_angles +================= +*/ +void CL_PredictMovement (void) +{ + int ack, current; + int frame; + int oldframe; + usercmd_t *cmd; + pmove_t pm; + int i; + int step; + int oldz; + + if (cls.state != ca_active) + return; + + if (cl_paused->value) + return; + + if (!cl_predict->value || (cl.frame.playerstate.pmove.pm_flags & PMF_NO_PREDICTION)) + { // just set angles + for (i=0 ; i<3 ; i++) + { + cl.predicted_angles[i] = cl.viewangles[i] + SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[i]); + } + return; + } + + ack = cls.netchan.incoming_acknowledged; + current = cls.netchan.outgoing_sequence; + + // if we are too far out of date, just freeze + if (current - ack >= CMD_BACKUP) + { + if (cl_showmiss->value) + Com_Printf ("exceeded CMD_BACKUP\n"); + return; + } + + // copy current state to pmove + memset (&pm, 0, sizeof(pm)); + pm.trace = CL_PMTrace; + pm.pointcontents = CL_PMpointcontents; + + pm_airaccelerate = atof(cl.configstrings[CS_AIRACCEL]); + + pm.s = cl.frame.playerstate.pmove; + +// SCR_DebugGraph (current - ack - 1, 0); + + frame = 0; + + // run frames + while (++ack < current) + { + frame = ack & (CMD_BACKUP-1); + cmd = &cl.cmds[frame]; + + pm.cmd = *cmd; + Pmove (&pm); + + // save for debug checking + VectorCopy (pm.s.origin, cl.predicted_origins[frame]); + } + + oldframe = (ack-2) & (CMD_BACKUP-1); + oldz = cl.predicted_origins[oldframe][2]; + step = pm.s.origin[2] - oldz; + if (step > 63 && step < 160 && (pm.s.pm_flags & PMF_ON_GROUND) ) + { + cl.predicted_step = step * 0.125; + cl.predicted_step_time = cls.realtime - cls.frametime * 500; + } + + + // copy results out for rendering + cl.predicted_origin[0] = pm.s.origin[0]*0.125; + cl.predicted_origin[1] = pm.s.origin[1]*0.125; + cl.predicted_origin[2] = pm.s.origin[2]*0.125; + + VectorCopy (pm.viewangles, cl.predicted_angles); +} diff --git a/client/cl_scrn.c b/client/cl_scrn.c new file mode 100644 index 000000000..8c09b3b5f --- /dev/null +++ b/client/cl_scrn.c @@ -0,0 +1,1401 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +/* + + full screen console + put up loading plaque + blanked background with loading plaque + blanked background with menu + cinematics + full screen image for quit and victory + + end of unit intermissions + + */ + +#include "client.h" + +float scr_con_current; // aproaches scr_conlines at scr_conspeed +float scr_conlines; // 0.0 to 1.0 lines of console to display + +qboolean scr_initialized; // ready to draw + +int scr_draw_loading; + +vrect_t scr_vrect; // position of render window on screen + + +cvar_t *scr_viewsize; +cvar_t *scr_conspeed; +cvar_t *scr_centertime; +cvar_t *scr_showturtle; +cvar_t *scr_showpause; +cvar_t *scr_printspeed; + +cvar_t *scr_netgraph; +cvar_t *scr_timegraph; +cvar_t *scr_debuggraph; +cvar_t *scr_graphheight; +cvar_t *scr_graphscale; +cvar_t *scr_graphshift; +cvar_t *scr_drawall; + +typedef struct +{ + int x1, y1, x2, y2; +} dirty_t; + +dirty_t scr_dirty, scr_old_dirty[2]; + +char crosshair_pic[MAX_QPATH]; +int crosshair_width, crosshair_height; + +void SCR_TimeRefresh_f (void); +void SCR_Loading_f (void); + + +/* +=============================================================================== + +BAR GRAPHS + +=============================================================================== +*/ + +/* +============== +CL_AddNetgraph + +A new packet was just parsed +============== +*/ +void CL_AddNetgraph (void) +{ + int i; + int in; + int ping; + + // if using the debuggraph for something else, don't + // add the net lines + if (scr_debuggraph->value || scr_timegraph->value) + return; + + for (i=0 ; i 30) + ping = 30; + SCR_DebugGraph (ping, 0xd0); +} + + +typedef struct +{ + float value; + int color; +} graphsamp_t; + +static int current; +static graphsamp_t values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph (float value, int color) +{ + values[current&1023].value = value; + values[current&1023].color = color; + current++; +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph (void) +{ + int a, x, y, w, i, h; + float v; + int color; + + // + // draw the graph + // + w = scr_vrect.width; + + x = scr_vrect.x; + y = scr_vrect.y+scr_vrect.height; + re.DrawFill (x, y-scr_graphheight->value, + w, scr_graphheight->value, 8); + + for (a=0 ; avalue + scr_graphshift->value; + + if (v < 0) + v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value)); + h = (int)v % (int)scr_graphheight->value; + re.DrawFill (x+w-1-a, y - h, 1, h, color); + } +} + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + +char scr_centerstring[1024]; +float scr_centertime_start; // for slow victory printing +float scr_centertime_off; +int scr_center_lines; +int scr_erase_center; + +/* +============== +SCR_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void SCR_CenterPrint (char *str) +{ + char *s; + char line[64]; + int i, j, l; + + strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1); + scr_centertime_off = scr_centertime->value; + scr_centertime_start = cl.time; + + // count the number of lines for centering + scr_center_lines = 1; + s = str; + while (*s) + { + if (*s == '\n') + scr_center_lines++; + s++; + } + + // echo it to the console + Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n"); + + s = str; + do + { + // scan the width of the line + for (l=0 ; l<40 ; l++) + if (s[l] == '\n' || !s[l]) + break; + for (i=0 ; i<(40-l)/2 ; i++) + line[i] = ' '; + + for (j=0 ; jvalue < 40) + Cvar_Set ("viewsize","40"); + if (scr_viewsize->value > 100) + Cvar_Set ("viewsize","100"); + + size = scr_viewsize->value; + + scr_vrect.width = viddef.width*size/100; + scr_vrect.width &= ~7; + + scr_vrect.height = viddef.height*size/100; + scr_vrect.height &= ~1; + + scr_vrect.x = (viddef.width - scr_vrect.width)/2; + scr_vrect.y = (viddef.height - scr_vrect.height)/2; +} + + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +void SCR_SizeUp_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize->value+10); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +void SCR_SizeDown_f (void) +{ + Cvar_SetValue ("viewsize",scr_viewsize->value-10); +} + +/* +================= +SCR_Sky_f + +Set a specific sky and rotation speed +================= +*/ +void SCR_Sky_f (void) +{ + float rotate; + vec3_t axis; + + if (Cmd_Argc() < 2) + { + Com_Printf ("Usage: sky \n"); + return; + } + if (Cmd_Argc() > 2) + rotate = atof(Cmd_Argv(2)); + else + rotate = 0; + if (Cmd_Argc() == 6) + { + axis[0] = atof(Cmd_Argv(3)); + axis[1] = atof(Cmd_Argv(4)); + axis[2] = atof(Cmd_Argv(5)); + } + else + { + axis[0] = 0; + axis[1] = 0; + axis[2] = 1; + } + + re.SetSky (Cmd_Argv(1), rotate, axis); +} + +//============================================================================ + +/* +================== +SCR_Init +================== +*/ +void SCR_Init (void) +{ + scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE); + scr_conspeed = Cvar_Get ("scr_conspeed", "3", 0); + scr_showturtle = Cvar_Get ("scr_showturtle", "0", 0); + scr_showpause = Cvar_Get ("scr_showpause", "1", 0); + scr_centertime = Cvar_Get ("scr_centertime", "2.5", 0); + scr_printspeed = Cvar_Get ("scr_printspeed", "8", 0); + scr_netgraph = Cvar_Get ("netgraph", "0", 0); + scr_timegraph = Cvar_Get ("timegraph", "0", 0); + scr_debuggraph = Cvar_Get ("debuggraph", "0", 0); + scr_graphheight = Cvar_Get ("graphheight", "32", 0); + scr_graphscale = Cvar_Get ("graphscale", "1", 0); + scr_graphshift = Cvar_Get ("graphshift", "0", 0); + scr_drawall = Cvar_Get ("scr_drawall", "0", 0); + +// +// register our commands +// + Cmd_AddCommand ("timerefresh",SCR_TimeRefresh_f); + Cmd_AddCommand ("loading",SCR_Loading_f); + Cmd_AddCommand ("sizeup",SCR_SizeUp_f); + Cmd_AddCommand ("sizedown",SCR_SizeDown_f); + Cmd_AddCommand ("sky",SCR_Sky_f); + + scr_initialized = true; +} + + +/* +============== +SCR_DrawNet +============== +*/ +void SCR_DrawNet (void) +{ + if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged + < CMD_BACKUP-1) + return; + + re.DrawPic (scr_vrect.x+64, scr_vrect.y, "net"); +} + +/* +============== +SCR_DrawPause +============== +*/ +void SCR_DrawPause (void) +{ + int w, h; + + if (!scr_showpause->value) // turn off for screenshots + return; + + if (!cl_paused->value) + return; + + re.DrawGetPicSize (&w, &h, "pause"); + re.DrawPic ((viddef.width-w)/2, viddef.height/2 + 8, "pause"); +} + +/* +============== +SCR_DrawLoading +============== +*/ +void SCR_DrawLoading (void) +{ + int w, h; + + if (!scr_draw_loading) + return; + + scr_draw_loading = false; + re.DrawGetPicSize (&w, &h, "loading"); + re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading"); +} + +//============================================================================= + +/* +================== +SCR_RunConsole + +Scroll it up or down +================== +*/ +void SCR_RunConsole (void) +{ +// decide on the height of the console + if (cls.key_dest == key_console) + scr_conlines = 0.5; // half screen + else + scr_conlines = 0; // none visible + + if (scr_conlines < scr_con_current) + { + scr_con_current -= scr_conspeed->value*cls.frametime; + if (scr_conlines > scr_con_current) + scr_con_current = scr_conlines; + + } + else if (scr_conlines > scr_con_current) + { + scr_con_current += scr_conspeed->value*cls.frametime; + if (scr_conlines < scr_con_current) + scr_con_current = scr_conlines; + } + +} + +/* +================== +SCR_DrawConsole +================== +*/ +void SCR_DrawConsole (void) +{ + Con_CheckResize (); + + if (cls.state == ca_disconnected || cls.state == ca_connecting) + { // forced full screen console + Con_DrawConsole (1.0); + return; + } + + if (cls.state != ca_active || !cl.refresh_prepped) + { // connected, but can't render + Con_DrawConsole (0.5); + re.DrawFill (0, viddef.height/2, viddef.width, viddef.height/2, 0); + return; + } + + if (scr_con_current) + { + Con_DrawConsole (scr_con_current); + } + else + { + if (cls.key_dest == key_game || cls.key_dest == key_message) + Con_DrawNotify (); // only draw notify in game + } +} + +//============================================================================= + +/* +================ +SCR_BeginLoadingPlaque +================ +*/ +void SCR_BeginLoadingPlaque (void) +{ + S_StopAllSounds (); + cl.sound_prepped = false; // don't play ambients + CDAudio_Stop (); + if (cls.disable_screen) + return; + if (developer->value) + return; + if (cls.state == ca_disconnected) + return; // if at console, don't bring up the plaque + if (cls.key_dest == key_console) + return; + if (cl.cinematictime > 0) + scr_draw_loading = 2; // clear to balack first + else + scr_draw_loading = 1; + SCR_UpdateScreen (); + cls.disable_screen = Sys_Milliseconds (); + cls.disable_servercount = cl.servercount; +} + +/* +================ +SCR_EndLoadingPlaque +================ +*/ +void SCR_EndLoadingPlaque (void) +{ + cls.disable_screen = 0; + Con_ClearNotify (); +} + +/* +================ +SCR_Loading_f +================ +*/ +void SCR_Loading_f (void) +{ + SCR_BeginLoadingPlaque (); +} + +/* +================ +SCR_TimeRefresh_f +================ +*/ +int entitycmpfnc( const entity_t *a, const entity_t *b ) +{ + /* + ** all other models are sorted by model then skin + */ + if ( a->model == b->model ) + { + return ( ( int ) a->skin - ( int ) b->skin ); + } + else + { + return ( ( int ) a->model - ( int ) b->model ); + } +} + +void SCR_TimeRefresh_f (void) +{ + int i; + int start, stop; + float time; + + if ( cls.state != ca_active ) + return; + + start = Sys_Milliseconds (); + + if (Cmd_Argc() == 2) + { // run without page flipping + re.BeginFrame( 0 ); + for (i=0 ; i<128 ; i++) + { + cl.refdef.viewangles[1] = i/128.0*360.0; + re.RenderFrame (&cl.refdef); + } + re.EndFrame(); + } + else + { + for (i=0 ; i<128 ; i++) + { + cl.refdef.viewangles[1] = i/128.0*360.0; + + re.BeginFrame( 0 ); + re.RenderFrame (&cl.refdef); + re.EndFrame(); + } + } + + stop = Sys_Milliseconds (); + time = (stop-start)/1000.0; + Com_Printf ("%f seconds (%f fps)\n", time, 128/time); +} + +/* +================= +SCR_AddDirtyPoint +================= +*/ +void SCR_AddDirtyPoint (int x, int y) +{ + if (x < scr_dirty.x1) + scr_dirty.x1 = x; + if (x > scr_dirty.x2) + scr_dirty.x2 = x; + if (y < scr_dirty.y1) + scr_dirty.y1 = y; + if (y > scr_dirty.y2) + scr_dirty.y2 = y; +} + +void SCR_DirtyScreen (void) +{ + SCR_AddDirtyPoint (0, 0); + SCR_AddDirtyPoint (viddef.width-1, viddef.height-1); +} + +/* +============== +SCR_TileClear + +Clear any parts of the tiled background that were drawn on last frame +============== +*/ +void SCR_TileClear (void) +{ + int i; + int top, bottom, left, right; + dirty_t clear; + + if (scr_drawall->value) + SCR_DirtyScreen (); // for power vr or broken page flippers... + + if (scr_con_current == 1.0) + return; // full screen console + if (scr_viewsize->value == 100) + return; // full screen rendering + if (cl.cinematictime > 0) + return; // full screen cinematic + + // erase rect will be the union of the past three frames + // so tripple buffering works properly + clear = scr_dirty; + for (i=0 ; i<2 ; i++) + { + if (scr_old_dirty[i].x1 < clear.x1) + clear.x1 = scr_old_dirty[i].x1; + if (scr_old_dirty[i].x2 > clear.x2) + clear.x2 = scr_old_dirty[i].x2; + if (scr_old_dirty[i].y1 < clear.y1) + clear.y1 = scr_old_dirty[i].y1; + if (scr_old_dirty[i].y2 > clear.y2) + clear.y2 = scr_old_dirty[i].y2; + } + + scr_old_dirty[1] = scr_old_dirty[0]; + scr_old_dirty[0] = scr_dirty; + + scr_dirty.x1 = 9999; + scr_dirty.x2 = -9999; + scr_dirty.y1 = 9999; + scr_dirty.y2 = -9999; + + // don't bother with anything convered by the console) + top = scr_con_current*viddef.height; + if (top >= clear.y1) + clear.y1 = top; + + if (clear.y2 <= clear.y1) + return; // nothing disturbed + + top = scr_vrect.y; + bottom = top + scr_vrect.height-1; + left = scr_vrect.x; + right = left + scr_vrect.width-1; + + if (clear.y1 < top) + { // clear above view screen + i = clear.y2 < top-1 ? clear.y2 : top-1; + re.DrawTileClear (clear.x1 , clear.y1, + clear.x2 - clear.x1 + 1, i - clear.y1+1, "backtile"); + clear.y1 = top; + } + if (clear.y2 > bottom) + { // clear below view screen + i = clear.y1 > bottom+1 ? clear.y1 : bottom+1; + re.DrawTileClear (clear.x1, i, + clear.x2-clear.x1+1, clear.y2-i+1, "backtile"); + clear.y2 = bottom; + } + if (clear.x1 < left) + { // clear left of view screen + i = clear.x2 < left-1 ? clear.x2 : left-1; + re.DrawTileClear (clear.x1, clear.y1, + i-clear.x1+1, clear.y2 - clear.y1 + 1, "backtile"); + clear.x1 = left; + } + if (clear.x2 > right) + { // clear left of view screen + i = clear.x1 > right+1 ? clear.x1 : right+1; + re.DrawTileClear (i, clear.y1, + clear.x2-i+1, clear.y2 - clear.y1 + 1, "backtile"); + clear.x2 = right; + } + +} + + +//=============================================================== + + +#define STAT_MINUS 10 // num frame for '-' stats digit +char *sb_nums[2][11] = +{ + {"num_0", "num_1", "num_2", "num_3", "num_4", "num_5", + "num_6", "num_7", "num_8", "num_9", "num_minus"}, + {"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5", + "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"} +}; + +#define ICON_WIDTH 24 +#define ICON_HEIGHT 24 +#define CHAR_WIDTH 16 +#define ICON_SPACE 8 + + + +/* +================ +SizeHUDString + +Allow embedded \n in the string +================ +*/ +void SizeHUDString (char *string, int *w, int *h) +{ + int lines, width, current; + + lines = 1; + width = 0; + + current = 0; + while (*string) + { + if (*string == '\n') + { + lines++; + current = 0; + } + else + { + current++; + if (current > width) + width = current; + } + string++; + } + + *w = width * 8; + *h = lines * 8; +} + +void DrawHUDString (char *string, int x, int y, int centerwidth, int xor) +{ + int margin; + char line[1024]; + int width; + int i; + + margin = x; + + while (*string) + { + // scan out one line of text from the string + width = 0; + while (*string && *string != '\n') + line[width++] = *string++; + line[width] = 0; + + if (centerwidth) + x = margin + (centerwidth - width*8)/2; + else + x = margin; + for (i=0 ; i 5) + width = 5; + + SCR_AddDirtyPoint (x, y); + SCR_AddDirtyPoint (x+width*CHAR_WIDTH+2, y+23); + + 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'; + + re.DrawPic (x,y,sb_nums[color][frame]); + x += CHAR_WIDTH; + ptr++; + l--; + } +} + + +/* +=============== +SCR_TouchPics + +Allows rendering code to cache all needed sbar graphics +=============== +*/ +void SCR_TouchPics (void) +{ + int i, j; + + for (i=0 ; i<2 ; i++) + for (j=0 ; j<11 ; j++) + re.RegisterPic (sb_nums[i][j]); + + if (crosshair->value) + { + if (crosshair->value > 3 || crosshair->value < 0) + crosshair->value = 3; + + Com_sprintf (crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value)); + re.DrawGetPicSize (&crosshair_width, &crosshair_height, crosshair_pic); + if (!crosshair_width) + crosshair_pic[0] = 0; + } +} + +/* +================ +SCR_ExecuteLayoutString + +================ +*/ +void SCR_ExecuteLayoutString (char *s) +{ + int x, y; + int value; + char *token; + int width; + int index; + clientinfo_t *ci; + + if (cls.state != ca_active || !cl.refresh_prepped) + return; + + if (!s[0]) + return; + + x = 0; + y = 0; + width = 3; + + while (s) + { + token = COM_Parse (&s); + if (!strcmp(token, "xl")) + { + token = COM_Parse (&s); + x = atoi(token); + continue; + } + if (!strcmp(token, "xr")) + { + token = COM_Parse (&s); + x = viddef.width + atoi(token); + continue; + } + if (!strcmp(token, "xv")) + { + token = COM_Parse (&s); + x = viddef.width/2 - 160 + atoi(token); + continue; + } + + if (!strcmp(token, "yt")) + { + token = COM_Parse (&s); + y = atoi(token); + continue; + } + if (!strcmp(token, "yb")) + { + token = COM_Parse (&s); + y = viddef.height + atoi(token); + continue; + } + if (!strcmp(token, "yv")) + { + token = COM_Parse (&s); + y = viddef.height/2 - 120 + atoi(token); + continue; + } + + if (!strcmp(token, "pic")) + { // draw a pic from a stat number + token = COM_Parse (&s); + value = cl.frame.playerstate.stats[atoi(token)]; + if (value >= MAX_IMAGES) + Com_Error (ERR_DROP, "Pic >= MAX_IMAGES"); + if (cl.configstrings[CS_IMAGES+value]) + { + SCR_AddDirtyPoint (x, y); + SCR_AddDirtyPoint (x+23, y+23); + re.DrawPic (x, y, cl.configstrings[CS_IMAGES+value]); + } + continue; + } + + if (!strcmp(token, "client")) + { // draw a deathmatch client block + int score, ping, time; + + token = COM_Parse (&s); + x = viddef.width/2 - 160 + atoi(token); + token = COM_Parse (&s); + y = viddef.height/2 - 120 + atoi(token); + SCR_AddDirtyPoint (x, y); + SCR_AddDirtyPoint (x+159, y+31); + + token = COM_Parse (&s); + value = atoi(token); + if (value >= MAX_CLIENTS || value < 0) + Com_Error (ERR_DROP, "client >= MAX_CLIENTS"); + ci = &cl.clientinfo[value]; + + token = COM_Parse (&s); + score = atoi(token); + + token = COM_Parse (&s); + ping = atoi(token); + + token = COM_Parse (&s); + time = atoi(token); + + DrawAltString (x+32, y, ci->name); + DrawString (x+32, y+8, "Score: "); + DrawAltString (x+32+7*8, y+8, va("%i", score)); + DrawString (x+32, y+16, va("Ping: %i", ping)); + DrawString (x+32, y+24, va("Time: %i", time)); + + if (!ci->icon) + ci = &cl.baseclientinfo; + re.DrawPic (x, y, ci->iconname); + continue; + } + + if (!strcmp(token, "ctf")) + { // draw a ctf client block + int score, ping; + char block[80]; + + token = COM_Parse (&s); + x = viddef.width/2 - 160 + atoi(token); + token = COM_Parse (&s); + y = viddef.height/2 - 120 + atoi(token); + SCR_AddDirtyPoint (x, y); + SCR_AddDirtyPoint (x+159, y+31); + + token = COM_Parse (&s); + value = atoi(token); + if (value >= MAX_CLIENTS || value < 0) + Com_Error (ERR_DROP, "client >= MAX_CLIENTS"); + ci = &cl.clientinfo[value]; + + token = COM_Parse (&s); + score = atoi(token); + + token = COM_Parse (&s); + ping = atoi(token); + if (ping > 999) + ping = 999; + + sprintf(block, "%3d %3d %-12.12s", score, ping, ci->name); + + if (value == cl.playernum) + DrawAltString (x, y, block); + else + DrawString (x, y, block); + continue; + } + + if (!strcmp(token, "picn")) + { // draw a pic from a name + token = COM_Parse (&s); + SCR_AddDirtyPoint (x, y); + SCR_AddDirtyPoint (x+23, y+23); + re.DrawPic (x, y, token); + continue; + } + + if (!strcmp(token, "num")) + { // draw a number + token = COM_Parse (&s); + width = atoi(token); + token = COM_Parse (&s); + value = cl.frame.playerstate.stats[atoi(token)]; + SCR_DrawField (x, y, 0, width, value); + continue; + } + + if (!strcmp(token, "hnum")) + { // health number + int color; + + width = 3; + value = cl.frame.playerstate.stats[STAT_HEALTH]; + if (value > 25) + color = 0; // green + else if (value > 0) + color = (cl.frame.serverframe>>2) & 1; // flash + else + color = 1; + + if (cl.frame.playerstate.stats[STAT_FLASHES] & 1) + re.DrawPic (x, y, "field_3"); + + SCR_DrawField (x, y, color, width, value); + continue; + } + + if (!strcmp(token, "anum")) + { // ammo number + int color; + + width = 3; + value = cl.frame.playerstate.stats[STAT_AMMO]; + if (value > 5) + color = 0; // green + else if (value >= 0) + color = (cl.frame.serverframe>>2) & 1; // flash + else + continue; // negative number = don't show + + if (cl.frame.playerstate.stats[STAT_FLASHES] & 4) + re.DrawPic (x, y, "field_3"); + + SCR_DrawField (x, y, color, width, value); + continue; + } + + if (!strcmp(token, "rnum")) + { // armor number + int color; + + width = 3; + value = cl.frame.playerstate.stats[STAT_ARMOR]; + if (value < 1) + continue; + + color = 0; // green + + if (cl.frame.playerstate.stats[STAT_FLASHES] & 2) + re.DrawPic (x, y, "field_3"); + + SCR_DrawField (x, y, color, width, value); + continue; + } + + + if (!strcmp(token, "stat_string")) + { + token = COM_Parse (&s); + index = atoi(token); + if (index < 0 || index >= MAX_CONFIGSTRINGS) + Com_Error (ERR_DROP, "Bad stat_string index"); + index = cl.frame.playerstate.stats[index]; + if (index < 0 || index >= MAX_CONFIGSTRINGS) + Com_Error (ERR_DROP, "Bad stat_string index"); + DrawString (x, y, cl.configstrings[index]); + continue; + } + + if (!strcmp(token, "cstring")) + { + token = COM_Parse (&s); + DrawHUDString (token, x, y, 320, 0); + continue; + } + + if (!strcmp(token, "string")) + { + token = COM_Parse (&s); + DrawString (x, y, token); + continue; + } + + if (!strcmp(token, "cstring2")) + { + token = COM_Parse (&s); + DrawHUDString (token, x, y, 320,0x80); + continue; + } + + if (!strcmp(token, "string2")) + { + token = COM_Parse (&s); + DrawAltString (x, y, token); + continue; + } + + if (!strcmp(token, "if")) + { // draw a number + token = COM_Parse (&s); + value = cl.frame.playerstate.stats[atoi(token)]; + if (!value) + { // skip to endif + while (s && strcmp(token, "endif") ) + { + token = COM_Parse (&s); + } + } + + continue; + } + + + } +} + + +/* +================ +SCR_DrawStats + +The status bar is a small layout program that +is based on the stats array +================ +*/ +void SCR_DrawStats (void) +{ + SCR_ExecuteLayoutString (cl.configstrings[CS_STATUSBAR]); +} + + +/* +================ +SCR_DrawLayout + +================ +*/ +#define STAT_LAYOUTS 13 + +void SCR_DrawLayout (void) +{ + if (!cl.frame.playerstate.stats[STAT_LAYOUTS]) + return; + SCR_ExecuteLayoutString (cl.layout); +} + +//======================================================= + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen (void) +{ + int numframes; + int i; + float separation[2] = { 0, 0 }; + + // if the screen is disabled (loading plaque is up, or vid mode changing) + // do nothing at all + if (cls.disable_screen) + { + if (Sys_Milliseconds() - cls.disable_screen > 120000) + { + cls.disable_screen = 0; + Com_Printf ("Loading plaque timed out.\n"); + } + return; + } + + if (!scr_initialized || !con.initialized) + return; // not initialized yet + + /* + ** range check cl_camera_separation so we don't inadvertently fry someone's + ** brain + */ + if ( cl_stereo_separation->value > 1.0 ) + Cvar_SetValue( "cl_stereo_separation", 1.0 ); + else if ( cl_stereo_separation->value < 0 ) + Cvar_SetValue( "cl_stereo_separation", 0.0 ); + + if ( cl_stereo->value ) + { + numframes = 2; + separation[0] = -cl_stereo_separation->value / 2; + separation[1] = cl_stereo_separation->value / 2; + } + else + { + separation[0] = 0; + separation[1] = 0; + numframes = 1; + } + + for ( i = 0; i < numframes; i++ ) + { + re.BeginFrame( separation[i] ); + + if (scr_draw_loading == 2) + { // loading plaque over black screen + int w, h; + + re.CinematicSetPalette(NULL); + scr_draw_loading = false; + re.DrawGetPicSize (&w, &h, "loading"); + re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading"); +// re.EndFrame(); +// return; + } + // if a cinematic is supposed to be running, handle menus + // and console specially + else if (cl.cinematictime > 0) + { + if (cls.key_dest == key_menu) + { + if (cl.cinematicpalette_active) + { + re.CinematicSetPalette(NULL); + cl.cinematicpalette_active = false; + } + M_Draw (); +// re.EndFrame(); +// return; + } + else if (cls.key_dest == key_console) + { + if (cl.cinematicpalette_active) + { + re.CinematicSetPalette(NULL); + cl.cinematicpalette_active = false; + } + SCR_DrawConsole (); +// re.EndFrame(); +// return; + } + else + { + SCR_DrawCinematic(); +// re.EndFrame(); +// return; + } + } + else + { + + // make sure the game palette is active + if (cl.cinematicpalette_active) + { + re.CinematicSetPalette(NULL); + cl.cinematicpalette_active = false; + } + + // do 3D refresh drawing, and then update the screen + SCR_CalcVrect (); + + // clear any dirty part of the background + SCR_TileClear (); + + V_RenderView ( separation[i] ); + + SCR_DrawStats (); + if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1) + SCR_DrawLayout (); + if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2) + CL_DrawInventory (); + + SCR_DrawNet (); + SCR_CheckDrawCenterString (); + + if (scr_timegraph->value) + SCR_DebugGraph (cls.frametime*300, 0); + + if (scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value) + SCR_DrawDebugGraph (); + + SCR_DrawPause (); + + SCR_DrawConsole (); + + M_Draw (); + + SCR_DrawLoading (); + } + } + re.EndFrame(); +} diff --git a/client/cl_tent.c b/client/cl_tent.c new file mode 100644 index 000000000..c039b497e --- /dev/null +++ b/client/cl_tent.c @@ -0,0 +1,1745 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_tent.c -- client side temporary entities + +#include "client.h" + +typedef enum +{ + ex_free, ex_explosion, ex_misc, ex_flash, ex_mflash, ex_poly, ex_poly2 +} exptype_t; + +typedef struct +{ + exptype_t type; + entity_t ent; + + int frames; + float light; + vec3_t lightcolor; + float start; + int baseframe; +} explosion_t; + + + +#define MAX_EXPLOSIONS 32 +explosion_t cl_explosions[MAX_EXPLOSIONS]; + + +#define MAX_BEAMS 32 +typedef struct +{ + int entity; + int dest_entity; + struct model_s *model; + int endtime; + vec3_t offset; + vec3_t start, end; +} beam_t; +beam_t cl_beams[MAX_BEAMS]; +//PMM - added this for player-linked beams. Currently only used by the plasma beam +beam_t cl_playerbeams[MAX_BEAMS]; + + +#define MAX_LASERS 32 +typedef struct +{ + entity_t ent; + int endtime; +} laser_t; +laser_t cl_lasers[MAX_LASERS]; + +//ROGUE +cl_sustain_t cl_sustains[MAX_SUSTAINS]; +//ROGUE + +//PGM +extern void CL_TeleportParticles (vec3_t org); +//PGM + +void CL_BlasterParticles (vec3_t org, vec3_t dir); +void CL_ExplosionParticles (vec3_t org); +void CL_BFGExplosionParticles (vec3_t org); +// RAFAEL +void CL_BlueBlasterParticles (vec3_t org, vec3_t dir); + +struct sfx_s *cl_sfx_ric1; +struct sfx_s *cl_sfx_ric2; +struct sfx_s *cl_sfx_ric3; +struct sfx_s *cl_sfx_lashit; +struct sfx_s *cl_sfx_spark5; +struct sfx_s *cl_sfx_spark6; +struct sfx_s *cl_sfx_spark7; +struct sfx_s *cl_sfx_railg; +struct sfx_s *cl_sfx_rockexp; +struct sfx_s *cl_sfx_grenexp; +struct sfx_s *cl_sfx_watrexp; +// RAFAEL +struct sfx_s *cl_sfx_plasexp; +struct sfx_s *cl_sfx_footsteps[4]; + +struct model_s *cl_mod_explode; +struct model_s *cl_mod_smoke; +struct model_s *cl_mod_flash; +struct model_s *cl_mod_parasite_segment; +struct model_s *cl_mod_grapple_cable; +struct model_s *cl_mod_parasite_tip; +struct model_s *cl_mod_explo4; +struct model_s *cl_mod_bfg_explo; +struct model_s *cl_mod_powerscreen; +// RAFAEL +struct model_s *cl_mod_plasmaexplo; + +//ROGUE +struct sfx_s *cl_sfx_lightning; +struct sfx_s *cl_sfx_disrexp; +struct model_s *cl_mod_lightning; +struct model_s *cl_mod_heatbeam; +struct model_s *cl_mod_monster_heatbeam; +struct model_s *cl_mod_explo4_big; + +//ROGUE +/* +================= +CL_RegisterTEntSounds +================= +*/ +void CL_RegisterTEntSounds (void) +{ + int i; + char name[MAX_QPATH]; + + // PMM - version stuff +// Com_Printf ("%s\n", ROGUE_VERSION_STRING); + // PMM + cl_sfx_ric1 = S_RegisterSound ("world/ric1.wav"); + cl_sfx_ric2 = S_RegisterSound ("world/ric2.wav"); + cl_sfx_ric3 = S_RegisterSound ("world/ric3.wav"); + cl_sfx_lashit = S_RegisterSound("weapons/lashit.wav"); + cl_sfx_spark5 = S_RegisterSound ("world/spark5.wav"); + cl_sfx_spark6 = S_RegisterSound ("world/spark6.wav"); + cl_sfx_spark7 = S_RegisterSound ("world/spark7.wav"); + cl_sfx_railg = S_RegisterSound ("weapons/railgf1a.wav"); + cl_sfx_rockexp = S_RegisterSound ("weapons/rocklx1a.wav"); + cl_sfx_grenexp = S_RegisterSound ("weapons/grenlx1a.wav"); + cl_sfx_watrexp = S_RegisterSound ("weapons/xpld_wat.wav"); + // RAFAEL + // cl_sfx_plasexp = S_RegisterSound ("weapons/plasexpl.wav"); + S_RegisterSound ("player/land1.wav"); + + S_RegisterSound ("player/fall2.wav"); + S_RegisterSound ("player/fall1.wav"); + + for (i=0 ; i<4 ; i++) + { + Com_sprintf (name, sizeof(name), "player/step%i.wav", i+1); + cl_sfx_footsteps[i] = S_RegisterSound (name); + } + +//PGM + cl_sfx_lightning = S_RegisterSound ("weapons/tesla.wav"); + cl_sfx_disrexp = S_RegisterSound ("weapons/disrupthit.wav"); + // version stuff + sprintf (name, "weapons/sound%d.wav", ROGUE_VERSION_ID); + if (name[0] == 'w') + name[0] = 'W'; +//PGM +} + +/* +================= +CL_RegisterTEntModels +================= +*/ +void CL_RegisterTEntModels (void) +{ + cl_mod_explode = re.RegisterModel ("models/objects/explode/tris.md2"); + cl_mod_smoke = re.RegisterModel ("models/objects/smoke/tris.md2"); + cl_mod_flash = re.RegisterModel ("models/objects/flash/tris.md2"); + cl_mod_parasite_segment = re.RegisterModel ("models/monsters/parasite/segment/tris.md2"); + cl_mod_grapple_cable = re.RegisterModel ("models/ctf/segment/tris.md2"); + cl_mod_parasite_tip = re.RegisterModel ("models/monsters/parasite/tip/tris.md2"); + cl_mod_explo4 = re.RegisterModel ("models/objects/r_explode/tris.md2"); + cl_mod_bfg_explo = re.RegisterModel ("sprites/s_bfg2.sp2"); + cl_mod_powerscreen = re.RegisterModel ("models/items/armor/effect/tris.md2"); + +re.RegisterModel ("models/objects/laser/tris.md2"); +re.RegisterModel ("models/objects/grenade2/tris.md2"); +re.RegisterModel ("models/weapons/v_machn/tris.md2"); +re.RegisterModel ("models/weapons/v_handgr/tris.md2"); +re.RegisterModel ("models/weapons/v_shotg2/tris.md2"); +re.RegisterModel ("models/objects/gibs/bone/tris.md2"); +re.RegisterModel ("models/objects/gibs/sm_meat/tris.md2"); +re.RegisterModel ("models/objects/gibs/bone2/tris.md2"); +// RAFAEL +// re.RegisterModel ("models/objects/blaser/tris.md2"); + +re.RegisterPic ("w_machinegun"); +re.RegisterPic ("a_bullets"); +re.RegisterPic ("i_health"); +re.RegisterPic ("a_grenades"); + +//ROGUE + cl_mod_explo4_big = re.RegisterModel ("models/objects/r_explode2/tris.md2"); + cl_mod_lightning = re.RegisterModel ("models/proj/lightning/tris.md2"); + cl_mod_heatbeam = re.RegisterModel ("models/proj/beam/tris.md2"); + cl_mod_monster_heatbeam = re.RegisterModel ("models/proj/widowbeam/tris.md2"); +//ROGUE +} + +/* +================= +CL_ClearTEnts +================= +*/ +void CL_ClearTEnts (void) +{ + memset (cl_beams, 0, sizeof(cl_beams)); + memset (cl_explosions, 0, sizeof(cl_explosions)); + memset (cl_lasers, 0, sizeof(cl_lasers)); + +//ROGUE + memset (cl_playerbeams, 0, sizeof(cl_playerbeams)); + memset (cl_sustains, 0, sizeof(cl_sustains)); +//ROGUE +} + +/* +================= +CL_AllocExplosion +================= +*/ +explosion_t *CL_AllocExplosion (void) +{ + int i; + int time; + int index; + + for (i=0 ; ient.origin); + ex->type = ex_misc; + ex->frames = 4; + ex->ent.flags = RF_TRANSLUCENT; + ex->start = cl.frame.servertime - 100; + ex->ent.model = cl_mod_smoke; + + ex = CL_AllocExplosion (); + VectorCopy (origin, ex->ent.origin); + ex->type = ex_flash; + ex->ent.flags = RF_FULLBRIGHT; + ex->frames = 2; + ex->start = cl.frame.servertime - 100; + ex->ent.model = cl_mod_flash; +} + +/* +================= +CL_ParseParticles +================= +*/ +void CL_ParseParticles (void) +{ + int color, count; + vec3_t pos, dir; + + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + + color = MSG_ReadByte (&net_message); + + count = MSG_ReadByte (&net_message); + + CL_ParticleEffect (pos, dir, color, count); +} + +/* +================= +CL_ParseBeam +================= +*/ +int CL_ParseBeam (struct model_s *model) +{ + int ent; + vec3_t start, end; + beam_t *b; + int i; + + ent = MSG_ReadShort (&net_message); + + MSG_ReadPos (&net_message, start); + MSG_ReadPos (&net_message, end); + +// override any beam with the same entity + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->entity == ent) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorClear (b->offset); + return ent; + } + +// find a free beam + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorClear (b->offset); + return ent; + } + } + Com_Printf ("beam list overflow!\n"); + return ent; +} + +/* +================= +CL_ParseBeam2 +================= +*/ +int CL_ParseBeam2 (struct model_s *model) +{ + int ent; + vec3_t start, end, offset; + beam_t *b; + int i; + + ent = MSG_ReadShort (&net_message); + + MSG_ReadPos (&net_message, start); + MSG_ReadPos (&net_message, end); + MSG_ReadPos (&net_message, offset); + +// Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]); + +// override any beam with the same entity + + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->entity == ent) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorCopy (offset, b->offset); + return ent; + } + +// find a free beam + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorCopy (offset, b->offset); + return ent; + } + } + Com_Printf ("beam list overflow!\n"); + return ent; +} + +// ROGUE +/* +================= +CL_ParsePlayerBeam + - adds to the cl_playerbeam array instead of the cl_beams array +================= +*/ +int CL_ParsePlayerBeam (struct model_s *model) +{ + int ent; + vec3_t start, end, offset; + beam_t *b; + int i; + + ent = MSG_ReadShort (&net_message); + + MSG_ReadPos (&net_message, start); + MSG_ReadPos (&net_message, end); + // PMM - network optimization + if (model == cl_mod_heatbeam) + VectorSet(offset, 2, 7, -3); + else if (model == cl_mod_monster_heatbeam) + { + model = cl_mod_heatbeam; + VectorSet(offset, 0, 0, 0); + } + else + MSG_ReadPos (&net_message, offset); + +// Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]); + +// override any beam with the same entity +// PMM - For player beams, we only want one per player (entity) so.. + for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++) + { + if (b->entity == ent) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorCopy (offset, b->offset); + return ent; + } + } + +// find a free beam + for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { + b->entity = ent; + b->model = model; + b->endtime = cl.time + 100; // PMM - this needs to be 100 to prevent multiple heatbeams + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorCopy (offset, b->offset); + return ent; + } + } + Com_Printf ("beam list overflow!\n"); + return ent; +} +//rogue + +/* +================= +CL_ParseLightning +================= +*/ +int CL_ParseLightning (struct model_s *model) +{ + int srcEnt, destEnt; + vec3_t start, end; + beam_t *b; + int i; + + srcEnt = MSG_ReadShort (&net_message); + destEnt = MSG_ReadShort (&net_message); + + MSG_ReadPos (&net_message, start); + MSG_ReadPos (&net_message, end); + +// override any beam with the same source AND destination entities + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + if (b->entity == srcEnt && b->dest_entity == destEnt) + { +// Com_Printf("%d: OVERRIDE %d -> %d\n", cl.time, srcEnt, destEnt); + b->entity = srcEnt; + b->dest_entity = destEnt; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorClear (b->offset); + return srcEnt; + } + +// find a free beam + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + { +// Com_Printf("%d: NORMAL %d -> %d\n", cl.time, srcEnt, destEnt); + b->entity = srcEnt; + b->dest_entity = destEnt; + b->model = model; + b->endtime = cl.time + 200; + VectorCopy (start, b->start); + VectorCopy (end, b->end); + VectorClear (b->offset); + return srcEnt; + } + } + Com_Printf ("beam list overflow!\n"); + return srcEnt; +} + +/* +================= +CL_ParseLaser +================= +*/ +void CL_ParseLaser (int colors) +{ + vec3_t start; + vec3_t end; + laser_t *l; + int i; + + MSG_ReadPos (&net_message, start); + MSG_ReadPos (&net_message, end); + + for (i=0, l=cl_lasers ; i< MAX_LASERS ; i++, l++) + { + if (l->endtime < cl.time) + { + l->ent.flags = RF_TRANSLUCENT | RF_BEAM; + VectorCopy (start, l->ent.origin); + VectorCopy (end, l->ent.oldorigin); + l->ent.alpha = 0.30; + l->ent.skinnum = (colors >> ((rand() % 4)*8)) & 0xff; + l->ent.model = NULL; + l->ent.frame = 4; + l->endtime = cl.time + 100; + return; + } + } +} + +//============= +//ROGUE +void CL_ParseSteam (void) +{ + vec3_t pos, dir; + int id, i; + int r; + int cnt; + int color; + int magnitude; + cl_sustain_t *s, *free_sustain; + + id = MSG_ReadShort (&net_message); // an id of -1 is an instant effect + if (id != -1) // sustains + { +// Com_Printf ("Sustain effect id %d\n", id); + free_sustain = NULL; + for (i=0, s=cl_sustains; iid == 0) + { + free_sustain = s; + break; + } + } + if (free_sustain) + { + s->id = id; + s->count = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, s->org); + MSG_ReadDir (&net_message, s->dir); + r = MSG_ReadByte (&net_message); + s->color = r & 0xff; + s->magnitude = MSG_ReadShort (&net_message); + s->endtime = cl.time + MSG_ReadLong (&net_message); + s->think = CL_ParticleSteamEffect2; + s->thinkinterval = 100; + s->nextthink = cl.time; + } + else + { +// Com_Printf ("No free sustains!\n"); + // FIXME - read the stuff anyway + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + r = MSG_ReadByte (&net_message); + magnitude = MSG_ReadShort (&net_message); + magnitude = MSG_ReadLong (&net_message); // really interval + } + } + else // instant + { + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + r = MSG_ReadByte (&net_message); + magnitude = MSG_ReadShort (&net_message); + color = r & 0xff; + CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude); +// S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + } +} + +void CL_ParseWidow (void) +{ + vec3_t pos; + int id, i; + cl_sustain_t *s, *free_sustain; + + id = MSG_ReadShort (&net_message); + + free_sustain = NULL; + for (i=0, s=cl_sustains; iid == 0) + { + free_sustain = s; + break; + } + } + if (free_sustain) + { + s->id = id; + MSG_ReadPos (&net_message, s->org); + s->endtime = cl.time + 2100; + s->think = CL_Widowbeamout; + s->thinkinterval = 1; + s->nextthink = cl.time; + } + else // no free sustains + { + // FIXME - read the stuff anyway + MSG_ReadPos (&net_message, pos); + } +} + +void CL_ParseNuke (void) +{ + vec3_t pos; + int i; + cl_sustain_t *s, *free_sustain; + + free_sustain = NULL; + for (i=0, s=cl_sustains; iid == 0) + { + free_sustain = s; + break; + } + } + if (free_sustain) + { + s->id = 21000; + MSG_ReadPos (&net_message, s->org); + s->endtime = cl.time + 1000; + s->think = CL_Nukeblast; + s->thinkinterval = 1; + s->nextthink = cl.time; + } + else // no free sustains + { + // FIXME - read the stuff anyway + MSG_ReadPos (&net_message, pos); + } +} + +//ROGUE +//============= + + +/* +================= +CL_ParseTEnt +================= +*/ +static byte splash_color[] = {0x00, 0xe0, 0xb0, 0x50, 0xd0, 0xe0, 0xe8}; + +void CL_ParseTEnt (void) +{ + int type; + vec3_t pos, pos2, dir; + explosion_t *ex; + int cnt; + int color; + int r; + int ent; + int magnitude; + + type = MSG_ReadByte (&net_message); + + switch (type) + { + case TE_BLOOD: // bullet hitting flesh + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + CL_ParticleEffect (pos, dir, 0xe8, 60); + break; + + case TE_GUNSHOT: // bullet hitting wall + case TE_SPARKS: + case TE_BULLET_SPARKS: + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + if (type == TE_GUNSHOT) + CL_ParticleEffect (pos, dir, 0, 40); + else + CL_ParticleEffect (pos, dir, 0xe0, 6); + + if (type != TE_SPARKS) + { + CL_SmokeAndFlash(pos); + + // impact sound + cnt = rand()&15; + if (cnt == 1) + S_StartSound (pos, 0, 0, cl_sfx_ric1, 1, ATTN_NORM, 0); + else if (cnt == 2) + S_StartSound (pos, 0, 0, cl_sfx_ric2, 1, ATTN_NORM, 0); + else if (cnt == 3) + S_StartSound (pos, 0, 0, cl_sfx_ric3, 1, ATTN_NORM, 0); + } + + break; + + case TE_SCREEN_SPARKS: + case TE_SHIELD_SPARKS: + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + if (type == TE_SCREEN_SPARKS) + CL_ParticleEffect (pos, dir, 0xd0, 40); + else + CL_ParticleEffect (pos, dir, 0xb0, 40); + //FIXME : replace or remove this sound + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_SHOTGUN: // bullet hitting wall + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + CL_ParticleEffect (pos, dir, 0, 20); + CL_SmokeAndFlash(pos); + break; + + case TE_SPLASH: // bullet hitting water + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + r = MSG_ReadByte (&net_message); + if (r > 6) + color = 0x00; + else + color = splash_color[r]; + CL_ParticleEffect (pos, dir, color, cnt); + + if (r == SPLASH_SPARKS) + { + r = rand() & 3; + if (r == 0) + S_StartSound (pos, 0, 0, cl_sfx_spark5, 1, ATTN_STATIC, 0); + else if (r == 1) + S_StartSound (pos, 0, 0, cl_sfx_spark6, 1, ATTN_STATIC, 0); + else + S_StartSound (pos, 0, 0, cl_sfx_spark7, 1, ATTN_STATIC, 0); + } + break; + + case TE_LASER_SPARKS: + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + color = MSG_ReadByte (&net_message); + CL_ParticleEffect2 (pos, dir, color, cnt); + break; + + // RAFAEL + case TE_BLUEHYPERBLASTER: + MSG_ReadPos (&net_message, pos); + MSG_ReadPos (&net_message, dir); + CL_BlasterParticles (pos, dir); + break; + + case TE_BLASTER: // blaster hitting wall + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + CL_BlasterParticles (pos, dir); + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->ent.angles[0] = acos(dir[2])/M_PI*180; + // PMM - fixed to correct for pitch of 0 + if (dir[0]) + ex->ent.angles[1] = atan2(dir[1], dir[0])/M_PI*180; + else if (dir[1] > 0) + ex->ent.angles[1] = 90; + else if (dir[1] < 0) + ex->ent.angles[1] = 270; + else + ex->ent.angles[1] = 0; + + ex->type = ex_misc; + ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT; + ex->start = cl.frame.servertime - 100; + ex->light = 150; + ex->lightcolor[0] = 1; + ex->lightcolor[1] = 1; + ex->ent.model = cl_mod_explode; + ex->frames = 4; + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_RAILTRAIL: // railgun effect + MSG_ReadPos (&net_message, pos); + MSG_ReadPos (&net_message, pos2); + CL_RailTrail (pos, pos2); + S_StartSound (pos2, 0, 0, cl_sfx_railg, 1, ATTN_NORM, 0); + break; + + case TE_EXPLOSION2: + case TE_GRENADE_EXPLOSION: + case TE_GRENADE_EXPLOSION_WATER: + MSG_ReadPos (&net_message, pos); + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_poly; + ex->ent.flags = RF_FULLBRIGHT; + ex->start = cl.frame.servertime - 100; + ex->light = 350; + ex->lightcolor[0] = 1.0; + ex->lightcolor[1] = 0.5; + ex->lightcolor[2] = 0.5; + ex->ent.model = cl_mod_explo4; + ex->frames = 19; + ex->baseframe = 30; + ex->ent.angles[1] = rand() % 360; + CL_ExplosionParticles (pos); + if (type == TE_GRENADE_EXPLOSION_WATER) + S_StartSound (pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); + else + S_StartSound (pos, 0, 0, cl_sfx_grenexp, 1, ATTN_NORM, 0); + break; + + // RAFAEL + case TE_PLASMA_EXPLOSION: + MSG_ReadPos (&net_message, pos); + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_poly; + ex->ent.flags = RF_FULLBRIGHT; + ex->start = cl.frame.servertime - 100; + ex->light = 350; + ex->lightcolor[0] = 1.0; + ex->lightcolor[1] = 0.5; + ex->lightcolor[2] = 0.5; + ex->ent.angles[1] = rand() % 360; + ex->ent.model = cl_mod_explo4; + if (frand() < 0.5) + ex->baseframe = 15; + ex->frames = 15; + CL_ExplosionParticles (pos); + S_StartSound (pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); + break; + + case TE_EXPLOSION1: + case TE_EXPLOSION1_BIG: // PMM + case TE_ROCKET_EXPLOSION: + case TE_ROCKET_EXPLOSION_WATER: + case TE_EXPLOSION1_NP: // PMM + MSG_ReadPos (&net_message, pos); + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_poly; + ex->ent.flags = RF_FULLBRIGHT; + ex->start = cl.frame.servertime - 100; + ex->light = 350; + ex->lightcolor[0] = 1.0; + ex->lightcolor[1] = 0.5; + ex->lightcolor[2] = 0.5; + ex->ent.angles[1] = rand() % 360; + if (type != TE_EXPLOSION1_BIG) // PMM + ex->ent.model = cl_mod_explo4; // PMM + else + ex->ent.model = cl_mod_explo4_big; + if (frand() < 0.5) + ex->baseframe = 15; + ex->frames = 15; + if ((type != TE_EXPLOSION1_BIG) && (type != TE_EXPLOSION1_NP)) // PMM + CL_ExplosionParticles (pos); // PMM + if (type == TE_ROCKET_EXPLOSION_WATER) + S_StartSound (pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); + else + S_StartSound (pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); + break; + + case TE_BFG_EXPLOSION: + MSG_ReadPos (&net_message, pos); + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_poly; + ex->ent.flags = RF_FULLBRIGHT; + ex->start = cl.frame.servertime - 100; + ex->light = 350; + ex->lightcolor[0] = 0.0; + ex->lightcolor[1] = 1.0; + ex->lightcolor[2] = 0.0; + ex->ent.model = cl_mod_bfg_explo; + ex->ent.flags |= RF_TRANSLUCENT; + ex->ent.alpha = 0.30; + ex->frames = 4; + break; + + case TE_BFG_BIGEXPLOSION: + MSG_ReadPos (&net_message, pos); + CL_BFGExplosionParticles (pos); + break; + + case TE_BFG_LASER: + CL_ParseLaser (0xd0d1d2d3); + break; + + case TE_BUBBLETRAIL: + MSG_ReadPos (&net_message, pos); + MSG_ReadPos (&net_message, pos2); + CL_BubbleTrail (pos, pos2); + break; + + case TE_PARASITE_ATTACK: + case TE_MEDIC_CABLE_ATTACK: + ent = CL_ParseBeam (cl_mod_parasite_segment); + break; + + case TE_BOSSTPORT: // boss teleporting to station + MSG_ReadPos (&net_message, pos); + CL_BigTeleportParticles (pos); + S_StartSound (pos, 0, 0, S_RegisterSound ("misc/bigtele.wav"), 1, ATTN_NONE, 0); + break; + + case TE_GRAPPLE_CABLE: + ent = CL_ParseBeam2 (cl_mod_grapple_cable); + break; + + // RAFAEL + case TE_WELDING_SPARKS: + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + color = MSG_ReadByte (&net_message); + CL_ParticleEffect2 (pos, dir, color, cnt); + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_flash; + // note to self + // we need a better no draw flag + ex->ent.flags = RF_BEAM; + ex->start = cl.frame.servertime - 0.1; + ex->light = 100 + (rand()%75); + ex->lightcolor[0] = 1.0; + ex->lightcolor[1] = 1.0; + ex->lightcolor[2] = 0.3; + ex->ent.model = cl_mod_flash; + ex->frames = 2; + break; + + case TE_GREENBLOOD: + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + CL_ParticleEffect2 (pos, dir, 0xdf, 30); + break; + + // RAFAEL + case TE_TUNNEL_SPARKS: + cnt = MSG_ReadByte (&net_message); + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + color = MSG_ReadByte (&net_message); + CL_ParticleEffect3 (pos, dir, color, cnt); + break; + +//============= +//PGM + // PMM -following code integrated for flechette (different color) + case TE_BLASTER2: // green blaster hitting wall + case TE_FLECHETTE: // flechette + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + + // PMM + if (type == TE_BLASTER2) + CL_BlasterParticles2 (pos, dir, 0xd0); + else + CL_BlasterParticles2 (pos, dir, 0x6f); // 75 + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->ent.angles[0] = acos(dir[2])/M_PI*180; + // PMM - fixed to correct for pitch of 0 + if (dir[0]) + ex->ent.angles[1] = atan2(dir[1], dir[0])/M_PI*180; + else if (dir[1] > 0) + ex->ent.angles[1] = 90; + else if (dir[1] < 0) + ex->ent.angles[1] = 270; + else + ex->ent.angles[1] = 0; + + ex->type = ex_misc; + ex->ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT; + + // PMM + if (type == TE_BLASTER2) + ex->ent.skinnum = 1; + else // flechette + ex->ent.skinnum = 2; + + ex->start = cl.frame.servertime - 100; + ex->light = 150; + // PMM + if (type == TE_BLASTER2) + ex->lightcolor[1] = 1; + else // flechette + { + ex->lightcolor[0] = 0.19; + ex->lightcolor[1] = 0.41; + ex->lightcolor[2] = 0.75; + } + ex->ent.model = cl_mod_explode; + ex->frames = 4; + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + + case TE_LIGHTNING: + ent = CL_ParseLightning (cl_mod_lightning); + S_StartSound (NULL, ent, CHAN_WEAPON, cl_sfx_lightning, 1, ATTN_NORM, 0); + break; + + case TE_DEBUGTRAIL: + MSG_ReadPos (&net_message, pos); + MSG_ReadPos (&net_message, pos2); + CL_DebugTrail (pos, pos2); + break; + + case TE_PLAIN_EXPLOSION: + MSG_ReadPos (&net_message, pos); + + ex = CL_AllocExplosion (); + VectorCopy (pos, ex->ent.origin); + ex->type = ex_poly; + ex->ent.flags = RF_FULLBRIGHT; + ex->start = cl.frame.servertime - 100; + ex->light = 350; + ex->lightcolor[0] = 1.0; + ex->lightcolor[1] = 0.5; + ex->lightcolor[2] = 0.5; + ex->ent.angles[1] = rand() % 360; + ex->ent.model = cl_mod_explo4; + if (frand() < 0.5) + ex->baseframe = 15; + ex->frames = 15; + if (type == TE_ROCKET_EXPLOSION_WATER) + S_StartSound (pos, 0, 0, cl_sfx_watrexp, 1, ATTN_NORM, 0); + else + S_StartSound (pos, 0, 0, cl_sfx_rockexp, 1, ATTN_NORM, 0); + break; + + case TE_FLASHLIGHT: + MSG_ReadPos(&net_message, pos); + ent = MSG_ReadShort(&net_message); + CL_Flashlight(ent, pos); + break; + + case TE_FORCEWALL: + MSG_ReadPos(&net_message, pos); + MSG_ReadPos(&net_message, pos2); + color = MSG_ReadByte (&net_message); + CL_ForceWall(pos, pos2, color); + break; + + case TE_HEATBEAM: + ent = CL_ParsePlayerBeam (cl_mod_heatbeam); + break; + + case TE_MONSTER_HEATBEAM: + ent = CL_ParsePlayerBeam (cl_mod_monster_heatbeam); + break; + + case TE_HEATBEAM_SPARKS: +// cnt = MSG_ReadByte (&net_message); + cnt = 50; + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); +// r = MSG_ReadByte (&net_message); +// magnitude = MSG_ReadShort (&net_message); + r = 8; + magnitude = 60; + color = r & 0xff; + CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude); + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_HEATBEAM_STEAM: +// cnt = MSG_ReadByte (&net_message); + cnt = 20; + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); +// r = MSG_ReadByte (&net_message); +// magnitude = MSG_ReadShort (&net_message); +// color = r & 0xff; + color = 0xe0; + magnitude = 60; + CL_ParticleSteamEffect (pos, dir, color, cnt, magnitude); + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_STEAM: + CL_ParseSteam(); + break; + + case TE_BUBBLETRAIL2: +// cnt = MSG_ReadByte (&net_message); + cnt = 8; + MSG_ReadPos (&net_message, pos); + MSG_ReadPos (&net_message, pos2); + CL_BubbleTrail2 (pos, pos2, cnt); + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_MOREBLOOD: + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); + CL_ParticleEffect (pos, dir, 0xe8, 250); + break; + + case TE_CHAINFIST_SMOKE: + dir[0]=0; dir[1]=0; dir[2]=1; + MSG_ReadPos(&net_message, pos); + CL_ParticleSmokeEffect (pos, dir, 0, 20, 20); + break; + + case TE_ELECTRIC_SPARKS: + MSG_ReadPos (&net_message, pos); + MSG_ReadDir (&net_message, dir); +// CL_ParticleEffect (pos, dir, 109, 40); + CL_ParticleEffect (pos, dir, 0x75, 40); + //FIXME : replace or remove this sound + S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0); + break; + + case TE_TRACKER_EXPLOSION: + MSG_ReadPos (&net_message, pos); + CL_ColorFlash (pos, 0, 150, -1, -1, -1); + CL_ColorExplosionParticles (pos, 0, 1); +// CL_Tracker_Explode (pos); + S_StartSound (pos, 0, 0, cl_sfx_disrexp, 1, ATTN_NORM, 0); + break; + + case TE_TELEPORT_EFFECT: + case TE_DBALL_GOAL: + MSG_ReadPos (&net_message, pos); + CL_TeleportParticles (pos); + break; + + case TE_WIDOWBEAMOUT: + CL_ParseWidow (); + break; + + case TE_NUKEBLAST: + CL_ParseNuke (); + break; + + case TE_WIDOWSPLASH: + MSG_ReadPos (&net_message, pos); + CL_WidowSplash (pos); + break; +//PGM +//============== + + default: + Com_Error (ERR_DROP, "CL_ParseTEnt: bad type"); + } +} + +/* +================= +CL_AddBeams +================= +*/ +void CL_AddBeams (void) +{ + int i,j; + beam_t *b; + vec3_t dist, org; + float d; + entity_t ent; + float yaw, pitch; + float forward; + float len, steps; + float model_length; + +// update beams + for (i=0, b=cl_beams ; i< MAX_BEAMS ; i++, b++) + { + if (!b->model || b->endtime < cl.time) + continue; + + // if coming from the player, update the start position + if (b->entity == cl.playernum+1) // entity 0 is the world + { + VectorCopy (cl.refdef.vieworg, b->start); + b->start[2] -= 22; // adjust for view height + } + VectorAdd (b->start, b->offset, org); + + // calculate pitch and yaw + VectorSubtract (b->end, org, dist); + + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + // PMM - fixed to correct for pitch of 0 + if (dist[0]) + yaw = (atan2(dist[1], dist[0]) * 180 / M_PI); + else if (dist[1] > 0) + yaw = 90; + else + yaw = 270; + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = (atan2(dist[2], forward) * -180.0 / M_PI); + if (pitch < 0) + pitch += 360.0; + } + + // add new entities for the beams + d = VectorNormalize(dist); + + memset (&ent, 0, sizeof(ent)); + if (b->model == cl_mod_lightning) + { + model_length = 35.0; + d-= 20.0; // correction so it doesn't end in middle of tesla + } + else + { + model_length = 30.0; + } + steps = ceil(d/model_length); + len = (d-model_length)/(steps-1); + + // PMM - special case for lightning model .. if the real length is shorter than the model, + // flip it around & draw it from the end to the start. This prevents the model from going + // through the tesla mine (instead it goes through the target) + if ((b->model == cl_mod_lightning) && (d <= model_length)) + { +// Com_Printf ("special case\n"); + VectorCopy (b->end, ent.origin); + // offset to push beam outside of tesla model (negative because dist is from end to start + // for this beam) +// for (j=0 ; j<3 ; j++) +// ent.origin[j] -= dist[j]*10.0; + ent.model = b->model; + ent.flags = RF_FULLBRIGHT; + ent.angles[0] = pitch; + ent.angles[1] = yaw; + ent.angles[2] = rand()%360; + V_AddEntity (&ent); + return; + } + while (d > 0) + { + VectorCopy (org, ent.origin); + ent.model = b->model; + if (b->model == cl_mod_lightning) + { + ent.flags = RF_FULLBRIGHT; + ent.angles[0] = -pitch; + ent.angles[1] = yaw + 180.0; + ent.angles[2] = rand()%360; + } + else + { + ent.angles[0] = pitch; + ent.angles[1] = yaw; + ent.angles[2] = rand()%360; + } + +// Com_Printf("B: %d -> %d\n", b->entity, b->dest_entity); + V_AddEntity (&ent); + + for (j=0 ; j<3 ; j++) + org[j] += dist[j]*len; + d -= model_length; + } + } +} + + +/* +// Com_Printf ("Endpoint: %f %f %f\n", b->end[0], b->end[1], b->end[2]); +// Com_Printf ("Pred View Angles: %f %f %f\n", cl.predicted_angles[0], cl.predicted_angles[1], cl.predicted_angles[2]); +// Com_Printf ("Act View Angles: %f %f %f\n", cl.refdef.viewangles[0], cl.refdef.viewangles[1], cl.refdef.viewangles[2]); +// VectorCopy (cl.predicted_origin, b->start); +// b->start[2] += 22; // adjust for view height +// if (fabs(cl.refdef.vieworg[2] - b->start[2]) >= 10) { +// b->start[2] = cl.refdef.vieworg[2]; +// } + +// Com_Printf ("Time: %d %d %f\n", cl.time, cls.realtime, cls.frametime); +*/ + +extern cvar_t *hand; + +/* +================= +ROGUE - draw player locked beams +CL_AddPlayerBeams +================= +*/ +void CL_AddPlayerBeams (void) +{ + int i,j; + beam_t *b; + vec3_t dist, org; + float d; + entity_t ent; + float yaw, pitch; + float forward; + float len, steps; + int framenum; + float model_length; + + float hand_multiplier; + frame_t *oldframe; + player_state_t *ps, *ops; + +//PMM + if (hand) + { + if (hand->value == 2) + hand_multiplier = 0; + else if (hand->value == 1) + hand_multiplier = -1; + else + hand_multiplier = 1; + } + else + { + hand_multiplier = 1; + } +//PMM + +// update beams + for (i=0, b=cl_playerbeams ; i< MAX_BEAMS ; i++, b++) + { + vec3_t f,r,u; + if (!b->model || b->endtime < cl.time) + continue; + + if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) + { + + // if coming from the player, update the start position + if (b->entity == cl.playernum+1) // entity 0 is the world + { + // set up gun position + // code straight out of CL_AddViewWeapon + ps = &cl.frame.playerstate; + j = (cl.frame.serverframe - 1) & UPDATE_MASK; + oldframe = &cl.frames[j]; + if (oldframe->serverframe != cl.frame.serverframe-1 || !oldframe->valid) + oldframe = &cl.frame; // previous frame was dropped or involid + ops = &oldframe->playerstate; + for (j=0 ; j<3 ; j++) + { + b->start[j] = cl.refdef.vieworg[j] + ops->gunoffset[j] + + cl.lerpfrac * (ps->gunoffset[j] - ops->gunoffset[j]); + } + VectorMA (b->start, (hand_multiplier * b->offset[0]), cl.v_right, org); + VectorMA ( org, b->offset[1], cl.v_forward, org); + VectorMA ( org, b->offset[2], cl.v_up, org); + if ((hand) && (hand->value == 2)) { + VectorMA (org, -1, cl.v_up, org); + } + // FIXME - take these out when final + VectorCopy (cl.v_right, r); + VectorCopy (cl.v_forward, f); + VectorCopy (cl.v_up, u); + + } + else + VectorCopy (b->start, org); + } + else + { + // if coming from the player, update the start position + if (b->entity == cl.playernum+1) // entity 0 is the world + { + VectorCopy (cl.refdef.vieworg, b->start); + b->start[2] -= 22; // adjust for view height + } + VectorAdd (b->start, b->offset, org); + } + + // calculate pitch and yaw + VectorSubtract (b->end, org, dist); + +//PMM + if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum+1)) + { + vec_t len; + + len = VectorLength (dist); + VectorScale (f, len, dist); + VectorMA (dist, (hand_multiplier * b->offset[0]), r, dist); + VectorMA (dist, b->offset[1], f, dist); + VectorMA (dist, b->offset[2], u, dist); + if ((hand) && (hand->value == 2)) { + VectorMA (org, -1, cl.v_up, org); + } + } +//PMM + + if (dist[1] == 0 && dist[0] == 0) + { + yaw = 0; + if (dist[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + // PMM - fixed to correct for pitch of 0 + if (dist[0]) + yaw = (atan2(dist[1], dist[0]) * 180 / M_PI); + else if (dist[1] > 0) + yaw = 90; + else + yaw = 270; + if (yaw < 0) + yaw += 360; + + forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); + pitch = (atan2(dist[2], forward) * -180.0 / M_PI); + if (pitch < 0) + pitch += 360.0; + } + + if (cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) + { + if (b->entity != cl.playernum+1) + { + framenum = 2; +// Com_Printf ("Third person\n"); + ent.angles[0] = -pitch; + ent.angles[1] = yaw + 180.0; + ent.angles[2] = 0; +// Com_Printf ("%f %f - %f %f %f\n", -pitch, yaw+180.0, b->offset[0], b->offset[1], b->offset[2]); + AngleVectors(ent.angles, f, r, u); + + // if it's a non-origin offset, it's a player, so use the hardcoded player offset + if (!VectorCompare (b->offset, vec3_origin)) + { + VectorMA (org, -(b->offset[0])+1, r, org); + VectorMA (org, -(b->offset[1]), f, org); + VectorMA (org, -(b->offset[2])-10, u, org); + } + else + { + // if it's a monster, do the particle effect + CL_MonsterPlasma_Shell(b->start); + } + } + else + { + framenum = 1; + } + } + + // if it's the heatbeam, draw the particle effect + if ((cl_mod_heatbeam && (b->model == cl_mod_heatbeam) && (b->entity == cl.playernum+1))) + { + CL_Heatbeam (org, dist); + } + + // add new entities for the beams + d = VectorNormalize(dist); + + memset (&ent, 0, sizeof(ent)); + if (b->model == cl_mod_heatbeam) + { + model_length = 32.0; + } + else if (b->model == cl_mod_lightning) + { + model_length = 35.0; + d-= 20.0; // correction so it doesn't end in middle of tesla + } + else + { + model_length = 30.0; + } + steps = ceil(d/model_length); + len = (d-model_length)/(steps-1); + + // PMM - special case for lightning model .. if the real length is shorter than the model, + // flip it around & draw it from the end to the start. This prevents the model from going + // through the tesla mine (instead it goes through the target) + if ((b->model == cl_mod_lightning) && (d <= model_length)) + { +// Com_Printf ("special case\n"); + VectorCopy (b->end, ent.origin); + // offset to push beam outside of tesla model (negative because dist is from end to start + // for this beam) +// for (j=0 ; j<3 ; j++) +// ent.origin[j] -= dist[j]*10.0; + ent.model = b->model; + ent.flags = RF_FULLBRIGHT; + ent.angles[0] = pitch; + ent.angles[1] = yaw; + ent.angles[2] = rand()%360; + V_AddEntity (&ent); + return; + } + while (d > 0) + { + VectorCopy (org, ent.origin); + ent.model = b->model; + if(cl_mod_heatbeam && (b->model == cl_mod_heatbeam)) + { +// ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT; +// ent.alpha = 0.3; + ent.flags = RF_FULLBRIGHT; + ent.angles[0] = -pitch; + ent.angles[1] = yaw + 180.0; + ent.angles[2] = (cl.time) % 360; +// ent.angles[2] = rand()%360; + ent.frame = framenum; + } + else if (b->model == cl_mod_lightning) + { + ent.flags = RF_FULLBRIGHT; + ent.angles[0] = -pitch; + ent.angles[1] = yaw + 180.0; + ent.angles[2] = rand()%360; + } + else + { + ent.angles[0] = pitch; + ent.angles[1] = yaw; + ent.angles[2] = rand()%360; + } + +// Com_Printf("B: %d -> %d\n", b->entity, b->dest_entity); + V_AddEntity (&ent); + + for (j=0 ; j<3 ; j++) + org[j] += dist[j]*len; + d -= model_length; + } + } +} + +/* +================= +CL_AddExplosions +================= +*/ +void CL_AddExplosions (void) +{ + entity_t *ent; + int i; + explosion_t *ex; + float frac; + int f; + + memset (&ent, 0, sizeof(ent)); + + for (i=0, ex=cl_explosions ; i< MAX_EXPLOSIONS ; i++, ex++) + { + if (ex->type == ex_free) + continue; + frac = (cl.time - ex->start)/100.0; + f = floor(frac); + + ent = &ex->ent; + + switch (ex->type) + { + case ex_mflash: + if (f >= ex->frames-1) + ex->type = ex_free; + break; + case ex_misc: + if (f >= ex->frames-1) + { + ex->type = ex_free; + break; + } + ent->alpha = 1.0 - frac/(ex->frames-1); + break; + case ex_flash: + if (f >= 1) + { + ex->type = ex_free; + break; + } + ent->alpha = 1.0; + break; + case ex_poly: + if (f >= ex->frames-1) + { + ex->type = ex_free; + break; + } + + ent->alpha = (16.0 - (float)f)/16.0; + + if (f < 10) + { + ent->skinnum = (f>>1); + if (ent->skinnum < 0) + ent->skinnum = 0; + } + else + { + ent->flags |= RF_TRANSLUCENT; + if (f < 13) + ent->skinnum = 5; + else + ent->skinnum = 6; + } + break; + case ex_poly2: + if (f >= ex->frames-1) + { + ex->type = ex_free; + break; + } + + ent->alpha = (5.0 - (float)f)/5.0; + ent->skinnum = 0; + ent->flags |= RF_TRANSLUCENT; + break; + } + + if (ex->type == ex_free) + continue; + if (ex->light) + { + V_AddLight (ent->origin, ex->light*ent->alpha, + ex->lightcolor[0], ex->lightcolor[1], ex->lightcolor[2]); + } + + VectorCopy (ent->origin, ent->oldorigin); + + if (f < 0) + f = 0; + ent->frame = ex->baseframe + f + 1; + ent->oldframe = ex->baseframe + f; + ent->backlerp = 1.0 - cl.lerpfrac; + + V_AddEntity (ent); + } +} + + +/* +================= +CL_AddLasers +================= +*/ +void CL_AddLasers (void) +{ + laser_t *l; + int i; + + for (i=0, l=cl_lasers ; i< MAX_LASERS ; i++, l++) + { + if (l->endtime >= cl.time) + V_AddEntity (&l->ent); + } +} + +/* PMM - CL_Sustains */ +void CL_ProcessSustain () +{ + cl_sustain_t *s; + int i; + + for (i=0, s=cl_sustains; i< MAX_SUSTAINS; i++, s++) + { + if (s->id) + if ((s->endtime >= cl.time) && (cl.time >= s->nextthink)) + { +// Com_Printf ("think %d %d %d\n", cl.time, s->nextthink, s->thinkinterval); + s->think (s); + } + else if (s->endtime < cl.time) + s->id = 0; + } +} + +/* +================= +CL_AddTEnts +================= +*/ +void CL_AddTEnts (void) +{ + CL_AddBeams (); + // PMM - draw plasma beams + CL_AddPlayerBeams (); + CL_AddExplosions (); + CL_AddLasers (); + // PMM - set up sustain + CL_ProcessSustain(); +} diff --git a/client/cl_view.c b/client/cl_view.c new file mode 100644 index 000000000..4b758096b --- /dev/null +++ b/client/cl_view.c @@ -0,0 +1,584 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// cl_view.c -- player rendering positioning + +#include "client.h" + +//============= +// +// development tools for weapons +// +int gun_frame; +struct model_s *gun_model; + +//============= + +cvar_t *crosshair; +cvar_t *cl_testparticles; +cvar_t *cl_testentities; +cvar_t *cl_testlights; +cvar_t *cl_testblend; + +cvar_t *cl_stats; + + +int r_numdlights; +dlight_t r_dlights[MAX_DLIGHTS]; + +int r_numentities; +entity_t r_entities[MAX_ENTITIES]; + +int r_numparticles; +particle_t r_particles[MAX_PARTICLES]; + +lightstyle_t r_lightstyles[MAX_LIGHTSTYLES]; + +char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH]; +int num_cl_weaponmodels; + +/* +==================== +V_ClearScene + +Specifies the model that will be used as the world +==================== +*/ +void V_ClearScene (void) +{ + r_numdlights = 0; + r_numentities = 0; + r_numparticles = 0; +} + + +/* +===================== +V_AddEntity + +===================== +*/ +void V_AddEntity (entity_t *ent) +{ + if (r_numentities >= MAX_ENTITIES) + return; + r_entities[r_numentities++] = *ent; +} + + +/* +===================== +V_AddParticle + +===================== +*/ +void V_AddParticle (vec3_t org, int color, float alpha) +{ + particle_t *p; + + if (r_numparticles >= MAX_PARTICLES) + return; + p = &r_particles[r_numparticles++]; + VectorCopy (org, p->origin); + p->color = color; + p->alpha = alpha; +} + +/* +===================== +V_AddLight + +===================== +*/ +void V_AddLight (vec3_t org, float intensity, float r, float g, float b) +{ + dlight_t *dl; + + if (r_numdlights >= MAX_DLIGHTS) + return; + dl = &r_dlights[r_numdlights++]; + VectorCopy (org, dl->origin); + dl->intensity = intensity; + dl->color[0] = r; + dl->color[1] = g; + dl->color[2] = b; +} + + +/* +===================== +V_AddLightStyle + +===================== +*/ +void V_AddLightStyle (int style, float r, float g, float b) +{ + lightstyle_t *ls; + + if (style < 0 || style > MAX_LIGHTSTYLES) + Com_Error (ERR_DROP, "Bad light style %i", style); + ls = &r_lightstyles[style]; + + ls->white = r+g+b; + ls->rgb[0] = r; + ls->rgb[1] = g; + ls->rgb[2] = b; +} + +/* +================ +V_TestParticles + +If cl_testparticles is set, create 4096 particles in the view +================ +*/ +void V_TestParticles (void) +{ + particle_t *p; + int i, j; + float d, r, u; + + r_numparticles = MAX_PARTICLES; + for (i=0 ; i>3)&7)-3.5); + p = &r_particles[i]; + + for (j=0 ; j<3 ; j++) + p->origin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*d + + cl.v_right[j]*r + cl.v_up[j]*u; + + p->color = 8; + p->alpha = cl_testparticles->value; + } +} + +/* +================ +V_TestEntities + +If cl_testentities is set, create 32 player models +================ +*/ +void V_TestEntities (void) +{ + int i, j; + float f, r; + entity_t *ent; + + r_numentities = 32; + memset (r_entities, 0, sizeof(r_entities)); + + for (i=0 ; iorigin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*f + + cl.v_right[j]*r; + + ent->model = cl.baseclientinfo.model; + ent->skin = cl.baseclientinfo.skin; + } +} + +/* +================ +V_TestLights + +If cl_testlights is set, create 32 lights models +================ +*/ +void V_TestLights (void) +{ + int i, j; + float f, r; + dlight_t *dl; + + r_numdlights = 32; + memset (r_dlights, 0, sizeof(r_dlights)); + + for (i=0 ; iorigin[j] = cl.refdef.vieworg[j] + cl.v_forward[j]*f + + cl.v_right[j]*r; + dl->color[0] = ((i%6)+1) & 1; + dl->color[1] = (((i%6)+1) & 2)>>1; + dl->color[2] = (((i%6)+1) & 4)>>2; + dl->intensity = 200; + } +} + +//=================================================================== + +/* +================= +CL_PrepRefresh + +Call before entering a new level, or after changing dlls +================= +*/ +void CL_PrepRefresh (void) +{ + char mapname[32]; + int i; + char name[MAX_QPATH]; + float rotate; + vec3_t axis; + + if (!cl.configstrings[CS_MODELS+1][0]) + return; // no map loaded + + SCR_AddDirtyPoint (0, 0); + SCR_AddDirtyPoint (viddef.width-1, viddef.height-1); + + // let the render dll load the map + strcpy (mapname, cl.configstrings[CS_MODELS+1] + 5); // skip "maps/" + mapname[strlen(mapname)-4] = 0; // cut off ".bsp" + + // register models, pics, and skins + Com_Printf ("Map: %s\r", mapname); + SCR_UpdateScreen (); + re.BeginRegistration (mapname); + Com_Printf (" \r"); + + // precache status bar pics + Com_Printf ("pics\r"); + SCR_UpdateScreen (); + SCR_TouchPics (); + Com_Printf (" \r"); + + CL_RegisterTEntModels (); + + num_cl_weaponmodels = 1; + strcpy(cl_weaponmodels[0], "weapon.md2"); + + for (i=1 ; i 179) + Com_Error (ERR_DROP, "Bad fov: %f", fov_x); + + x = width/tan(fov_x/360*M_PI); + + a = atan (height/x); + + a = a*360/M_PI; + + return a; +} + +//============================================================================ + +// gun frame debugging functions +void V_Gun_Next_f (void) +{ + gun_frame++; + Com_Printf ("frame %i\n", gun_frame); +} + +void V_Gun_Prev_f (void) +{ + gun_frame--; + if (gun_frame < 0) + gun_frame = 0; + Com_Printf ("frame %i\n", gun_frame); +} + +void V_Gun_Model_f (void) +{ + char name[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + gun_model = NULL; + return; + } + Com_sprintf (name, sizeof(name), "models/%s/tris.md2", Cmd_Argv(1)); + gun_model = re.RegisterModel (name); +} + +//============================================================================ + + +/* +================= +SCR_DrawCrosshair +================= +*/ +void SCR_DrawCrosshair (void) +{ + if (!crosshair->value) + return; + + if (crosshair->modified) + { + crosshair->modified = false; + SCR_TouchPics (); + } + + if (!crosshair_pic[0]) + return; + + re.DrawPic (scr_vrect.x + ((scr_vrect.width - crosshair_width)>>1) + , scr_vrect.y + ((scr_vrect.height - crosshair_height)>>1), crosshair_pic); +} + +/* +================== +V_RenderView + +================== +*/ +void V_RenderView( float stereo_separation ) +{ + extern int entitycmpfnc( const entity_t *, const entity_t * ); + + if (cls.state != ca_active) + return; + + if (!cl.refresh_prepped) + return; // still loading + + if (cl_timedemo->value) + { + if (!cl.timedemo_start) + cl.timedemo_start = Sys_Milliseconds (); + cl.timedemo_frames++; + } + + // an invalid frame will just use the exact previous refdef + // we can't use the old frame if the video mode has changed, though... + if ( cl.frame.valid && (cl.force_refdef || !cl_paused->value) ) + { + cl.force_refdef = false; + + V_ClearScene (); + + // build a refresh entity list and calc cl.sim* + // this also calls CL_CalcViewValues which loads + // v_forward, etc. + CL_AddEntities (); + + if (cl_testparticles->value) + V_TestParticles (); + if (cl_testentities->value) + V_TestEntities (); + if (cl_testlights->value) + V_TestLights (); + if (cl_testblend->value) + { + cl.refdef.blend[0] = 1; + cl.refdef.blend[1] = 0.5; + cl.refdef.blend[2] = 0.25; + cl.refdef.blend[3] = 0.5; + } + + // offset vieworg appropriately if we're doing stereo separation + if ( stereo_separation != 0 ) + { + vec3_t tmp; + + VectorScale( cl.v_right, stereo_separation, tmp ); + VectorAdd( cl.refdef.vieworg, tmp, cl.refdef.vieworg ); + } + + // never let it sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis + cl.refdef.vieworg[0] += 1.0/16; + cl.refdef.vieworg[1] += 1.0/16; + cl.refdef.vieworg[2] += 1.0/16; + + cl.refdef.x = scr_vrect.x; + cl.refdef.y = scr_vrect.y; + cl.refdef.width = scr_vrect.width; + cl.refdef.height = scr_vrect.height; + cl.refdef.fov_y = CalcFov (cl.refdef.fov_x, cl.refdef.width, cl.refdef.height); + cl.refdef.time = cl.time*0.001; + + cl.refdef.areabits = cl.frame.areabits; + + if (!cl_add_entities->value) + r_numentities = 0; + if (!cl_add_particles->value) + r_numparticles = 0; + if (!cl_add_lights->value) + r_numdlights = 0; + if (!cl_add_blend->value) + { + VectorClear (cl.refdef.blend); + } + + cl.refdef.num_entities = r_numentities; + cl.refdef.entities = r_entities; + cl.refdef.num_particles = r_numparticles; + cl.refdef.particles = r_particles; + cl.refdef.num_dlights = r_numdlights; + cl.refdef.dlights = r_dlights; + cl.refdef.lightstyles = r_lightstyles; + + cl.refdef.rdflags = cl.frame.playerstate.rdflags; + + // sort entities for better cache locality + qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), (int (*)(const void *, const void *))entitycmpfnc ); + } + + re.RenderFrame (&cl.refdef); + if (cl_stats->value) + Com_Printf ("ent:%i lt:%i part:%i\n", r_numentities, r_numdlights, r_numparticles); + if ( log_stats->value && ( log_stats_file != 0 ) ) + fprintf( log_stats_file, "%i,%i,%i,",r_numentities, r_numdlights, r_numparticles); + + + SCR_AddDirtyPoint (scr_vrect.x, scr_vrect.y); + SCR_AddDirtyPoint (scr_vrect.x+scr_vrect.width-1, + scr_vrect.y+scr_vrect.height-1); + + SCR_DrawCrosshair (); +} + + +/* +============= +V_Viewpos_f +============= +*/ +void V_Viewpos_f (void) +{ + Com_Printf ("(%i %i %i) : %i\n", (int)cl.refdef.vieworg[0], + (int)cl.refdef.vieworg[1], (int)cl.refdef.vieworg[2], + (int)cl.refdef.viewangles[YAW]); +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + Cmd_AddCommand ("gun_next", V_Gun_Next_f); + Cmd_AddCommand ("gun_prev", V_Gun_Prev_f); + Cmd_AddCommand ("gun_model", V_Gun_Model_f); + + Cmd_AddCommand ("viewpos", V_Viewpos_f); + + crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE); + + cl_testblend = Cvar_Get ("cl_testblend", "0", 0); + cl_testparticles = Cvar_Get ("cl_testparticles", "0", 0); + cl_testentities = Cvar_Get ("cl_testentities", "0", 0); + cl_testlights = Cvar_Get ("cl_testlights", "0", 0); + + cl_stats = Cvar_Get ("cl_stats", "0", 0); +} diff --git a/client/client.h b/client/client.h new file mode 100644 index 000000000..2dc57a26e --- /dev/null +++ b/client/client.h @@ -0,0 +1,584 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// client.h -- primary header for client + +//define PARANOID // speed sapping error checking + +#include +#include +#include +#include +#include + +#include "ref.h" + +#include "vid.h" +#include "screen.h" +#include "sound.h" +#include "input.h" +#include "keys.h" +#include "console.h" +#include "cdaudio.h" + +//============================================================================= + +typedef struct +{ + qboolean valid; // cleared if delta parsing was invalid + int serverframe; + int servertime; // server time the message is valid for (in msec) + int deltaframe; + byte areabits[MAX_MAP_AREAS/8]; // portalarea visibility bits + player_state_t playerstate; + int num_entities; + int parse_entities; // non-masked index into cl_parse_entities array +} frame_t; + +typedef struct +{ + entity_state_t baseline; // delta from this if not from a previous frame + entity_state_t current; + entity_state_t prev; // will always be valid, but might just be a copy of current + + int serverframe; // if not current, this ent isn't in the frame + + int trailcount; // for diminishing grenade trails + vec3_t lerp_origin; // for trails (variable hz) + + int fly_stoptime; +} centity_t; + +#define MAX_CLIENTWEAPONMODELS 20 // PGM -- upped from 16 to fit the chainfist vwep + +typedef struct +{ + char name[MAX_QPATH]; + char cinfo[MAX_QPATH]; + struct image_s *skin; + struct image_s *icon; + char iconname[MAX_QPATH]; + struct model_s *model; + struct model_s *weaponmodel[MAX_CLIENTWEAPONMODELS]; +} clientinfo_t; + +extern char cl_weaponmodels[MAX_CLIENTWEAPONMODELS][MAX_QPATH]; +extern int num_cl_weaponmodels; + +#define CMD_BACKUP 64 // allow a lot of command backups for very fast systems + +// +// the client_state_t structure is wiped completely at every +// server map change +// +typedef struct +{ + int timeoutcount; + + int timedemo_frames; + int timedemo_start; + + qboolean refresh_prepped; // false if on new level or new ref dll + qboolean sound_prepped; // ambient sounds can start + qboolean force_refdef; // vid has changed, so we can't use a paused refdef + + int parse_entities; // index (not anded off) into cl_parse_entities[] + + usercmd_t cmd; + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmd_time[CMD_BACKUP]; // time sent, for calculating pings + short predicted_origins[CMD_BACKUP][3]; // for debug comparing against server + + float predicted_step; // for stair up smoothing + unsigned predicted_step_time; + + vec3_t predicted_origin; // generated by CL_PredictMovement + vec3_t predicted_angles; + vec3_t prediction_error; + + frame_t frame; // received from server + int surpressCount; // number of messages rate supressed + frame_t frames[UPDATE_BACKUP]; + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int time; // this is the time value that the client + // is rendering at. always <= cls.realtime + float lerpfrac; // between oldframe and frame + + refdef_t refdef; + + vec3_t v_forward, v_right, v_up; // set when refdef.angles is set + + // + // transient data from server + // + char layout[1024]; // general 2D overlay + int inventory[MAX_ITEMS]; + + // + // non-gameserver infornamtion + // FIXME: move this cinematic stuff into the cin_t structure + FILE *cinematic_file; + int cinematictime; // cls.realtime for first cinematic frame + int cinematicframe; + char cinematicpalette[768]; + qboolean cinematicpalette_active; + + // + // server state information + // + qboolean attractloop; // running the attract loop, any key will menu + int servercount; // server identification for prespawns + char gamedir[MAX_QPATH]; + int playernum; + + char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH]; + + // + // locally derived information from server state + // + struct model_s *model_draw[MAX_MODELS]; + struct cmodel_s *model_clip[MAX_MODELS]; + + struct sfx_s *sound_precache[MAX_SOUNDS]; + struct image_s *image_precache[MAX_IMAGES]; + + clientinfo_t clientinfo[MAX_CLIENTS]; + clientinfo_t baseclientinfo; +} client_state_t; + +extern client_state_t cl; + +/* +================================================================== + +the client_static_t structure is persistant through an arbitrary number +of server connections + +================================================================== +*/ + +typedef enum { + ca_uninitialized, + ca_disconnected, // not talking to a server + ca_connecting, // sending request packets to the server + ca_connected, // netchan_t established, waiting for svc_serverdata + ca_active // game views should be displayed +} connstate_t; + +typedef enum { + dl_none, + dl_model, + dl_sound, + dl_skin, + dl_single +} dltype_t; // download type + +typedef enum {key_game, key_console, key_message, key_menu} keydest_t; + +typedef struct +{ + connstate_t state; + keydest_t key_dest; + + int framecount; + int realtime; // always increasing, no clamping, etc + float frametime; // seconds since last frame + +// screen rendering information + float disable_screen; // showing loading plaque between levels + // or changing rendering dlls + // if time gets > 30 seconds ahead, break it + int disable_servercount; // when we receive a frame and cl.servercount + // > cls.disable_servercount, clear disable_screen + +// connection information + char servername[MAX_OSPATH]; // name of server from original connect + float connect_time; // for connection retransmits + + int quakePort; // a 16 bit value that allows quake servers + // to work around address translating routers + netchan_t netchan; + int serverProtocol; // in case we are doing some kind of version hack + + int challenge; // from the server to use for connecting + + FILE *download; // file transfer from server + char downloadtempname[MAX_OSPATH]; + char downloadname[MAX_OSPATH]; + int downloadnumber; + dltype_t downloadtype; + int downloadpercent; + +// demo recording info must be here, so it isn't cleared on level change + qboolean demorecording; + qboolean demowaiting; // don't record until a non-delta message is received + FILE *demofile; +} client_static_t; + +extern client_static_t cls; + +//============================================================================= + +// +// cvars +// +extern cvar_t *cl_stereo_separation; +extern cvar_t *cl_stereo; + +extern cvar_t *cl_gun; +extern cvar_t *cl_add_blend; +extern cvar_t *cl_add_lights; +extern cvar_t *cl_add_particles; +extern cvar_t *cl_add_entities; +extern cvar_t *cl_predict; +extern cvar_t *cl_footsteps; +extern cvar_t *cl_noskins; +extern cvar_t *cl_autoskins; + +extern cvar_t *cl_upspeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_sidespeed; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; + +extern cvar_t *cl_run; + +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_shownet; +extern cvar_t *cl_showmiss; +extern cvar_t *cl_showclamp; + +extern cvar_t *lookspring; +extern cvar_t *lookstrafe; +extern cvar_t *sensitivity; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *freelook; + +extern cvar_t *cl_lightlevel; // FIXME HACK + +extern cvar_t *cl_paused; +extern cvar_t *cl_timedemo; + +extern cvar_t *cl_vwep; + +typedef struct +{ + int key; // so entities can reuse same entry + vec3_t color; + vec3_t origin; + float radius; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less +} cdlight_t; + +extern centity_t cl_entities[MAX_EDICTS]; +extern cdlight_t cl_dlights[MAX_DLIGHTS]; + +// the cl_parse_entities must be large enough to hold UPDATE_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#define MAX_PARSE_ENTITIES 1024 +extern entity_state_t cl_parse_entities[MAX_PARSE_ENTITIES]; + +//============================================================================= + +extern netadr_t net_from; +extern sizebuf_t net_message; + +void DrawString (int x, int y, char *s); +void DrawAltString (int x, int y, char *s); // toggle high bit +qboolean CL_CheckOrDownloadFile (char *filename); + +void CL_AddNetgraph (void); + +//ROGUE +typedef struct cl_sustain +{ + int id; + int type; + int endtime; + int nextthink; + int thinkinterval; + vec3_t org; + vec3_t dir; + int color; + int count; + int magnitude; + void (*think)(struct cl_sustain *self); +} cl_sustain_t; + +#define MAX_SUSTAINS 32 +void CL_ParticleSteamEffect2(cl_sustain_t *self); + +void CL_TeleporterParticles (entity_state_t *ent); +void CL_ParticleEffect (vec3_t org, vec3_t dir, int color, int count); +void CL_ParticleEffect2 (vec3_t org, vec3_t dir, int color, int count); + +// RAFAEL +void CL_ParticleEffect3 (vec3_t org, vec3_t dir, int color, int count); + + +//================================================= + +// ======== +// PGM +typedef struct particle_s +{ + struct particle_s *next; + + float time; + + vec3_t org; + vec3_t vel; + vec3_t accel; + float color; + float colorvel; + float alpha; + float alphavel; +} cparticle_t; + + +#define PARTICLE_GRAVITY 40 +#define BLASTER_PARTICLE_COLOR 0xe0 +// PMM +#define INSTANT_PARTICLE -10000.0 +// PGM +// ======== + +void CL_ClearEffects (void); +void CL_ClearTEnts (void); +void CL_BlasterTrail (vec3_t start, vec3_t end); +void CL_QuadTrail (vec3_t start, vec3_t end); +void CL_RailTrail (vec3_t start, vec3_t end); +void CL_BubbleTrail (vec3_t start, vec3_t end); +void CL_FlagTrail (vec3_t start, vec3_t end, float color); + +// RAFAEL +void CL_IonripperTrail (vec3_t start, vec3_t end); + +// ======== +// PGM +void CL_BlasterParticles2 (vec3_t org, vec3_t dir, unsigned int color); +void CL_BlasterTrail2 (vec3_t start, vec3_t end); +void CL_DebugTrail (vec3_t start, vec3_t end); +void CL_SmokeTrail (vec3_t start, vec3_t end, int colorStart, int colorRun, int spacing); +void CL_Flashlight (int ent, vec3_t pos); +void CL_ForceWall (vec3_t start, vec3_t end, int color); +void CL_FlameEffects (centity_t *ent, vec3_t origin); +void CL_GenericParticleEffect (vec3_t org, vec3_t dir, int color, int count, int numcolors, int dirspread, float alphavel); +void CL_BubbleTrail2 (vec3_t start, vec3_t end, int dist); +void CL_Heatbeam (vec3_t start, vec3_t end); +void CL_ParticleSteamEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude); +void CL_TrackerTrail (vec3_t start, vec3_t end, int particleColor); +void CL_Tracker_Explode(vec3_t origin); +void CL_TagTrail (vec3_t start, vec3_t end, float color); +void CL_ColorFlash (vec3_t pos, int ent, int intensity, float r, float g, float b); +void CL_Tracker_Shell(vec3_t origin); +void CL_MonsterPlasma_Shell(vec3_t origin); +void CL_ColorExplosionParticles (vec3_t org, int color, int run); +void CL_ParticleSmokeEffect (vec3_t org, vec3_t dir, int color, int count, int magnitude); +void CL_Widowbeamout (cl_sustain_t *self); +void CL_Nukeblast (cl_sustain_t *self); +void CL_WidowSplash (vec3_t org); +// PGM +// ======== + +int CL_ParseEntityBits (unsigned *bits); +void CL_ParseDelta (entity_state_t *from, entity_state_t *to, int number, int bits); +void CL_ParseFrame (void); + +void CL_ParseTEnt (void); +void CL_ParseConfigString (void); +void CL_ParseMuzzleFlash (void); +void CL_ParseMuzzleFlash2 (void); +void SmokeAndFlash(vec3_t origin); + +void CL_SetLightstyle (int i); + +void CL_RunParticles (void); +void CL_RunDLights (void); +void CL_RunLightStyles (void); + +void CL_AddEntities (void); +void CL_AddDLights (void); +void CL_AddTEnts (void); +void CL_AddLightStyles (void); + +//================================================= + +void CL_PrepRefresh (void); +void CL_RegisterSounds (void); + +void CL_Quit_f (void); + +void IN_Accumulate (void); + +void CL_ParseLayout (void); + + +// +// cl_main +// +extern refexport_t re; // interface to refresh .dll + +void CL_Init (void); + +void CL_FixUpGender(void); +void CL_Disconnect (void); +void CL_Disconnect_f (void); +void CL_GetChallengePacket (void); +void CL_PingServers_f (void); +void CL_Snd_Restart_f (void); +void CL_RequestNextDownload (void); + +// +// cl_input +// +typedef struct +{ + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame + int state; +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +void CL_InitInput (void); +void CL_SendCmd (void); +void CL_SendMove (usercmd_t *cmd); + +void CL_ClearState (void); + +void CL_ReadPackets (void); + +int CL_ReadFromServer (void); +void CL_WriteToServer (usercmd_t *cmd); +void CL_BaseMove (usercmd_t *cmd); + +void IN_CenterView (void); + +float CL_KeyState (kbutton_t *key); +char *Key_KeynumToString (int keynum); + +// +// cl_demo.c +// +void CL_WriteDemoMessage (void); +void CL_Stop_f (void); +void CL_Record_f (void); + +// +// cl_parse.c +// +extern char *svc_strings[256]; + +void CL_ParseServerMessage (void); +void CL_LoadClientinfo (clientinfo_t *ci, char *s); +void SHOWNET(char *s); +void CL_ParseClientinfo (int player); +void CL_Download_f (void); + +// +// cl_view.c +// +extern int gun_frame; +extern struct model_s *gun_model; + +void V_Init (void); +void V_RenderView( float stereo_separation ); +void V_AddEntity (entity_t *ent); +void V_AddParticle (vec3_t org, int color, float alpha); +void V_AddLight (vec3_t org, float intensity, float r, float g, float b); +void V_AddLightStyle (int style, float r, float g, float b); + +// +// cl_tent.c +// +void CL_RegisterTEntSounds (void); +void CL_RegisterTEntModels (void); +void CL_SmokeAndFlash(vec3_t origin); + + +// +// cl_pred.c +// +void CL_InitPrediction (void); +void CL_PredictMove (void); +void CL_CheckPredictionError (void); + +// +// cl_fx.c +// +cdlight_t *CL_AllocDlight (int key); +void CL_BigTeleportParticles (vec3_t org); +void CL_RocketTrail (vec3_t start, vec3_t end, centity_t *old); +void CL_DiminishingTrail (vec3_t start, vec3_t end, centity_t *old, int flags); +void CL_FlyEffect (centity_t *ent, vec3_t origin); +void CL_BfgParticles (entity_t *ent); +void CL_AddParticles (void); +void CL_EntityEvent (entity_state_t *ent); +// RAFAEL +void CL_TrapParticles (entity_t *ent); + +// +// menus +// +void M_Init (void); +void M_Keydown (int key); +void M_Draw (void); +void M_Menu_Main_f (void); +void M_ForceMenuOff (void); +void M_AddToServerList (netadr_t adr, char *info); + +// +// cl_inv.c +// +void CL_ParseInventory (void); +void CL_KeyInventory (int key); +void CL_DrawInventory (void); + +// +// cl_pred.c +// +void CL_PredictMovement (void); + +#if id386 +void x86_TimerStart( void ); +void x86_TimerStop( void ); +void x86_TimerInit( unsigned long smallest, unsigned longest ); +unsigned long *x86_TimerGetHistogram( void ); +#endif diff --git a/client/console.c b/client/console.c new file mode 100644 index 000000000..99e2e86eb --- /dev/null +++ b/client/console.c @@ -0,0 +1,682 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// console.c + +#include "client.h" + +console_t con; + +cvar_t *con_notifytime; + + +#define MAXCMDLINE 256 +extern char key_lines[32][MAXCMDLINE]; +extern int edit_line; +extern int key_linepos; + + +void DrawString (int x, int y, char *s) +{ + while (*s) + { + re.DrawChar (x, y, *s); + x+=8; + s++; + } +} + +void DrawAltString (int x, int y, char *s) +{ + while (*s) + { + re.DrawChar (x, y, *s ^ 0x80); + x+=8; + s++; + } +} + + +void Key_ClearTyping (void) +{ + key_lines[edit_line][1] = 0; // clear any typing + key_linepos = 1; +} + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) +{ + SCR_EndLoadingPlaque (); // get rid of loading plaque + + if (cl.attractloop) + { + Cbuf_AddText ("killserver\n"); + return; + } + + if (cls.state == ca_disconnected) + { // start the demo loop again + Cbuf_AddText ("d1\n"); + return; + } + + Key_ClearTyping (); + Con_ClearNotify (); + + if (cls.key_dest == key_console) + { + M_ForceMenuOff (); + Cvar_Set ("paused", "0"); + } + else + { + M_ForceMenuOff (); + cls.key_dest = key_console; + + if (Cvar_VariableValue ("maxclients") == 1 + && Com_ServerState ()) + Cvar_Set ("paused", "1"); + } +} + +/* +================ +Con_ToggleChat_f +================ +*/ +void Con_ToggleChat_f (void) +{ + Key_ClearTyping (); + + if (cls.key_dest == key_console) + { + if (cls.state == ca_active) + { + M_ForceMenuOff (); + cls.key_dest = key_game; + } + } + else + cls.key_dest = key_console; + + Con_ClearNotify (); +} + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) +{ + memset (con.text, ' ', CON_TEXTSIZE); +} + + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f (void) +{ + int l, x; + char *line; + FILE *f; + char buffer[1024]; + char name[MAX_OSPATH]; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: condump \n"); + return; + } + + Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1)); + + Com_Printf ("Dumped console text to %s.\n", name); + FS_CreatePath (name); + f = fopen (name, "w"); + if (!f) + { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + + // skip empty lines + for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) + { + line = con.text + (l%con.totallines)*con.linewidth; + for (x=0 ; x=0 ; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + for (x=0; buffer[x]; x++) + buffer[x] &= 0x7f; + + fprintf (f, "%s\n", buffer); + } + + fclose (f); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + int i; + + for (i=0 ; i> 3) - 2; + + if (width == con.linewidth) + return; + + if (width < 1) // video hasn't been initialized yet + { + width = 38; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + memset (con.text, ' ', CON_TEXTSIZE); + } + else + { + oldwidth = con.linewidth; + con.linewidth = width; + oldtotallines = con.totallines; + con.totallines = CON_TEXTSIZE / con.linewidth; + numlines = oldtotallines; + + if (con.totallines < numlines) + numlines = con.totallines; + + numchars = oldwidth; + + if (con.linewidth < numchars) + numchars = con.linewidth; + + memcpy (tbuf, con.text, CON_TEXTSIZE); + memset (con.text, ' ', CON_TEXTSIZE); + + for (i=0 ; i con.linewidth) ) + con.x = 0; + + txt++; + + if (cr) + { + con.current--; + cr = false; + } + + + if (!con.x) + { + Con_Linefeed (); + // mark time for transparent overlay + if (con.current >= 0) + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + + switch (c) + { + case '\n': + con.x = 0; + break; + + case '\r': + con.x = 0; + cr = 1; + break; + + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = c | mask | con.ormask; + con.x++; + if (con.x >= con.linewidth) + con.x = 0; + break; + } + + } +} + + +/* +============== +Con_CenteredPrint +============== +*/ +void Con_CenteredPrint (char *text) +{ + int l; + char buffer[1024]; + + l = strlen(text); + l = (con.linewidth-l)/2; + if (l < 0) + l = 0; + memset (buffer, ' ', l); + strcpy (buffer+l, text); + strcat (buffer, "\n"); + Con_Print (buffer); +} + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +The input line scrolls horizontally if typing goes beyond the right edge +================ +*/ +void Con_DrawInput (void) +{ + int y; + int i; + char *text; + + if (cls.key_dest == key_menu) + return; + if (cls.key_dest != key_console && cls.state == ca_active) + return; // don't draw anything (always draw if not active) + + text = key_lines[edit_line]; + +// add the cursor frame + text[key_linepos] = 10+((int)(cls.realtime>>8)&1); + +// fill out remainder with spaces + for (i=key_linepos+1 ; i< con.linewidth ; i++) + text[i] = ' '; + +// prestep if horizontally scrolling + if (key_linepos >= con.linewidth) + text += 1 + key_linepos - con.linewidth; + +// draw it + y = con.vislines-16; + + for (i=0 ; i con_notifytime->value*1000) + continue; + text = con.text + (i % con.totallines)*con.linewidth; + + for (x = 0 ; x < con.linewidth ; x++) + re.DrawChar ( (x+1)<<3, v, text[x]); + + v += 8; + } + + + if (cls.key_dest == key_message) + { + if (chat_team) + { + DrawString (8, v, "say_team:"); + skip = 11; + } + else + { + DrawString (8, v, "say:"); + skip = 5; + } + + s = chat_buffer; + if (chat_bufferlen > (viddef.width>>3)-(skip+1)) + s += chat_bufferlen - ((viddef.width>>3)-(skip+1)); + x = 0; + while(s[x]) + { + re.DrawChar ( (x+skip)<<3, v, s[x]); + x++; + } + re.DrawChar ( (x+skip)<<3, v, 10+((cls.realtime>>8)&1)); + v += 8; + } + + if (v) + { + SCR_AddDirtyPoint (0,0); + SCR_AddDirtyPoint (viddef.width-1, v); + } +} + +/* +================ +Con_DrawConsole + +Draws the console with the solid background +================ +*/ +void Con_DrawConsole (float frac) +{ + int i, j, x, y, n; + int rows; + char *text; + int row; + int lines; + char version[64]; + char dlbar[1024]; + + lines = viddef.height * frac; + if (lines <= 0) + return; + + if (lines > viddef.height) + lines = viddef.height; + +// draw the background + re.DrawStretchPic (0, -viddef.height+lines, viddef.width, viddef.height, "conback"); + SCR_AddDirtyPoint (0,0); + SCR_AddDirtyPoint (viddef.width-1,lines-1); + + Com_sprintf (version, sizeof(version), "v%4.2f", VERSION); + for (x=0 ; x<5 ; x++) + re.DrawChar (viddef.width-44+x*8, lines-12, 128 + version[x] ); + +// draw the text + con.vislines = lines; + +#if 0 + rows = (lines-8)>>3; // rows of text to draw + + y = lines - 24; +#else + rows = (lines-22)>>3; // rows of text to draw + + y = lines - 30; +#endif + +// draw from the bottom up + if (con.display != con.current) + { + // draw arrows to show the buffer is backscrolled + for (x=0 ; x= con.totallines) + break; // past scrollback wrap point + + text = con.text + (row % con.totallines)*con.linewidth; + + for (x=0 ; x i) { + y = x - i - 11; + strncpy(dlbar, text, i); + dlbar[i] = 0; + strcat(dlbar, "..."); + } else + strcpy(dlbar, text); + strcat(dlbar, ": "); + i = strlen(dlbar); + dlbar[i++] = '\x80'; + // where's the dot go? + if (cls.downloadpercent == 0) + n = 0; + else + n = y * cls.downloadpercent / 100; + + for (j = 0; j < y; j++) + if (j == n) + dlbar[i++] = '\x83'; + else + dlbar[i++] = '\x81'; + dlbar[i++] = '\x82'; + dlbar[i] = 0; + + sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent); + + // draw it + y = con.vislines-12; + for (i = 0; i < strlen(dlbar); i++) + re.DrawChar ( (i+1)<<3, y, dlbar[i]); + } +//ZOID + +// draw the input prompt, user text, and cursor if desired + Con_DrawInput (); +} + + diff --git a/client/console.h b/client/console.h new file mode 100644 index 000000000..6e109fe1d --- /dev/null +++ b/client/console.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// +// console +// + +#define NUM_CON_TIMES 4 + +#define CON_TEXTSIZE 32768 +typedef struct +{ + qboolean initialized; + + char text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int ormask; // high bit mask for colored characters + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float cursorspeed; + + int vislines; + + float times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines +} console_t; + +extern console_t con; + +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init (void); +void Con_DrawConsole (float frac); +void Con_Print (char *txt); +void Con_CenteredPrint (char *text); +void Con_Clear_f (void); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_ToggleConsole_f (void); diff --git a/client/input.h b/client/input.h new file mode 100644 index 000000000..8ce345771 --- /dev/null +++ b/client/input.h @@ -0,0 +1,34 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// input.h -- external (non-keyboard) input devices + +void IN_Init (void); + +void IN_Shutdown (void); + +void IN_Commands (void); +// oportunity for devices to stick commands on the script buffer + +void IN_Frame (void); + +void IN_Move (usercmd_t *cmd); +// add additional movement on top of the keyboard move cmd + +void IN_Activate (qboolean active); diff --git a/client/keys.c b/client/keys.c new file mode 100644 index 000000000..6f6728a72 --- /dev/null +++ b/client/keys.c @@ -0,0 +1,943 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + + +#define MAXCMDLINE 256 +char key_lines[32][MAXCMDLINE]; +int key_linepos; +int shift_down=false; +int anykeydown; + +int edit_line=0; +int history_line=0; + +int key_waiting; +char *keybindings[256]; +qboolean consolekeys[256]; // if true, can't be rebound while in console +qboolean menubound[256]; // if true, can't be rebound while in menu +int keyshift[256]; // key to map to if shift held down in console +int key_repeats[256]; // if > 1, it is autorepeating +qboolean keydown[256]; + +typedef struct +{ + char *name; + int keynum; +} keyname_t; + +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + {"AUX17", K_AUX17}, + {"AUX18", K_AUX18}, + {"AUX19", K_AUX19}, + {"AUX20", K_AUX20}, + {"AUX21", K_AUX21}, + {"AUX22", K_AUX22}, + {"AUX23", K_AUX23}, + {"AUX24", K_AUX24}, + {"AUX25", K_AUX25}, + {"AUX26", K_AUX26}, + {"AUX27", K_AUX27}, + {"AUX28", K_AUX28}, + {"AUX29", K_AUX29}, + {"AUX30", K_AUX30}, + {"AUX31", K_AUX31}, + {"AUX32", K_AUX32}, + + {"KP_HOME", K_KP_HOME }, + {"KP_UPARROW", K_KP_UPARROW }, + {"KP_PGUP", K_KP_PGUP }, + {"KP_LEFTARROW", K_KP_LEFTARROW }, + {"KP_5", K_KP_5 }, + {"KP_RIGHTARROW", K_KP_RIGHTARROW }, + {"KP_END", K_KP_END }, + {"KP_DOWNARROW", K_KP_DOWNARROW }, + {"KP_PGDN", K_KP_PGDN }, + {"KP_ENTER", K_KP_ENTER }, + {"KP_INS", K_KP_INS }, + {"KP_DEL", K_KP_DEL }, + {"KP_SLASH", K_KP_SLASH }, + {"KP_MINUS", K_KP_MINUS }, + {"KP_PLUS", K_KP_PLUS }, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {NULL,0} +}; + +/* +============================================================================== + + LINE TYPING INTO THE CONSOLE + +============================================================================== +*/ + +void CompleteCommand (void) +{ + char *cmd, *s; + + s = key_lines[edit_line]+1; + if (*s == '\\' || *s == '/') + s++; + + cmd = Cmd_CompleteCommand (s); + if (!cmd) + cmd = Cvar_CompleteVariable (s); + if (cmd) + { + key_lines[edit_line][1] = '/'; + strcpy (key_lines[edit_line]+2, cmd); + key_linepos = strlen(cmd)+2; + key_lines[edit_line][key_linepos] = ' '; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + return; + } +} + +/* +==================== +Key_Console + +Interactive line editing and console scrollback +==================== +*/ +void Key_Console (int key) +{ + + switch ( key ) + { + case K_KP_SLASH: + key = '/'; + break; + case K_KP_MINUS: + key = '-'; + break; + case K_KP_PLUS: + key = '+'; + break; + case K_KP_HOME: + key = '7'; + break; + case K_KP_UPARROW: + key = '8'; + break; + case K_KP_PGUP: + key = '9'; + break; + case K_KP_LEFTARROW: + key = '4'; + break; + case K_KP_5: + key = '5'; + break; + case K_KP_RIGHTARROW: + key = '6'; + break; + case K_KP_END: + key = '1'; + break; + case K_KP_DOWNARROW: + key = '2'; + break; + case K_KP_PGDN: + key = '3'; + break; + case K_KP_INS: + key = '0'; + break; + case K_KP_DEL: + key = '.'; + break; + } + + if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) || + ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) ) + { + char *cbd; + + if ( ( cbd = Sys_GetClipboardData() ) != 0 ) + { + int i; + + strtok( cbd, "\n\r\b" ); + + i = strlen( cbd ); + if ( i + key_linepos >= MAXCMDLINE) + i= MAXCMDLINE - key_linepos; + + if ( i > 0 ) + { + cbd[i]=0; + strcat( key_lines[edit_line], cbd ); + key_linepos += i; + } + free( cbd ); + } + + return; + } + + if ( key == 'l' ) + { + if ( keydown[K_CTRL] ) + { + Cbuf_AddText ("clear\n"); + return; + } + } + + if ( key == K_ENTER || key == K_KP_ENTER ) + { // backslash text are commands, else chat + if (key_lines[edit_line][1] == '\\' || key_lines[edit_line][1] == '/') + Cbuf_AddText (key_lines[edit_line]+2); // skip the > + else + Cbuf_AddText (key_lines[edit_line]+1); // valid command + + Cbuf_AddText ("\n"); + Com_Printf ("%s\n",key_lines[edit_line]); + edit_line = (edit_line + 1) & 31; + history_line = edit_line; + key_lines[edit_line][0] = ']'; + key_linepos = 1; + if (cls.state == ca_disconnected) + SCR_UpdateScreen (); // force an update, because the command + // may take some time + return; + } + + if (key == K_TAB) + { // command completion + CompleteCommand (); + return; + } + + if ( ( key == K_BACKSPACE ) || ( key == K_LEFTARROW ) || ( key == K_KP_LEFTARROW ) || ( ( key == 'h' ) && ( keydown[K_CTRL] ) ) ) + { + if (key_linepos > 1) + key_linepos--; + return; + } + + if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( key == 'p' ) && keydown[K_CTRL] ) ) + { + do + { + history_line = (history_line - 1) & 31; + } while (history_line != edit_line + && !key_lines[history_line][1]); + if (history_line == edit_line) + history_line = (edit_line+1)&31; + strcpy(key_lines[edit_line], key_lines[history_line]); + key_linepos = strlen(key_lines[edit_line]); + return; + } + + if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( key == 'n' ) && keydown[K_CTRL] ) ) + { + if (history_line == edit_line) return; + do + { + history_line = (history_line + 1) & 31; + } + while (history_line != edit_line + && !key_lines[history_line][1]); + if (history_line == edit_line) + { + key_lines[edit_line][0] = ']'; + key_linepos = 1; + } + else + { + strcpy(key_lines[edit_line], key_lines[history_line]); + key_linepos = strlen(key_lines[edit_line]); + } + return; + } + + if (key == K_PGUP || key == K_KP_PGUP ) + { + con.display -= 2; + return; + } + + if (key == K_PGDN || key == K_KP_PGDN ) + { + con.display += 2; + if (con.display > con.current) + con.display = con.current; + return; + } + + if (key == K_HOME || key == K_KP_HOME ) + { + con.display = con.current - con.totallines + 10; + return; + } + + if (key == K_END || key == K_KP_END ) + { + con.display = con.current; + return; + } + + if (key < 32 || key > 127) + return; // non printable + + if (key_linepos < MAXCMDLINE-1) + { + key_lines[edit_line][key_linepos] = key; + key_linepos++; + key_lines[edit_line][key_linepos] = 0; + } + +} + +//============================================================================ + +qboolean chat_team; +char chat_buffer[MAXCMDLINE]; +int chat_bufferlen = 0; + +void Key_Message (int key) +{ + + if ( key == K_ENTER || key == K_KP_ENTER ) + { + if (chat_team) + Cbuf_AddText ("say_team \""); + else + Cbuf_AddText ("say \""); + Cbuf_AddText(chat_buffer); + Cbuf_AddText("\"\n"); + + cls.key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + if (key == K_ESCAPE) + { + cls.key_dest = key_game; + chat_bufferlen = 0; + chat_buffer[0] = 0; + return; + } + + if (key < 32 || key > 127) + return; // non printable + + if (key == K_BACKSPACE) + { + if (chat_bufferlen) + { + chat_bufferlen--; + chat_buffer[chat_bufferlen] = 0; + } + return; + } + + if (chat_bufferlen == sizeof(chat_buffer)-1) + return; // all full + + chat_buffer[chat_bufferlen++] = key; + chat_buffer[chat_bufferlen] = 0; +} + +//============================================================================ + + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keybindings[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. +=================== +*/ +int Key_StringToKeynum (char *str) +{ + keyname_t *kn; + + if (!str || !str[0]) + return -1; + if (!str[1]) + return str[0]; + + for (kn=keynames ; kn->name ; kn++) + { + if (!Q_strcasecmp(str,kn->name)) + return kn->keynum; + } + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, or a K_* name) for the +given keynum. +FIXME: handle quote special (general escape sequence?) +=================== +*/ +char *Key_KeynumToString (int keynum) +{ + keyname_t *kn; + static char tinystr[2]; + + if (keynum == -1) + return ""; + if (keynum > 32 && keynum < 127) + { // printable ascii + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + for (kn=keynames ; kn->name ; kn++) + if (keynum == kn->keynum) + return kn->name; + + return ""; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding (int keynum, char *binding) +{ + char *new; + int l; + + if (keynum == -1) + return; + +// free old bindings + if (keybindings[keynum]) + { + Z_Free (keybindings[keynum]); + keybindings[keynum] = NULL; + } + +// allocate memory for new binding + l = strlen (binding); + new = Z_Malloc (l+1); + strcpy (new, binding); + new[l] = 0; + keybindings[keynum] = new; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, ""); +} + +void Key_Unbindall_f (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + if (keybindings[i]) + Key_SetBinding (i, ""); +} + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c < 2) + { + Com_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { + if (keybindings[b]) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keybindings[b] ); + else + Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i=2 ; i< c ; i++) + { + strcat (cmd, Cmd_Argv(i)); + if (i != (c-1)) + strcat (cmd, " "); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings (FILE *f) +{ + int i; + + for (i=0 ; i<256 ; i++) + if (keybindings[i] && keybindings[i][0]) + fprintf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keybindings[i]); +} + + +/* +============ +Key_Bindlist_f + +============ +*/ +void Key_Bindlist_f (void) +{ + int i; + + for (i=0 ; i<256 ; i++) + if (keybindings[i] && keybindings[i][0]) + Com_Printf ("%s \"%s\"\n", Key_KeynumToString(i), keybindings[i]); +} + + +/* +=================== +Key_Init +=================== +*/ +void Key_Init (void) +{ + int i; + + for (i=0 ; i<32 ; i++) + { + key_lines[i][0] = ']'; + key_lines[i][1] = 0; + } + key_linepos = 1; + +// +// init ascii characters in console mode +// + for (i=32 ; i<128 ; i++) + consolekeys[i] = true; + consolekeys[K_ENTER] = true; + consolekeys[K_KP_ENTER] = true; + consolekeys[K_TAB] = true; + consolekeys[K_LEFTARROW] = true; + consolekeys[K_KP_LEFTARROW] = true; + consolekeys[K_RIGHTARROW] = true; + consolekeys[K_KP_RIGHTARROW] = true; + consolekeys[K_UPARROW] = true; + consolekeys[K_KP_UPARROW] = true; + consolekeys[K_DOWNARROW] = true; + consolekeys[K_KP_DOWNARROW] = true; + consolekeys[K_BACKSPACE] = true; + consolekeys[K_HOME] = true; + consolekeys[K_KP_HOME] = true; + consolekeys[K_END] = true; + consolekeys[K_KP_END] = true; + consolekeys[K_PGUP] = true; + consolekeys[K_KP_PGUP] = true; + consolekeys[K_PGDN] = true; + consolekeys[K_KP_PGDN] = true; + consolekeys[K_SHIFT] = true; + consolekeys[K_INS] = true; + consolekeys[K_KP_INS] = true; + consolekeys[K_KP_DEL] = true; + consolekeys[K_KP_SLASH] = true; + consolekeys[K_KP_PLUS] = true; + consolekeys[K_KP_MINUS] = true; + consolekeys[K_KP_5] = true; + + consolekeys['`'] = false; + consolekeys['~'] = false; + + for (i=0 ; i<256 ; i++) + keyshift[i] = i; + for (i='a' ; i<='z' ; i++) + keyshift[i] = i - 'a' + 'A'; + keyshift['1'] = '!'; + keyshift['2'] = '@'; + keyshift['3'] = '#'; + keyshift['4'] = '$'; + keyshift['5'] = '%'; + keyshift['6'] = '^'; + keyshift['7'] = '&'; + keyshift['8'] = '*'; + keyshift['9'] = '('; + keyshift['0'] = ')'; + keyshift['-'] = '_'; + keyshift['='] = '+'; + keyshift[','] = '<'; + keyshift['.'] = '>'; + keyshift['/'] = '?'; + keyshift[';'] = ':'; + keyshift['\''] = '"'; + keyshift['['] = '{'; + keyshift[']'] = '}'; + keyshift['`'] = '~'; + keyshift['\\'] = '|'; + + menubound[K_ESCAPE] = true; + for (i=0 ; i<12 ; i++) + menubound[K_F1+i] = true; + +// +// register our functions +// + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); + Cmd_AddCommand ("bindlist",Key_Bindlist_f); +} + +/* +=================== +Key_Event + +Called by the system between frames for both key up and key down events +Should NOT be called during an interrupt! +=================== +*/ +void Key_Event (int key, qboolean down, unsigned time) +{ + char *kb; + char cmd[1024]; + + // hack for modal presses + if (key_waiting == -1) + { + if (down) + key_waiting = key; + return; + } + + // update auto-repeat status + if (down) + { + key_repeats[key]++; + if (key != K_BACKSPACE + && key != K_PAUSE + && key != K_PGUP + && key != K_KP_PGUP + && key != K_PGDN + && key != K_KP_PGDN + && key_repeats[key] > 1) + return; // ignore most autorepeats + + if (key >= 200 && !keybindings[key]) + Com_Printf ("%s is unbound, hit F4 to set.\n", Key_KeynumToString (key) ); + } + else + { + key_repeats[key] = 0; + } + + if (key == K_SHIFT) + shift_down = down; + + // console key is hardcoded, so the user can never unbind it + if (key == '`' || key == '~') + { + if (!down) + return; + Con_ToggleConsole_f (); + return; + } + + // any key during the attract mode will bring up the menu + if (cl.attractloop && cls.key_dest != key_menu) + key = K_ESCAPE; + + // menu key is hardcoded, so the user can never unbind it + if (key == K_ESCAPE) + { + if (!down) + return; + + if (cl.frame.playerstate.stats[STAT_LAYOUTS] && cls.key_dest == key_game) + { // put away help computer / inventory + Cbuf_AddText ("cmd putaway\n"); + return; + } + switch (cls.key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + case key_game: + case key_console: + M_Menu_Main_f (); + break; + default: + Com_Error (ERR_FATAL, "Bad cls.key_dest"); + } + return; + } + + // track if any key is down for BUTTON_ANY + keydown[key] = down; + if (down) + { + if (key_repeats[key] == 1) + anykeydown++; + } + else + { + anykeydown--; + if (anykeydown < 0) + anykeydown = 0; + } + +// +// key up events only generate commands if the game key binding is +// a button command (leading + sign). These will occur even in console mode, +// to keep the character from continuing an action started before a console +// switch. Button commands include the kenum as a parameter, so multiple +// downs can be matched with ups +// + if (!down) + { + kb = keybindings[key]; + if (kb && kb[0] == '+') + { + Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time); + Cbuf_AddText (cmd); + } + if (keyshift[key] != key) + { + kb = keybindings[keyshift[key]]; + if (kb && kb[0] == '+') + { + Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", kb+1, key, time); + Cbuf_AddText (cmd); + } + } + return; + } + +// +// if not a consolekey, send to the interpreter no matter what mode is +// + if ( (cls.key_dest == key_menu && menubound[key]) + || (cls.key_dest == key_console && !consolekeys[key]) + || (cls.key_dest == key_game && ( cls.state == ca_active || !consolekeys[key] ) ) ) + { + kb = keybindings[key]; + if (kb) + { + if (kb[0] == '+') + { // button commands add keynum and time as a parm + Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", kb, key, time); + Cbuf_AddText (cmd); + } + else + { + Cbuf_AddText (kb); + Cbuf_AddText ("\n"); + } + } + return; + } + + if (!down) + return; // other systems only care about key down events + + if (shift_down) + key = keyshift[key]; + + switch (cls.key_dest) + { + case key_message: + Key_Message (key); + break; + case key_menu: + M_Keydown (key); + break; + + case key_game: + case key_console: + Key_Console (key); + break; + default: + Com_Error (ERR_FATAL, "Bad cls.key_dest"); + } +} + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + anykeydown = false; + + for (i=0 ; i<256 ; i++) + { + if ( keydown[i] || key_repeats[i] ) + Key_Event( i, false, 0 ); + keydown[i] = 0; + key_repeats[i] = 0; + } +} + + +/* +=================== +Key_GetKey +=================== +*/ +int Key_GetKey (void) +{ + key_waiting = -1; + + while (key_waiting == -1) + Sys_SendKeyEvents (); + + return key_waiting; +} + diff --git a/client/keys.h b/client/keys.h new file mode 100644 index 000000000..bed9735e6 --- /dev/null +++ b/client/keys.h @@ -0,0 +1,146 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 +#define K_KP_RIGHTARROW 165 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 200 +#define K_MOUSE2 201 +#define K_MOUSE3 202 + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 + +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 + +extern char *keybindings[256]; +extern int key_repeats[256]; + +extern int anykeydown; +extern char chat_buffer[]; +extern int chat_bufferlen; +extern qboolean chat_team; + +void Key_Event (int key, qboolean down, unsigned time); +void Key_Init (void); +void Key_WriteBindings (FILE *f); +void Key_SetBinding (int keynum, char *binding); +void Key_ClearStates (void); +int Key_GetKey (void); + diff --git a/client/menu.c b/client/menu.c new file mode 100644 index 000000000..1835a86c5 --- /dev/null +++ b/client/menu.c @@ -0,0 +1,4016 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include +#ifdef _WIN32 +#include +#endif +#include "client.h" +#include "../client/qmenu.h" + +static int m_main_cursor; + +#define NUM_CURSOR_FRAMES 15 + +static char *menu_in_sound = "misc/menu1.wav"; +static char *menu_move_sound = "misc/menu2.wav"; +static char *menu_out_sound = "misc/menu3.wav"; + +void M_Menu_Main_f (void); + void M_Menu_Game_f (void); + void M_Menu_LoadGame_f (void); + void M_Menu_SaveGame_f (void); + void M_Menu_PlayerConfig_f (void); + void M_Menu_DownloadOptions_f (void); + void M_Menu_Credits_f( void ); + void M_Menu_Multiplayer_f( void ); + void M_Menu_JoinServer_f (void); + void M_Menu_AddressBook_f( void ); + void M_Menu_StartServer_f (void); + void M_Menu_DMOptions_f (void); + void M_Menu_Video_f (void); + void M_Menu_Options_f (void); + void M_Menu_Keys_f (void); + void M_Menu_Quit_f (void); + + void M_Menu_Credits( void ); + +qboolean m_entersound; // play after drawing a frame, so caching + // won't disrupt the sound + +void (*m_drawfunc) (void); +const char *(*m_keyfunc) (int key); + +//============================================================================= +/* Support Routines */ + +#define MAX_MENU_DEPTH 8 + + +typedef struct +{ + void (*draw) (void); + const char *(*key) (int k); +} menulayer_t; + +menulayer_t m_layers[MAX_MENU_DEPTH]; +int m_menudepth; + +static void M_Banner( char *name ) +{ + int w, h; + + re.DrawGetPicSize (&w, &h, name ); + re.DrawPic( viddef.width / 2 - w / 2, viddef.height / 2 - 110, name ); +} + +void M_PushMenu ( void (*draw) (void), const char *(*key) (int k) ) +{ + int i; + + if (Cvar_VariableValue ("maxclients") == 1 + && Com_ServerState ()) + Cvar_Set ("paused", "1"); + + // if this menu is already present, drop back to that level + // to avoid stacking menus by hotkeys + for (i=0 ; i= MAX_MENU_DEPTH) + Com_Error (ERR_FATAL, "M_PushMenu: MAX_MENU_DEPTH"); + m_layers[m_menudepth].draw = m_drawfunc; + m_layers[m_menudepth].key = m_keyfunc; + m_menudepth++; + } + + m_drawfunc = draw; + m_keyfunc = key; + + m_entersound = true; + + cls.key_dest = key_menu; +} + +void M_ForceMenuOff (void) +{ + m_drawfunc = 0; + m_keyfunc = 0; + cls.key_dest = key_game; + m_menudepth = 0; + Key_ClearStates (); + Cvar_Set ("paused", "0"); +} + +void M_PopMenu (void) +{ + S_StartLocalSound( menu_out_sound ); + if (m_menudepth < 1) + Com_Error (ERR_FATAL, "M_PopMenu: depth < 1"); + m_menudepth--; + + m_drawfunc = m_layers[m_menudepth].draw; + m_keyfunc = m_layers[m_menudepth].key; + + if (!m_menudepth) + M_ForceMenuOff (); +} + + +const char *Default_MenuKey( menuframework_s *m, int key ) +{ + const char *sound = NULL; + menucommon_s *item; + + if ( m ) + { + if ( ( item = Menu_ItemAtCursor( m ) ) != 0 ) + { + if ( item->type == MTYPE_FIELD ) + { + if ( Field_Key( ( menufield_s * ) item, key ) ) + return NULL; + } + } + } + + switch ( key ) + { + case K_ESCAPE: + M_PopMenu(); + return menu_out_sound; + case K_KP_UPARROW: + case K_UPARROW: + if ( m ) + { + m->cursor--; + Menu_AdjustCursor( m, -1 ); + sound = menu_move_sound; + } + break; + case K_TAB: + if ( m ) + { + m->cursor++; + Menu_AdjustCursor( m, 1 ); + sound = menu_move_sound; + } + break; + case K_KP_DOWNARROW: + case K_DOWNARROW: + if ( m ) + { + m->cursor++; + Menu_AdjustCursor( m, 1 ); + sound = menu_move_sound; + } + break; + case K_KP_LEFTARROW: + case K_LEFTARROW: + if ( m ) + { + Menu_SlideItem( m, -1 ); + sound = menu_move_sound; + } + break; + case K_KP_RIGHTARROW: + case K_RIGHTARROW: + if ( m ) + { + Menu_SlideItem( m, 1 ); + sound = menu_move_sound; + } + break; + + case K_MOUSE1: + case K_MOUSE2: + case K_MOUSE3: + case K_JOY1: + case K_JOY2: + case K_JOY3: + case K_JOY4: + case K_AUX1: + case K_AUX2: + case K_AUX3: + case K_AUX4: + case K_AUX5: + case K_AUX6: + case K_AUX7: + case K_AUX8: + case K_AUX9: + case K_AUX10: + case K_AUX11: + case K_AUX12: + case K_AUX13: + case K_AUX14: + case K_AUX15: + case K_AUX16: + case K_AUX17: + case K_AUX18: + case K_AUX19: + case K_AUX20: + case K_AUX21: + case K_AUX22: + case K_AUX23: + case K_AUX24: + case K_AUX25: + case K_AUX26: + case K_AUX27: + case K_AUX28: + case K_AUX29: + case K_AUX30: + case K_AUX31: + case K_AUX32: + + case K_KP_ENTER: + case K_ENTER: + if ( m ) + Menu_SelectItem( m ); + sound = menu_move_sound; + break; + } + + return sound; +} + +//============================================================================= + +/* +================ +M_DrawCharacter + +Draws one solid graphics character +cx and cy are in 320*240 coordinates, and will be centered on +higher res screens. +================ +*/ +void M_DrawCharacter (int cx, int cy, int num) +{ + re.DrawChar ( cx + ((viddef.width - 320)>>1), cy + ((viddef.height - 240)>>1), num); +} + +void M_Print (int cx, int cy, char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, (*str)+128); + str++; + cx += 8; + } +} + +void M_PrintWhite (int cx, int cy, char *str) +{ + while (*str) + { + M_DrawCharacter (cx, cy, *str); + str++; + cx += 8; + } +} + +void M_DrawPic (int x, int y, char *pic) +{ + re.DrawPic (x + ((viddef.width - 320)>>1), y + ((viddef.height - 240)>>1), pic); +} + + +/* +============= +M_DrawCursor + +Draws an animating cursor with the point at +x,y. The pic will extend to the left of x, +and both above and below y. +============= +*/ +void M_DrawCursor( int x, int y, int f ) +{ + char cursorname[80]; + static qboolean cached; + + if ( !cached ) + { + int i; + + for ( i = 0; i < NUM_CURSOR_FRAMES; i++ ) + { + Com_sprintf( cursorname, sizeof( cursorname ), "m_cursor%d", i ); + + re.RegisterPic( cursorname ); + } + cached = true; + } + + Com_sprintf( cursorname, sizeof(cursorname), "m_cursor%d", f ); + re.DrawPic( x, y, cursorname ); +} + +void M_DrawTextBox (int x, int y, int width, int lines) +{ + int cx, cy; + int n; + + // draw left side + cx = x; + cy = y; + M_DrawCharacter (cx, cy, 1); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawCharacter (cx, cy, 4); + } + M_DrawCharacter (cx, cy+8, 7); + + // draw middle + cx += 8; + while (width > 0) + { + cy = y; + M_DrawCharacter (cx, cy, 2); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawCharacter (cx, cy, 5); + } + M_DrawCharacter (cx, cy+8, 8); + width -= 1; + cx += 8; + } + + // draw right side + cy = y; + M_DrawCharacter (cx, cy, 3); + for (n = 0; n < lines; n++) + { + cy += 8; + M_DrawCharacter (cx, cy, 6); + } + M_DrawCharacter (cx, cy+8, 9); +} + + +/* +======================================================================= + +MAIN MENU + +======================================================================= +*/ +#define MAIN_ITEMS 5 + + +void M_Main_Draw (void) +{ + int i; + int w, h; + int ystart; + int xoffset; + int widest = -1; + int totalheight = 0; + char litname[80]; + char *names[] = + { + "m_main_game", + "m_main_multiplayer", + "m_main_options", + "m_main_video", + "m_main_quit", + 0 + }; + + for ( i = 0; names[i] != 0; i++ ) + { + re.DrawGetPicSize( &w, &h, names[i] ); + + if ( w > widest ) + widest = w; + totalheight += ( h + 12 ); + } + + ystart = ( viddef.height / 2 - 110 ); + xoffset = ( viddef.width - widest + 70 ) / 2; + + for ( i = 0; names[i] != 0; i++ ) + { + if ( i != m_main_cursor ) + re.DrawPic( xoffset, ystart + i * 40 + 13, names[i] ); + } + strcpy( litname, names[m_main_cursor] ); + strcat( litname, "_sel" ); + re.DrawPic( xoffset, ystart + m_main_cursor * 40 + 13, litname ); + + M_DrawCursor( xoffset - 25, ystart + m_main_cursor * 40 + 11, (int)(cls.realtime / 100)%NUM_CURSOR_FRAMES ); + + re.DrawGetPicSize( &w, &h, "m_main_plaque" ); + re.DrawPic( xoffset - 30 - w, ystart, "m_main_plaque" ); + + re.DrawPic( xoffset - 30 - w, ystart + h + 5, "m_main_logo" ); +} + + +const char *M_Main_Key (int key) +{ + const char *sound = menu_move_sound; + + switch (key) + { + case K_ESCAPE: + M_PopMenu (); + break; + + case K_KP_DOWNARROW: + case K_DOWNARROW: + if (++m_main_cursor >= MAIN_ITEMS) + m_main_cursor = 0; + return sound; + + case K_KP_UPARROW: + case K_UPARROW: + if (--m_main_cursor < 0) + m_main_cursor = MAIN_ITEMS - 1; + return sound; + + case K_KP_ENTER: + case K_ENTER: + m_entersound = true; + + switch (m_main_cursor) + { + case 0: + M_Menu_Game_f (); + break; + + case 1: + M_Menu_Multiplayer_f(); + break; + + case 2: + M_Menu_Options_f (); + break; + + case 3: + M_Menu_Video_f (); + break; + + case 4: + M_Menu_Quit_f (); + break; + } + } + + return NULL; +} + + +void M_Menu_Main_f (void) +{ + M_PushMenu (M_Main_Draw, M_Main_Key); +} + +/* +======================================================================= + +MULTIPLAYER MENU + +======================================================================= +*/ +static menuframework_s s_multiplayer_menu; +static menuaction_s s_join_network_server_action; +static menuaction_s s_start_network_server_action; +static menuaction_s s_player_setup_action; + +static void Multiplayer_MenuDraw (void) +{ + M_Banner( "m_banner_multiplayer" ); + + Menu_AdjustCursor( &s_multiplayer_menu, 1 ); + Menu_Draw( &s_multiplayer_menu ); +} + +static void PlayerSetupFunc( void *unused ) +{ + M_Menu_PlayerConfig_f(); +} + +static void JoinNetworkServerFunc( void *unused ) +{ + M_Menu_JoinServer_f(); +} + +static void StartNetworkServerFunc( void *unused ) +{ + M_Menu_StartServer_f (); +} + +void Multiplayer_MenuInit( void ) +{ + s_multiplayer_menu.x = viddef.width * 0.50 - 64; + s_multiplayer_menu.nitems = 0; + + s_join_network_server_action.generic.type = MTYPE_ACTION; + s_join_network_server_action.generic.flags = QMF_LEFT_JUSTIFY; + s_join_network_server_action.generic.x = 0; + s_join_network_server_action.generic.y = 0; + s_join_network_server_action.generic.name = " join network server"; + s_join_network_server_action.generic.callback = JoinNetworkServerFunc; + + s_start_network_server_action.generic.type = MTYPE_ACTION; + s_start_network_server_action.generic.flags = QMF_LEFT_JUSTIFY; + s_start_network_server_action.generic.x = 0; + s_start_network_server_action.generic.y = 10; + s_start_network_server_action.generic.name = " start network server"; + s_start_network_server_action.generic.callback = StartNetworkServerFunc; + + s_player_setup_action.generic.type = MTYPE_ACTION; + s_player_setup_action.generic.flags = QMF_LEFT_JUSTIFY; + s_player_setup_action.generic.x = 0; + s_player_setup_action.generic.y = 20; + s_player_setup_action.generic.name = " player setup"; + s_player_setup_action.generic.callback = PlayerSetupFunc; + + Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_join_network_server_action ); + Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_start_network_server_action ); + Menu_AddItem( &s_multiplayer_menu, ( void * ) &s_player_setup_action ); + + Menu_SetStatusBar( &s_multiplayer_menu, NULL ); + + Menu_Center( &s_multiplayer_menu ); +} + +const char *Multiplayer_MenuKey( int key ) +{ + return Default_MenuKey( &s_multiplayer_menu, key ); +} + +void M_Menu_Multiplayer_f( void ) +{ + Multiplayer_MenuInit(); + M_PushMenu( Multiplayer_MenuDraw, Multiplayer_MenuKey ); +} + +/* +======================================================================= + +KEYS MENU + +======================================================================= +*/ +char *bindnames[][2] = +{ +{"+attack", "attack"}, +{"weapnext", "next weapon"}, +{"+forward", "walk forward"}, +{"+back", "backpedal"}, +{"+left", "turn left"}, +{"+right", "turn right"}, +{"+speed", "run"}, +{"+moveleft", "step left"}, +{"+moveright", "step right"}, +{"+strafe", "sidestep"}, +{"+lookup", "look up"}, +{"+lookdown", "look down"}, +{"centerview", "center view"}, +{"+mlook", "mouse look"}, +{"+klook", "keyboard look"}, +{"+moveup", "up / jump"}, +{"+movedown", "down / crouch"}, + +{"inven", "inventory"}, +{"invuse", "use item"}, +{"invdrop", "drop item"}, +{"invprev", "prev item"}, +{"invnext", "next item"}, + +{"cmd help", "help computer" }, +{ 0, 0 } +}; + +int keys_cursor; +static int bind_grab; + +static menuframework_s s_keys_menu; +static menuaction_s s_keys_attack_action; +static menuaction_s s_keys_change_weapon_action; +static menuaction_s s_keys_walk_forward_action; +static menuaction_s s_keys_backpedal_action; +static menuaction_s s_keys_turn_left_action; +static menuaction_s s_keys_turn_right_action; +static menuaction_s s_keys_run_action; +static menuaction_s s_keys_step_left_action; +static menuaction_s s_keys_step_right_action; +static menuaction_s s_keys_sidestep_action; +static menuaction_s s_keys_look_up_action; +static menuaction_s s_keys_look_down_action; +static menuaction_s s_keys_center_view_action; +static menuaction_s s_keys_mouse_look_action; +static menuaction_s s_keys_keyboard_look_action; +static menuaction_s s_keys_move_up_action; +static menuaction_s s_keys_move_down_action; +static menuaction_s s_keys_inventory_action; +static menuaction_s s_keys_inv_use_action; +static menuaction_s s_keys_inv_drop_action; +static menuaction_s s_keys_inv_prev_action; +static menuaction_s s_keys_inv_next_action; + +static menuaction_s s_keys_help_computer_action; + +static void M_UnbindCommand (char *command) +{ + int j; + int l; + char *b; + + l = strlen(command); + + for (j=0 ; j<256 ; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + Key_SetBinding (j, ""); + } +} + +static void M_FindKeysForCommand (char *command, int *twokeys) +{ + int count; + int j; + int l; + char *b; + + twokeys[0] = twokeys[1] = -1; + l = strlen(command); + count = 0; + + for (j=0 ; j<256 ; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) ) + { + twokeys[count] = j; + count++; + if (count == 2) + break; + } + } +} + +static void KeyCursorDrawFunc( menuframework_s *menu ) +{ + if ( bind_grab ) + re.DrawChar( menu->x, menu->y + menu->cursor * 9, '=' ); + else + re.DrawChar( menu->x, menu->y + menu->cursor * 9, 12 + ( ( int ) ( Sys_Milliseconds() / 250 ) & 1 ) ); +} + +static void DrawKeyBindingFunc( void *self ) +{ + int keys[2]; + menuaction_s *a = ( menuaction_s * ) self; + + M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys); + + if (keys[0] == -1) + { + Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, "???" ); + } + else + { + int x; + const char *name; + + name = Key_KeynumToString (keys[0]); + + Menu_DrawString( a->generic.x + a->generic.parent->x + 16, a->generic.y + a->generic.parent->y, name ); + + x = strlen(name) * 8; + + if (keys[1] != -1) + { + Menu_DrawString( a->generic.x + a->generic.parent->x + 24 + x, a->generic.y + a->generic.parent->y, "or" ); + Menu_DrawString( a->generic.x + a->generic.parent->x + 48 + x, a->generic.y + a->generic.parent->y, Key_KeynumToString (keys[1]) ); + } + } +} + +static void KeyBindingFunc( void *self ) +{ + menuaction_s *a = ( menuaction_s * ) self; + int keys[2]; + + M_FindKeysForCommand( bindnames[a->generic.localdata[0]][0], keys ); + + if (keys[1] != -1) + M_UnbindCommand( bindnames[a->generic.localdata[0]][0]); + + bind_grab = true; + + Menu_SetStatusBar( &s_keys_menu, "press a key or button for this action" ); +} + +static void Keys_MenuInit( void ) +{ + int y = 0; + int i = 0; + + s_keys_menu.x = viddef.width * 0.50; + s_keys_menu.nitems = 0; + s_keys_menu.cursordraw = KeyCursorDrawFunc; + + s_keys_attack_action.generic.type = MTYPE_ACTION; + s_keys_attack_action.generic.flags = QMF_GRAYED; + s_keys_attack_action.generic.x = 0; + s_keys_attack_action.generic.y = y; + s_keys_attack_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_attack_action.generic.localdata[0] = i; + s_keys_attack_action.generic.name = bindnames[s_keys_attack_action.generic.localdata[0]][1]; + + s_keys_change_weapon_action.generic.type = MTYPE_ACTION; + s_keys_change_weapon_action.generic.flags = QMF_GRAYED; + s_keys_change_weapon_action.generic.x = 0; + s_keys_change_weapon_action.generic.y = y += 9; + s_keys_change_weapon_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_change_weapon_action.generic.localdata[0] = ++i; + s_keys_change_weapon_action.generic.name = bindnames[s_keys_change_weapon_action.generic.localdata[0]][1]; + + s_keys_walk_forward_action.generic.type = MTYPE_ACTION; + s_keys_walk_forward_action.generic.flags = QMF_GRAYED; + s_keys_walk_forward_action.generic.x = 0; + s_keys_walk_forward_action.generic.y = y += 9; + s_keys_walk_forward_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_walk_forward_action.generic.localdata[0] = ++i; + s_keys_walk_forward_action.generic.name = bindnames[s_keys_walk_forward_action.generic.localdata[0]][1]; + + s_keys_backpedal_action.generic.type = MTYPE_ACTION; + s_keys_backpedal_action.generic.flags = QMF_GRAYED; + s_keys_backpedal_action.generic.x = 0; + s_keys_backpedal_action.generic.y = y += 9; + s_keys_backpedal_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_backpedal_action.generic.localdata[0] = ++i; + s_keys_backpedal_action.generic.name = bindnames[s_keys_backpedal_action.generic.localdata[0]][1]; + + s_keys_turn_left_action.generic.type = MTYPE_ACTION; + s_keys_turn_left_action.generic.flags = QMF_GRAYED; + s_keys_turn_left_action.generic.x = 0; + s_keys_turn_left_action.generic.y = y += 9; + s_keys_turn_left_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_turn_left_action.generic.localdata[0] = ++i; + s_keys_turn_left_action.generic.name = bindnames[s_keys_turn_left_action.generic.localdata[0]][1]; + + s_keys_turn_right_action.generic.type = MTYPE_ACTION; + s_keys_turn_right_action.generic.flags = QMF_GRAYED; + s_keys_turn_right_action.generic.x = 0; + s_keys_turn_right_action.generic.y = y += 9; + s_keys_turn_right_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_turn_right_action.generic.localdata[0] = ++i; + s_keys_turn_right_action.generic.name = bindnames[s_keys_turn_right_action.generic.localdata[0]][1]; + + s_keys_run_action.generic.type = MTYPE_ACTION; + s_keys_run_action.generic.flags = QMF_GRAYED; + s_keys_run_action.generic.x = 0; + s_keys_run_action.generic.y = y += 9; + s_keys_run_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_run_action.generic.localdata[0] = ++i; + s_keys_run_action.generic.name = bindnames[s_keys_run_action.generic.localdata[0]][1]; + + s_keys_step_left_action.generic.type = MTYPE_ACTION; + s_keys_step_left_action.generic.flags = QMF_GRAYED; + s_keys_step_left_action.generic.x = 0; + s_keys_step_left_action.generic.y = y += 9; + s_keys_step_left_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_step_left_action.generic.localdata[0] = ++i; + s_keys_step_left_action.generic.name = bindnames[s_keys_step_left_action.generic.localdata[0]][1]; + + s_keys_step_right_action.generic.type = MTYPE_ACTION; + s_keys_step_right_action.generic.flags = QMF_GRAYED; + s_keys_step_right_action.generic.x = 0; + s_keys_step_right_action.generic.y = y += 9; + s_keys_step_right_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_step_right_action.generic.localdata[0] = ++i; + s_keys_step_right_action.generic.name = bindnames[s_keys_step_right_action.generic.localdata[0]][1]; + + s_keys_sidestep_action.generic.type = MTYPE_ACTION; + s_keys_sidestep_action.generic.flags = QMF_GRAYED; + s_keys_sidestep_action.generic.x = 0; + s_keys_sidestep_action.generic.y = y += 9; + s_keys_sidestep_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_sidestep_action.generic.localdata[0] = ++i; + s_keys_sidestep_action.generic.name = bindnames[s_keys_sidestep_action.generic.localdata[0]][1]; + + s_keys_look_up_action.generic.type = MTYPE_ACTION; + s_keys_look_up_action.generic.flags = QMF_GRAYED; + s_keys_look_up_action.generic.x = 0; + s_keys_look_up_action.generic.y = y += 9; + s_keys_look_up_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_look_up_action.generic.localdata[0] = ++i; + s_keys_look_up_action.generic.name = bindnames[s_keys_look_up_action.generic.localdata[0]][1]; + + s_keys_look_down_action.generic.type = MTYPE_ACTION; + s_keys_look_down_action.generic.flags = QMF_GRAYED; + s_keys_look_down_action.generic.x = 0; + s_keys_look_down_action.generic.y = y += 9; + s_keys_look_down_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_look_down_action.generic.localdata[0] = ++i; + s_keys_look_down_action.generic.name = bindnames[s_keys_look_down_action.generic.localdata[0]][1]; + + s_keys_center_view_action.generic.type = MTYPE_ACTION; + s_keys_center_view_action.generic.flags = QMF_GRAYED; + s_keys_center_view_action.generic.x = 0; + s_keys_center_view_action.generic.y = y += 9; + s_keys_center_view_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_center_view_action.generic.localdata[0] = ++i; + s_keys_center_view_action.generic.name = bindnames[s_keys_center_view_action.generic.localdata[0]][1]; + + s_keys_mouse_look_action.generic.type = MTYPE_ACTION; + s_keys_mouse_look_action.generic.flags = QMF_GRAYED; + s_keys_mouse_look_action.generic.x = 0; + s_keys_mouse_look_action.generic.y = y += 9; + s_keys_mouse_look_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_mouse_look_action.generic.localdata[0] = ++i; + s_keys_mouse_look_action.generic.name = bindnames[s_keys_mouse_look_action.generic.localdata[0]][1]; + + s_keys_keyboard_look_action.generic.type = MTYPE_ACTION; + s_keys_keyboard_look_action.generic.flags = QMF_GRAYED; + s_keys_keyboard_look_action.generic.x = 0; + s_keys_keyboard_look_action.generic.y = y += 9; + s_keys_keyboard_look_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_keyboard_look_action.generic.localdata[0] = ++i; + s_keys_keyboard_look_action.generic.name = bindnames[s_keys_keyboard_look_action.generic.localdata[0]][1]; + + s_keys_move_up_action.generic.type = MTYPE_ACTION; + s_keys_move_up_action.generic.flags = QMF_GRAYED; + s_keys_move_up_action.generic.x = 0; + s_keys_move_up_action.generic.y = y += 9; + s_keys_move_up_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_move_up_action.generic.localdata[0] = ++i; + s_keys_move_up_action.generic.name = bindnames[s_keys_move_up_action.generic.localdata[0]][1]; + + s_keys_move_down_action.generic.type = MTYPE_ACTION; + s_keys_move_down_action.generic.flags = QMF_GRAYED; + s_keys_move_down_action.generic.x = 0; + s_keys_move_down_action.generic.y = y += 9; + s_keys_move_down_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_move_down_action.generic.localdata[0] = ++i; + s_keys_move_down_action.generic.name = bindnames[s_keys_move_down_action.generic.localdata[0]][1]; + + s_keys_inventory_action.generic.type = MTYPE_ACTION; + s_keys_inventory_action.generic.flags = QMF_GRAYED; + s_keys_inventory_action.generic.x = 0; + s_keys_inventory_action.generic.y = y += 9; + s_keys_inventory_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_inventory_action.generic.localdata[0] = ++i; + s_keys_inventory_action.generic.name = bindnames[s_keys_inventory_action.generic.localdata[0]][1]; + + s_keys_inv_use_action.generic.type = MTYPE_ACTION; + s_keys_inv_use_action.generic.flags = QMF_GRAYED; + s_keys_inv_use_action.generic.x = 0; + s_keys_inv_use_action.generic.y = y += 9; + s_keys_inv_use_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_inv_use_action.generic.localdata[0] = ++i; + s_keys_inv_use_action.generic.name = bindnames[s_keys_inv_use_action.generic.localdata[0]][1]; + + s_keys_inv_drop_action.generic.type = MTYPE_ACTION; + s_keys_inv_drop_action.generic.flags = QMF_GRAYED; + s_keys_inv_drop_action.generic.x = 0; + s_keys_inv_drop_action.generic.y = y += 9; + s_keys_inv_drop_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_inv_drop_action.generic.localdata[0] = ++i; + s_keys_inv_drop_action.generic.name = bindnames[s_keys_inv_drop_action.generic.localdata[0]][1]; + + s_keys_inv_prev_action.generic.type = MTYPE_ACTION; + s_keys_inv_prev_action.generic.flags = QMF_GRAYED; + s_keys_inv_prev_action.generic.x = 0; + s_keys_inv_prev_action.generic.y = y += 9; + s_keys_inv_prev_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_inv_prev_action.generic.localdata[0] = ++i; + s_keys_inv_prev_action.generic.name = bindnames[s_keys_inv_prev_action.generic.localdata[0]][1]; + + s_keys_inv_next_action.generic.type = MTYPE_ACTION; + s_keys_inv_next_action.generic.flags = QMF_GRAYED; + s_keys_inv_next_action.generic.x = 0; + s_keys_inv_next_action.generic.y = y += 9; + s_keys_inv_next_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_inv_next_action.generic.localdata[0] = ++i; + s_keys_inv_next_action.generic.name = bindnames[s_keys_inv_next_action.generic.localdata[0]][1]; + + s_keys_help_computer_action.generic.type = MTYPE_ACTION; + s_keys_help_computer_action.generic.flags = QMF_GRAYED; + s_keys_help_computer_action.generic.x = 0; + s_keys_help_computer_action.generic.y = y += 9; + s_keys_help_computer_action.generic.ownerdraw = DrawKeyBindingFunc; + s_keys_help_computer_action.generic.localdata[0] = ++i; + s_keys_help_computer_action.generic.name = bindnames[s_keys_help_computer_action.generic.localdata[0]][1]; + + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_attack_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_change_weapon_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_walk_forward_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_backpedal_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_left_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_turn_right_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_run_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_left_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_step_right_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_sidestep_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_up_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_look_down_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_center_view_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_mouse_look_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_keyboard_look_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_up_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_move_down_action ); + + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inventory_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_use_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_drop_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_prev_action ); + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_inv_next_action ); + + Menu_AddItem( &s_keys_menu, ( void * ) &s_keys_help_computer_action ); + + Menu_SetStatusBar( &s_keys_menu, "enter to change, backspace to clear" ); + Menu_Center( &s_keys_menu ); +} + +static void Keys_MenuDraw (void) +{ + Menu_AdjustCursor( &s_keys_menu, 1 ); + Menu_Draw( &s_keys_menu ); +} + +static const char *Keys_MenuKey( int key ) +{ + menuaction_s *item = ( menuaction_s * ) Menu_ItemAtCursor( &s_keys_menu ); + + if ( bind_grab ) + { + if ( key != K_ESCAPE && key != '`' ) + { + char cmd[1024]; + + Com_sprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString(key), bindnames[item->generic.localdata[0]][0]); + Cbuf_InsertText (cmd); + } + + Menu_SetStatusBar( &s_keys_menu, "enter to change, backspace to clear" ); + bind_grab = false; + return menu_out_sound; + } + + switch ( key ) + { + case K_KP_ENTER: + case K_ENTER: + KeyBindingFunc( item ); + return menu_in_sound; + case K_BACKSPACE: // delete bindings + case K_DEL: // delete bindings + case K_KP_DEL: + M_UnbindCommand( bindnames[item->generic.localdata[0]][0] ); + return menu_out_sound; + default: + return Default_MenuKey( &s_keys_menu, key ); + } +} + +void M_Menu_Keys_f (void) +{ + Keys_MenuInit(); + M_PushMenu( Keys_MenuDraw, Keys_MenuKey ); +} + + +/* +======================================================================= + +CONTROLS MENU + +======================================================================= +*/ +static cvar_t *win_noalttab; +extern cvar_t *in_joystick; + +static menuframework_s s_options_menu; +static menuaction_s s_options_defaults_action; +static menuaction_s s_options_customize_options_action; +static menuslider_s s_options_sensitivity_slider; +static menulist_s s_options_freelook_box; +static menulist_s s_options_noalttab_box; +static menulist_s s_options_alwaysrun_box; +static menulist_s s_options_invertmouse_box; +static menulist_s s_options_lookspring_box; +static menulist_s s_options_lookstrafe_box; +static menulist_s s_options_crosshair_box; +static menuslider_s s_options_sfxvolume_slider; +static menulist_s s_options_joystick_box; +static menulist_s s_options_cdvolume_box; +static menulist_s s_options_quality_list; +static menulist_s s_options_compatibility_list; +static menulist_s s_options_console_action; + +static void CrosshairFunc( void *unused ) +{ + Cvar_SetValue( "crosshair", s_options_crosshair_box.curvalue ); +} + +static void JoystickFunc( void *unused ) +{ + Cvar_SetValue( "in_joystick", s_options_joystick_box.curvalue ); +} + +static void CustomizeControlsFunc( void *unused ) +{ + M_Menu_Keys_f(); +} + +static void AlwaysRunFunc( void *unused ) +{ + Cvar_SetValue( "cl_run", s_options_alwaysrun_box.curvalue ); +} + +static void FreeLookFunc( void *unused ) +{ + Cvar_SetValue( "freelook", s_options_freelook_box.curvalue ); +} + +static void MouseSpeedFunc( void *unused ) +{ + Cvar_SetValue( "sensitivity", s_options_sensitivity_slider.curvalue / 2.0F ); +} + +static void NoAltTabFunc( void *unused ) +{ + Cvar_SetValue( "win_noalttab", s_options_noalttab_box.curvalue ); +} + +static float ClampCvar( float min, float max, float value ) +{ + if ( value < min ) return min; + if ( value > max ) return max; + return value; +} + +static void ControlsSetMenuItemValues( void ) +{ + s_options_sfxvolume_slider.curvalue = Cvar_VariableValue( "s_volume" ) * 10; + s_options_cdvolume_box.curvalue = !Cvar_VariableValue("cd_nocd"); + s_options_quality_list.curvalue = !Cvar_VariableValue( "s_loadas8bit" ); + s_options_sensitivity_slider.curvalue = ( sensitivity->value ) * 2; + + Cvar_SetValue( "cl_run", ClampCvar( 0, 1, cl_run->value ) ); + s_options_alwaysrun_box.curvalue = cl_run->value; + + s_options_invertmouse_box.curvalue = m_pitch->value < 0; + + Cvar_SetValue( "lookspring", ClampCvar( 0, 1, lookspring->value ) ); + s_options_lookspring_box.curvalue = lookspring->value; + + Cvar_SetValue( "lookstrafe", ClampCvar( 0, 1, lookstrafe->value ) ); + s_options_lookstrafe_box.curvalue = lookstrafe->value; + + Cvar_SetValue( "freelook", ClampCvar( 0, 1, freelook->value ) ); + s_options_freelook_box.curvalue = freelook->value; + + Cvar_SetValue( "crosshair", ClampCvar( 0, 3, crosshair->value ) ); + s_options_crosshair_box.curvalue = crosshair->value; + + Cvar_SetValue( "in_joystick", ClampCvar( 0, 1, in_joystick->value ) ); + s_options_joystick_box.curvalue = in_joystick->value; + + s_options_noalttab_box.curvalue = win_noalttab->value; +} + +static void ControlsResetDefaultsFunc( void *unused ) +{ + Cbuf_AddText ("exec default.cfg\n"); + Cbuf_Execute(); + + ControlsSetMenuItemValues(); +} + +static void InvertMouseFunc( void *unused ) +{ + if ( s_options_invertmouse_box.curvalue == 0 ) + { + Cvar_SetValue( "m_pitch", fabs( m_pitch->value ) ); + } + else + { + Cvar_SetValue( "m_pitch", -fabs( m_pitch->value ) ); + } +} + +static void LookspringFunc( void *unused ) +{ + Cvar_SetValue( "lookspring", s_options_lookspring_box.curvalue ); +} + +static void LookstrafeFunc( void *unused ) +{ + Cvar_SetValue( "lookstrafe", s_options_lookstrafe_box.curvalue ); +} + +static void UpdateVolumeFunc( void *unused ) +{ + Cvar_SetValue( "s_volume", s_options_sfxvolume_slider.curvalue / 10 ); +} + +static void UpdateCDVolumeFunc( void *unused ) +{ + Cvar_SetValue( "cd_nocd", !s_options_cdvolume_box.curvalue ); +} + +static void ConsoleFunc( void *unused ) +{ + /* + ** the proper way to do this is probably to have ToggleConsole_f accept a parameter + */ + extern void Key_ClearTyping( void ); + + if ( cl.attractloop ) + { + Cbuf_AddText ("killserver\n"); + return; + } + + Key_ClearTyping (); + Con_ClearNotify (); + + M_ForceMenuOff (); + cls.key_dest = key_console; +} + +static void UpdateSoundQualityFunc( void *unused ) +{ + if ( s_options_quality_list.curvalue ) + { + Cvar_SetValue( "s_khz", 22 ); + Cvar_SetValue( "s_loadas8bit", false ); + } + else + { + Cvar_SetValue( "s_khz", 11 ); + Cvar_SetValue( "s_loadas8bit", true ); + } + + Cvar_SetValue( "s_primary", s_options_compatibility_list.curvalue ); + + M_DrawTextBox( 8, 120 - 48, 36, 3 ); + M_Print( 16 + 16, 120 - 48 + 8, "Restarting the sound system. This" ); + M_Print( 16 + 16, 120 - 48 + 16, "could take up to a minute, so" ); + M_Print( 16 + 16, 120 - 48 + 24, "please be patient." ); + + // the text box won't show up unless we do a buffer swap + re.EndFrame(); + + CL_Snd_Restart_f(); +} + +void Options_MenuInit( void ) +{ + static const char *cd_music_items[] = + { + "disabled", + "enabled", + 0 + }; + static const char *quality_items[] = + { + "low", "high", 0 + }; + + static const char *compatibility_items[] = + { + "max compatibility", "max performance", 0 + }; + + static const char *yesno_names[] = + { + "no", + "yes", + 0 + }; + + static const char *crosshair_names[] = + { + "none", + "cross", + "dot", + "angle", + 0 + }; + + win_noalttab = Cvar_Get( "win_noalttab", "0", CVAR_ARCHIVE ); + + /* + ** configure controls menu and menu items + */ + s_options_menu.x = viddef.width / 2; + s_options_menu.y = viddef.height / 2 - 58; + s_options_menu.nitems = 0; + + s_options_sfxvolume_slider.generic.type = MTYPE_SLIDER; + s_options_sfxvolume_slider.generic.x = 0; + s_options_sfxvolume_slider.generic.y = 0; + s_options_sfxvolume_slider.generic.name = "effects volume"; + s_options_sfxvolume_slider.generic.callback = UpdateVolumeFunc; + s_options_sfxvolume_slider.minvalue = 0; + s_options_sfxvolume_slider.maxvalue = 10; + s_options_sfxvolume_slider.curvalue = Cvar_VariableValue( "s_volume" ) * 10; + + s_options_cdvolume_box.generic.type = MTYPE_SPINCONTROL; + s_options_cdvolume_box.generic.x = 0; + s_options_cdvolume_box.generic.y = 10; + s_options_cdvolume_box.generic.name = "CD music"; + s_options_cdvolume_box.generic.callback = UpdateCDVolumeFunc; + s_options_cdvolume_box.itemnames = cd_music_items; + s_options_cdvolume_box.curvalue = !Cvar_VariableValue("cd_nocd"); + + s_options_quality_list.generic.type = MTYPE_SPINCONTROL; + s_options_quality_list.generic.x = 0; + s_options_quality_list.generic.y = 20;; + s_options_quality_list.generic.name = "sound quality"; + s_options_quality_list.generic.callback = UpdateSoundQualityFunc; + s_options_quality_list.itemnames = quality_items; + s_options_quality_list.curvalue = !Cvar_VariableValue( "s_loadas8bit" ); + + s_options_compatibility_list.generic.type = MTYPE_SPINCONTROL; + s_options_compatibility_list.generic.x = 0; + s_options_compatibility_list.generic.y = 30; + s_options_compatibility_list.generic.name = "sound compatibility"; + s_options_compatibility_list.generic.callback = UpdateSoundQualityFunc; + s_options_compatibility_list.itemnames = compatibility_items; + s_options_compatibility_list.curvalue = Cvar_VariableValue( "s_primary" ); + + s_options_sensitivity_slider.generic.type = MTYPE_SLIDER; + s_options_sensitivity_slider.generic.x = 0; + s_options_sensitivity_slider.generic.y = 50; + s_options_sensitivity_slider.generic.name = "mouse speed"; + s_options_sensitivity_slider.generic.callback = MouseSpeedFunc; + s_options_sensitivity_slider.minvalue = 2; + s_options_sensitivity_slider.maxvalue = 22; + + s_options_alwaysrun_box.generic.type = MTYPE_SPINCONTROL; + s_options_alwaysrun_box.generic.x = 0; + s_options_alwaysrun_box.generic.y = 60; + s_options_alwaysrun_box.generic.name = "always run"; + s_options_alwaysrun_box.generic.callback = AlwaysRunFunc; + s_options_alwaysrun_box.itemnames = yesno_names; + + s_options_invertmouse_box.generic.type = MTYPE_SPINCONTROL; + s_options_invertmouse_box.generic.x = 0; + s_options_invertmouse_box.generic.y = 70; + s_options_invertmouse_box.generic.name = "invert mouse"; + s_options_invertmouse_box.generic.callback = InvertMouseFunc; + s_options_invertmouse_box.itemnames = yesno_names; + + s_options_lookspring_box.generic.type = MTYPE_SPINCONTROL; + s_options_lookspring_box.generic.x = 0; + s_options_lookspring_box.generic.y = 80; + s_options_lookspring_box.generic.name = "lookspring"; + s_options_lookspring_box.generic.callback = LookspringFunc; + s_options_lookspring_box.itemnames = yesno_names; + + s_options_lookstrafe_box.generic.type = MTYPE_SPINCONTROL; + s_options_lookstrafe_box.generic.x = 0; + s_options_lookstrafe_box.generic.y = 90; + s_options_lookstrafe_box.generic.name = "lookstrafe"; + s_options_lookstrafe_box.generic.callback = LookstrafeFunc; + s_options_lookstrafe_box.itemnames = yesno_names; + + s_options_freelook_box.generic.type = MTYPE_SPINCONTROL; + s_options_freelook_box.generic.x = 0; + s_options_freelook_box.generic.y = 100; + s_options_freelook_box.generic.name = "free look"; + s_options_freelook_box.generic.callback = FreeLookFunc; + s_options_freelook_box.itemnames = yesno_names; + + s_options_crosshair_box.generic.type = MTYPE_SPINCONTROL; + s_options_crosshair_box.generic.x = 0; + s_options_crosshair_box.generic.y = 110; + s_options_crosshair_box.generic.name = "crosshair"; + s_options_crosshair_box.generic.callback = CrosshairFunc; + s_options_crosshair_box.itemnames = crosshair_names; +/* + s_options_noalttab_box.generic.type = MTYPE_SPINCONTROL; + s_options_noalttab_box.generic.x = 0; + s_options_noalttab_box.generic.y = 110; + s_options_noalttab_box.generic.name = "disable alt-tab"; + s_options_noalttab_box.generic.callback = NoAltTabFunc; + s_options_noalttab_box.itemnames = yesno_names; +*/ + s_options_joystick_box.generic.type = MTYPE_SPINCONTROL; + s_options_joystick_box.generic.x = 0; + s_options_joystick_box.generic.y = 120; + s_options_joystick_box.generic.name = "use joystick"; + s_options_joystick_box.generic.callback = JoystickFunc; + s_options_joystick_box.itemnames = yesno_names; + + s_options_customize_options_action.generic.type = MTYPE_ACTION; + s_options_customize_options_action.generic.x = 0; + s_options_customize_options_action.generic.y = 140; + s_options_customize_options_action.generic.name = "customize controls"; + s_options_customize_options_action.generic.callback = CustomizeControlsFunc; + + s_options_defaults_action.generic.type = MTYPE_ACTION; + s_options_defaults_action.generic.x = 0; + s_options_defaults_action.generic.y = 150; + s_options_defaults_action.generic.name = "reset defaults"; + s_options_defaults_action.generic.callback = ControlsResetDefaultsFunc; + + s_options_console_action.generic.type = MTYPE_ACTION; + s_options_console_action.generic.x = 0; + s_options_console_action.generic.y = 160; + s_options_console_action.generic.name = "go to console"; + s_options_console_action.generic.callback = ConsoleFunc; + + ControlsSetMenuItemValues(); + + Menu_AddItem( &s_options_menu, ( void * ) &s_options_sfxvolume_slider ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_cdvolume_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_quality_list ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_compatibility_list ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_sensitivity_slider ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_alwaysrun_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_invertmouse_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookspring_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_lookstrafe_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_freelook_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_crosshair_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_joystick_box ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_customize_options_action ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_defaults_action ); + Menu_AddItem( &s_options_menu, ( void * ) &s_options_console_action ); +} + +void Options_MenuDraw (void) +{ + M_Banner( "m_banner_options" ); + Menu_AdjustCursor( &s_options_menu, 1 ); + Menu_Draw( &s_options_menu ); +} + +const char *Options_MenuKey( int key ) +{ + return Default_MenuKey( &s_options_menu, key ); +} + +void M_Menu_Options_f (void) +{ + Options_MenuInit(); + M_PushMenu ( Options_MenuDraw, Options_MenuKey ); +} + +/* +======================================================================= + +VIDEO MENU + +======================================================================= +*/ + +void M_Menu_Video_f (void) +{ + VID_MenuInit(); + M_PushMenu( VID_MenuDraw, VID_MenuKey ); +} + +/* +============================================================================= + +END GAME MENU + +============================================================================= +*/ +static int credits_start_time; +static const char **credits; +static char *creditsIndex[256]; +static char *creditsBuffer; +static const char *idcredits[] = +{ + "+QUAKE II BY ID SOFTWARE", + "", + "+PROGRAMMING", + "John Carmack", + "John Cash", + "Brian Hook", + "", + "+ART", + "Adrian Carmack", + "Kevin Cloud", + "Paul Steed", + "", + "+LEVEL DESIGN", + "Tim Willits", + "American McGee", + "Christian Antkow", + "Paul Jaquays", + "Brandon James", + "", + "+BIZ", + "Todd Hollenshead", + "Barrett (Bear) Alexander", + "Donna Jackson", + "", + "", + "+SPECIAL THANKS", + "Ben Donges for beta testing", + "", + "", + "", + "", + "", + "", + "+ADDITIONAL SUPPORT", + "", + "+LINUX PORT AND CTF", + "Dave \"Zoid\" Kirsch", + "", + "+CINEMATIC SEQUENCES", + "Ending Cinematic by Blur Studio - ", + "Venice, CA", + "", + "Environment models for Introduction", + "Cinematic by Karl Dolgener", + "", + "Assistance with environment design", + "by Cliff Iwai", + "", + "+SOUND EFFECTS AND MUSIC", + "Sound Design by Soundelux Media Labs.", + "Music Composed and Produced by", + "Soundelux Media Labs. Special thanks", + "to Bill Brown, Tom Ozanich, Brian", + "Celano, Jeff Eisner, and The Soundelux", + "Players.", + "", + "\"Level Music\" by Sonic Mayhem", + "www.sonicmayhem.com", + "", + "\"Quake II Theme Song\"", + "(C) 1997 Rob Zombie. All Rights", + "Reserved.", + "", + "Track 10 (\"Climb\") by Jer Sypult", + "", + "Voice of computers by", + "Carly Staehlin-Taylor", + "", + "+THANKS TO ACTIVISION", + "+IN PARTICULAR:", + "", + "John Tam", + "Steve Rosenthal", + "Marty Stratton", + "Henk Hartong", + "", + "Quake II(tm) (C)1997 Id Software, Inc.", + "All Rights Reserved. Distributed by", + "Activision, Inc. under license.", + "Quake II(tm), the Id Software name,", + "the \"Q II\"(tm) logo and id(tm)", + "logo are trademarks of Id Software,", + "Inc. Activision(R) is a registered", + "trademark of Activision, Inc. All", + "other trademarks and trade names are", + "properties of their respective owners.", + 0 +}; + +static const char *xatcredits[] = +{ + "+QUAKE II MISSION PACK: THE RECKONING", + "+BY", + "+XATRIX ENTERTAINMENT, INC.", + "", + "+DESIGN AND DIRECTION", + "Drew Markham", + "", + "+PRODUCED BY", + "Greg Goodrich", + "", + "+PROGRAMMING", + "Rafael Paiz", + "", + "+LEVEL DESIGN / ADDITIONAL GAME DESIGN", + "Alex Mayberry", + "", + "+LEVEL DESIGN", + "Mal Blackwell", + "Dan Koppel", + "", + "+ART DIRECTION", + "Michael \"Maxx\" Kaufman", + "", + "+COMPUTER GRAPHICS SUPERVISOR AND", + "+CHARACTER ANIMATION DIRECTION", + "Barry Dempsey", + "", + "+SENIOR ANIMATOR AND MODELER", + "Jason Hoover", + "", + "+CHARACTER ANIMATION AND", + "+MOTION CAPTURE SPECIALIST", + "Amit Doron", + "", + "+ART", + "Claire Praderie-Markham", + "Viktor Antonov", + "Corky Lehmkuhl", + "", + "+INTRODUCTION ANIMATION", + "Dominique Drozdz", + "", + "+ADDITIONAL LEVEL DESIGN", + "Aaron Barber", + "Rhett Baldwin", + "", + "+3D CHARACTER ANIMATION TOOLS", + "Gerry Tyra, SA Technology", + "", + "+ADDITIONAL EDITOR TOOL PROGRAMMING", + "Robert Duffy", + "", + "+ADDITIONAL PROGRAMMING", + "Ryan Feltrin", + "", + "+PRODUCTION COORDINATOR", + "Victoria Sylvester", + "", + "+SOUND DESIGN", + "Gary Bradfield", + "", + "+MUSIC BY", + "Sonic Mayhem", + "", + "", + "", + "+SPECIAL THANKS", + "+TO", + "+OUR FRIENDS AT ID SOFTWARE", + "", + "John Carmack", + "John Cash", + "Brian Hook", + "Adrian Carmack", + "Kevin Cloud", + "Paul Steed", + "Tim Willits", + "Christian Antkow", + "Paul Jaquays", + "Brandon James", + "Todd Hollenshead", + "Barrett (Bear) Alexander", + "Dave \"Zoid\" Kirsch", + "Donna Jackson", + "", + "", + "", + "+THANKS TO ACTIVISION", + "+IN PARTICULAR:", + "", + "Marty Stratton", + "Henk \"The Original Ripper\" Hartong", + "Kevin Kraff", + "Jamey Gottlieb", + "Chris Hepburn", + "", + "+AND THE GAME TESTERS", + "", + "Tim Vanlaw", + "Doug Jacobs", + "Steven Rosenthal", + "David Baker", + "Chris Campbell", + "Aaron Casillas", + "Steve Elwell", + "Derek Johnstone", + "Igor Krinitskiy", + "Samantha Lee", + "Michael Spann", + "Chris Toft", + "Juan Valdes", + "", + "+THANKS TO INTERGRAPH COMPUTER SYTEMS", + "+IN PARTICULAR:", + "", + "Michael T. Nicolaou", + "", + "", + "Quake II Mission Pack: The Reckoning", + "(tm) (C)1998 Id Software, Inc. All", + "Rights Reserved. Developed by Xatrix", + "Entertainment, Inc. for Id Software,", + "Inc. Distributed by Activision Inc.", + "under license. Quake(R) is a", + "registered trademark of Id Software,", + "Inc. Quake II Mission Pack: The", + "Reckoning(tm), Quake II(tm), the Id", + "Software name, the \"Q II\"(tm) logo", + "and id(tm) logo are trademarks of Id", + "Software, Inc. Activision(R) is a", + "registered trademark of Activision,", + "Inc. Xatrix(R) is a registered", + "trademark of Xatrix Entertainment,", + "Inc. All other trademarks and trade", + "names are properties of their", + "respective owners.", + 0 +}; + +static const char *roguecredits[] = +{ + "+QUAKE II MISSION PACK 2: GROUND ZERO", + "+BY", + "+ROGUE ENTERTAINMENT, INC.", + "", + "+PRODUCED BY", + "Jim Molinets", + "", + "+PROGRAMMING", + "Peter Mack", + "Patrick Magruder", + "", + "+LEVEL DESIGN", + "Jim Molinets", + "Cameron Lamprecht", + "Berenger Fish", + "Robert Selitto", + "Steve Tietze", + "Steve Thoms", + "", + "+ART DIRECTION", + "Rich Fleider", + "", + "+ART", + "Rich Fleider", + "Steve Maines", + "Won Choi", + "", + "+ANIMATION SEQUENCES", + "Creat Studios", + "Steve Maines", + "", + "+ADDITIONAL LEVEL DESIGN", + "Rich Fleider", + "Steve Maines", + "Peter Mack", + "", + "+SOUND", + "James Grunke", + "", + "+GROUND ZERO THEME", + "+AND", + "+MUSIC BY", + "Sonic Mayhem", + "", + "+VWEP MODELS", + "Brent \"Hentai\" Dill", + "", + "", + "", + "+SPECIAL THANKS", + "+TO", + "+OUR FRIENDS AT ID SOFTWARE", + "", + "John Carmack", + "John Cash", + "Brian Hook", + "Adrian Carmack", + "Kevin Cloud", + "Paul Steed", + "Tim Willits", + "Christian Antkow", + "Paul Jaquays", + "Brandon James", + "Todd Hollenshead", + "Barrett (Bear) Alexander", + "Katherine Anna Kang", + "Donna Jackson", + "Dave \"Zoid\" Kirsch", + "", + "", + "", + "+THANKS TO ACTIVISION", + "+IN PARTICULAR:", + "", + "Marty Stratton", + "Henk Hartong", + "Mitch Lasky", + "Steve Rosenthal", + "Steve Elwell", + "", + "+AND THE GAME TESTERS", + "", + "The Ranger Clan", + "Dave \"Zoid\" Kirsch", + "Nihilistic Software", + "Robert Duffy", + "", + "And Countless Others", + "", + "", + "", + "Quake II Mission Pack 2: Ground Zero", + "(tm) (C)1998 Id Software, Inc. All", + "Rights Reserved. Developed by Rogue", + "Entertainment, Inc. for Id Software,", + "Inc. Distributed by Activision Inc.", + "under license. Quake(R) is a", + "registered trademark of Id Software,", + "Inc. Quake II Mission Pack 2: Ground", + "Zero(tm), Quake II(tm), the Id", + "Software name, the \"Q II\"(tm) logo", + "and id(tm) logo are trademarks of Id", + "Software, Inc. Activision(R) is a", + "registered trademark of Activision,", + "Inc. Rogue(R) is a registered", + "trademark of Rogue Entertainment,", + "Inc. All other trademarks and trade", + "names are properties of their", + "respective owners.", + 0 +}; + + +void M_Credits_MenuDraw( void ) +{ + int i, y; + + /* + ** draw the credits + */ + for ( i = 0, y = viddef.height - ( ( cls.realtime - credits_start_time ) / 40.0F ); credits[i] && y < viddef.height; y += 10, i++ ) + { + int j, stringoffset = 0; + int bold = false; + + if ( y <= -8 ) + continue; + + if ( credits[i][0] == '+' ) + { + bold = true; + stringoffset = 1; + } + else + { + bold = false; + stringoffset = 0; + } + + for ( j = 0; credits[i][j+stringoffset]; j++ ) + { + int x; + + x = ( viddef.width - strlen( credits[i] ) * 8 - stringoffset * 8 ) / 2 + ( j + stringoffset ) * 8; + + if ( bold ) + re.DrawChar( x, y, credits[i][j+stringoffset] + 128 ); + else + re.DrawChar( x, y, credits[i][j+stringoffset] ); + } + } + + if ( y < 0 ) + credits_start_time = cls.realtime; +} + +const char *M_Credits_Key( int key ) +{ + switch (key) + { + case K_ESCAPE: + if (creditsBuffer) + FS_FreeFile (creditsBuffer); + M_PopMenu (); + break; + } + + return menu_out_sound; + +} + +extern int Developer_searchpath (int who); + +void M_Menu_Credits_f( void ) +{ + int n; + int count; + char *p; + int isdeveloper = 0; + + creditsBuffer = NULL; + count = FS_LoadFile ("credits", &creditsBuffer); + if (count != -1) + { + p = creditsBuffer; + for (n = 0; n < 255; n++) + { + creditsIndex[n] = p; + while (*p != '\r' && *p != '\n') + { + p++; + if (--count == 0) + break; + } + if (*p == '\r') + { + *p++ = 0; + if (--count == 0) + break; + } + *p++ = 0; + if (--count == 0) + break; + } + creditsIndex[++n] = 0; + credits = creditsIndex; + } + else + { + isdeveloper = Developer_searchpath (1); + + if (isdeveloper == 1) // xatrix + credits = xatcredits; + else if (isdeveloper == 2) // ROGUE + credits = roguecredits; + else + { + credits = idcredits; + } + + } + + credits_start_time = cls.realtime; + M_PushMenu( M_Credits_MenuDraw, M_Credits_Key); +} + +/* +============================================================================= + +GAME MENU + +============================================================================= +*/ + +static int m_game_cursor; + +static menuframework_s s_game_menu; +static menuaction_s s_easy_game_action; +static menuaction_s s_medium_game_action; +static menuaction_s s_hard_game_action; +static menuaction_s s_load_game_action; +static menuaction_s s_save_game_action; +static menuaction_s s_credits_action; +static menuseparator_s s_blankline; + +static void StartGame( void ) +{ + // disable updates and start the cinematic going + cl.servercount = -1; + M_ForceMenuOff (); + Cvar_SetValue( "deathmatch", 0 ); + Cvar_SetValue( "coop", 0 ); + + Cvar_SetValue( "gamerules", 0 ); //PGM + + Cbuf_AddText ("loading ; killserver ; wait ; newgame\n"); + cls.key_dest = key_game; +} + +static void EasyGameFunc( void *data ) +{ + Cvar_ForceSet( "skill", "0" ); + StartGame(); +} + +static void MediumGameFunc( void *data ) +{ + Cvar_ForceSet( "skill", "1" ); + StartGame(); +} + +static void HardGameFunc( void *data ) +{ + Cvar_ForceSet( "skill", "2" ); + StartGame(); +} + +static void LoadGameFunc( void *unused ) +{ + M_Menu_LoadGame_f (); +} + +static void SaveGameFunc( void *unused ) +{ + M_Menu_SaveGame_f(); +} + +static void CreditsFunc( void *unused ) +{ + M_Menu_Credits_f(); +} + +void Game_MenuInit( void ) +{ + static const char *difficulty_names[] = + { + "easy", + "medium", + "hard", + 0 + }; + + s_game_menu.x = viddef.width * 0.50; + s_game_menu.nitems = 0; + + s_easy_game_action.generic.type = MTYPE_ACTION; + s_easy_game_action.generic.flags = QMF_LEFT_JUSTIFY; + s_easy_game_action.generic.x = 0; + s_easy_game_action.generic.y = 0; + s_easy_game_action.generic.name = "easy"; + s_easy_game_action.generic.callback = EasyGameFunc; + + s_medium_game_action.generic.type = MTYPE_ACTION; + s_medium_game_action.generic.flags = QMF_LEFT_JUSTIFY; + s_medium_game_action.generic.x = 0; + s_medium_game_action.generic.y = 10; + s_medium_game_action.generic.name = "medium"; + s_medium_game_action.generic.callback = MediumGameFunc; + + s_hard_game_action.generic.type = MTYPE_ACTION; + s_hard_game_action.generic.flags = QMF_LEFT_JUSTIFY; + s_hard_game_action.generic.x = 0; + s_hard_game_action.generic.y = 20; + s_hard_game_action.generic.name = "hard"; + s_hard_game_action.generic.callback = HardGameFunc; + + s_blankline.generic.type = MTYPE_SEPARATOR; + + s_load_game_action.generic.type = MTYPE_ACTION; + s_load_game_action.generic.flags = QMF_LEFT_JUSTIFY; + s_load_game_action.generic.x = 0; + s_load_game_action.generic.y = 40; + s_load_game_action.generic.name = "load game"; + s_load_game_action.generic.callback = LoadGameFunc; + + s_save_game_action.generic.type = MTYPE_ACTION; + s_save_game_action.generic.flags = QMF_LEFT_JUSTIFY; + s_save_game_action.generic.x = 0; + s_save_game_action.generic.y = 50; + s_save_game_action.generic.name = "save game"; + s_save_game_action.generic.callback = SaveGameFunc; + + s_credits_action.generic.type = MTYPE_ACTION; + s_credits_action.generic.flags = QMF_LEFT_JUSTIFY; + s_credits_action.generic.x = 0; + s_credits_action.generic.y = 60; + s_credits_action.generic.name = "credits"; + s_credits_action.generic.callback = CreditsFunc; + + Menu_AddItem( &s_game_menu, ( void * ) &s_easy_game_action ); + Menu_AddItem( &s_game_menu, ( void * ) &s_medium_game_action ); + Menu_AddItem( &s_game_menu, ( void * ) &s_hard_game_action ); + Menu_AddItem( &s_game_menu, ( void * ) &s_blankline ); + Menu_AddItem( &s_game_menu, ( void * ) &s_load_game_action ); + Menu_AddItem( &s_game_menu, ( void * ) &s_save_game_action ); + Menu_AddItem( &s_game_menu, ( void * ) &s_blankline ); + Menu_AddItem( &s_game_menu, ( void * ) &s_credits_action ); + + Menu_Center( &s_game_menu ); +} + +void Game_MenuDraw( void ) +{ + M_Banner( "m_banner_game" ); + Menu_AdjustCursor( &s_game_menu, 1 ); + Menu_Draw( &s_game_menu ); +} + +const char *Game_MenuKey( int key ) +{ + return Default_MenuKey( &s_game_menu, key ); +} + +void M_Menu_Game_f (void) +{ + Game_MenuInit(); + M_PushMenu( Game_MenuDraw, Game_MenuKey ); + m_game_cursor = 1; +} + +/* +============================================================================= + +LOADGAME MENU + +============================================================================= +*/ + +#define MAX_SAVEGAMES 15 + +static menuframework_s s_savegame_menu; + +static menuframework_s s_loadgame_menu; +static menuaction_s s_loadgame_actions[MAX_SAVEGAMES]; + +char m_savestrings[MAX_SAVEGAMES][32]; +qboolean m_savevalid[MAX_SAVEGAMES]; + +void Create_Savestrings (void) +{ + int i; + FILE *f; + char name[MAX_OSPATH]; + + for (i=0 ; i"); + m_savevalid[i] = false; + } + else + { + FS_Read (m_savestrings[i], sizeof(m_savestrings[i]), f); + fclose (f); + m_savevalid[i] = true; + } + } +} + +void LoadGameCallback( void *self ) +{ + menuaction_s *a = ( menuaction_s * ) self; + + if ( m_savevalid[ a->generic.localdata[0] ] ) + Cbuf_AddText (va("load save%i\n", a->generic.localdata[0] ) ); + M_ForceMenuOff (); +} + +void LoadGame_MenuInit( void ) +{ + int i; + + s_loadgame_menu.x = viddef.width / 2 - 120; + s_loadgame_menu.y = viddef.height / 2 - 58; + s_loadgame_menu.nitems = 0; + + Create_Savestrings(); + + for ( i = 0; i < MAX_SAVEGAMES; i++ ) + { + s_loadgame_actions[i].generic.name = m_savestrings[i]; + s_loadgame_actions[i].generic.flags = QMF_LEFT_JUSTIFY; + s_loadgame_actions[i].generic.localdata[0] = i; + s_loadgame_actions[i].generic.callback = LoadGameCallback; + + s_loadgame_actions[i].generic.x = 0; + s_loadgame_actions[i].generic.y = ( i ) * 10; + if (i>0) // separate from autosave + s_loadgame_actions[i].generic.y += 10; + + s_loadgame_actions[i].generic.type = MTYPE_ACTION; + + Menu_AddItem( &s_loadgame_menu, &s_loadgame_actions[i] ); + } +} + +void LoadGame_MenuDraw( void ) +{ + M_Banner( "m_banner_load_game" ); +// Menu_AdjustCursor( &s_loadgame_menu, 1 ); + Menu_Draw( &s_loadgame_menu ); +} + +const char *LoadGame_MenuKey( int key ) +{ + if ( key == K_ESCAPE || key == K_ENTER ) + { + s_savegame_menu.cursor = s_loadgame_menu.cursor - 1; + if ( s_savegame_menu.cursor < 0 ) + s_savegame_menu.cursor = 0; + } + return Default_MenuKey( &s_loadgame_menu, key ); +} + +void M_Menu_LoadGame_f (void) +{ + LoadGame_MenuInit(); + M_PushMenu( LoadGame_MenuDraw, LoadGame_MenuKey ); +} + + +/* +============================================================================= + +SAVEGAME MENU + +============================================================================= +*/ +static menuframework_s s_savegame_menu; +static menuaction_s s_savegame_actions[MAX_SAVEGAMES]; + +void SaveGameCallback( void *self ) +{ + menuaction_s *a = ( menuaction_s * ) self; + + Cbuf_AddText (va("save save%i\n", a->generic.localdata[0] )); + M_ForceMenuOff (); +} + +void SaveGame_MenuDraw( void ) +{ + M_Banner( "m_banner_save_game" ); + Menu_AdjustCursor( &s_savegame_menu, 1 ); + Menu_Draw( &s_savegame_menu ); +} + +void SaveGame_MenuInit( void ) +{ + int i; + + s_savegame_menu.x = viddef.width / 2 - 120; + s_savegame_menu.y = viddef.height / 2 - 58; + s_savegame_menu.nitems = 0; + + Create_Savestrings(); + + // don't include the autosave slot + for ( i = 0; i < MAX_SAVEGAMES-1; i++ ) + { + s_savegame_actions[i].generic.name = m_savestrings[i+1]; + s_savegame_actions[i].generic.localdata[0] = i+1; + s_savegame_actions[i].generic.flags = QMF_LEFT_JUSTIFY; + s_savegame_actions[i].generic.callback = SaveGameCallback; + + s_savegame_actions[i].generic.x = 0; + s_savegame_actions[i].generic.y = ( i ) * 10; + + s_savegame_actions[i].generic.type = MTYPE_ACTION; + + Menu_AddItem( &s_savegame_menu, &s_savegame_actions[i] ); + } +} + +const char *SaveGame_MenuKey( int key ) +{ + if ( key == K_ENTER || key == K_ESCAPE ) + { + s_loadgame_menu.cursor = s_savegame_menu.cursor - 1; + if ( s_loadgame_menu.cursor < 0 ) + s_loadgame_menu.cursor = 0; + } + return Default_MenuKey( &s_savegame_menu, key ); +} + +void M_Menu_SaveGame_f (void) +{ + if (!Com_ServerState()) + return; // not playing a game + + SaveGame_MenuInit(); + M_PushMenu( SaveGame_MenuDraw, SaveGame_MenuKey ); + Create_Savestrings (); +} + + +/* +============================================================================= + +JOIN SERVER MENU + +============================================================================= +*/ +#define MAX_LOCAL_SERVERS 8 + +static menuframework_s s_joinserver_menu; +static menuseparator_s s_joinserver_server_title; +static menuaction_s s_joinserver_search_action; +static menuaction_s s_joinserver_address_book_action; +static menuaction_s s_joinserver_server_actions[MAX_LOCAL_SERVERS]; + +int m_num_servers; +#define NO_SERVER_STRING "" + +// user readable information +static char local_server_names[MAX_LOCAL_SERVERS][80]; + +// network address +static netadr_t local_server_netadr[MAX_LOCAL_SERVERS]; + +void M_AddToServerList (netadr_t adr, char *info) +{ + int i; + + if (m_num_servers == MAX_LOCAL_SERVERS) + return; + while ( *info == ' ' ) + info++; + + // ignore if duplicated + for (i=0 ; i= m_num_servers) + return; + + Com_sprintf (buffer, sizeof(buffer), "connect %s\n", NET_AdrToString (local_server_netadr[index])); + Cbuf_AddText (buffer); + M_ForceMenuOff (); +} + +void AddressBookFunc( void *self ) +{ + M_Menu_AddressBook_f(); +} + +void NullCursorDraw( void *self ) +{ +} + +void SearchLocalGames( void ) +{ + int i; + + m_num_servers = 0; + for (i=0 ; i 4) + strcpy( s_maxclients_field.buffer, "4" ); + s_startserver_dmoptions_action.generic.statusbar = "N/A for cooperative"; + } +//===== +//PGM + // ROGUE GAMES + else if(Developer_searchpath(2) == 2) + { + if (s_rules_box.curvalue == 2) // tag + { + s_maxclients_field.generic.statusbar = NULL; + s_startserver_dmoptions_action.generic.statusbar = NULL; + } +/* + else if(s_rules_box.curvalue == 3) // deathball + { + s_maxclients_field.generic.statusbar = NULL; + s_startserver_dmoptions_action.generic.statusbar = NULL; + } +*/ + } +//PGM +//===== +} + +void StartServerActionFunc( void *self ) +{ + char startmap[1024]; + int timelimit; + int fraglimit; + int maxclients; + char *spot; + + strcpy( startmap, strchr( mapnames[s_startmap_list.curvalue], '\n' ) + 1 ); + + maxclients = atoi( s_maxclients_field.buffer ); + timelimit = atoi( s_timelimit_field.buffer ); + fraglimit = atoi( s_fraglimit_field.buffer ); + + Cvar_SetValue( "maxclients", ClampCvar( 0, maxclients, maxclients ) ); + Cvar_SetValue ("timelimit", ClampCvar( 0, timelimit, timelimit ) ); + Cvar_SetValue ("fraglimit", ClampCvar( 0, fraglimit, fraglimit ) ); + Cvar_Set("hostname", s_hostname_field.buffer ); +// Cvar_SetValue ("deathmatch", !s_rules_box.curvalue ); +// Cvar_SetValue ("coop", s_rules_box.curvalue ); + +//PGM + if((s_rules_box.curvalue < 2) || (Developer_searchpath(2) != 2)) + { + Cvar_SetValue ("deathmatch", !s_rules_box.curvalue ); + Cvar_SetValue ("coop", s_rules_box.curvalue ); + Cvar_SetValue ("gamerules", 0 ); + } + else + { + Cvar_SetValue ("deathmatch", 1 ); // deathmatch is always true for rogue games, right? + Cvar_SetValue ("coop", 0 ); // FIXME - this might need to depend on which game we're running + Cvar_SetValue ("gamerules", s_rules_box.curvalue ); + } +//PGM + + spot = NULL; + if (s_rules_box.curvalue == 1) // PGM + { + if(Q_stricmp(startmap, "bunk1") == 0) + spot = "start"; + else if(Q_stricmp(startmap, "mintro") == 0) + spot = "start"; + else if(Q_stricmp(startmap, "fact1") == 0) + spot = "start"; + else if(Q_stricmp(startmap, "power1") == 0) + spot = "pstart"; + else if(Q_stricmp(startmap, "biggun") == 0) + spot = "bstart"; + else if(Q_stricmp(startmap, "hangar1") == 0) + spot = "unitstart"; + else if(Q_stricmp(startmap, "city1") == 0) + spot = "unitstart"; + else if(Q_stricmp(startmap, "boss1") == 0) + spot = "bosstart"; + } + + if (spot) + { + if (Com_ServerState()) + Cbuf_AddText ("disconnect\n"); + Cbuf_AddText (va("gamemap \"*%s$%s\"\n", startmap, spot)); + } + else + { + Cbuf_AddText (va("map %s\n", startmap)); + } + + M_ForceMenuOff (); +} + +void StartServer_MenuInit( void ) +{ + static const char *dm_coop_names[] = + { + "deathmatch", + "cooperative", + 0 + }; +//======= +//PGM + static const char *dm_coop_names_rogue[] = + { + "deathmatch", + "cooperative", + "tag", +// "deathball", + 0 + }; +//PGM +//======= + char *buffer; + char mapsname[1024]; + char *s; + int length; + int i; + FILE *fp; + + /* + ** load the list of map names + */ + Com_sprintf( mapsname, sizeof( mapsname ), "%s/maps.lst", FS_Gamedir() ); + if ( ( fp = fopen( mapsname, "rb" ) ) == 0 ) + { + if ( ( length = FS_LoadFile( "maps.lst", ( void ** ) &buffer ) ) == -1 ) + Com_Error( ERR_DROP, "couldn't find maps.lst\n" ); + } + else + { +#ifdef _WIN32 + length = filelength( fileno( fp ) ); +#else + fseek(fp, 0, SEEK_END); + length = ftell(fp); + fseek(fp, 0, SEEK_SET); +#endif + buffer = malloc( length ); + fread( buffer, length, 1, fp ); + } + + s = buffer; + + i = 0; + while ( i < length ) + { + if ( s[i] == '\r' ) + nummaps++; + i++; + } + + if ( nummaps == 0 ) + Com_Error( ERR_DROP, "no maps in maps.lst\n" ); + + mapnames = malloc( sizeof( char * ) * ( nummaps + 1 ) ); + memset( mapnames, 0, sizeof( char * ) * ( nummaps + 1 ) ); + + s = buffer; + + for ( i = 0; i < nummaps; i++ ) + { + char shortname[MAX_TOKEN_CHARS]; + char longname[MAX_TOKEN_CHARS]; + char scratch[200]; + int j, l; + + strcpy( shortname, COM_Parse( &s ) ); + l = strlen(shortname); + for (j=0 ; jcurvalue ) + flags &= ~DF_NO_FRIENDLY_FIRE; + else + flags |= DF_NO_FRIENDLY_FIRE; + goto setvalue; + } + else if ( f == &s_falls_box ) + { + if ( f->curvalue ) + flags &= ~DF_NO_FALLING; + else + flags |= DF_NO_FALLING; + goto setvalue; + } + else if ( f == &s_weapons_stay_box ) + { + bit = DF_WEAPONS_STAY; + } + else if ( f == &s_instant_powerups_box ) + { + bit = DF_INSTANT_ITEMS; + } + else if ( f == &s_allow_exit_box ) + { + bit = DF_ALLOW_EXIT; + } + else if ( f == &s_powerups_box ) + { + if ( f->curvalue ) + flags &= ~DF_NO_ITEMS; + else + flags |= DF_NO_ITEMS; + goto setvalue; + } + else if ( f == &s_health_box ) + { + if ( f->curvalue ) + flags &= ~DF_NO_HEALTH; + else + flags |= DF_NO_HEALTH; + goto setvalue; + } + else if ( f == &s_spawn_farthest_box ) + { + bit = DF_SPAWN_FARTHEST; + } + else if ( f == &s_teamplay_box ) + { + if ( f->curvalue == 1 ) + { + flags |= DF_SKINTEAMS; + flags &= ~DF_MODELTEAMS; + } + else if ( f->curvalue == 2 ) + { + flags |= DF_MODELTEAMS; + flags &= ~DF_SKINTEAMS; + } + else + { + flags &= ~( DF_MODELTEAMS | DF_SKINTEAMS ); + } + + goto setvalue; + } + else if ( f == &s_samelevel_box ) + { + bit = DF_SAME_LEVEL; + } + else if ( f == &s_force_respawn_box ) + { + bit = DF_FORCE_RESPAWN; + } + else if ( f == &s_armor_box ) + { + if ( f->curvalue ) + flags &= ~DF_NO_ARMOR; + else + flags |= DF_NO_ARMOR; + goto setvalue; + } + else if ( f == &s_infinite_ammo_box ) + { + bit = DF_INFINITE_AMMO; + } + else if ( f == &s_fixed_fov_box ) + { + bit = DF_FIXED_FOV; + } + else if ( f == &s_quad_drop_box ) + { + bit = DF_QUAD_DROP; + } + +//======= +//ROGUE + else if (Developer_searchpath(2) == 2) + { + if ( f == &s_no_mines_box) + { + bit = DF_NO_MINES; + } + else if ( f == &s_no_nukes_box) + { + bit = DF_NO_NUKES; + } + else if ( f == &s_stack_double_box) + { + bit = DF_NO_STACK_DOUBLE; + } + else if ( f == &s_no_spheres_box) + { + bit = DF_NO_SPHERES; + } + } +//ROGUE +//======= + + if ( f ) + { + if ( f->curvalue == 0 ) + flags &= ~bit; + else + flags |= bit; + } + +setvalue: + Cvar_SetValue ("dmflags", flags); + + Com_sprintf( dmoptions_statusbar, sizeof( dmoptions_statusbar ), "dmflags = %d", flags ); + +} + +void DMOptions_MenuInit( void ) +{ + static const char *yes_no_names[] = + { + "no", "yes", 0 + }; + static const char *teamplay_names[] = + { + "disabled", "by skin", "by model", 0 + }; + int dmflags = Cvar_VariableValue( "dmflags" ); + int y = 0; + + s_dmoptions_menu.x = viddef.width * 0.50; + s_dmoptions_menu.nitems = 0; + + s_falls_box.generic.type = MTYPE_SPINCONTROL; + s_falls_box.generic.x = 0; + s_falls_box.generic.y = y; + s_falls_box.generic.name = "falling damage"; + s_falls_box.generic.callback = DMFlagCallback; + s_falls_box.itemnames = yes_no_names; + s_falls_box.curvalue = ( dmflags & DF_NO_FALLING ) == 0; + + s_weapons_stay_box.generic.type = MTYPE_SPINCONTROL; + s_weapons_stay_box.generic.x = 0; + s_weapons_stay_box.generic.y = y += 10; + s_weapons_stay_box.generic.name = "weapons stay"; + s_weapons_stay_box.generic.callback = DMFlagCallback; + s_weapons_stay_box.itemnames = yes_no_names; + s_weapons_stay_box.curvalue = ( dmflags & DF_WEAPONS_STAY ) != 0; + + s_instant_powerups_box.generic.type = MTYPE_SPINCONTROL; + s_instant_powerups_box.generic.x = 0; + s_instant_powerups_box.generic.y = y += 10; + s_instant_powerups_box.generic.name = "instant powerups"; + s_instant_powerups_box.generic.callback = DMFlagCallback; + s_instant_powerups_box.itemnames = yes_no_names; + s_instant_powerups_box.curvalue = ( dmflags & DF_INSTANT_ITEMS ) != 0; + + s_powerups_box.generic.type = MTYPE_SPINCONTROL; + s_powerups_box.generic.x = 0; + s_powerups_box.generic.y = y += 10; + s_powerups_box.generic.name = "allow powerups"; + s_powerups_box.generic.callback = DMFlagCallback; + s_powerups_box.itemnames = yes_no_names; + s_powerups_box.curvalue = ( dmflags & DF_NO_ITEMS ) == 0; + + s_health_box.generic.type = MTYPE_SPINCONTROL; + s_health_box.generic.x = 0; + s_health_box.generic.y = y += 10; + s_health_box.generic.callback = DMFlagCallback; + s_health_box.generic.name = "allow health"; + s_health_box.itemnames = yes_no_names; + s_health_box.curvalue = ( dmflags & DF_NO_HEALTH ) == 0; + + s_armor_box.generic.type = MTYPE_SPINCONTROL; + s_armor_box.generic.x = 0; + s_armor_box.generic.y = y += 10; + s_armor_box.generic.name = "allow armor"; + s_armor_box.generic.callback = DMFlagCallback; + s_armor_box.itemnames = yes_no_names; + s_armor_box.curvalue = ( dmflags & DF_NO_ARMOR ) == 0; + + s_spawn_farthest_box.generic.type = MTYPE_SPINCONTROL; + s_spawn_farthest_box.generic.x = 0; + s_spawn_farthest_box.generic.y = y += 10; + s_spawn_farthest_box.generic.name = "spawn farthest"; + s_spawn_farthest_box.generic.callback = DMFlagCallback; + s_spawn_farthest_box.itemnames = yes_no_names; + s_spawn_farthest_box.curvalue = ( dmflags & DF_SPAWN_FARTHEST ) != 0; + + s_samelevel_box.generic.type = MTYPE_SPINCONTROL; + s_samelevel_box.generic.x = 0; + s_samelevel_box.generic.y = y += 10; + s_samelevel_box.generic.name = "same map"; + s_samelevel_box.generic.callback = DMFlagCallback; + s_samelevel_box.itemnames = yes_no_names; + s_samelevel_box.curvalue = ( dmflags & DF_SAME_LEVEL ) != 0; + + s_force_respawn_box.generic.type = MTYPE_SPINCONTROL; + s_force_respawn_box.generic.x = 0; + s_force_respawn_box.generic.y = y += 10; + s_force_respawn_box.generic.name = "force respawn"; + s_force_respawn_box.generic.callback = DMFlagCallback; + s_force_respawn_box.itemnames = yes_no_names; + s_force_respawn_box.curvalue = ( dmflags & DF_FORCE_RESPAWN ) != 0; + + s_teamplay_box.generic.type = MTYPE_SPINCONTROL; + s_teamplay_box.generic.x = 0; + s_teamplay_box.generic.y = y += 10; + s_teamplay_box.generic.name = "teamplay"; + s_teamplay_box.generic.callback = DMFlagCallback; + s_teamplay_box.itemnames = teamplay_names; + + s_allow_exit_box.generic.type = MTYPE_SPINCONTROL; + s_allow_exit_box.generic.x = 0; + s_allow_exit_box.generic.y = y += 10; + s_allow_exit_box.generic.name = "allow exit"; + s_allow_exit_box.generic.callback = DMFlagCallback; + s_allow_exit_box.itemnames = yes_no_names; + s_allow_exit_box.curvalue = ( dmflags & DF_ALLOW_EXIT ) != 0; + + s_infinite_ammo_box.generic.type = MTYPE_SPINCONTROL; + s_infinite_ammo_box.generic.x = 0; + s_infinite_ammo_box.generic.y = y += 10; + s_infinite_ammo_box.generic.name = "infinite ammo"; + s_infinite_ammo_box.generic.callback = DMFlagCallback; + s_infinite_ammo_box.itemnames = yes_no_names; + s_infinite_ammo_box.curvalue = ( dmflags & DF_INFINITE_AMMO ) != 0; + + s_fixed_fov_box.generic.type = MTYPE_SPINCONTROL; + s_fixed_fov_box.generic.x = 0; + s_fixed_fov_box.generic.y = y += 10; + s_fixed_fov_box.generic.name = "fixed FOV"; + s_fixed_fov_box.generic.callback = DMFlagCallback; + s_fixed_fov_box.itemnames = yes_no_names; + s_fixed_fov_box.curvalue = ( dmflags & DF_FIXED_FOV ) != 0; + + s_quad_drop_box.generic.type = MTYPE_SPINCONTROL; + s_quad_drop_box.generic.x = 0; + s_quad_drop_box.generic.y = y += 10; + s_quad_drop_box.generic.name = "quad drop"; + s_quad_drop_box.generic.callback = DMFlagCallback; + s_quad_drop_box.itemnames = yes_no_names; + s_quad_drop_box.curvalue = ( dmflags & DF_QUAD_DROP ) != 0; + + s_friendlyfire_box.generic.type = MTYPE_SPINCONTROL; + s_friendlyfire_box.generic.x = 0; + s_friendlyfire_box.generic.y = y += 10; + s_friendlyfire_box.generic.name = "friendly fire"; + s_friendlyfire_box.generic.callback = DMFlagCallback; + s_friendlyfire_box.itemnames = yes_no_names; + s_friendlyfire_box.curvalue = ( dmflags & DF_NO_FRIENDLY_FIRE ) == 0; + +//============ +//ROGUE + if(Developer_searchpath(2) == 2) + { + s_no_mines_box.generic.type = MTYPE_SPINCONTROL; + s_no_mines_box.generic.x = 0; + s_no_mines_box.generic.y = y += 10; + s_no_mines_box.generic.name = "remove mines"; + s_no_mines_box.generic.callback = DMFlagCallback; + s_no_mines_box.itemnames = yes_no_names; + s_no_mines_box.curvalue = ( dmflags & DF_NO_MINES ) != 0; + + s_no_nukes_box.generic.type = MTYPE_SPINCONTROL; + s_no_nukes_box.generic.x = 0; + s_no_nukes_box.generic.y = y += 10; + s_no_nukes_box.generic.name = "remove nukes"; + s_no_nukes_box.generic.callback = DMFlagCallback; + s_no_nukes_box.itemnames = yes_no_names; + s_no_nukes_box.curvalue = ( dmflags & DF_NO_NUKES ) != 0; + + s_stack_double_box.generic.type = MTYPE_SPINCONTROL; + s_stack_double_box.generic.x = 0; + s_stack_double_box.generic.y = y += 10; + s_stack_double_box.generic.name = "2x/4x stacking off"; + s_stack_double_box.generic.callback = DMFlagCallback; + s_stack_double_box.itemnames = yes_no_names; + s_stack_double_box.curvalue = ( dmflags & DF_NO_STACK_DOUBLE ) != 0; + + s_no_spheres_box.generic.type = MTYPE_SPINCONTROL; + s_no_spheres_box.generic.x = 0; + s_no_spheres_box.generic.y = y += 10; + s_no_spheres_box.generic.name = "remove spheres"; + s_no_spheres_box.generic.callback = DMFlagCallback; + s_no_spheres_box.itemnames = yes_no_names; + s_no_spheres_box.curvalue = ( dmflags & DF_NO_SPHERES ) != 0; + + } +//ROGUE +//============ + + Menu_AddItem( &s_dmoptions_menu, &s_falls_box ); + Menu_AddItem( &s_dmoptions_menu, &s_weapons_stay_box ); + Menu_AddItem( &s_dmoptions_menu, &s_instant_powerups_box ); + Menu_AddItem( &s_dmoptions_menu, &s_powerups_box ); + Menu_AddItem( &s_dmoptions_menu, &s_health_box ); + Menu_AddItem( &s_dmoptions_menu, &s_armor_box ); + Menu_AddItem( &s_dmoptions_menu, &s_spawn_farthest_box ); + Menu_AddItem( &s_dmoptions_menu, &s_samelevel_box ); + Menu_AddItem( &s_dmoptions_menu, &s_force_respawn_box ); + Menu_AddItem( &s_dmoptions_menu, &s_teamplay_box ); + Menu_AddItem( &s_dmoptions_menu, &s_allow_exit_box ); + Menu_AddItem( &s_dmoptions_menu, &s_infinite_ammo_box ); + Menu_AddItem( &s_dmoptions_menu, &s_fixed_fov_box ); + Menu_AddItem( &s_dmoptions_menu, &s_quad_drop_box ); + Menu_AddItem( &s_dmoptions_menu, &s_friendlyfire_box ); + +//======= +//ROGUE + if(Developer_searchpath(2) == 2) + { + Menu_AddItem( &s_dmoptions_menu, &s_no_mines_box ); + Menu_AddItem( &s_dmoptions_menu, &s_no_nukes_box ); + Menu_AddItem( &s_dmoptions_menu, &s_stack_double_box ); + Menu_AddItem( &s_dmoptions_menu, &s_no_spheres_box ); + } +//ROGUE +//======= + + Menu_Center( &s_dmoptions_menu ); + + // set the original dmflags statusbar + DMFlagCallback( 0 ); + Menu_SetStatusBar( &s_dmoptions_menu, dmoptions_statusbar ); +} + +void DMOptions_MenuDraw(void) +{ + Menu_Draw( &s_dmoptions_menu ); +} + +const char *DMOptions_MenuKey( int key ) +{ + return Default_MenuKey( &s_dmoptions_menu, key ); +} + +void M_Menu_DMOptions_f (void) +{ + DMOptions_MenuInit(); + M_PushMenu( DMOptions_MenuDraw, DMOptions_MenuKey ); +} + +/* +============================================================================= + +DOWNLOADOPTIONS BOOK MENU + +============================================================================= +*/ +static menuframework_s s_downloadoptions_menu; + +static menuseparator_s s_download_title; +static menulist_s s_allow_download_box; +static menulist_s s_allow_download_maps_box; +static menulist_s s_allow_download_models_box; +static menulist_s s_allow_download_players_box; +static menulist_s s_allow_download_sounds_box; + +static void DownloadCallback( void *self ) +{ + menulist_s *f = ( menulist_s * ) self; + + if (f == &s_allow_download_box) + { + Cvar_SetValue("allow_download", f->curvalue); + } + + else if (f == &s_allow_download_maps_box) + { + Cvar_SetValue("allow_download_maps", f->curvalue); + } + + else if (f == &s_allow_download_models_box) + { + Cvar_SetValue("allow_download_models", f->curvalue); + } + + else if (f == &s_allow_download_players_box) + { + Cvar_SetValue("allow_download_players", f->curvalue); + } + + else if (f == &s_allow_download_sounds_box) + { + Cvar_SetValue("allow_download_sounds", f->curvalue); + } +} + +void DownloadOptions_MenuInit( void ) +{ + static const char *yes_no_names[] = + { + "no", "yes", 0 + }; + int y = 0; + + s_downloadoptions_menu.x = viddef.width * 0.50; + s_downloadoptions_menu.nitems = 0; + + s_download_title.generic.type = MTYPE_SEPARATOR; + s_download_title.generic.name = "Download Options"; + s_download_title.generic.x = 48; + s_download_title.generic.y = y; + + s_allow_download_box.generic.type = MTYPE_SPINCONTROL; + s_allow_download_box.generic.x = 0; + s_allow_download_box.generic.y = y += 20; + s_allow_download_box.generic.name = "allow downloading"; + s_allow_download_box.generic.callback = DownloadCallback; + s_allow_download_box.itemnames = yes_no_names; + s_allow_download_box.curvalue = (Cvar_VariableValue("allow_download") != 0); + + s_allow_download_maps_box.generic.type = MTYPE_SPINCONTROL; + s_allow_download_maps_box.generic.x = 0; + s_allow_download_maps_box.generic.y = y += 20; + s_allow_download_maps_box.generic.name = "maps"; + s_allow_download_maps_box.generic.callback = DownloadCallback; + s_allow_download_maps_box.itemnames = yes_no_names; + s_allow_download_maps_box.curvalue = (Cvar_VariableValue("allow_download_maps") != 0); + + s_allow_download_players_box.generic.type = MTYPE_SPINCONTROL; + s_allow_download_players_box.generic.x = 0; + s_allow_download_players_box.generic.y = y += 10; + s_allow_download_players_box.generic.name = "player models/skins"; + s_allow_download_players_box.generic.callback = DownloadCallback; + s_allow_download_players_box.itemnames = yes_no_names; + s_allow_download_players_box.curvalue = (Cvar_VariableValue("allow_download_players") != 0); + + s_allow_download_models_box.generic.type = MTYPE_SPINCONTROL; + s_allow_download_models_box.generic.x = 0; + s_allow_download_models_box.generic.y = y += 10; + s_allow_download_models_box.generic.name = "models"; + s_allow_download_models_box.generic.callback = DownloadCallback; + s_allow_download_models_box.itemnames = yes_no_names; + s_allow_download_models_box.curvalue = (Cvar_VariableValue("allow_download_models") != 0); + + s_allow_download_sounds_box.generic.type = MTYPE_SPINCONTROL; + s_allow_download_sounds_box.generic.x = 0; + s_allow_download_sounds_box.generic.y = y += 10; + s_allow_download_sounds_box.generic.name = "sounds"; + s_allow_download_sounds_box.generic.callback = DownloadCallback; + s_allow_download_sounds_box.itemnames = yes_no_names; + s_allow_download_sounds_box.curvalue = (Cvar_VariableValue("allow_download_sounds") != 0); + + Menu_AddItem( &s_downloadoptions_menu, &s_download_title ); + Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_box ); + Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_maps_box ); + Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_players_box ); + Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_models_box ); + Menu_AddItem( &s_downloadoptions_menu, &s_allow_download_sounds_box ); + + Menu_Center( &s_downloadoptions_menu ); + + // skip over title + if (s_downloadoptions_menu.cursor == 0) + s_downloadoptions_menu.cursor = 1; +} + +void DownloadOptions_MenuDraw(void) +{ + Menu_Draw( &s_downloadoptions_menu ); +} + +const char *DownloadOptions_MenuKey( int key ) +{ + return Default_MenuKey( &s_downloadoptions_menu, key ); +} + +void M_Menu_DownloadOptions_f (void) +{ + DownloadOptions_MenuInit(); + M_PushMenu( DownloadOptions_MenuDraw, DownloadOptions_MenuKey ); +} +/* +============================================================================= + +ADDRESS BOOK MENU + +============================================================================= +*/ +#define NUM_ADDRESSBOOK_ENTRIES 9 + +static menuframework_s s_addressbook_menu; +static menufield_s s_addressbook_fields[NUM_ADDRESSBOOK_ENTRIES]; + +void AddressBook_MenuInit( void ) +{ + int i; + + s_addressbook_menu.x = viddef.width / 2 - 142; + s_addressbook_menu.y = viddef.height / 2 - 58; + s_addressbook_menu.nitems = 0; + + for ( i = 0; i < NUM_ADDRESSBOOK_ENTRIES; i++ ) + { + cvar_t *adr; + char buffer[20]; + + Com_sprintf( buffer, sizeof( buffer ), "adr%d", i ); + + adr = Cvar_Get( buffer, "", CVAR_ARCHIVE ); + + s_addressbook_fields[i].generic.type = MTYPE_FIELD; + s_addressbook_fields[i].generic.name = 0; + s_addressbook_fields[i].generic.callback = 0; + s_addressbook_fields[i].generic.x = 0; + s_addressbook_fields[i].generic.y = i * 18 + 0; + s_addressbook_fields[i].generic.localdata[0] = i; + s_addressbook_fields[i].cursor = 0; + s_addressbook_fields[i].length = 60; + s_addressbook_fields[i].visible_length = 30; + + strcpy( s_addressbook_fields[i].buffer, adr->string ); + + Menu_AddItem( &s_addressbook_menu, &s_addressbook_fields[i] ); + } +} + +const char *AddressBook_MenuKey( int key ) +{ + if ( key == K_ESCAPE ) + { + int index; + char buffer[20]; + + for ( index = 0; index < NUM_ADDRESSBOOK_ENTRIES; index++ ) + { + Com_sprintf( buffer, sizeof( buffer ), "adr%d", index ); + Cvar_Set( buffer, s_addressbook_fields[index].buffer ); + } + } + return Default_MenuKey( &s_addressbook_menu, key ); +} + +void AddressBook_MenuDraw(void) +{ + M_Banner( "m_banner_addressbook" ); + Menu_Draw( &s_addressbook_menu ); +} + +void M_Menu_AddressBook_f(void) +{ + AddressBook_MenuInit(); + M_PushMenu( AddressBook_MenuDraw, AddressBook_MenuKey ); +} + +/* +============================================================================= + +PLAYER CONFIG MENU + +============================================================================= +*/ +static menuframework_s s_player_config_menu; +static menufield_s s_player_name_field; +static menulist_s s_player_model_box; +static menulist_s s_player_skin_box; +static menulist_s s_player_handedness_box; +static menulist_s s_player_rate_box; +static menuseparator_s s_player_skin_title; +static menuseparator_s s_player_model_title; +static menuseparator_s s_player_hand_title; +static menuseparator_s s_player_rate_title; +static menuaction_s s_player_download_action; + +#define MAX_DISPLAYNAME 16 +#define MAX_PLAYERMODELS 1024 + +typedef struct +{ + int nskins; + char **skindisplaynames; + char displayname[MAX_DISPLAYNAME]; + char directory[MAX_QPATH]; +} playermodelinfo_s; + +static playermodelinfo_s s_pmi[MAX_PLAYERMODELS]; +static char *s_pmnames[MAX_PLAYERMODELS]; +static int s_numplayermodels; + +static int rate_tbl[] = { 2500, 3200, 5000, 10000, 25000, 0 }; +static const char *rate_names[] = { "28.8 Modem", "33.6 Modem", "Single ISDN", + "Dual ISDN/Cable", "T1/LAN", "User defined", 0 }; + +void DownloadOptionsFunc( void *self ) +{ + M_Menu_DownloadOptions_f(); +} + +static void HandednessCallback( void *unused ) +{ + Cvar_SetValue( "hand", s_player_handedness_box.curvalue ); +} + +static void RateCallback( void *unused ) +{ + if (s_player_rate_box.curvalue != sizeof(rate_tbl) / sizeof(*rate_tbl) - 1) + Cvar_SetValue( "rate", rate_tbl[s_player_rate_box.curvalue] ); +} + +static void ModelCallback( void *unused ) +{ + s_player_skin_box.itemnames = s_pmi[s_player_model_box.curvalue].skindisplaynames; + s_player_skin_box.curvalue = 0; +} + +static void FreeFileList( char **list, int n ) +{ + int i; + + for ( i = 0; i < n; i++ ) + { + if ( list[i] ) + { + free( list[i] ); + list[i] = 0; + } + } + free( list ); +} + +static qboolean IconOfSkinExists( char *skin, char **pcxfiles, int npcxfiles ) +{ + int i; + char scratch[1024]; + + strcpy( scratch, skin ); + *strrchr( scratch, '.' ) = 0; + strcat( scratch, "_i.pcx" ); + + for ( i = 0; i < npcxfiles; i++ ) + { + if ( strcmp( pcxfiles[i], scratch ) == 0 ) + return true; + } + + return false; +} + +static qboolean PlayerConfig_ScanDirectories( void ) +{ + char findname[1024]; + char scratch[1024]; + int ndirs = 0, npms = 0; + char **dirnames; + char *path = NULL; + int i; + + extern char **FS_ListFiles( char *, int *, unsigned, unsigned ); + + s_numplayermodels = 0; + + /* + ** get a list of directories + */ + do + { + path = FS_NextPath( path ); + Com_sprintf( findname, sizeof(findname), "%s/players/*.*", path ); + + if ( ( dirnames = FS_ListFiles( findname, &ndirs, SFF_SUBDIR, 0 ) ) != 0 ) + break; + } while ( path ); + + if ( !dirnames ) + return false; + + /* + ** go through the subdirectories + */ + npms = ndirs; + if ( npms > MAX_PLAYERMODELS ) + npms = MAX_PLAYERMODELS; + + for ( i = 0; i < npms; i++ ) + { + int k, s; + char *a, *b, *c; + char **pcxnames; + char **skinnames; + int npcxfiles; + int nskins = 0; + + if ( dirnames[i] == 0 ) + continue; + + // verify the existence of tris.md2 + strcpy( scratch, dirnames[i] ); + strcat( scratch, "/tris.md2" ); + if ( !Sys_FindFirst( scratch, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM ) ) + { + free( dirnames[i] ); + dirnames[i] = 0; + Sys_FindClose(); + continue; + } + Sys_FindClose(); + + // verify the existence of at least one pcx skin + strcpy( scratch, dirnames[i] ); + strcat( scratch, "/*.pcx" ); + pcxnames = FS_ListFiles( scratch, &npcxfiles, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM ); + + if ( !pcxnames ) + { + free( dirnames[i] ); + dirnames[i] = 0; + continue; + } + + // count valid skins, which consist of a skin with a matching "_i" icon + for ( k = 0; k < npcxfiles-1; k++ ) + { + if ( !strstr( pcxnames[k], "_i.pcx" ) ) + { + if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles - 1 ) ) + { + nskins++; + } + } + } + if ( !nskins ) + continue; + + skinnames = malloc( sizeof( char * ) * ( nskins + 1 ) ); + memset( skinnames, 0, sizeof( char * ) * ( nskins + 1 ) ); + + // copy the valid skins + for ( s = 0, k = 0; k < npcxfiles-1; k++ ) + { + char *a, *b, *c; + + if ( !strstr( pcxnames[k], "_i.pcx" ) ) + { + if ( IconOfSkinExists( pcxnames[k], pcxnames, npcxfiles - 1 ) ) + { + a = strrchr( pcxnames[k], '/' ); + b = strrchr( pcxnames[k], '\\' ); + + if ( a > b ) + c = a; + else + c = b; + + strcpy( scratch, c + 1 ); + + if ( strrchr( scratch, '.' ) ) + *strrchr( scratch, '.' ) = 0; + + skinnames[s] = strdup( scratch ); + s++; + } + } + } + + // at this point we have a valid player model + s_pmi[s_numplayermodels].nskins = nskins; + s_pmi[s_numplayermodels].skindisplaynames = skinnames; + + // make short name for the model + a = strrchr( dirnames[i], '/' ); + b = strrchr( dirnames[i], '\\' ); + + if ( a > b ) + c = a; + else + c = b; + + strncpy( s_pmi[s_numplayermodels].displayname, c + 1, MAX_DISPLAYNAME-1 ); + strcpy( s_pmi[s_numplayermodels].directory, c + 1 ); + + FreeFileList( pcxnames, npcxfiles ); + + s_numplayermodels++; + } + if ( dirnames ) + FreeFileList( dirnames, ndirs ); +} + +static int pmicmpfnc( const void *_a, const void *_b ) +{ + const playermodelinfo_s *a = ( const playermodelinfo_s * ) _a; + const playermodelinfo_s *b = ( const playermodelinfo_s * ) _b; + + /* + ** sort by male, female, then alphabetical + */ + if ( strcmp( a->directory, "male" ) == 0 ) + return -1; + else if ( strcmp( b->directory, "male" ) == 0 ) + return 1; + + if ( strcmp( a->directory, "female" ) == 0 ) + return -1; + else if ( strcmp( b->directory, "female" ) == 0 ) + return 1; + + return strcmp( a->directory, b->directory ); +} + + +qboolean PlayerConfig_MenuInit( void ) +{ + extern cvar_t *name; + extern cvar_t *team; + extern cvar_t *skin; + char currentdirectory[1024]; + char currentskin[1024]; + int i = 0; + + int currentdirectoryindex = 0; + int currentskinindex = 0; + + cvar_t *hand = Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + static const char *handedness[] = { "right", "left", "center", 0 }; + + PlayerConfig_ScanDirectories(); + + if (s_numplayermodels == 0) + return false; + + if ( hand->value < 0 || hand->value > 2 ) + Cvar_SetValue( "hand", 0 ); + + strcpy( currentdirectory, skin->string ); + + if ( strchr( currentdirectory, '/' ) ) + { + strcpy( currentskin, strchr( currentdirectory, '/' ) + 1 ); + *strchr( currentdirectory, '/' ) = 0; + } + else if ( strchr( currentdirectory, '\\' ) ) + { + strcpy( currentskin, strchr( currentdirectory, '\\' ) + 1 ); + *strchr( currentdirectory, '\\' ) = 0; + } + else + { + strcpy( currentdirectory, "male" ); + strcpy( currentskin, "grunt" ); + } + + qsort( s_pmi, s_numplayermodels, sizeof( s_pmi[0] ), pmicmpfnc ); + + memset( s_pmnames, 0, sizeof( s_pmnames ) ); + for ( i = 0; i < s_numplayermodels; i++ ) + { + s_pmnames[i] = s_pmi[i].displayname; + if ( Q_stricmp( s_pmi[i].directory, currentdirectory ) == 0 ) + { + int j; + + currentdirectoryindex = i; + + for ( j = 0; j < s_pmi[i].nskins; j++ ) + { + if ( Q_stricmp( s_pmi[i].skindisplaynames[j], currentskin ) == 0 ) + { + currentskinindex = j; + break; + } + } + } + } + + s_player_config_menu.x = viddef.width / 2 - 95; + s_player_config_menu.y = viddef.height / 2 - 97; + s_player_config_menu.nitems = 0; + + s_player_name_field.generic.type = MTYPE_FIELD; + s_player_name_field.generic.name = "name"; + s_player_name_field.generic.callback = 0; + s_player_name_field.generic.x = 0; + s_player_name_field.generic.y = 0; + s_player_name_field.length = 20; + s_player_name_field.visible_length = 20; + strcpy( s_player_name_field.buffer, name->string ); + s_player_name_field.cursor = strlen( name->string ); + + s_player_model_title.generic.type = MTYPE_SEPARATOR; + s_player_model_title.generic.name = "model"; + s_player_model_title.generic.x = -8; + s_player_model_title.generic.y = 60; + + s_player_model_box.generic.type = MTYPE_SPINCONTROL; + s_player_model_box.generic.x = -56; + s_player_model_box.generic.y = 70; + s_player_model_box.generic.callback = ModelCallback; + s_player_model_box.generic.cursor_offset = -48; + s_player_model_box.curvalue = currentdirectoryindex; + s_player_model_box.itemnames = s_pmnames; + + s_player_skin_title.generic.type = MTYPE_SEPARATOR; + s_player_skin_title.generic.name = "skin"; + s_player_skin_title.generic.x = -16; + s_player_skin_title.generic.y = 84; + + s_player_skin_box.generic.type = MTYPE_SPINCONTROL; + s_player_skin_box.generic.x = -56; + s_player_skin_box.generic.y = 94; + s_player_skin_box.generic.name = 0; + s_player_skin_box.generic.callback = 0; + s_player_skin_box.generic.cursor_offset = -48; + s_player_skin_box.curvalue = currentskinindex; + s_player_skin_box.itemnames = s_pmi[currentdirectoryindex].skindisplaynames; + + s_player_hand_title.generic.type = MTYPE_SEPARATOR; + s_player_hand_title.generic.name = "handedness"; + s_player_hand_title.generic.x = 32; + s_player_hand_title.generic.y = 108; + + s_player_handedness_box.generic.type = MTYPE_SPINCONTROL; + s_player_handedness_box.generic.x = -56; + s_player_handedness_box.generic.y = 118; + s_player_handedness_box.generic.name = 0; + s_player_handedness_box.generic.cursor_offset = -48; + s_player_handedness_box.generic.callback = HandednessCallback; + s_player_handedness_box.curvalue = Cvar_VariableValue( "hand" ); + s_player_handedness_box.itemnames = handedness; + + for (i = 0; i < sizeof(rate_tbl) / sizeof(*rate_tbl) - 1; i++) + if (Cvar_VariableValue("rate") == rate_tbl[i]) + break; + + s_player_rate_title.generic.type = MTYPE_SEPARATOR; + s_player_rate_title.generic.name = "connect speed"; + s_player_rate_title.generic.x = 56; + s_player_rate_title.generic.y = 156; + + s_player_rate_box.generic.type = MTYPE_SPINCONTROL; + s_player_rate_box.generic.x = -56; + s_player_rate_box.generic.y = 166; + s_player_rate_box.generic.name = 0; + s_player_rate_box.generic.cursor_offset = -48; + s_player_rate_box.generic.callback = RateCallback; + s_player_rate_box.curvalue = i; + s_player_rate_box.itemnames = rate_names; + + s_player_download_action.generic.type = MTYPE_ACTION; + s_player_download_action.generic.name = "download options"; + s_player_download_action.generic.flags= QMF_LEFT_JUSTIFY; + s_player_download_action.generic.x = -24; + s_player_download_action.generic.y = 186; + s_player_download_action.generic.statusbar = NULL; + s_player_download_action.generic.callback = DownloadOptionsFunc; + + Menu_AddItem( &s_player_config_menu, &s_player_name_field ); + Menu_AddItem( &s_player_config_menu, &s_player_model_title ); + Menu_AddItem( &s_player_config_menu, &s_player_model_box ); + if ( s_player_skin_box.itemnames ) + { + Menu_AddItem( &s_player_config_menu, &s_player_skin_title ); + Menu_AddItem( &s_player_config_menu, &s_player_skin_box ); + } + Menu_AddItem( &s_player_config_menu, &s_player_hand_title ); + Menu_AddItem( &s_player_config_menu, &s_player_handedness_box ); + Menu_AddItem( &s_player_config_menu, &s_player_rate_title ); + Menu_AddItem( &s_player_config_menu, &s_player_rate_box ); + Menu_AddItem( &s_player_config_menu, &s_player_download_action ); + + return true; +} + +void PlayerConfig_MenuDraw( void ) +{ + extern float CalcFov( float fov_x, float w, float h ); + refdef_t refdef; + char scratch[MAX_QPATH]; + + memset( &refdef, 0, sizeof( refdef ) ); + + refdef.x = viddef.width / 2; + refdef.y = viddef.height / 2 - 72; + refdef.width = 144; + refdef.height = 168; + refdef.fov_x = 40; + refdef.fov_y = CalcFov( refdef.fov_x, refdef.width, refdef.height ); + refdef.time = cls.realtime*0.001; + + if ( s_pmi[s_player_model_box.curvalue].skindisplaynames ) + { + static int yaw; + int maxframe = 29; + entity_t entity; + + memset( &entity, 0, sizeof( entity ) ); + + Com_sprintf( scratch, sizeof( scratch ), "players/%s/tris.md2", s_pmi[s_player_model_box.curvalue].directory ); + entity.model = re.RegisterModel( scratch ); + Com_sprintf( scratch, sizeof( scratch ), "players/%s/%s.pcx", s_pmi[s_player_model_box.curvalue].directory, s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] ); + entity.skin = re.RegisterSkin( scratch ); + entity.flags = RF_FULLBRIGHT; + entity.origin[0] = 80; + entity.origin[1] = 0; + entity.origin[2] = 0; + VectorCopy( entity.origin, entity.oldorigin ); + entity.frame = 0; + entity.oldframe = 0; + entity.backlerp = 0.0; + entity.angles[1] = yaw++; + if ( ++yaw > 360 ) + yaw -= 360; + + refdef.areabits = 0; + refdef.num_entities = 1; + refdef.entities = &entity; + refdef.lightstyles = 0; + refdef.rdflags = RDF_NOWORLDMODEL; + + Menu_Draw( &s_player_config_menu ); + + M_DrawTextBox( ( refdef.x ) * ( 320.0F / viddef.width ) - 8, ( viddef.height / 2 ) * ( 240.0F / viddef.height) - 77, refdef.width / 8, refdef.height / 8 ); + refdef.height += 4; + + re.RenderFrame( &refdef ); + + Com_sprintf( scratch, sizeof( scratch ), "/players/%s/%s_i.pcx", + s_pmi[s_player_model_box.curvalue].directory, + s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] ); + re.DrawPic( s_player_config_menu.x - 40, refdef.y, scratch ); + } +} + +const char *PlayerConfig_MenuKey (int key) +{ + int i; + + if ( key == K_ESCAPE ) + { + char scratch[1024]; + + Cvar_Set( "name", s_player_name_field.buffer ); + + Com_sprintf( scratch, sizeof( scratch ), "%s/%s", + s_pmi[s_player_model_box.curvalue].directory, + s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue] ); + + Cvar_Set( "skin", scratch ); + + for ( i = 0; i < s_numplayermodels; i++ ) + { + int j; + + for ( j = 0; j < s_pmi[i].nskins; j++ ) + { + if ( s_pmi[i].skindisplaynames[j] ) + free( s_pmi[i].skindisplaynames[j] ); + s_pmi[i].skindisplaynames[j] = 0; + } + free( s_pmi[i].skindisplaynames ); + s_pmi[i].skindisplaynames = 0; + s_pmi[i].nskins = 0; + } + } + return Default_MenuKey( &s_player_config_menu, key ); +} + + +void M_Menu_PlayerConfig_f (void) +{ + if (!PlayerConfig_MenuInit()) + { + Menu_SetStatusBar( &s_multiplayer_menu, "No valid player models found" ); + return; + } + Menu_SetStatusBar( &s_multiplayer_menu, NULL ); + M_PushMenu( PlayerConfig_MenuDraw, PlayerConfig_MenuKey ); +} + + +/* +======================================================================= + +GALLERY MENU + +======================================================================= +*/ +#if 0 +void M_Menu_Gallery_f( void ) +{ + extern void Gallery_MenuDraw( void ); + extern const char *Gallery_MenuKey( int key ); + + M_PushMenu( Gallery_MenuDraw, Gallery_MenuKey ); +} +#endif + +/* +======================================================================= + +QUIT MENU + +======================================================================= +*/ + +const char *M_Quit_Key (int key) +{ + switch (key) + { + case K_ESCAPE: + case 'n': + case 'N': + M_PopMenu (); + break; + + case 'Y': + case 'y': + cls.key_dest = key_console; + CL_Quit_f (); + break; + + default: + break; + } + + return NULL; + +} + + +void M_Quit_Draw (void) +{ + int w, h; + + re.DrawGetPicSize (&w, &h, "quit"); + re.DrawPic ( (viddef.width-w)/2, (viddef.height-h)/2, "quit"); +} + + +void M_Menu_Quit_f (void) +{ + M_PushMenu (M_Quit_Draw, M_Quit_Key); +} + + + +//============================================================================= +/* Menu Subsystem */ + + +/* +================= +M_Init +================= +*/ +void M_Init (void) +{ + Cmd_AddCommand ("menu_main", M_Menu_Main_f); + Cmd_AddCommand ("menu_game", M_Menu_Game_f); + Cmd_AddCommand ("menu_loadgame", M_Menu_LoadGame_f); + Cmd_AddCommand ("menu_savegame", M_Menu_SaveGame_f); + Cmd_AddCommand ("menu_joinserver", M_Menu_JoinServer_f); + Cmd_AddCommand ("menu_addressbook", M_Menu_AddressBook_f); + Cmd_AddCommand ("menu_startserver", M_Menu_StartServer_f); + Cmd_AddCommand ("menu_dmoptions", M_Menu_DMOptions_f); + Cmd_AddCommand ("menu_playerconfig", M_Menu_PlayerConfig_f); + Cmd_AddCommand ("menu_downloadoptions", M_Menu_DownloadOptions_f); + Cmd_AddCommand ("menu_credits", M_Menu_Credits_f ); + Cmd_AddCommand ("menu_multiplayer", M_Menu_Multiplayer_f ); + Cmd_AddCommand ("menu_video", M_Menu_Video_f); + Cmd_AddCommand ("menu_options", M_Menu_Options_f); + Cmd_AddCommand ("menu_keys", M_Menu_Keys_f); + Cmd_AddCommand ("menu_quit", M_Menu_Quit_f); +} + + +/* +================= +M_Draw +================= +*/ +void M_Draw (void) +{ + if (cls.key_dest != key_menu) + return; + + // repaint everything next frame + SCR_DirtyScreen (); + + // dim everything behind it down + if (cl.cinematictime > 0) + re.DrawFill (0,0,viddef.width, viddef.height, 0); + else + re.DrawFadeScreen (); + + m_drawfunc (); + + // delay playing the enter sound until after the + // menu has been drawn, to avoid delay while + // caching images + if (m_entersound) + { + S_StartLocalSound( menu_in_sound ); + m_entersound = false; + } +} + + +/* +================= +M_Keydown +================= +*/ +void M_Keydown (int key) +{ + const char *s; + + if (m_keyfunc) + if ( ( s = m_keyfunc( key ) ) != 0 ) + S_StartLocalSound( ( char * ) s ); +} + + diff --git a/client/qmenu.c b/client/qmenu.c new file mode 100644 index 000000000..3f82deeb2 --- /dev/null +++ b/client/qmenu.c @@ -0,0 +1,674 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include +#include + +#include "client.h" +#include "qmenu.h" + +static void Action_DoEnter( menuaction_s *a ); +static void Action_Draw( menuaction_s *a ); +static void Menu_DrawStatusBar( const char *string ); +static void Menulist_DoEnter( menulist_s *l ); +static void MenuList_Draw( menulist_s *l ); +static void Separator_Draw( menuseparator_s *s ); +static void Slider_DoSlide( menuslider_s *s, int dir ); +static void Slider_Draw( menuslider_s *s ); +static void SpinControl_DoEnter( menulist_s *s ); +static void SpinControl_Draw( menulist_s *s ); +static void SpinControl_DoSlide( menulist_s *s, int dir ); + +#define RCOLUMN_OFFSET 16 +#define LCOLUMN_OFFSET -16 + +extern refexport_t re; +extern viddef_t viddef; + +#define VID_WIDTH viddef.width +#define VID_HEIGHT viddef.height + +#define Draw_Char re.DrawChar +#define Draw_Fill re.DrawFill + +void Action_DoEnter( menuaction_s *a ) +{ + if ( a->generic.callback ) + a->generic.callback( a ); +} + +void Action_Draw( menuaction_s *a ) +{ + if ( a->generic.flags & QMF_LEFT_JUSTIFY ) + { + if ( a->generic.flags & QMF_GRAYED ) + Menu_DrawStringDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name ); + else + Menu_DrawString( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name ); + } + else + { + if ( a->generic.flags & QMF_GRAYED ) + Menu_DrawStringR2LDark( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name ); + else + Menu_DrawStringR2L( a->generic.x + a->generic.parent->x + LCOLUMN_OFFSET, a->generic.y + a->generic.parent->y, a->generic.name ); + } + if ( a->generic.ownerdraw ) + a->generic.ownerdraw( a ); +} + +qboolean Field_DoEnter( menufield_s *f ) +{ + if ( f->generic.callback ) + { + f->generic.callback( f ); + return true; + } + return false; +} + +void Field_Draw( menufield_s *f ) +{ + int i; + char tempbuffer[128]=""; + + if ( f->generic.name ) + Menu_DrawStringR2LDark( f->generic.x + f->generic.parent->x + LCOLUMN_OFFSET, f->generic.y + f->generic.parent->y, f->generic.name ); + + strncpy( tempbuffer, f->buffer + f->visible_offset, f->visible_length ); + + Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y - 4, 18 ); + Draw_Char( f->generic.x + f->generic.parent->x + 16, f->generic.y + f->generic.parent->y + 4, 24 ); + + Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y - 4, 20 ); + Draw_Char( f->generic.x + f->generic.parent->x + 24 + f->visible_length * 8, f->generic.y + f->generic.parent->y + 4, 26 ); + + for ( i = 0; i < f->visible_length; i++ ) + { + Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y - 4, 19 ); + Draw_Char( f->generic.x + f->generic.parent->x + 24 + i * 8, f->generic.y + f->generic.parent->y + 4, 25 ); + } + + Menu_DrawString( f->generic.x + f->generic.parent->x + 24, f->generic.y + f->generic.parent->y, tempbuffer ); + + if ( Menu_ItemAtCursor( f->generic.parent ) == f ) + { + int offset; + + if ( f->visible_offset ) + offset = f->visible_length; + else + offset = f->cursor; + + if ( ( ( int ) ( Sys_Milliseconds() / 250 ) ) & 1 ) + { + Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8, + f->generic.y + f->generic.parent->y, + 11 ); + } + else + { + Draw_Char( f->generic.x + f->generic.parent->x + ( offset + 2 ) * 8 + 8, + f->generic.y + f->generic.parent->y, + ' ' ); + } + } +} + +qboolean Field_Key( menufield_s *f, int key ) +{ + extern int keydown[]; + + switch ( key ) + { + case K_KP_SLASH: + key = '/'; + break; + case K_KP_MINUS: + key = '-'; + break; + case K_KP_PLUS: + key = '+'; + break; + case K_KP_HOME: + key = '7'; + break; + case K_KP_UPARROW: + key = '8'; + break; + case K_KP_PGUP: + key = '9'; + break; + case K_KP_LEFTARROW: + key = '4'; + break; + case K_KP_5: + key = '5'; + break; + case K_KP_RIGHTARROW: + key = '6'; + break; + case K_KP_END: + key = '1'; + break; + case K_KP_DOWNARROW: + key = '2'; + break; + case K_KP_PGDN: + key = '3'; + break; + case K_KP_INS: + key = '0'; + break; + case K_KP_DEL: + key = '.'; + break; + } + + if ( key > 127 ) + { + switch ( key ) + { + case K_DEL: + default: + return false; + } + } + + /* + ** support pasting from the clipboard + */ + if ( ( toupper( key ) == 'V' && keydown[K_CTRL] ) || + ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keydown[K_SHIFT] ) ) + { + char *cbd; + + if ( ( cbd = Sys_GetClipboardData() ) != 0 ) + { + strtok( cbd, "\n\r\b" ); + + strncpy( f->buffer, cbd, f->length - 1 ); + f->cursor = strlen( f->buffer ); + f->visible_offset = f->cursor - f->visible_length; + if ( f->visible_offset < 0 ) + f->visible_offset = 0; + + free( cbd ); + } + return true; + } + + switch ( key ) + { + case K_KP_LEFTARROW: + case K_LEFTARROW: + case K_BACKSPACE: + if ( f->cursor > 0 ) + { + memmove( &f->buffer[f->cursor-1], &f->buffer[f->cursor], strlen( &f->buffer[f->cursor] ) + 1 ); + f->cursor--; + + if ( f->visible_offset ) + { + f->visible_offset--; + } + } + break; + + case K_KP_DEL: + case K_DEL: + memmove( &f->buffer[f->cursor], &f->buffer[f->cursor+1], strlen( &f->buffer[f->cursor+1] ) + 1 ); + break; + + case K_KP_ENTER: + case K_ENTER: + case K_ESCAPE: + case K_TAB: + return false; + + case K_SPACE: + default: + if ( !isdigit( key ) && ( f->generic.flags & QMF_NUMBERSONLY ) ) + return false; + + if ( f->cursor < f->length ) + { + f->buffer[f->cursor++] = key; + f->buffer[f->cursor] = 0; + + if ( f->cursor > f->visible_length ) + { + f->visible_offset++; + } + } + } + + return true; +} + +void Menu_AddItem( menuframework_s *menu, void *item ) +{ + if ( menu->nitems == 0 ) + menu->nslots = 0; + + if ( menu->nitems < MAXMENUITEMS ) + { + menu->items[menu->nitems] = item; + ( ( menucommon_s * ) menu->items[menu->nitems] )->parent = menu; + menu->nitems++; + } + + menu->nslots = Menu_TallySlots( menu ); +} + +/* +** Menu_AdjustCursor +** +** This function takes the given menu, the direction, and attempts +** to adjust the menu's cursor so that it's at the next available +** slot. +*/ +void Menu_AdjustCursor( menuframework_s *m, int dir ) +{ + menucommon_s *citem; + + /* + ** see if it's in a valid spot + */ + if ( m->cursor >= 0 && m->cursor < m->nitems ) + { + if ( ( citem = Menu_ItemAtCursor( m ) ) != 0 ) + { + if ( citem->type != MTYPE_SEPARATOR ) + return; + } + } + + /* + ** it's not in a valid spot, so crawl in the direction indicated until we + ** find a valid spot + */ + if ( dir == 1 ) + { + while ( 1 ) + { + citem = Menu_ItemAtCursor( m ); + if ( citem ) + if ( citem->type != MTYPE_SEPARATOR ) + break; + m->cursor += dir; + if ( m->cursor >= m->nitems ) + m->cursor = 0; + } + } + else + { + while ( 1 ) + { + citem = Menu_ItemAtCursor( m ); + if ( citem ) + if ( citem->type != MTYPE_SEPARATOR ) + break; + m->cursor += dir; + if ( m->cursor < 0 ) + m->cursor = m->nitems - 1; + } + } +} + +void Menu_Center( menuframework_s *menu ) +{ + int height; + + height = ( ( menucommon_s * ) menu->items[menu->nitems-1])->y; + height += 10; + + menu->y = ( VID_HEIGHT - height ) / 2; +} + +void Menu_Draw( menuframework_s *menu ) +{ + int i; + menucommon_s *item; + + /* + ** draw contents + */ + for ( i = 0; i < menu->nitems; i++ ) + { + switch ( ( ( menucommon_s * ) menu->items[i] )->type ) + { + case MTYPE_FIELD: + Field_Draw( ( menufield_s * ) menu->items[i] ); + break; + case MTYPE_SLIDER: + Slider_Draw( ( menuslider_s * ) menu->items[i] ); + break; + case MTYPE_LIST: + MenuList_Draw( ( menulist_s * ) menu->items[i] ); + break; + case MTYPE_SPINCONTROL: + SpinControl_Draw( ( menulist_s * ) menu->items[i] ); + break; + case MTYPE_ACTION: + Action_Draw( ( menuaction_s * ) menu->items[i] ); + break; + case MTYPE_SEPARATOR: + Separator_Draw( ( menuseparator_s * ) menu->items[i] ); + break; + } + } + + item = Menu_ItemAtCursor( menu ); + + if ( item && item->cursordraw ) + { + item->cursordraw( item ); + } + else if ( menu->cursordraw ) + { + menu->cursordraw( menu ); + } + else if ( item && item->type != MTYPE_FIELD ) + { + if ( item->flags & QMF_LEFT_JUSTIFY ) + { + Draw_Char( menu->x + item->x - 24 + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) ); + } + else + { + Draw_Char( menu->x + item->cursor_offset, menu->y + item->y, 12 + ( ( int ) ( Sys_Milliseconds()/250 ) & 1 ) ); + } + } + + if ( item ) + { + if ( item->statusbarfunc ) + item->statusbarfunc( ( void * ) item ); + else if ( item->statusbar ) + Menu_DrawStatusBar( item->statusbar ); + else + Menu_DrawStatusBar( menu->statusbar ); + + } + else + { + Menu_DrawStatusBar( menu->statusbar ); + } +} + +void Menu_DrawStatusBar( const char *string ) +{ + if ( string ) + { + int l = strlen( string ); + int maxrow = VID_HEIGHT / 8; + int maxcol = VID_WIDTH / 8; + int col = maxcol / 2 - l / 2; + + Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 4 ); + Menu_DrawString( col*8, VID_HEIGHT - 8, string ); + } + else + { + Draw_Fill( 0, VID_HEIGHT-8, VID_WIDTH, 8, 0 ); + } +} + +void Menu_DrawString( int x, int y, const char *string ) +{ + unsigned i; + + for ( i = 0; i < strlen( string ); i++ ) + { + Draw_Char( ( x + i*8 ), y, string[i] ); + } +} + +void Menu_DrawStringDark( int x, int y, const char *string ) +{ + unsigned i; + + for ( i = 0; i < strlen( string ); i++ ) + { + Draw_Char( ( x + i*8 ), y, string[i] + 128 ); + } +} + +void Menu_DrawStringR2L( int x, int y, const char *string ) +{ + unsigned i; + + for ( i = 0; i < strlen( string ); i++ ) + { + Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1] ); + } +} + +void Menu_DrawStringR2LDark( int x, int y, const char *string ) +{ + unsigned i; + + for ( i = 0; i < strlen( string ); i++ ) + { + Draw_Char( ( x - i*8 ), y, string[strlen(string)-i-1]+128 ); + } +} + +void *Menu_ItemAtCursor( menuframework_s *m ) +{ + if ( m->cursor < 0 || m->cursor >= m->nitems ) + return 0; + + return m->items[m->cursor]; +} + +qboolean Menu_SelectItem( menuframework_s *s ) +{ + menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s ); + + if ( item ) + { + switch ( item->type ) + { + case MTYPE_FIELD: + return Field_DoEnter( ( menufield_s * ) item ) ; + case MTYPE_ACTION: + Action_DoEnter( ( menuaction_s * ) item ); + return true; + case MTYPE_LIST: +// Menulist_DoEnter( ( menulist_s * ) item ); + return false; + case MTYPE_SPINCONTROL: +// SpinControl_DoEnter( ( menulist_s * ) item ); + return false; + } + } + return false; +} + +void Menu_SetStatusBar( menuframework_s *m, const char *string ) +{ + m->statusbar = string; +} + +void Menu_SlideItem( menuframework_s *s, int dir ) +{ + menucommon_s *item = ( menucommon_s * ) Menu_ItemAtCursor( s ); + + if ( item ) + { + switch ( item->type ) + { + case MTYPE_SLIDER: + Slider_DoSlide( ( menuslider_s * ) item, dir ); + break; + case MTYPE_SPINCONTROL: + SpinControl_DoSlide( ( menulist_s * ) item, dir ); + break; + } + } +} + +int Menu_TallySlots( menuframework_s *menu ) +{ + int i; + int total = 0; + + for ( i = 0; i < menu->nitems; i++ ) + { + if ( ( ( menucommon_s * ) menu->items[i] )->type == MTYPE_LIST ) + { + int nitems = 0; + const char **n = ( ( menulist_s * ) menu->items[i] )->itemnames; + + while (*n) + nitems++, n++; + + total += nitems; + } + else + { + total++; + } + } + + return total; +} + +void Menulist_DoEnter( menulist_s *l ) +{ + int start; + + start = l->generic.y / 10 + 1; + + l->curvalue = l->generic.parent->cursor - start; + + if ( l->generic.callback ) + l->generic.callback( l ); +} + +void MenuList_Draw( menulist_s *l ) +{ + const char **n; + int y = 0; + + Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y, l->generic.name ); + + n = l->itemnames; + + Draw_Fill( l->generic.x - 112 + l->generic.parent->x, l->generic.parent->y + l->generic.y + l->curvalue*10 + 10, 128, 10, 16 ); + while ( *n ) + { + Menu_DrawStringR2LDark( l->generic.x + l->generic.parent->x + LCOLUMN_OFFSET, l->generic.y + l->generic.parent->y + y + 10, *n ); + + n++; + y += 10; + } +} + +void Separator_Draw( menuseparator_s *s ) +{ + if ( s->generic.name ) + Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->generic.name ); +} + +void Slider_DoSlide( menuslider_s *s, int dir ) +{ + s->curvalue += dir; + + if ( s->curvalue > s->maxvalue ) + s->curvalue = s->maxvalue; + else if ( s->curvalue < s->minvalue ) + s->curvalue = s->minvalue; + + if ( s->generic.callback ) + s->generic.callback( s ); +} + +#define SLIDER_RANGE 10 + +void Slider_Draw( menuslider_s *s ) +{ + int i; + + Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET, + s->generic.y + s->generic.parent->y, + s->generic.name ); + + s->range = ( s->curvalue - s->minvalue ) / ( float ) ( s->maxvalue - s->minvalue ); + + if ( s->range < 0) + s->range = 0; + if ( s->range > 1) + s->range = 1; + Draw_Char( s->generic.x + s->generic.parent->x + RCOLUMN_OFFSET, s->generic.y + s->generic.parent->y, 128); + for ( i = 0; i < SLIDER_RANGE; i++ ) + Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 129); + Draw_Char( RCOLUMN_OFFSET + s->generic.x + i*8 + s->generic.parent->x + 8, s->generic.y + s->generic.parent->y, 130); + Draw_Char( ( int ) ( 8 + RCOLUMN_OFFSET + s->generic.parent->x + s->generic.x + (SLIDER_RANGE-1)*8 * s->range ), s->generic.y + s->generic.parent->y, 131); +} + +void SpinControl_DoEnter( menulist_s *s ) +{ + s->curvalue++; + if ( s->itemnames[s->curvalue] == 0 ) + s->curvalue = 0; + + if ( s->generic.callback ) + s->generic.callback( s ); +} + +void SpinControl_DoSlide( menulist_s *s, int dir ) +{ + s->curvalue += dir; + + if ( s->curvalue < 0 ) + s->curvalue = 0; + else if ( s->itemnames[s->curvalue] == 0 ) + s->curvalue--; + + if ( s->generic.callback ) + s->generic.callback( s ); +} + +void SpinControl_Draw( menulist_s *s ) +{ + char buffer[100]; + + if ( s->generic.name ) + { + Menu_DrawStringR2LDark( s->generic.x + s->generic.parent->x + LCOLUMN_OFFSET, + s->generic.y + s->generic.parent->y, + s->generic.name ); + } + if ( !strchr( s->itemnames[s->curvalue], '\n' ) ) + { + Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, s->itemnames[s->curvalue] ); + } + else + { + strcpy( buffer, s->itemnames[s->curvalue] ); + *strchr( buffer, '\n' ) = 0; + Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y, buffer ); + strcpy( buffer, strchr( s->itemnames[s->curvalue], '\n' ) + 1 ); + Menu_DrawString( RCOLUMN_OFFSET + s->generic.x + s->generic.parent->x, s->generic.y + s->generic.parent->y + 10, buffer ); + } +} + diff --git a/client/qmenu.h b/client/qmenu.h new file mode 100644 index 000000000..bf3b51d5d --- /dev/null +++ b/client/qmenu.h @@ -0,0 +1,140 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#ifndef __QMENU_H__ +#define __QMENU_H__ + +#define MAXMENUITEMS 64 + +#define MTYPE_SLIDER 0 +#define MTYPE_LIST 1 +#define MTYPE_ACTION 2 +#define MTYPE_SPINCONTROL 3 +#define MTYPE_SEPARATOR 4 +#define MTYPE_FIELD 5 + +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define QMF_LEFT_JUSTIFY 0x00000001 +#define QMF_GRAYED 0x00000002 +#define QMF_NUMBERSONLY 0x00000004 + +typedef struct _tag_menuframework +{ + int x, y; + int cursor; + + int nitems; + int nslots; + void *items[64]; + + const char *statusbar; + + void (*cursordraw)( struct _tag_menuframework *m ); + +} menuframework_s; + +typedef struct +{ + int type; + const char *name; + int x, y; + menuframework_s *parent; + int cursor_offset; + int localdata[4]; + unsigned flags; + + const char *statusbar; + + void (*callback)( void *self ); + void (*statusbarfunc)( void *self ); + void (*ownerdraw)( void *self ); + void (*cursordraw)( void *self ); +} menucommon_s; + +typedef struct +{ + menucommon_s generic; + + char buffer[80]; + int cursor; + int length; + int visible_length; + int visible_offset; +} menufield_s; + +typedef struct +{ + menucommon_s generic; + + float minvalue; + float maxvalue; + float curvalue; + + float range; +} menuslider_s; + +typedef struct +{ + menucommon_s generic; + + int curvalue; + + const char **itemnames; +} menulist_s; + +typedef struct +{ + menucommon_s generic; +} menuaction_s; + +typedef struct +{ + menucommon_s generic; +} menuseparator_s; + +qboolean Field_Key( menufield_s *field, int key ); + +void Menu_AddItem( menuframework_s *menu, void *item ); +void Menu_AdjustCursor( menuframework_s *menu, int dir ); +void Menu_Center( menuframework_s *menu ); +void Menu_Draw( menuframework_s *menu ); +void *Menu_ItemAtCursor( menuframework_s *m ); +qboolean Menu_SelectItem( menuframework_s *s ); +void Menu_SetStatusBar( menuframework_s *s, const char *string ); +void Menu_SlideItem( menuframework_s *s, int dir ); +int Menu_TallySlots( menuframework_s *menu ); + +void Menu_DrawString( int, int, const char * ); +void Menu_DrawStringDark( int, int, const char * ); +void Menu_DrawStringR2L( int, int, const char * ); +void Menu_DrawStringR2LDark( int, int, const char * ); + +#endif diff --git a/client/ref.h b/client/ref.h new file mode 100644 index 000000000..18ad12119 --- /dev/null +++ b/client/ref.h @@ -0,0 +1,224 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "../qcommon/qcommon.h" + +#define MAX_DLIGHTS 32 +#define MAX_ENTITIES 128 +#define MAX_PARTICLES 4096 +#define MAX_LIGHTSTYLES 256 + +#define POWERSUIT_SCALE 4.0F + +#define SHELL_RED_COLOR 0xF2 +#define SHELL_GREEN_COLOR 0xD0 +#define SHELL_BLUE_COLOR 0xF3 + +#define SHELL_RG_COLOR 0xDC +//#define SHELL_RB_COLOR 0x86 +#define SHELL_RB_COLOR 0x68 +#define SHELL_BG_COLOR 0x78 + +//ROGUE +#define SHELL_DOUBLE_COLOR 0xDF // 223 +#define SHELL_HALF_DAM_COLOR 0x90 +#define SHELL_CYAN_COLOR 0x72 +//ROGUE + +#define SHELL_WHITE_COLOR 0xD7 + +typedef struct entity_s +{ + struct model_s *model; // opaque type outside refresh + float angles[3]; + + /* + ** most recent data + */ + float origin[3]; // also used as RF_BEAM's "from" + int frame; // also used as RF_BEAM's diameter + + /* + ** previous data for lerping + */ + float oldorigin[3]; // also used as RF_BEAM's "to" + int oldframe; + + /* + ** misc + */ + float backlerp; // 0.0 = current, 1.0 = old + int skinnum; // also used as RF_BEAM's palette index + + int lightstyle; // for flashing entities + float alpha; // ignore if RF_TRANSLUCENT isn't set + + struct image_s *skin; // NULL for inline skin + int flags; + +} entity_t; + +#define ENTITY_FLAGS 68 + +typedef struct +{ + vec3_t origin; + vec3_t color; + float intensity; +} dlight_t; + +typedef struct +{ + vec3_t origin; + int color; + float alpha; +} particle_t; + +typedef struct +{ + float rgb[3]; // 0.0 - 2.0 + float white; // highest of rgb +} lightstyle_t; + +typedef struct +{ + int x, y, width, height;// in virtual screen coordinates + float fov_x, fov_y; + float vieworg[3]; + float viewangles[3]; + float blend[4]; // rgba 0-1 full screen blend + float time; // time is uesed to auto animate + int rdflags; // RDF_UNDERWATER, etc + + byte *areabits; // if not NULL, only areas with set bits will be drawn + + lightstyle_t *lightstyles; // [MAX_LIGHTSTYLES] + + int num_entities; + entity_t *entities; + + int num_dlights; + dlight_t *dlights; + + int num_particles; + particle_t *particles; +} refdef_t; + + + +#define API_VERSION 3 + +// +// these are the functions exported by the refresh module +// +typedef struct +{ + // if api_version is different, the dll cannot be used + int api_version; + + // called when the library is loaded + qboolean (*Init) ( void *hinstance, void *wndproc ); + + // called before the library is unloaded + void (*Shutdown) (void); + + // All data that will be used in a level should be + // registered before rendering any frames to prevent disk hits, + // but they can still be registered at a later time + // if necessary. + // + // EndRegistration will free any remaining data that wasn't registered. + // Any model_s or skin_s pointers from before the BeginRegistration + // are no longer valid after EndRegistration. + // + // Skins and images need to be differentiated, because skins + // are flood filled to eliminate mip map edge errors, and pics have + // an implicit "pics/" prepended to the name. (a pic name that starts with a + // slash will not use the "pics/" prefix or the ".pcx" postfix) + void (*BeginRegistration) (char *map); + struct model_s *(*RegisterModel) (char *name); + struct image_s *(*RegisterSkin) (char *name); + struct image_s *(*RegisterPic) (char *name); + void (*SetSky) (char *name, float rotate, vec3_t axis); + void (*EndRegistration) (void); + + void (*RenderFrame) (refdef_t *fd); + + void (*DrawGetPicSize) (int *w, int *h, char *name); // will return 0 0 if not found + void (*DrawPic) (int x, int y, char *name); + void (*DrawStretchPic) (int x, int y, int w, int h, char *name); + void (*DrawChar) (int x, int y, int c); + void (*DrawTileClear) (int x, int y, int w, int h, char *name); + void (*DrawFill) (int x, int y, int w, int h, int c); + void (*DrawFadeScreen) (void); + + // Draw images for cinematic rendering (which can have a different palette). Note that calls + void (*DrawStretchRaw) (int x, int y, int w, int h, int cols, int rows, byte *data); + + /* + ** video mode and refresh state management entry points + */ + void (*CinematicSetPalette)( const unsigned char *palette); // NULL = game palette + void (*BeginFrame)( float camera_separation ); + void (*EndFrame) (void); + + void (*AppActivate)( qboolean activate ); + +} refexport_t; + +// +// these are the functions imported by the refresh module +// +typedef struct +{ + void (*Sys_Error) (int err_level, char *str, ...); + + void (*Cmd_AddCommand) (char *name, void(*cmd)(void)); + void (*Cmd_RemoveCommand) (char *name); + int (*Cmd_Argc) (void); + char *(*Cmd_Argv) (int i); + void (*Cmd_ExecuteText) (int exec_when, char *text); + + void (*Con_Printf) (int print_level, char *str, ...); + + // files will be memory mapped read only + // the returned buffer may be part of a larger pak file, + // or a discrete file from anywhere in the quake search path + // a -1 return means the file does not exist + // NULL can be passed for buf to just determine existance + int (*FS_LoadFile) (char *name, void **buf); + void (*FS_FreeFile) (void *buf); + + // gamedir will be the current directory that generated + // files should be stored to, ie: "f:\quake\id1" + char *(*FS_Gamedir) (void); + + cvar_t *(*Cvar_Get) (char *name, char *value, int flags); + cvar_t *(*Cvar_Set)( char *name, char *value ); + void (*Cvar_SetValue)( char *name, float value ); + + qboolean (*Vid_GetModeInfo)( int *width, int *height, int mode ); + void (*Vid_MenuInit)( void ); + void (*Vid_NewWindow)( int width, int height ); +} refimport_t; + + +// this is the only function actually exported at the linker level +typedef refexport_t (*GetRefAPI_t) (refimport_t); diff --git a/client/screen.h b/client/screen.h new file mode 100644 index 000000000..3dbed2556 --- /dev/null +++ b/client/screen.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// screen.h + +void SCR_Init (void); + +void SCR_UpdateScreen (void); + +void SCR_SizeUp (void); +void SCR_SizeDown (void); +void SCR_CenterPrint (char *str); +void SCR_BeginLoadingPlaque (void); +void SCR_EndLoadingPlaque (void); + +void SCR_DebugGraph (float value, int color); + +void SCR_TouchPics (void); + +void SCR_RunConsole (void); + +extern float scr_con_current; +extern float scr_conlines; // lines of console to display + +extern int sb_lines; + +extern cvar_t *scr_viewsize; +extern cvar_t *crosshair; + +extern vrect_t scr_vrect; // position of render window + +extern char crosshair_pic[MAX_QPATH]; +extern int crosshair_width, crosshair_height; + +void SCR_AddDirtyPoint (int x, int y); +void SCR_DirtyScreen (void); + +// +// scr_cin.c +// +void SCR_PlayCinematic (char *name); +qboolean SCR_DrawCinematic (void); +void SCR_RunCinematic (void); +void SCR_StopCinematic (void); +void SCR_FinishCinematic (void); + diff --git a/client/snd_dma.c b/client/snd_dma.c new file mode 100644 index 000000000..09e408da8 --- /dev/null +++ b/client/snd_dma.c @@ -0,0 +1,1214 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_dma.c -- main control for any streaming sound output device + +#include "client.h" +#include "snd_loc.h" + +void S_Play(void); +void S_SoundList(void); +void S_Update_(); +void S_StopAllSounds(void); + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_LOOPATTENUATE 0.003 + +int s_registration_sequence; + +channel_t channels[MAX_CHANNELS]; + +qboolean snd_initialized = false; +int sound_started=0; + +dma_t dma; + +vec3_t listener_origin; +vec3_t listener_forward; +vec3_t listener_right; +vec3_t listener_up; + +qboolean s_registering; + +int soundtime; // sample PAIRS +int paintedtime; // sample PAIRS + +// during registration it is possible to have more sounds +// than could actually be referenced during gameplay, +// because we don't want to free anything until we are +// sure we won't need it. +#define MAX_SFX (MAX_SOUNDS*2) +sfx_t known_sfx[MAX_SFX]; +int num_sfx; + +#define MAX_PLAYSOUNDS 128 +playsound_t s_playsounds[MAX_PLAYSOUNDS]; +playsound_t s_freeplays; +playsound_t s_pendingplays; + +int s_beginofs; + +cvar_t *s_volume; +cvar_t *s_testsound; +cvar_t *s_loadas8bit; +cvar_t *s_khz; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_primary; + + +int s_rawend; +portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + + +// ==================================================================== +// User-setable variables +// ==================================================================== + + +void S_SoundInfo_f(void) +{ + if (!sound_started) + { + Com_Printf ("sound system not started\n"); + return; + } + + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + Com_Printf("%5d samplepos\n", dma.samplepos); + Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d submission_chunk\n", dma.submission_chunk); + Com_Printf("%5d speed\n", dma.speed); + Com_Printf("0x%x dma buffer\n", dma.buffer); +} + + + +/* +================ +S_Init +================ +*/ +void S_Init (void) +{ + cvar_t *cv; + + Com_Printf("\n------- sound initialization -------\n"); + + cv = Cvar_Get ("s_initsound", "1", 0); + if (!cv->value) + Com_Printf ("not initializing.\n"); + else + { + s_volume = Cvar_Get ("s_volume", "0.7", CVAR_ARCHIVE); + s_khz = Cvar_Get ("s_khz", "11", CVAR_ARCHIVE); + s_loadas8bit = Cvar_Get ("s_loadas8bit", "1", CVAR_ARCHIVE); + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", 0); + s_testsound = Cvar_Get ("s_testsound", "0", 0); + s_primary = Cvar_Get ("s_primary", "0", CVAR_ARCHIVE); // win32 specific + + Cmd_AddCommand("play", S_Play); + Cmd_AddCommand("stopsound", S_StopAllSounds); + Cmd_AddCommand("soundlist", S_SoundList); + Cmd_AddCommand("soundinfo", S_SoundInfo_f); + + if (!SNDDMA_Init()) + return; + + S_InitScaletable (); + + sound_started = 1; + num_sfx = 0; + + soundtime = 0; + paintedtime = 0; + + Com_Printf ("sound sampling rate: %i\n", dma.speed); + + S_StopAllSounds (); + } + + Com_Printf("------------------------------------\n"); +} + + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Shutdown(void) +{ + int i; + sfx_t *sfx; + + if (!sound_started) + return; + + SNDDMA_Shutdown(); + + sound_started = 0; + + Cmd_RemoveCommand("play"); + Cmd_RemoveCommand("stopsound"); + Cmd_RemoveCommand("soundlist"); + Cmd_RemoveCommand("soundinfo"); + + // free all sounds + for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++) + { + if (!sfx->name[0]) + continue; + if (sfx->cache) + Z_Free (sfx->cache); + memset (sfx, 0, sizeof(*sfx)); + } + + num_sfx = 0; +} + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================== +S_FindName + +================== +*/ +sfx_t *S_FindName (char *name, qboolean create) +{ + int i; + sfx_t *sfx; + + if (!name) + Com_Error (ERR_FATAL, "S_FindName: NULL\n"); + if (!name[0]) + Com_Error (ERR_FATAL, "S_FindName: empty name\n"); + + if (strlen(name) >= MAX_QPATH) + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + + // see if already loaded + for (i=0 ; i < num_sfx ; i++) + if (!strcmp(known_sfx[i].name, name)) + { + return &known_sfx[i]; + } + + if (!create) + return NULL; + + // find a free sfx + for (i=0 ; i < num_sfx ; i++) + if (!known_sfx[i].name[0]) +// registration_sequence < s_registration_sequence) + break; + + if (i == num_sfx) + { + if (num_sfx == MAX_SFX) + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + num_sfx++; + } + + sfx = &known_sfx[i]; + memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->name, name); + sfx->registration_sequence = s_registration_sequence; + + return sfx; +} + + +/* +================== +S_AliasName + +================== +*/ +sfx_t *S_AliasName (char *aliasname, char *truename) +{ + sfx_t *sfx; + char *s; + int i; + + s = Z_Malloc (MAX_QPATH); + strcpy (s, truename); + + // find a free sfx + for (i=0 ; i < num_sfx ; i++) + if (!known_sfx[i].name[0]) + break; + + if (i == num_sfx) + { + if (num_sfx == MAX_SFX) + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + num_sfx++; + } + + sfx = &known_sfx[i]; + memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->name, aliasname); + sfx->registration_sequence = s_registration_sequence; + sfx->truename = s; + + return sfx; +} + + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_BeginRegistration (void) +{ + s_registration_sequence++; + s_registering = true; +} + +/* +================== +S_RegisterSound + +================== +*/ +sfx_t *S_RegisterSound (char *name) +{ + sfx_t *sfx; + + if (!sound_started) + return NULL; + + sfx = S_FindName (name, true); + sfx->registration_sequence = s_registration_sequence; + + if (!s_registering) + S_LoadSound (sfx); + + return sfx; +} + + +/* +===================== +S_EndRegistration + +===================== +*/ +void S_EndRegistration (void) +{ + int i; + sfx_t *sfx; + int size; + + // free any sounds not from this registration sequence + for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++) + { + if (!sfx->name[0]) + continue; + if (sfx->registration_sequence != s_registration_sequence) + { // don't need this sound + if (sfx->cache) // it is possible to have a leftover + Z_Free (sfx->cache); // from a server that didn't finish loading + memset (sfx, 0, sizeof(*sfx)); + } + else + { // make sure it is paged in + if (sfx->cache) + { + size = sfx->cache->length*sfx->cache->width; + Com_PageInMemory ((byte *)sfx->cache, size); + } + } + + } + + // load everything in + for (i=0, sfx=known_sfx ; i < num_sfx ; i++,sfx++) + { + if (!sfx->name[0]) + continue; + S_LoadSound (sfx); + } + + s_registering = false; +} + + +//============================================================================= + +/* +================= +S_PickChannel +================= +*/ +channel_t *S_PickChannel(int entnum, int entchannel) +{ + int ch_idx; + int first_to_die; + int life_left; + channel_t *ch; + + if (entchannel<0) + Com_Error (ERR_DROP, "S_PickChannel: entchannel<0"); + +// Check for replacement sound, or find the best one to replace + first_to_die = -1; + life_left = 0x7fffffff; + for (ch_idx=0 ; ch_idx < MAX_CHANNELS ; ch_idx++) + { + if (entchannel != 0 // channel 0 never overrides + && channels[ch_idx].entnum == entnum + && channels[ch_idx].entchannel == entchannel) + { // always override sound from same entity + first_to_die = ch_idx; + break; + } + + // don't let monster sounds override player sounds + if (channels[ch_idx].entnum == cl.playernum+1 && entnum != cl.playernum+1 && channels[ch_idx].sfx) + continue; + + if (channels[ch_idx].end - paintedtime < life_left) + { + life_left = channels[ch_idx].end - paintedtime; + first_to_die = ch_idx; + } + } + + if (first_to_die == -1) + return NULL; + + ch = &channels[first_to_die]; + memset (ch, 0, sizeof(*ch)); + + return ch; +} + +/* +================= +S_SpatializeOrigin + +Used for spatializing channels and autosounds +================= +*/ +void S_SpatializeOrigin (vec3_t origin, float master_vol, float dist_mult, int *left_vol, int *right_vol) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + + if (cls.state != ca_active) + { + *left_vol = *right_vol = 255; + return; + } + +// calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener_origin, source_vec); + + dist = VectorNormalize(source_vec); + dist -= SOUND_FULLVOLUME; + if (dist < 0) + dist = 0; // close enough to be at full volume + dist *= dist_mult; // different attenuation levels + + dot = DotProduct(listener_right, source_vec); + + if (dma.channels == 1 || !dist_mult) + { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 0.5 * (1.0 + dot); + lscale = 0.5*(1.0 - dot); + } + + // add in distance effect + scale = (1.0 - dist) * rscale; + *right_vol = (int) (master_vol * scale); + if (*right_vol < 0) + *right_vol = 0; + + scale = (1.0 - dist) * lscale; + *left_vol = (int) (master_vol * scale); + if (*left_vol < 0) + *left_vol = 0; +} + +/* +================= +S_Spatialize +================= +*/ +void S_Spatialize(channel_t *ch) +{ + vec3_t origin; + + // anything coming from the view entity will always be full volume + if (ch->entnum == cl.playernum+1) + { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + return; + } + + if (ch->fixed_origin) + { + VectorCopy (ch->origin, origin); + } + else + CL_GetEntitySoundOrigin (ch->entnum, origin); + + S_SpatializeOrigin (origin, ch->master_vol, ch->dist_mult, &ch->leftvol, &ch->rightvol); +} + + +/* +================= +S_AllocPlaysound +================= +*/ +playsound_t *S_AllocPlaysound (void) +{ + playsound_t *ps; + + ps = s_freeplays.next; + if (ps == &s_freeplays) + return NULL; // no free playsounds + + // unlink from freelist + ps->prev->next = ps->next; + ps->next->prev = ps->prev; + + return ps; +} + + +/* +================= +S_FreePlaysound +================= +*/ +void S_FreePlaysound (playsound_t *ps) +{ + // unlink from channel + ps->prev->next = ps->next; + ps->next->prev = ps->prev; + + // add to free list + ps->next = s_freeplays.next; + s_freeplays.next->prev = ps; + ps->prev = &s_freeplays; + s_freeplays.next = ps; +} + + + +/* +=============== +S_IssuePlaysound + +Take the next playsound and begin it on the channel +This is never called directly by S_Play*, but only +by the update loop. +=============== +*/ +void S_IssuePlaysound (playsound_t *ps) +{ + channel_t *ch; + sfxcache_t *sc; + + if (s_show->value) + Com_Printf ("Issue %i\n", ps->begin); + // pick a channel to play on + ch = S_PickChannel(ps->entnum, ps->entchannel); + if (!ch) + { + S_FreePlaysound (ps); + return; + } + + // spatialize + if (ps->attenuation == ATTN_STATIC) + ch->dist_mult = ps->attenuation * 0.001; + else + ch->dist_mult = ps->attenuation * 0.0005; + ch->master_vol = ps->volume; + ch->entnum = ps->entnum; + ch->entchannel = ps->entchannel; + ch->sfx = ps->sfx; + VectorCopy (ps->origin, ch->origin); + ch->fixed_origin = ps->fixed_origin; + + S_Spatialize(ch); + + ch->pos = 0; + sc = S_LoadSound (ch->sfx); + ch->end = paintedtime + sc->length; + + // free the playsound + S_FreePlaysound (ps); +} + +struct sfx_s *S_RegisterSexedSound (entity_state_t *ent, char *base) +{ + int n; + char *p; + struct sfx_s *sfx; + FILE *f; + char model[MAX_QPATH]; + char sexedFilename[MAX_QPATH]; + char maleFilename[MAX_QPATH]; + + // determine what model the client is using + model[0] = 0; + n = CS_PLAYERSKINS + ent->number - 1; + if (cl.configstrings[n][0]) + { + p = strchr(cl.configstrings[n], '\\'); + if (p) + { + p += 1; + strcpy(model, p); + p = strchr(model, '/'); + if (p) + *p = 0; + } + } + // if we can't figure it out, they're male + if (!model[0]) + strcpy(model, "male"); + + // see if we already know of the model specific sound + Com_sprintf (sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base+1); + sfx = S_FindName (sexedFilename, false); + + if (!sfx) + { + // no, so see if it exists + FS_FOpenFile (&sexedFilename[1], &f); + if (f) + { + // yes, close the file and register it + FS_FCloseFile (f); + sfx = S_RegisterSound (sexedFilename); + } + else + { + // no, revert to the male sound in the pak0.pak + Com_sprintf (maleFilename, sizeof(maleFilename), "player/%s/%s", "male", base+1); + sfx = S_AliasName (sexedFilename, maleFilename); + } + } + + return sfx; +} + + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +void S_StartSound(vec3_t origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs) +{ + sfxcache_t *sc; + int vol; + playsound_t *ps, *sort; + int start; + + if (!sound_started) + return; + + if (!sfx) + return; + + if (sfx->name[0] == '*') + sfx = S_RegisterSexedSound(&cl_entities[entnum].current, sfx->name); + + // make sure the sound is loaded + sc = S_LoadSound (sfx); + if (!sc) + return; // couldn't load the sound's data + + vol = fvol*255; + + // make the playsound_t + ps = S_AllocPlaysound (); + if (!ps) + return; + + if (origin) + { + VectorCopy (origin, ps->origin); + ps->fixed_origin = true; + } + else + ps->fixed_origin = false; + + ps->entnum = entnum; + ps->entchannel = entchannel; + ps->attenuation = attenuation; + ps->volume = vol; + ps->sfx = sfx; + + // drift s_beginofs + start = cl.frame.servertime * 0.001 * dma.speed + s_beginofs; + if (start < paintedtime) + { + start = paintedtime; + s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed); + } + else if (start > paintedtime + 0.3 * dma.speed) + { + start = paintedtime + 0.1 * dma.speed; + s_beginofs = start - (cl.frame.servertime * 0.001 * dma.speed); + } + else + { + s_beginofs-=10; + } + + if (!timeofs) + ps->begin = paintedtime; + else + ps->begin = start + timeofs * dma.speed; + + // sort into the pending sound list + for (sort = s_pendingplays.next ; + sort != &s_pendingplays && sort->begin < ps->begin ; + sort = sort->next) + ; + + ps->next = sort; + ps->prev = sort->prev; + + ps->next->prev = ps; + ps->prev->next = ps; +} + + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound (char *sound) +{ + sfx_t *sfx; + + if (!sound_started) + return; + + sfx = S_RegisterSound (sound); + if (!sfx) + { + Com_Printf ("S_StartLocalSound: can't cache %s\n", sound); + return; + } + S_StartSound (NULL, cl.playernum+1, 0, sfx, 1, 1, 0); +} + + +/* +================== +S_ClearBuffer +================== +*/ +void S_ClearBuffer (void) +{ + int clear; + + if (!sound_started) + return; + + s_rawend = 0; + + if (dma.samplebits == 8) + clear = 0x80; + else + clear = 0; + + SNDDMA_BeginPainting (); + if (dma.buffer) + memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopAllSounds(void) +{ + int i; + + if (!sound_started) + return; + + // clear all the playsounds + memset(s_playsounds, 0, sizeof(s_playsounds)); + s_freeplays.next = s_freeplays.prev = &s_freeplays; + s_pendingplays.next = s_pendingplays.prev = &s_pendingplays; + + for (i=0 ; inext = &s_playsounds[i]; + s_playsounds[i].next->prev = &s_playsounds[i]; + } + + // clear all the channels + memset(channels, 0, sizeof(channels)); + + S_ClearBuffer (); +} + +/* +================== +S_AddLoopSounds + +Entities with a ->sound field will generated looped sounds +that are automatically started, stopped, and merged together +as the entities are sent to the client +================== +*/ +void S_AddLoopSounds (void) +{ + int i, j; + int sounds[MAX_EDICTS]; + int left, right, left_total, right_total; + channel_t *ch; + sfx_t *sfx; + sfxcache_t *sc; + int num; + entity_state_t *ent; + + if (cl_paused->value) + return; + + if (cls.state != ca_active) + return; + + if (!cl.sound_prepped) + return; + + for (i=0 ; isound; + } + + for (i=0 ; icache; + if (!sc) + continue; + + num = (cl.frame.parse_entities + i)&(MAX_PARSE_ENTITIES-1); + ent = &cl_parse_entities[num]; + + // find the total contribution of all sounds of this type + S_SpatializeOrigin (ent->origin, 255.0, SOUND_LOOPATTENUATE, + &left_total, &right_total); + for (j=i+1 ; jorigin, 255.0, SOUND_LOOPATTENUATE, + &left, &right); + left_total += left; + right_total += right; + } + + if (left_total == 0 && right_total == 0) + continue; // not audible + + // allocate a channel + ch = S_PickChannel(0, 0); + if (!ch) + return; + + if (left_total > 255) + left_total = 255; + if (right_total > 255) + right_total = 255; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->autosound = true; // remove next frame + ch->sfx = sfx; + ch->pos = paintedtime % sc->length; + ch->end = paintedtime + sc->length - ch->pos; + } +} + +//============================================================================= + +/* +============ +S_RawSamples + +Cinematic streaming and voice over network +============ +*/ +void S_RawSamples (int samples, int rate, int width, int channels, byte *data) +{ + int i; + int src, dst; + float scale; + + if (!sound_started) + return; + + if (s_rawend < paintedtime) + s_rawend = paintedtime; + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", soundtime, paintedtime, s_rawend); + if (channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + for (i=0 ; i= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = + LittleShort(((short *)data)[src*2]) << 8; + s_rawsamples[dst].right = + LittleShort(((short *)data)[src*2+1]) << 8; + } + } + } + else if (channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = + LittleShort(((short *)data)[src]) << 8; + s_rawsamples[dst].right = + LittleShort(((short *)data)[src]) << 8; + } + } + else if (channels == 2 && width == 1) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = + ((char *)data)[src*2] << 16; + s_rawsamples[dst].right = + ((char *)data)[src*2+1] << 16; + } + } + else if (channels == 1 && width == 1) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend&(MAX_RAW_SAMPLES-1); + s_rawend++; + s_rawsamples[dst].left = + (((byte *)data)[src]-128) << 16; + s_rawsamples[dst].right = (((byte *)data)[src]-128) << 16; + } + } +} + +//============================================================================= + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Update(vec3_t origin, vec3_t forward, vec3_t right, vec3_t up) +{ + int i; + int total; + channel_t *ch; + channel_t *combine; + + if (!sound_started) + return; + + // if the laoding plaque is up, clear everything + // out to make sure we aren't looping a dirty + // dma buffer while loading + if (cls.disable_screen) + { + S_ClearBuffer (); + return; + } + + // rebuild scale tables if volume is modified + if (s_volume->modified) + S_InitScaletable (); + + VectorCopy(origin, listener_origin); + VectorCopy(forward, listener_forward); + VectorCopy(right, listener_right); + VectorCopy(up, listener_up); + + combine = NULL; + + // update spatialization for dynamic sounds + ch = channels; + for (i=0 ; isfx) + continue; + if (ch->autosound) + { // autosounds are regenerated fresh each frame + memset (ch, 0, sizeof(*ch)); + continue; + } + S_Spatialize(ch); // respatialize channel + if (!ch->leftvol && !ch->rightvol) + { + memset (ch, 0, sizeof(*ch)); + continue; + } + } + + // add loopsounds + S_AddLoopSounds (); + + // + // debugging output + // + if (s_show->value) + { + total = 0; + ch = channels; + for (i=0 ; isfx && (ch->leftvol || ch->rightvol) ) + { + Com_Printf ("%3i %3i %s\n", ch->leftvol, ch->rightvol, ch->sfx->name); + total++; + } + + Com_Printf ("----(%i)---- painted: %i\n", total, paintedtime); + } + +// mix some sound + S_Update_(); +} + +void GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + +// it is possible to miscount buffers if it has wrapped twice between +// calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + paintedtime = fullsamples; + S_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + soundtime = buffers*fullsamples + samplepos/dma.channels; +} + + +void S_Update_(void) +{ + unsigned endtime; + int samps; + + if (!sound_started) + return; + + SNDDMA_BeginPainting (); + + if (!dma.buffer) + return; + +// Updates DMA time + GetSoundtime(); + +// check to make sure that we haven't overshot + if (paintedtime < soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + paintedtime = soundtime; + } + +// mix ahead of current position + endtime = soundtime + s_mixahead->value * dma.speed; +//endtime = (soundtime + 4096) & ~4095; + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + samps = dma.samples >> (dma.channels-1); + if (endtime - soundtime > samps) + endtime = soundtime + samps; + + S_PaintChannels (endtime); + + SNDDMA_Submit (); +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ + +void S_Play(void) +{ + int i; + char name[256]; + sfx_t *sfx; + + i = 1; + while (iregistration_sequence) + continue; + sc = sfx->cache; + if (sc) + { + size = sc->length*sc->width*(sc->stereo+1); + total += size; + if (sc->loopstart >= 0) + Com_Printf ("L"); + else + Com_Printf (" "); + Com_Printf("(%2db) %6i : %s\n",sc->width*8, size, sfx->name); + } + else + { + if (sfx->name[0] == '*') + Com_Printf(" placeholder : %s\n", sfx->name); + else + Com_Printf(" not loaded : %s\n", sfx->name); + } + } + Com_Printf ("Total resident: %i\n", total); +} + diff --git a/client/snd_loc.h b/client/snd_loc.h new file mode 100644 index 000000000..6208c43bd --- /dev/null +++ b/client/snd_loc.h @@ -0,0 +1,164 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_loc.h -- private sound functions + +// !!! if this is changed, the asm code must change !!! +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +typedef struct +{ + int length; + int loopstart; + int speed; // not needed, because converted on load? + int width; + int stereo; + byte data[1]; // variable sized +} sfxcache_t; + +typedef struct sfx_s +{ + char name[MAX_QPATH]; + int registration_sequence; + sfxcache_t *cache; + char *truename; +} sfx_t; + +// a playsound_t will be generated by each call to S_StartSound, +// when the mixer reaches playsound->begin, the playsound will +// be assigned to a channel +typedef struct playsound_s +{ + struct playsound_s *prev, *next; + sfx_t *sfx; + float volume; + float attenuation; + int entnum; + int entchannel; + qboolean fixed_origin; // use origin field instead of entnum's origin + vec3_t origin; + unsigned begin; // begin on this sample +} playsound_t; + +typedef struct +{ + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplepos; // in mono samples + int samplebits; + int speed; + byte *buffer; +} dma_t; + +// !!! if this is changed, the asm code must change !!! +typedef struct +{ + sfx_t *sfx; // sfx number + int leftvol; // 0-255 volume + int rightvol; // 0-255 volume + int end; // end time in global paintsamples + int pos; // sample position in sfx + int looping; // where to loop, -1 = no looping OBSOLETE? + int entnum; // to allow overriding a specific sound + int entchannel; // + vec3_t origin; // only use if fixed_origin is set + vec_t dist_mult; // distance multiplier (attenuation/clipK) + int master_vol; // 0-255 master volume + qboolean fixed_origin; // use origin instead of fetching entnum's origin + qboolean autosound; // from an entity->sound, cleared each frame +} channel_t; + +typedef struct +{ + int rate; + int width; + int channels; + int loopstart; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init(void); + +// gets the current DMA position +int SNDDMA_GetDMAPos(void); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown(void); + +void SNDDMA_BeginPainting (void); + +void SNDDMA_Submit(void); + +//==================================================================== + +#define MAX_CHANNELS 32 +extern channel_t channels[MAX_CHANNELS]; + +extern int paintedtime; +extern int s_rawend; +extern vec3_t listener_origin; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; +extern playsound_t s_pendingplays; + +#define MAX_RAW_SAMPLES 8192 +extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; + +extern cvar_t *s_volume; +extern cvar_t *s_nosound; +extern cvar_t *s_loadas8bit; +extern cvar_t *s_khz; +extern cvar_t *s_show; +extern cvar_t *s_mixahead; +extern cvar_t *s_testsound; +extern cvar_t *s_primary; + +wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength); + +void S_InitScaletable (void); + +sfxcache_t *S_LoadSound (sfx_t *s); + +void S_IssuePlaysound (playsound_t *ps); + +void S_PaintChannels(int endtime); + +// picks a channel based on priorities, empty slots, number of channels +channel_t *S_PickChannel(int entnum, int entchannel); + +// spatializes a channel +void S_Spatialize(channel_t *ch); diff --git a/client/snd_mem.c b/client/snd_mem.c new file mode 100644 index 000000000..7dd8d9c42 --- /dev/null +++ b/client/snd_mem.c @@ -0,0 +1,359 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_mem.c: sound caching + +#include "client.h" +#include "snd_loc.h" + +int cache_full_cycle; + +byte *S_Alloc (int size); + +/* +================ +ResampleSfx +================ +*/ +void ResampleSfx (sfx_t *sfx, int inrate, int inwidth, byte *data) +{ + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + sfxcache_t *sc; + + sc = sfx->cache; + if (!sc) + return; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = sc->length / stepscale; + sc->length = outcount; + if (sc->loopstart != -1) + sc->loopstart = sc->loopstart / stepscale; + + sc->speed = dma.speed; + if (s_loadas8bit->value) + sc->width = 1; + else + sc->width = inwidth; + sc->stereo = 0; + +// resample / decimate to the current source rate + + if (stepscale == 1 && inwidth == 1 && sc->width == 1) + { +// fast special case + for (i=0 ; idata)[i] + = (int)( (unsigned char)(data[i]) - 128); + } + else + { +// general case + samplefrac = 0; + fracstep = stepscale*256; + for (i=0 ; i> 8; + samplefrac += fracstep; + if (inwidth == 2) + sample = LittleShort ( ((short *)data)[srcsample] ); + else + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + if (sc->width == 2) + ((short *)sc->data)[i] = sample; + else + ((signed char *)sc->data)[i] = sample >> 8; + } + } +} + +//============================================================================= + +/* +============== +S_LoadSound +============== +*/ +sfxcache_t *S_LoadSound (sfx_t *s) +{ + char namebuffer[MAX_QPATH]; + byte *data; + wavinfo_t info; + int len; + float stepscale; + sfxcache_t *sc; + int size; + char *name; + + if (s->name[0] == '*') + return NULL; + +// see if still in memory + sc = s->cache; + if (sc) + return sc; + +//Com_Printf ("S_LoadSound: %x\n", (int)stackbuf); +// load it in + if (s->truename) + name = s->truename; + else + name = s->name; + + if (name[0] == '#') + strcpy(namebuffer, &name[1]); + else + Com_sprintf (namebuffer, sizeof(namebuffer), "sound/%s", name); + +// Com_Printf ("loading %s\n",namebuffer); + + size = FS_LoadFile (namebuffer, (void **)&data); + + if (!data) + { + Com_DPrintf ("Couldn't load %s\n", namebuffer); + return NULL; + } + + info = GetWavinfo (s->name, data, size); + if (info.channels != 1) + { + Com_Printf ("%s is a stereo sample\n",s->name); + FS_FreeFile (data); + return NULL; + } + + stepscale = (float)info.rate / dma.speed; + len = info.samples / stepscale; + + len = len * info.width * info.channels; + + sc = s->cache = Z_Malloc (len + sizeof(sfxcache_t)); + if (!sc) + { + FS_FreeFile (data); + return NULL; + } + + sc->length = info.samples; + sc->loopstart = info.loopstart; + sc->speed = info.rate; + sc->width = info.width; + sc->stereo = info.channels; + + ResampleSfx (s, sc->speed, sc->width, data + info.dataofs); + + FS_FreeFile (data); + + return sc; +} + + + +/* +=============================================================================== + +WAV loading + +=============================================================================== +*/ + + +byte *data_p; +byte *iff_end; +byte *last_chunk; +byte *iff_data; +int iff_chunk_len; + + +short GetLittleShort(void) +{ + short val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + data_p += 2; + return val; +} + +int GetLittleLong(void) +{ + int val = 0; + val = *data_p; + val = val + (*(data_p+1)<<8); + val = val + (*(data_p+2)<<16); + val = val + (*(data_p+3)<<24); + data_p += 4; + return val; +} + +void FindNextChunk(char *name) +{ + while (1) + { + data_p=last_chunk; + + if (data_p >= iff_end) + { // didn't find the chunk + data_p = NULL; + return; + } + + data_p += 4; + iff_chunk_len = GetLittleLong(); + if (iff_chunk_len < 0) + { + data_p = NULL; + return; + } +// if (iff_chunk_len > 1024*1024) +// Sys_Error ("FindNextChunk: %i length is past the 1 meg sanity limit", iff_chunk_len); + data_p -= 8; + last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); + if (!strncmp(data_p, name, 4)) + return; + } +} + +void FindChunk(char *name) +{ + last_chunk = iff_data; + FindNextChunk (name); +} + + +void DumpChunks(void) +{ + char str[5]; + + str[4] = 0; + data_p=iff_data; + do + { + memcpy (str, data_p, 4); + data_p += 4; + iff_chunk_len = GetLittleLong(); + Com_Printf ("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); + data_p += (iff_chunk_len + 1) & ~1; + } while (data_p < iff_end); +} + +/* +============ +GetWavinfo +============ +*/ +wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) +{ + wavinfo_t info; + int i; + int format; + int samples; + + memset (&info, 0, sizeof(info)); + + if (!wav) + return info; + + iff_data = wav; + iff_end = wav + wavlength; + +// find "RIFF" chunk + FindChunk("RIFF"); + if (!(data_p && !strncmp(data_p+8, "WAVE", 4))) + { + Com_Printf("Missing RIFF/WAVE chunks\n"); + return info; + } + +// get "fmt " chunk + iff_data = data_p + 12; +// DumpChunks (); + + FindChunk("fmt "); + if (!data_p) + { + Com_Printf("Missing fmt chunk\n"); + return info; + } + data_p += 8; + format = GetLittleShort(); + if (format != 1) + { + Com_Printf("Microsoft PCM format only\n"); + return info; + } + + info.channels = GetLittleShort(); + info.rate = GetLittleLong(); + data_p += 4+2; + info.width = GetLittleShort() / 8; + +// get cue chunk + FindChunk("cue "); + if (data_p) + { + data_p += 32; + info.loopstart = GetLittleLong(); +// Com_Printf("loopstart=%d\n", sfx->loopstart); + + // if the next chunk is a LIST chunk, look for a cue length marker + FindNextChunk ("LIST"); + if (data_p) + { + if (!strncmp (data_p + 28, "mark", 4)) + { // this is not a proper parse, but it works with cooledit... + data_p += 24; + i = GetLittleLong (); // samples in loop + info.samples = info.loopstart + i; +// Com_Printf("looped length: %i\n", i); + } + } + } + else + info.loopstart = -1; + +// find data chunk + FindChunk("data"); + if (!data_p) + { + Com_Printf("Missing data chunk\n"); + return info; + } + + data_p += 4; + samples = GetLittleLong () / info.width; + + if (info.samples) + { + if (samples < info.samples) + Com_Error (ERR_DROP, "Sound %s has a bad loop length", name); + } + else + info.samples = samples; + + info.dataofs = data_p - wav; + + return info; +} + diff --git a/client/snd_mix.c b/client/snd_mix.c new file mode 100644 index 000000000..00f201ddb --- /dev/null +++ b/client/snd_mix.c @@ -0,0 +1,497 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "client.h" +#include "snd_loc.h" + +#define PAINTBUFFER_SIZE 2048 +portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +int snd_scaletable[32][256]; +int *snd_p, snd_linear_count, snd_vol; +short *snd_out; + +void S_WriteLinearBlastStereo16 (void); + +#if !(defined __linux__ && defined __i386__) +#if !id386 + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i] = (short)0x8000; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < (short)0x8000) + snd_out[i+1] = (short)0x8000; + else + snd_out[i+1] = val; + } +} +#else +__declspec( naked ) void S_WriteLinearBlastStereo16 (void) +{ + __asm { + + push edi + push ebx + mov ecx,ds:dword ptr[snd_linear_count] + mov ebx,ds:dword ptr[snd_p] + mov edi,ds:dword ptr[snd_out] +LWLBLoopTop: + mov eax,ds:dword ptr[-8+ebx+ecx*4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds:dword ptr[-4+ebx+ecx*4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds:dword ptr[-4+edi+ecx*2],edx + sub ecx,2 + jnz LWLBLoopTop + pop ebx + pop edi + ret + } +} + +#endif +#endif + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int lpaintedtime; + + snd_p = (int *) paintbuffer; + lpaintedtime = paintedtime; + + while (lpaintedtime < endtime) + { + // handle recirculating buffer issues + lpos = lpaintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (lpaintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - lpaintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + lpaintedtime += (snd_linear_count>>1); + } +} + +/* +=================== +S_TransferPaintBuffer + +=================== +*/ +void S_TransferPaintBuffer(int endtime) +{ + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + + if (s_testsound->value) + { + int i; + int count; + + // write a fixed sine wave + count = (endtime - paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (dma.samplebits == 8) + { + unsigned char *out = (unsigned char *) pbuf; + while (count--) + { + val = *p >> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < (short)0x8000) + val = (short)0x8000; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int endtime, int offset); +void S_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int endtime, int offset); + +void S_PaintChannels(int endtime) +{ + int i; + int end; + channel_t *ch; + sfxcache_t *sc; + int ltime, count; + playsound_t *ps; + + snd_vol = s_volume->value*256; + +//Com_Printf ("%i to %i\n", paintedtime, endtime); + while (paintedtime < endtime) + { + // if paintbuffer is smaller than DMA buffer + end = endtime; + if (endtime - paintedtime > PAINTBUFFER_SIZE) + end = paintedtime + PAINTBUFFER_SIZE; + + // start any playsounds + while (1) + { + ps = s_pendingplays.next; + if (ps == &s_pendingplays) + break; // no more pending sounds + if (ps->begin <= paintedtime) + { + S_IssuePlaysound (ps); + continue; + } + + if (ps->begin < end) + end = ps->begin; // stop here + break; + } + + // clear the paint buffer + if (s_rawend < paintedtime) + { +// Com_Printf ("clear\n"); + memset(paintbuffer, 0, (end - paintedtime) * sizeof(portable_samplepair_t)); + } + else + { // copy from the streaming sound source + int s; + int stop; + + stop = (end < s_rawend) ? end : s_rawend; + + for (i=paintedtime ; isfx || (!ch->leftvol && !ch->rightvol) ) + break; + + // max painting is to the end of the buffer + count = end - ltime; + + // might be stopped by running out of data + if (ch->end - ltime < count) + count = ch->end - ltime; + + sc = S_LoadSound (ch->sfx); + if (!sc) + break; + + if (count > 0 && ch->sfx) + { + if (sc->width == 1)// FIXME; 8 bit asm is wrong now + S_PaintChannelFrom8(ch, sc, count, ltime - paintedtime); + else + S_PaintChannelFrom16(ch, sc, count, ltime - paintedtime); + + ltime += count; + } + + // if at end of loop, restart + if (ltime >= ch->end) + { + if (ch->autosound) + { // autolooping sounds always go back to start + ch->pos = 0; + ch->end = ltime + sc->length; + } + else if (sc->loopstart >= 0) + { + ch->pos = sc->loopstart; + ch->end = ltime + sc->length - ch->pos; + } + else + { // channel just stopped + ch->sfx = NULL; + } + } + } + + } + + // transfer out according to DMA format + S_TransferPaintBuffer(end); + paintedtime = end; + } +} + +void S_InitScaletable (void) +{ + int i, j; + int scale; + + s_volume->modified = false; + for (i=0 ; i<32 ; i++) + { + scale = i * 8 * 256 * s_volume->value; + for (j=0 ; j<256 ; j++) + snd_scaletable[i][j] = ((signed char)j) * scale; + } +} + + +#if !(defined __linux__ && defined __i386__) +#if !id386 + +void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int offset) +{ + int data; + int *lscale, *rscale; + unsigned char *sfx; + int i; + portable_samplepair_t *samp; + + if (ch->leftvol > 255) + ch->leftvol = 255; + if (ch->rightvol > 255) + ch->rightvol = 255; + + lscale = snd_scaletable[ ch->leftvol >> 11]; + rscale = snd_scaletable[ ch->rightvol >> 11]; + sfx = (signed char *)sc->data + ch->pos; + + samp = &paintbuffer[offset]; + + for (i=0 ; ileft += lscale[data]; + samp->right += rscale[data]; + } + + ch->pos += count; +} + +#else + +__declspec( naked ) void S_PaintChannelFrom8 (channel_t *ch, sfxcache_t *sc, int count, int offset) +{ + __asm { + push esi + push edi + push ebx + push ebp + mov ebx,ds:dword ptr[4+16+esp] + mov esi,ds:dword ptr[8+16+esp] + mov eax,ds:dword ptr[4+ebx] + mov edx,ds:dword ptr[8+ebx] + cmp eax,255 + jna LLeftSet + mov eax,255 +LLeftSet: + cmp edx,255 + jna LRightSet + mov edx,255 +LRightSet: + and eax,0F8h + add esi,20 + and edx,0F8h + mov edi,ds:dword ptr[16+ebx] + mov ecx,ds:dword ptr[12+16+esp] + add esi,edi + shl eax,7 + add edi,ecx + shl edx,7 + mov ds:dword ptr[16+ebx],edi + add eax,offset snd_scaletable + add edx,offset snd_scaletable + sub ebx,ebx + mov bl,ds:byte ptr[-1+esi+ecx*1] + test ecx,1 + jz LMix8Loop + mov edi,ds:dword ptr[eax+ebx*4] + mov ebp,ds:dword ptr[edx+ebx*4] + add edi,ds:dword ptr[paintbuffer+0-8+ecx*8] + add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8] + mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi + mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp + mov bl,ds:byte ptr[-2+esi+ecx*1] + dec ecx + jz LDone +LMix8Loop: + mov edi,ds:dword ptr[eax+ebx*4] + mov ebp,ds:dword ptr[edx+ebx*4] + add edi,ds:dword ptr[paintbuffer+0-8+ecx*8] + add ebp,ds:dword ptr[paintbuffer+4-8+ecx*8] + mov bl,ds:byte ptr[-2+esi+ecx*1] + mov ds:dword ptr[paintbuffer+0-8+ecx*8],edi + mov ds:dword ptr[paintbuffer+4-8+ecx*8],ebp + mov edi,ds:dword ptr[eax+ebx*4] + mov ebp,ds:dword ptr[edx+ebx*4] + mov bl,ds:byte ptr[-3+esi+ecx*1] + add edi,ds:dword ptr[paintbuffer+0-8*2+ecx*8] + add ebp,ds:dword ptr[paintbuffer+4-8*2+ecx*8] + mov ds:dword ptr[paintbuffer+0-8*2+ecx*8],edi + mov ds:dword ptr[paintbuffer+4-8*2+ecx*8],ebp + sub ecx,2 + jnz LMix8Loop +LDone: + pop ebp + pop ebx + pop edi + pop esi + ret + } +} + +#endif +#endif + +void S_PaintChannelFrom16 (channel_t *ch, sfxcache_t *sc, int count, int offset) +{ + int data; + int left, right; + int leftvol, rightvol; + signed short *sfx; + int i; + portable_samplepair_t *samp; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + sfx = (signed short *)sc->data + ch->pos; + + samp = &paintbuffer[offset]; + for (i=0 ; i>8; + right = (data * rightvol)>>8; + samp->left += left; + samp->right += right; + } + + ch->pos += count; +} + diff --git a/client/sound.h b/client/sound.h new file mode 100644 index 000000000..e2d09dce3 --- /dev/null +++ b/client/sound.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +struct sfx_s; + +void S_Init (void); +void S_Shutdown (void); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_StartSound (vec3_t origin, int entnum, int entchannel, struct sfx_s *sfx, float fvol, float attenuation, float timeofs); +void S_StartLocalSound (char *s); + +void S_RawSamples (int samples, int rate, int width, int channels, byte *data); + +void S_StopAllSounds(void); +void S_Update (vec3_t origin, vec3_t v_forward, vec3_t v_right, vec3_t v_up); + +void S_Activate (qboolean active); + +void S_BeginRegistration (void); +struct sfx_s *S_RegisterSound (char *sample); +void S_EndRegistration (void); + +struct sfx_s *S_FindName (char *name, qboolean create); + +// the sound code makes callbacks to the client for entitiy position +// information, so entities can be dynamically re-spatialized +void CL_GetEntitySoundOrigin (int ent, vec3_t org); diff --git a/client/vid.h b/client/vid.h new file mode 100644 index 000000000..f38140f3b --- /dev/null +++ b/client/vid.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// vid.h -- video driver defs + +typedef struct vrect_s +{ + int x,y,width,height; +} vrect_t; + +typedef struct +{ + int width; + int height; +} viddef_t; + +extern viddef_t viddef; // global video state + +// Video module initialisation etc +void VID_Init (void); +void VID_Shutdown (void); +void VID_CheckChanges (void); + +void VID_MenuInit( void ); +void VID_MenuDraw( void ); +const char *VID_MenuKey( int ); diff --git a/client/x86.c b/client/x86.c new file mode 100644 index 000000000..82cb807de --- /dev/null +++ b/client/x86.c @@ -0,0 +1,95 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include +#include "client.h" + +#if id386 + +static unsigned long bias; +static unsigned long *histogram; +static unsigned long start, range; +static unsigned long bias; + +__declspec( naked ) void x86_TimerStart( void ) +{ + __asm _emit 0fh + __asm _emit 31h + __asm mov start, eax + __asm ret +} + +__declspec( naked ) void x86_TimerStop( void ) +{ + __asm push edi + __asm mov edi, histogram + __asm _emit 0fh + __asm _emit 31h + __asm sub eax, start + __asm sub eax, bias + __asm js discard + __asm cmp eax, range + __asm jge discard + __asm lea edi, [edi + eax*4] + __asm inc dword ptr [edi] +discard: + __asm pop edi + __asm ret +} + +#pragma warning( disable: 4035 ) +static __declspec( naked ) unsigned long x86_TimerStopBias( void ) +{ + __asm push edi + __asm mov edi, histogram + __asm _emit 0fh + __asm _emit 31h + __asm sub eax, start + __asm pop edi + __asm ret +} +#pragma warning( default:4035 ) + +void x86_TimerInit( unsigned long smallest, unsigned length ) +{ + int i; + unsigned long biastable[100]; + + range = length; + bias = 10000; + + for ( i = 0; i < 100; i++ ) + { + x86_TimerStart(); + biastable[i] = x86_TimerStopBias(); + + if ( bias > biastable[i] ) + bias = biastable[i]; + } + + bias += smallest; + histogram = Z_Malloc( range * sizeof( unsigned long ) ); +} + +unsigned long *x86_TimerGetHistogram( void ) +{ + return histogram; +} + +#endif diff --git a/ctf/2do.txt b/ctf/2do.txt new file mode 100644 index 000000000..169a6cfba --- /dev/null +++ b/ctf/2do.txt @@ -0,0 +1,16 @@ + +switching teams during setup should clear ready +center stuff should be left aligned on admin +match command for ingame match request +admin kick option (menu) +can't join team when match PREGAME (menu was still open) +server pause in 3.15? + +*time changes are broken +*ghosting in makes you invisible (svf_noclient?) +*ghost doesn't restore frags +*resetall needs to clear grapple (and match start) +*reset all techs at match start +*'and admin' +*timelimit/fraglimit disabled in match mode +*telefrags at start of match [needs tested] diff --git a/ctf/Makefile.Linux.i386 b/ctf/Makefile.Linux.i386 new file mode 100644 index 000000000..7ee6caae1 --- /dev/null +++ b/ctf/Makefile.Linux.i386 @@ -0,0 +1,159 @@ +# +# Quake2 gamei386.so Makefile for Linux 2.0 +# +# Jan '98 by Zoid +# +# ELF only +# +# Probably requires GNU make +# +# This builds the gamei386.so for Linux based on the q2source_12_11.zip +# release. +# Put his Makefile in the game subdirectory you get when you unzip +# q2source_12_11.zip. +# +# There are two compiler errors you'll get, the following fixes +# are necessary: +# +# In g_local.h (around line 828), you must change the +# typedef struct g_client_s { ... } gclient_t; +# to just: +# struct g_client_s { ... }; +# The typedef is already defined elsewhere (seems to compile fine under +# MSCV++ for Win32 for some reason). +# +# m_player.h has a Ctrl-Z at the end (damn DOS editors). Remove it or +# gcc complains. +# +# Note that the source in q2source_12_11.zip is for version 3.05. To +# get it to run with Linux 3.10, change the following in game.h: +# #define GAME_API_VERSION 1 +# change it to: +# #define GAME_API_VERSION 2 + +ARCH=i386 +CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp + +#use these cflags to optimize it +CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +OBJDIR=linux + +LDFLAGS=-ldl -lm +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GAME +############################################################################# + +GAME_OBJS = \ + $(OBJDIR)/g_ai.o $(OBJDIR)/p_client.o $(OBJDIR)/g_svcmds.o $(OBJDIR)/g_cmds.o \ + $(OBJDIR)/g_combat.o $(OBJDIR)/g_func.o $(OBJDIR)/g_items.o \ + $(OBJDIR)/g_main.o $(OBJDIR)/g_misc.o $(OBJDIR)/g_monster.o $(OBJDIR)/g_phys.o \ + $(OBJDIR)/g_save.o $(OBJDIR)/g_spawn.o \ + $(OBJDIR)/g_target.o $(OBJDIR)/g_trigger.o $(OBJDIR)/g_utils.o $(OBJDIR)/g_weapon.o \ + $(OBJDIR)/m_move.o \ + $(OBJDIR)/p_hud.o $(OBJDIR)/p_trail.o $(OBJDIR)/p_view.o $(OBJDIR)/p_weapon.o \ + $(OBJDIR)/q_shared.o $(OBJDIR)/g_ctf.o $(OBJDIR)/p_menu.o $(OBJDIR)/g_chase.o + +game$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) + +$(OBJDIR)/g_ai.o : g_ai.c + $(DO_CC) + +$(OBJDIR)/p_client.o : p_client.c + $(DO_CC) + +$(OBJDIR)/g_svcmds.o : g_svcmds.c + $(DO_CC) + +$(OBJDIR)/g_cmds.o : g_cmds.c + $(DO_CC) + +$(OBJDIR)/g_combat.o : g_combat.c + $(DO_CC) + +$(OBJDIR)/g_func.o : g_func.c + $(DO_CC) + +$(OBJDIR)/g_items.o : g_items.c + $(DO_CC) + +$(OBJDIR)/g_main.o : g_main.c + $(DO_CC) + +$(OBJDIR)/g_misc.o : g_misc.c + $(DO_CC) + +$(OBJDIR)/g_monster.o : g_monster.c + $(DO_CC) + +$(OBJDIR)/g_phys.o : g_phys.c + $(DO_CC) + +$(OBJDIR)/g_save.o : g_save.c + $(DO_CC) + +$(OBJDIR)/g_spawn.o : g_spawn.c + $(DO_CC) + +$(OBJDIR)/g_target.o : g_target.c + $(DO_CC) + +$(OBJDIR)/g_trigger.o : g_trigger.c + $(DO_CC) + +$(OBJDIR)/g_utils.o : g_utils.c + $(DO_CC) + +$(OBJDIR)/g_weapon.o : g_weapon.c + $(DO_CC) + +$(OBJDIR)/m_move.o : m_move.c + $(DO_CC) + +$(OBJDIR)/p_hud.o : p_hud.c + $(DO_CC) + +$(OBJDIR)/p_trail.o : p_trail.c + $(DO_CC) + +$(OBJDIR)/p_view.o : p_view.c + $(DO_CC) + +$(OBJDIR)/p_weapon.o : p_weapon.c + $(DO_CC) + +$(OBJDIR)/q_shared.o : q_shared.c + $(DO_CC) + +$(OBJDIR)/g_ctf.o : g_ctf.c + $(DO_CC) + +$(OBJDIR)/p_menu.o : p_menu.c + $(DO_CC) + +$(OBJDIR)/g_chase.o : g_chase.c + $(DO_CC) + +############################################################################# +# MISC +############################################################################# + +clean: + -rm -f $(GAME_OBJS) + +depend: + gcc -MM $(GAME_OBJS:.o=.c) + diff --git a/ctf/ctf.001 b/ctf/ctf.001 new file mode 100644 index 000000000..f6ea177d5 --- /dev/null +++ b/ctf/ctf.001 @@ -0,0 +1,1009 @@ +# Microsoft Developer Studio Project File - Name="ctf" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (ALPHA) Dynamic-Link Library" 0x0602 + +CFG=ctf - Win32 Debug Alpha +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ctf.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ctf.mak" CFG="ctf - Win32 Debug Alpha" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ctf - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Debug Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Release Alpha" (based on\ + "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +# PROP WCE_Configuration "H/PC Ver. 2.00" + +!IF "$(CFG)" == "ctf - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\release" +# PROP Intermediate_Dir ".\release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /machine:I386 /out:".\release\gamex86.dll" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debug" +# PROP Intermediate_Dir ".\debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /incremental:no /map /debug /machine:I386 /out:".\debug\gamex86.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ctf___Wi" +# PROP BASE Intermediate_Dir "ctf___Wi" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\DebugAXP" +# PROP Intermediate_Dir ".\DebugAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /MTd /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /MTd /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debug\gamex86.dll" /pdbtype:sept +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debugAXP\gameaxp.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ctf___W0" +# PROP BASE Intermediate_Dir "ctf___W0" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\ReleaseAXP" +# PROP Intermediate_Dir ".\ReleaseAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32 +CPP=cl.exe +# ADD BASE CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\release\gamex86.dll" +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\ReleaseAXP\gameaxp.dll" + +!ENDIF + +# Begin Target + +# Name "ctf - Win32 Release" +# Name "ctf - Win32 Debug" +# Name "ctf - Win32 Debug Alpha" +# Name "ctf - Win32 Release Alpha" +# Begin Group "Source Files" + +# PROP Default_Filter "*.c" +# Begin Source File + +SOURCE=.\g_ai.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_AI_=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_AI_=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_chase.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CHA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CHA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CMD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CMD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_COM=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_COM=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_ctf.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CTF=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CTF=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_func.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_FUN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_FUN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_items.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_ITE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_ITE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MAI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MAI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MIS=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MIS=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_monster.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MON=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MON=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_PHY=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_PHY=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_save.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SAV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SAV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SPA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SPA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SVC=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SVC=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_target.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_TAR=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_TAR=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_TRI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_TRI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_UTI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_UTI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_move.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_M_MOV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_M_MOV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_client.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_CLI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_CLI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_hud.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_HUD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_HUD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_menu.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_MEN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_MEN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_trail.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_TRA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_TRA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_view.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_VIE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_VIE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_weapon.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=.\g_ctf.h +# End Source File +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\m_player.h +# End Source File +# Begin Source File + +SOURCE=.\p_menu.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "*.def,*.res" +# Begin Source File + +SOURCE=.\ctf.def +# End Source File +# End Group +# End Target +# End Project diff --git a/ctf/ctf.def b/ctf/ctf.def new file mode 100644 index 000000000..44612438b --- /dev/null +++ b/ctf/ctf.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/ctf/ctf.dsp b/ctf/ctf.dsp new file mode 100644 index 000000000..466e2d45b --- /dev/null +++ b/ctf/ctf.dsp @@ -0,0 +1,1007 @@ +# Microsoft Developer Studio Project File - Name="ctf" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 +# TARGTYPE "Win32 (ALPHA) Dynamic-Link Library" 0x0602 + +CFG=ctf - Win32 Debug Alpha +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ctf.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ctf.mak" CFG="ctf - Win32 Debug Alpha" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ctf - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Debug Alpha" (based on "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE "ctf - Win32 Release Alpha" (based on "Win32 (ALPHA) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" + +!IF "$(CFG)" == "ctf - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\release" +# PROP Intermediate_Dir ".\release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /machine:I386 /out:".\release\gamex86.dll" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\debug" +# PROP Intermediate_Dir ".\debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /subsystem:windows /dll /incremental:no /map /debug /machine:I386 /out:".\debug\gamex86.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "ctf___Wi" +# PROP BASE Intermediate_Dir "ctf___Wi" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\DebugAXP" +# PROP Intermediate_Dir ".\DebugAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /MTd /c +# ADD CPP /nologo /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /MTd /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debug\gamex86.dll" /pdbtype:sept +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:ALPHA /out:".\debugAXP\gameaxp.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ctf___W0" +# PROP BASE Intermediate_Dir "ctf___W0" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\ReleaseAXP" +# PROP Intermediate_Dir ".\ReleaseAXP" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +CPP=cl.exe +# ADD BASE CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /Gt0 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +MTL=midl.exe +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +RSC=rc.exe +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\release\gamex86.dll" +# ADD LINK32 winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:ALPHA /out:".\ReleaseAXP\gameaxp.dll" + +!ENDIF + +# Begin Target + +# Name "ctf - Win32 Release" +# Name "ctf - Win32 Debug" +# Name "ctf - Win32 Debug Alpha" +# Name "ctf - Win32 Release Alpha" +# Begin Group "Source Files" + +# PROP Default_Filter "*.c" +# Begin Source File + +SOURCE=.\g_ai.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_AI_=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_AI_=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_chase.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CHA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CHA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_cmds.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CMD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CMD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_combat.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_COM=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_COM=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_ctf.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_CTF=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_CTF=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_func.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_FUN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_FUN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_items.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_ITE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_ITE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MAI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MAI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_misc.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MIS=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MIS=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_monster.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_MON=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_MON=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_PHY=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_PHY=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_save.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SAV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SAV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SPA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SPA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_svcmds.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_SVC=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_SVC=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_target.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_TAR=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_TAR=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_trigger.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_TRI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_TRI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_UTI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_UTI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_weapon.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_G_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_G_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\m_move.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_M_MOV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_M_MOV=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_client.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_CLI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_CLI=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_hud.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_HUD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_HUD=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_menu.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_MEN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_MEN=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_trail.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_TRA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_TRA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_view.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_VIE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_VIE=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\p_weapon.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_P_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_P_WEA=\ + ".\g_ctf.h"\ + ".\g_local.h"\ + ".\game.h"\ + ".\m_player.h"\ + ".\p_menu.h"\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "ctf - Win32 Release" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug" + +!ELSEIF "$(CFG)" == "ctf - Win32 Debug Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ELSEIF "$(CFG)" == "ctf - Win32 Release Alpha" + +DEP_CPP_Q_SHA=\ + ".\q_shared.h"\ + + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=.\g_ctf.h +# End Source File +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\m_player.h +# End Source File +# Begin Source File + +SOURCE=.\p_menu.h +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "*.def,*.res" +# Begin Source File + +SOURCE=.\ctf.def +# End Source File +# End Group +# End Target +# End Project diff --git a/ctf/ctf.plg b/ctf/ctf.plg new file mode 100644 index 000000000..72f3ddce0 --- /dev/null +++ b/ctf/ctf.plg @@ -0,0 +1,17 @@ +--------------------Configuration: ctf - Win32 Debug Alpha-------------------- +Begining build with project "G:\quake2\code\ctf\ctf.dsp", at root. +Active configuration is Win32 (ALPHA) Dynamic-Link Library (based on Win32 (ALPHA) Dynamic-Link Library) + +Project's tools are: + "OLE Type Library Maker" with flags "/nologo /D "_DEBUG" /mktyplib203 /o NUL /win32 " + "C/C++ Compiler for Alpha" with flags "/nologo /MTd /Gt0 /W3 /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR".\DebugAXP/" /Fp".\DebugAXP/ctf.pch" /YX /Fo".\DebugAXP/" /Fd".\DebugAXP/" /FD /c " + "Win32 Resource Compiler" with flags "/l 0x409 /d "_DEBUG" " + "Browser Database Maker" with flags "/nologo /o"..\DebugAXP/ctf.bsc" " + "COFF Linker for Alpha" with flags "winmm.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /incremental:no /pdb:"..\DebugAXP/gameaxp.pdb" /map:".\DebugAXP/gameaxp.map" /debug /machine:ALPHA /def:".\ctf.def" /out:".\debugAXP\gameaxp.dll" /implib:"..\DebugAXP/gameaxp.lib" /pdbtype:sept " + "Custom Build" with flags "" + "" with flags "" + + + + +gameaxp.dll - 0 error(s), 0 warning(s) diff --git a/ctf/docs/admin.gif b/ctf/docs/admin.gif new file mode 100644 index 0000000000000000000000000000000000000000..9ad0658a4c9838c91aff4a65fc8b72d03ee9ff9f GIT binary patch literal 11206 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 SoSXY0z42iHvG!Hd>i+=ajoz04 literal 0 HcmV?d00001 diff --git a/ctf/docs/adminset.gif b/ctf/docs/adminset.gif new file mode 100644 index 0000000000000000000000000000000000000000..d679caedebf888463c9dad71d09f386b304e06e4 GIT binary patch literal 12303 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 MVl|Q>5WxKZ0JuqFy8r+H literal 0 HcmV?d00001 diff --git a/ctf/docs/automac.gif b/ctf/docs/automac.gif new file mode 100644 index 0000000000000000000000000000000000000000..186674a6c31ccc489a065605ceaf26830fe36058 GIT binary patch literal 16924 zcmWh!cQhO97fvD}h>{?PnFxX)wo)V!dz2WpYu9Ye(jA1x7JE0;YKz*d+M+gXV-~H} zs?ut|(%nz>_x($@}T1o#2?zrkQI6bgmI;dFI% zO-xLvRH~n!UrbC)a&mG(K|yV8ZC6*<`1ttp^77u^p1X-8)c|F!i!#?l8WWJZID{q^ zfk*S>B>Ax@el!v$iGU&bp$Hh19|nOzAucXKRH`@C+QrVCYHea|Zfs(#YoM#CsYxK< z|2q)Ob5K7hbHpflpny_Ltlf7|4*66(w^mwx5oeQW8 zcjN_g1XUBJi-t-fAS_L62~IK>i%drI$D?4O2&gv#V#g0KfdLF)08JPG4+UVM07)nS z2?6j!05L8mp>_s-*1F#2nl8o!I|ID6F3yC2HNc~Fagqcy5+{j3Bl%GX*nbrM!ylxH za3LV9aR>t}f*|>Sg8#AmZ@-yp-X9Y*UXU^0)iyr=s(JW%N!z0vthUfx7WGDf@r7iZ zcRa#6h~LB;Wrlva{ zPsQQvuvimGNnI2Qk3?em`B5+!KNJduK>qX3Es43A8+s$zFaC=Ag&3F6P&+?Ab9Z-R zD%HT;T+_e+Pat4%xc^7=zi`4}&=M2f35q1<9;PTc*_5z|BrGqoOv`sD2{c-$Ea@>t zY11%Gw=LI;3bmxso~BjmyKN8Gc^f8Dx`NTYk<&#i3OD90hJDBIidmsuI79HA3ooyr z&ecXgKUZP6Y>O-NAjh%JDk^AlF6PrnzJkYY1e!>sJqAMGOPq2gDAXk!Ah7pho417R zvQUP-xs8FU;7a|6k?3H1rULnv>vZXtJB|#hY+2aU!rr{!NTZoh{lyZKoA%X8&Ud>m zIAYJ&q(TV^P{qGDN(Fk2Cmh5C&c!ZYC=& zhQz#`F0MW^HSVXm@wMwhL)g%uG)HU_>42+}hzgcrlP-Ug%jH#N{aA~qK;&|{q>hB8 zNRaG!wv$4Z0y`cQGC@iJrQlv);;IYw=H;qqRHbc44YItq)PJs}YhB#fB*751A`V>F zdd~3rXENDZ^G=SoLfevRAHuvfsBCLu6&EaufX;fa@uX7Zgm4Z5|MN45ISz#sQT#@LX z!a#eApu2lo3--6SF)Y>gy?U$b#c%V=JBdj}Rwk)!LQ{K~Lc=G+L8)A)06`M!4KVAz9W)^>N!NQpZCOEPPY3+OOBOWBh?+$?Cg>nkS+ zwzo8Nv$SsiiYQ;kLwL8}0OX$}rhHAY@v!HqFY7|;3D-$kSu!3ajL3*cw4(Uj3XuAF z!$BWUTRLvp6!|RX_5H+KM_!ru>!b&-oFVWY>x;2DaR~x7vDV&Jn<5-x-D%l4GTc;& zT0~{?U?*GMoMxz@^e6Wm11xnPhg66L;MPO5MmRZGdJ+q1ePqH;huws0Xuz#QF%I+{e>7)-ej@XjDTM_`+Nb|5p4 z?yJC^=G;*l#g5g3c^p{V5U0t~@upGy*9p6`nTSV1!n@c@bCp~-i|?NR%Ncvmq86`{ znQ6HriRFRMq}o_X zttSip8lHpVi|ngJRa^~7XXd=K2a+f7QDuCcZ0+r3Ejd_{|`~6j|#YJHa1R(@WiUz z)XwmK_D$BrOJFV3LhBTtXe3JTf@J1ySBj2bp0W_Y1bT??m~e9LVlvr+B1eqYr5fy zO`$AAlP^lIAwY>o6%j$K5i*qK8eC)(gc~dK#5S_cP-SUH%>pSFwr?Zz%iaur)B0^_ z`_?C`!sE}FP+J<`g|8o6dg?h{I-=5VILIXVWvr{SSCEf~pQsH!BA&gRD1DSPC!mq* zi?R4Hm}T9K?D-A`2?See-CoI8s`hyWA<%#qZCqk|uxOP#kw7vBA}%>VKR|K_I%bwv zhEs+d%75d2 zdEB?mHgDV(tnBq%8pr+2IkVEgyU}o8Gf2kx`AK~eN`u))^?5Rd;m)+SPg#BW=(~7O z)W@@Am&g}Yqoep8OLsNFa+Y`*q=eUvKav)F5xkFEzUU#fqin7eW=Yl z;+==$qI$&0t|Y5N@djX$m4-+^P??o4d9Mx&c2wU)Cof}U`DzF`3l^b0(j+ctv0S<3 zxAsyohA4YA&ZJm`ApXT1T|rAK2@;YtqOOjRRn?FxtHJ)RQSw% z-^8fD;h?Wa%j$#Zzd@=`OzAmo0-{>*m})^}wb$aH-<^$QLv`vgb{U~x&8UbZ+qIDh zwPMc&PZcrcejiFA7Db}kxTg?_h*Uw9?POj!ocBQVd@XlGE%&W$3*N z4t6M-M+frnCU0&hE&2%67a$tuZJ&`$J2n#TS0|N6tMtyA_8*!w4`qPS5>Ra&`Vj5u zp-5zKnxv1SlO9nbhJ0n*x^)rH(~Z2LC5*KYG8@mJz3}(Qh z(`%gh65^Yo_QO?9*lZ04b^AKu@nw(g`ftQ7u&Vj%yx{8Ym&i zSz6|NHD^6B#0#XKcH<*(WhGNQr08k~$G;Zzl^zF_$!m5u_VN%jSzTr`4a=lE-`k zHUg1@8Cxl2BtiRL0{OGNU~7^Me=YgLEaPdCz0prfl9>#OmEzEg8PLmkI)^w)TUbw^Q%P?STZFg_w+J8 zX`w7@sfxB|Sz3b?ofQeSOtKpi1hWx*4%mN?y1x{OR)^$8`@khvmRCpFS|j!Vc_uCe zG)fo66Rh({(KE>kJ-5{;ZT1S2kh*ty%?$VpgiQ=nG=2!=t`4X)Wi~9}zrQHeEP)t* zb-?s{9rhLRGBd*N1e{X3y(nPghJa(21u97CFMNd_cS0N+vBilFUVw@LoDn42Ym=Fy zxS3pJR%Ep)AHS246JRd_kc1>TR#fYSLX?~59Ia;Itq6(xL*!>nYD0q-WeEt6B#|4qHx&_(;4V- zaRg`%t}SuF_zq`~Em^lO1uJw4h%DnnH$=e);)=mMztWZalFr_Ri~n9kmBsMP zpG4B&M{Xk1Oa+cHHLfV(v}$7|0>Nug&DlX$*xl3gbRt`ki&C_l^g34ol*xU~MMQ8m79WLJxlJGlrNi3o2NH1rg4$05T zSCH>ObnT1%IwC;bJk22$$f^+vUXa|moehe#?<<7fkhT9R0Ox$b%?DHp^Nk?23D^_@ zB}vuc=A72=g%r>hM?*p6PL=Dc=NuI5*l4>h+3c;6pc}DqJzvbl?y7G3dV0_>e0%h3 z{n0{TReh!cU!d&qA!sf+RgxH=wJxtPD+{UC5qtppQ7a+}7w?FMX3p#VvO@Idp(5p4 zCrBr|eIImcJh;2z((DIK&6S*2hwsYcMoqNDeBfN@2yykx(vhdihAwk_){@K>7&$&e zoTa!_)}4QE=Ayab(q-Pc8+2aN;mUTAX4{X;V1kag{Dm12+n8Cw_eH*f=qQ9a0>kvJ zh<6T%LUgPkMhq_0x-hds-P>1@hXwE?Q~FMRjJu2gtNnH)Q#vu@+?AzsrIX#=L3 zJz>5vp7vO)joyPkSA*iJB2MlH@Sm*# zqgPMn8&gC35#sb&R1Q$Ny86Oz9qAkN{9|k8fOdEH zlFNjjFEE7c1ESRoy_m?lI{301iQqcFE|1WSetARgFj_^i2%*eG$U+7= z2-5dT1oYKKNa{k;bPv(m*rW02V=l70y$#3}Ijn``hchPA;pI_4H}ciF@*0yoQF!n) zsQ_rz1UVgxARUMXoBJ^x_@CF5xtRyA0J}-Er&VS}5ONL;>f%is;yLkQu9(8UnuM9< z`$f0e;Eb_mpmInVM7L4wo^Vy>OS*6u2284u=mePv>)}+6886&!83*V3b5-P)=a#!* zWPgBIF+NhnRIl71Z@qbppSqfTh!-oBFqa&*f{E)a!ceXGUJt#0MS{C#tPVHi>NGW-Sk*4bf@@8(S{orH#GpjuQ;Vh#Obf1!ohy`g5fXb%CG~F7YL;cR%~&U%C81Ry=tX? z-y{a@{!MoO^IdlLQqt4Iu<0lj>C*czsL9h&_{lt6k!A7bl)HLO>Q1};PmtshsSaG) zv_V=FNWMKi5BVsXBCQN~q4Daq9N;p#Y@=H3Lpl?_t6}t}VA~ zuq*9FvCH%1!-Xw}n|oe2v9agGJR6nmv7G(Lp#ncW^fRj(p`T$Om05O8CrBZ&>8~WJ z?La}8_(<(8_Ue{>XCO^CGG`1QSSHn?9Z-G+qI+xQp5Lx-sCS?H`2WX`fjBf;5EEjx>Wle4Q4*7 z21n6C&hUV?plttm)(X790dfCuc`4%Xe{b_R{8y(4H6TQ@Y%h^GbT^SoM{>i_;)eF`H4L=g}M!7h-^!%!_lMMB^^#8e78c;%V2$#O4$8g1flG z+Te_QNcxw4zXhd!cD~miz%JVH;k61itRtmb)~&7vVt=s7$Z`numcC zXmPDJ$XOz$6W(~{{>u4Px}&a12n?Og zk+v<<2#w;Vj@q1%2;%hWJ=n3kMY?vrVsAc$cUat~-3KVhky@%L9mZ~Dwy(1CwO`dX zoy5J(I?fdPq2d}5iq(R_hmO=h`p385i%+e)QT%o90y>y{T zNBe+BHMfZ=AlwU>y&>`SC?0uUgwPRWdrMRCk?p0!?_+kAnJCZR4l;z{)EGXJG^#t2 z#lcA!J&W>-uFd;5`1X%E@u`LlokQ;WG9jC%4Q2RtUJ|bkD!T@3J#jX1OnrAB$L9tLaRgcgNiy;Jg=+EaI)#tabX-3bf|OYex!G zy-dRYc6&D`T& z%(3498=LtuUhZRM(_FbwvoC9G?E#fvg()q_OkK3=ZE!3AW9IIC zIsz&KKr4*XaP!A7^0!_mw6m|?9~-BkIjTWu9>`O%)wuFa}qFB!eTm0?f^rPGRay6svNq1bD z;F#G!jQo{-?&!vA#*C%qWvQk;b%vDn^QN6tk9gm>uBU#wjH?JJhAu!=g=nk1$36Ti z^&!|YL+=x1i`OSzMMvsna3>@{yJSzT3R~ZBI~%W|Jn#JY-uQBQ2O(odg5M2E zntSd`pHa4aENF)li4xLC`S%hM=>|Tt_5R*JkA+Y@0CdOPZTv%s=4Z>_m7)4GHGKDg z|E!FE6~BwEy=r1*{7lmvy2(iWU}3wvD|Ws`Dhik|P!_8t< zmW>*-G}|`vuxrBA>KwsMl9KmhNHDzCG6!D3Ik>>=G7M6(G0BJ2lRJ0^8qW1j%_+W~ zAFg^jiqWn}lfAkNeXuaDsuH%6>=P|2J4KJbFJ>k9M_2`?5$@<%>nD7tS64a63g*}A z8Xfu8lxM&kTziWH7@pcWxO8c2Yd8tqh!mRU z;IDz&j1{5w#Ks^S9MU35^zyla0EJp#%cdGC-=AHwh)e76O_(2md8)<|h@-tG5So zO47(TCpmH$T-xE>iJio()h0$-xoMy2W_r5tnz_?9^oqeIU%S|#e@dq#FQ-1i&zRa` zG9Bp40x4W*aN=mFH zHHt9v7*o39Cl!%o${jweSuQE9i2oa9#-oPwv^ev)N+pDvbFMD6gg^SFMpRyL4bQs# z;wua9+7;Im_*hxfDPGWhZnyjBjM5za02S+b&Gc}JjP)v`tc*jj{va>-M}Rv$nhKZUx8S=Wk>K-JD2ss(QatP)B*(8~~G6~dS(u_37Fk45EN8hFycKl<6dmXASJkN%Yd zHflhW9{;k9v^@ND5C55YY&eGhIaO?kO-}UubRPZd{foq2Hq+w-z87a-fBLz2aW8EO zW3f?t(Kb@G;vv@=n>+kH?QC*=FGZyqdDh{jjxBn*2=T^M<1QPho`HYKTy(Mh{(U_Xp&M5#$bFpp=E+b9ovDp zOX^4)eGN+>B}{&{Cev-l4BkQc@JO+G!h!=o=b83i_J^$+gkWV_MjpXj$$uli)N~ec z)!}6EgOZk^UGX;q=bU?VNUTwUndh&u3nF3J@2Kktx8Wd;Uzb1oKa)37T~O85Uq~W` zcc-8|euJL8q+i%AhWuG*kd5jbOh1Mv76hD4B=@#M**iLG!WO3k7;`lDBS}H!FWKP) zIK%PEi~RZ}|5nOtBBn2L=9+TUMu0)k&!Azap!XJlpvfSSnIuxHrQ125qUZ@-cA%n% zuKT4Bifn9mKBlWhO8K-H7ZJ*=tPRL`GOOZveDx%L4{05B!sPsgm%=sTYu%*BAA25+ z7F0c5!dtX4t~d6UH$?jmiHlCf7~5~~ch;ww=CyF{)Zem?Uw;IU2T&e$-8m%F(`#yK zUMl$fxFBZ!5H~2S0LR?okN^}+$peIS^(7ik``muBxm};v<0W{`%Bk2y(b{cve<$G4 z$;$yl@0XnSV4nZJL88y{wjX<+F{fRR?{QMG$?hY`8JS%Uye00^A@HbA%ewR_PxNKa zH{2x@Jq|v;!^*4oM`#=xX+|!+i18kV86uX$^864L0$4P8J%aagES<(A?|tta?B5eB zHf`?~*p`yF$U0>+g&Zy^kmU4MwV(FA2+~c58DQkPL5`uW(k~Ibh97u5htjE^Pu}n4 zslHe3xXW|=frq1_`9cWVgg_6vl+m+})lo{^VPx$SkP18`_z`U}k=s=aP=d?*+Sm_d z8}KwHDNQ_J$f$v6L!YC&ww8qolK{|VoM^#)KCrN8z=MNm{Wr@(_Nzj4K;gZY^j?!h zPzE{I6`tOBTOr`Mu&Te@!J1pa9nLnnn=N1%{y?sMNL`hnr*XBYiT9R4VwxQMN&m@= zrZ9xqG%ct)Gk1@k811DYBG2v!`qJ5?0CP`4{ti*UrMp6 z7&-kDOfMU$CY<=R0xFgfW4kBz+XFL#vtPXgJ>AOY?6?`sd{h+H_kNv)B(}Tt!_Mww=M6-pyf=d$kYXyWwZN_7ThLx6y>`UeendVP@Mbj;$P!uwU{V+cF{$T!n zd8XQ*<7INX#VJ(hgXud4Up8eYWoaqkJA&+-9Ku}@b|+u(i8`e|ndGzqBH`~zLau%O zr4jKnktsje{J$oIJfozLqH=h>I^_g@7t1;!B2O_5>hP-qXpxgg%k?WWQf=&k2{1nG zfyvl%xsIFAQYO%XS<0^wDLta=m#jEWTLD| z(b}#Rx|_)T05>|LnZeuwBtr4mR+F+eN^F4D>nt&RsS15J9kq&io!$YUK#X5%qNehO zIyLLrDhAVNm0)z2MOB{wV|(|O-Vw9|dP`=q_sJ%pWRf??oz%}R6Kdr5d60Cxjg~7t zV)OyJRMxw@e)VJz0lISQm7jDbA)cxxREm5~^VcYPznc%6U_Kv^8}q3zh34TufL>?v z1#LbmNGw$@e+FG`f8J^QZkYSq6=2CqT6yEi6GY6ZgR}t){zQvTVfCDU1+#z{2FoJ* z+Z_EjBct3TF}6c$>sNA4q9Ic0odsZo$#PYY|<)IAQgm z;_h7T9YJu#vn}r4K4ecVL;r}Pl3;?g$k~Z}nvq}~d5=rFM&J{zv!mDgu%nCN%D^b* zJqpNp{FMj)tD{SakK?FA?G5A`p>zj{EpDJ_GgvRsf@k zt-B_cZ|a=CavDS}C}}O2u@YO0Z461z|0LVTm;1>0u%~>m@`Q}tmQCr-w(U-4=BPU# z;foD-itYf3K|Rw7Eq+s*2dcA`#lUE2dzW;i3V3 z`wBoFO|#Q>Iw7;zI7z#E_~b@1D#EHo(SP22NXB&0CHc5@zW4I!m{D%4ZCJqU1a}Qp zsq0yJtI!8O`HA)xRsu^pjn7p0xeWX{F^kWPZ=vZnqcpd6vDB2L4c%*lp4+OmGEL7C z5dJk)@CVP`PUg1ug@N^ZqJ~d+-Ax_%{B(p}`gA*KXCkAl_kL)Uivirt^l&kh&;#^w zClm^gx#sPNx~N=rD|F3bYZPP|WU!!=UD>H~-06h`G(ECew5xX{qwS?#vyf590wdx~^IE+$NgOtK=AW$>p}pKWPg!#~ zO;tT??n}TYAAIvD6`EeuHsUrFY|IXKQkmyI);4^R#V8tQ&&}n!YG}U-_Tn?Mbv+^4 z_z}M_JL;8fsQ0C5O~kcwytHr*x`_7?u5E4ZU1n%b6-di*Bh?x4QP1aCZf1n+EQXfS z^op!I7Wf;!>|bjqYI$fcvfu`-ZhB7k~Kjn@O*W5J7vmZE2OU$yReyIS$>ePIHVl=Ur3MX-mS#kNXi^{!@U;a8%ky4>>*YoKZvD%{#D$nEI|LZowHu*~A{p*&_)B_5AJQa9cU%~HTzzu~pl)lQzug>Ay7*?1#9Ab^ zU{)AUg0r)N1#Lbq7(~6|Jm4212KvlbZIq<1_zmLQI_$bs+{bu4QUjB(zd#{My{n4P zwxqsq^fTUh)5xhAZ`#4tC)ddRcw$Xb;>3LTn4F+$f!(*XqF>LD(DX`Co)+YM(d07b zQSZmk?`zh1fx$B8KZO}-Z{A3G4!ZlU09|uA=-p!9tL4VD<_?F`3{{odfMh*gIwRv> z;jAY_o?ky1hv~e>@&1$I1w%h%{FA&(Fuzvbz~{}M?#g5!14FBG?6O^*6Msn38ap$7 z(Cp)?p?~Z{ik86pa6e9-mE6LRs#f0RPI1SDq%Pppm;4ia0;T5H9#xiP*c=q*ScuhJ ztad6BdY`^R?$>kdHz9^nI%p_&A1$DuwnN69PxReqjD>}qcO!NW>zB<>`N8zD z_5F1o`mkj)`Ec10ns4uZ$%{*c8n=2UTs}G_%k9j4HW@b(Blw>q_-87A`TUQg<6ii? z>(c<8N^X|WbJ?*8cA$*n7xcty&_f+$s#~RudPY@R#?L-b)C&DKk%t{*A=s#`VKn|| zsIV7mVWe^FCBH=r3#;wScjSF3S(5wF?sKKex<#W?;>%|ra{$pCVd%@Az}|xgH&?(V zWg#NVX*mh!a}J>YgnsJ3be=`AF19by?|$K0Bc`^P^iiH5JdweqFuHts+C=|3)JgfG zlTxdhflYRE` zt7ScgqBP#k@gmH0RX<{0Z)z}D@Ez^_dM9GsBINKzQaYc&`0|yDz_Dw5!aK~5AN_dd zXmJPVNU3yzi{RH8u>G}_Hw(L!0*t7rNlAr6As?Qynl5F+Rsoye0r+a~npAqQh~8xJ z;_IX{$B1+VjiRg77H(H_UBVOLaK=C)H{k-h)>SfnIZfP{cWjxlmS7Htd`lRmWd~0S z#eEWP{dqTu`>CagsnwIRN$4#C?-r3KU38N4Jc(=$z8!*YjkGa4eZl-W!lL0T3iVGz zU+nJ0*L#;T`YfvSD$3rxWODO7-oZXwW~&sS2L^lYkdL-Y&r5GoRknK%|xFC#QYO z;0xN&>Wiw;+9wQ%cTGMB6hY3<8}MoTvE<$3$~F7c8zyM_ndl; zDvwjE0t(naXDm%_X1&jS14?~EfBty!KIHFu3BgitNq(r4> zaIlKMx-sb7i21kS`m6Knl{aKBA_4V}PG1kYcD=gqp3yP0Lnpi!L8^G$QxxSn^wDXm zvCA&7L{;gpkk9#@gew8hy?f!MU+ov?MOH5l{(HoZ5N7AF8sYD3(_S;w{*%M1`*Fg2 zd~+e%p^JCjT3MEYNc?=g%0RRnWK2j{iA7Y8+9*=__C9Kzm?I^kDt%AIeS6q+iq3nc zX(G4Uv7rEccHrmMm*>;P;yMH2vt$~ebTOf2p(fDZq)y>X&}1}1aS<nsJ{oZ&oGB~dTf9)g;$FwJjaCw&~ zEn1D#$rn@gOWW?u)Z0gWn)!9Gm5Y|1eo7N$suqPVOgTBbaYG?xQT2+K}m!7LkEV8oDNzr;&!KYnd zf#GjME`=$z_Sr}F6b2OADu25uPBlrHdL1cdaZZABX0}uQ4rgfVIc=TU&JgYK=2(f# z!)x!U>TBzOUD1bzOj;>kXN zvtpT8r3B+8!ELt_9=Wv;EyEf1SdTWguD)m1D%wK1bkV_tuJZ4Eo?Y6daz~#NgfC>h z>hTIM3#LJ6y!hwswTP+&j0%tVq-8^8f<`~&TuKclj+OMNVVe{E0U%O83|Tq#EAt+?{|$OW+5gP8S6?E9Es6!*@n{ zh|_S5@rujyYr=Uw9tElS$}K-fDpxPtN`z4s(tG~t%s5t*BVf?!4Rvm_R%%jf!t;g! z^v6+&f@47;`vV;k`THAJs!_45CqONB$k!b%67^btkv1M&1tgc$9t|Gdk!#7><+g%@RnoAF#gn3HS zaN#8@ll(!x){`k6i##&DQbNx@P{K*+`SzlD*|m&7WTT_GkAIO2rsauxpd#_`o#a|GG~So}cXLjxW5_74k z*%SPV=0!z3zbL05H_FV2y{k#y`+7q0&D^t|31X_&d7xW8K0wnpwa<{%dUdPkd3{mcX_emtoyi9bmt5HK5QLZDVeghd6Rw z$4+Lx#ea*Js1}gJA=@)%ul};ul5CWUhp!vxPC}k|hn0F*>~fQp-7|te>AmqQ)YdNH z3CsSdKG`G>eLUF(bDfu0jmSdY)e-XLR<5jYJ?DCL$8IR8!A87Qf%h&I<-!-)H~nYH z>ueEwkJ{7k7VzGzzZd>jUSM6^L`L{X}eh;-{2ZElQzh-06)%C9m1i)XOl zL8Shd*tp#xnh7| z3V&08pt^|8H zC63&zxsOWHF@+XYg*uk=<9i>b%y80Bgw?=V6ICIj zl_`}VpQ7S-k3#nkLB3L7Am95o{jxrG+xd1BzLLP{xEn`Ed zd4N6#fYzlnBhpG*u23CBD-E7vJNOoM&pl(mZg3{*I%p|H2LxYcNTC71TjmrQam(Oz zjfby=(uiQi8VMx6#~1&~W#xX6_EN_)6n!>0Ki);L7SE35QaC;*d)U zhF*i5$1Gki*CC$MS~Q^O@7z0@^|F=OP_t8%qQN(2DaqOioCzEYktgh~zIi5Hcj!-2 z=B&3%l$x|Zj{kMJ^Iuh*MEW}MLN5{*h^utxe)M2)xv;YXTla=Y@Mz7uP#$7A={Z-C z*SFagQLD{vr#@+xL|V;iV3)KQ+NTX{XMH?5UahI~xIgiDeK_(#O?~<-h9cnmsO^GM z>w!Jy0kGn~p)Aj!#^=|bvHKfvolJi(_|qGpTQ3YTSgRZ0#(xk0Hqq|MS5Ey%P zyeR3b_j15FIn{C`qEp1|t3Vr{_a7F|2eJ80hs@T5tdWJ}0FpQ6s*QEih=q_Ve+?Jc zlF6v&dc#pRDhe3md6REeoQv_AtDm-BTg5y|Pk;$D;lUs4tXyJEPppm2?N%W(ZB6i* z$M)$LT?Om*1rPRe4iW_p6WJj$WKlXvN-9ZOid^bTQ!+j43KO73(Hv+jfDhe^!Z6(~ zcF4^^f-Njy&Ro4y9j*Z1R=aJdGn#j#O;Y^KcO9H5*@!!X-L;!Mttq5IiFdgG?plUp zE#s{#r2zuv*x8h(Z<15W-qMTQSksmgTvV8?`$}@O&tMP5=Cq18{5SMCVFccMb(BXh z#fkkSqIu+E9nH}v&2*ts%8=rIb%J+Q!sR0dxG)~EAdO`6%z~eaDvsvv`*2M{cAVGa zx7QNvoOS<0G&h+T!BovPE7I)>Ef8xRWo)NllvNi+*gG#&H{ zj&p~-uTdDrxaZN{<}%)X_Dd%g%vJcA;Di$cSXf(xM*A_FgNGKS?Qi8t{W%DK7Y8b2 z3D`2IVs;~Xzek*6Uy`v4<*w;<%bUWr7R*8lQ_MC6D$f(K^DfQTm}kdW^>uKKHS^cY z^;cVDOwGQw1LzXyGS-dhRn(g$-*UAUL*KRt8m#{5dtq$5x&#zhK(@t` z0hJTxi@9^B1!l>@y#3f3-Xl{>o4ktw44wgs!}US?tGv|E!Q4!1;q!q0Ww6Ran5{*F z2ZC5%$8Za-bm_J;|5;%QkV%X6pW&WUt^=gi2GVNe;<$HX-A2sVudc6`2u+^w^rI;0 zL(H@&G*NmAYn-$?l3_b1rwtRyP5Bv%;5?3{ILNV@EbHG&EXpseIhGM+v>lVE06z-Y z^Qxg!~m0(aU;(KuPGKF{|t8rBgl1oGS8koxT_Wj1o*bx7p! zb^y3R5lSc8m@N|CiRztW5fwUE#CdHm8pXz<0DLi;NM8oKbyfmkX>uCo7SILX^Fz7Y z7~8?KxJPF{Of>MY6?i*~|5Zc*F47&bPFZ`-H_8Bb7gSwW_6_#kItM_Yl-Ij(Q}VAn zq5-H6u3(d2=CoQTo{8rSfc~4*B2ut(<%bYm32pE{Loa8EY1E-y$thr^6 zwLX)l(xlD6edj?RWH{~ExtA{{MXikVINPwIkZ#)(*zb(oImeZ((+VSn%o?tupx2LG zeAFP@`Q5~VRTE1(r7&(U-(h?=g;`W}4wxbxZnYGU72^MpUK&ap%^bl72H5Q$D{(2%k9lmkM&%<=B&v8y#3m1;$UHF8XzL8oo{(xdgYN1dr} zV*}UPPORIa6p+WX$tv1KXJYW8S8yDCA3Ap(5N3j{1Si~^S^LNxDQYH4QFjPce?(-3 z+)O>*s)#5@(kVo%_YeVamKP}-PNC$c+;JQ1$B_HnM|%6Skyo>bzJAv1ya)O(qSv;z zmy``p`M>%qQ*=J)@iG5?(BRJOho8dXBQR4ymAV!J1#5ChPZ~?>Vg1edM-3laXhU9>Dx#E3zwk zPpN+Mp78uMC6x13e#}WzYrWAI6dSdZRRAoq4(n)A0j|?}C$B}ZX48oqBbK-`oL{Su zU$>F>D}qJoml`W##MI_~&jm$O4fn&Ex$jHNJ3atixSOyR%;_%XJQL{UV_+CwaA~c- zg8T9yq+fK>`jyCOEaMx3`@e=3he{rAf!TD#J09AJU&r)cR#wxO&5nXNKfo0H0^omw z#*WHBHbJSlTd7i6S!mJW(G&Cm6Sq$3gW!K-tV#kf10Q65 z%tZwp@DOei$ae1m3!0CKXBXHYS*?LO(Mn-5Or-6qZjdt8gqCnMN zXjcp8+X6alMYFI-O&d}S)FU#TsA-~zfoSh>*4;|FfwDJ)l;9pJ9f)g?U;#|^f)4q3$>uV}0BmVd^-Nf$#C za$0r#W6WrG-x530@9CKgVYEExZ=sU@`GZ!9kWy2y0!(yGlB8Le13gEBST5uM1@Ll0B0hoLH;q3qfER7P1tpW42gm$NcMI&U4OvGW4s6O^7IMIpQAvCZNZ?+QmoSir7Ze`P@}eN z3!~%ZkO&vK6!i3MJjDv8rt6qBWp!t;4p*0MDx>lrwtA`;cq!!JWW4E{4yQo)R%Qgq zte-%wkHFLg8d%uGU1_mEg*~v_gd!%}v)4F2n-WcAn$QV29f{n)4WKaIK{JWq9V})V tET+f;W^K><2k4}T&ox>ndSWx>Fa=Cn3WPlwFvn1bdA`yFlm`R=06RcNFy^eNVg)*46T5~5DJJQ z`0~H+=YHSkeZD>Wd^>xub**b%YsdNR^LP30CV)yuLt6uYgM$Nbyn6tDF#uHnB`FyN z83`o?IR!NpB{c&-69XL`gD4jdE5G!8Ia#UuQj&@)=30u6j2=l!={(Ujvb1qXoOC=K9sn*i4jwhm-yr}C z0Dy~k7wms)IJkKD1cY~eN>l(GJY0MNTzq_d0)l@A-E~m|@Cj(R#R+L4#*cY?=_H~v z=uI+9c~$)S8JwCwZ%L|}#+>p=-B~l<<>3E!{Ezuv2Z$O6fQyTRkAshk_g}C$)VMU_ zc(mM)d3hklr_oy~rTu@=0CK#$fz)@1j{v7(`wB@?EJ-}%?A6zwl9G%JhA5?mMy(ZR z6d~o-k_en?qNX3P^1W(AqF|dYT$L0gOn|H&mT3#tIU0(&_kmLfBY-LSf)S-=w5se5I(PPd+r#a%sy%j$>xY4{g}CFqb5%CswVSfSNXMR-L&;;(N0RyEO!j1zd+b@G^js(sVXLC{vr~ zMUKGB%!b6p*2gHuVB}<~wM-tvoXdhv%wrxX=9L8%EHn`-qtfD7KcoB z8P&>~;iDK=CMqBnSQ}0m!Y>b>sBuB5< z?oIZx_O-MydW+3Sz;)=v-YSi4i1jui1RwkEh`4AGgO|-{)Nm9&Ui75kQMIF7wqeV< ze*x!NKEGK^V>?w6_)I-Ck6zKCpe;euX3Q`TOlbR83tTi(k^kIDADB6}@qS^e`@F zym9!RjC7U-$6Phmj`7Ot_rikI&2X$2uR3>7JprYUxjvg@LQS%3ZkI(?Kom-~O-m&g zI79MXu)&d6rg(mc#_+@j+hnV;$7n(K*6%)zmT29}BT!?~kaiB=H`7vh#{VpEjaXwf zNGy^wS1eZ57gVkmN=B)O$yl<{e zZ~hn&g8KB4xjc|6+UU9s*G%7 zMJSmD`rQmqG>y{0F%TZLXcogho82v@_Y4eEGUA837;Q4UP8e<`Ff(1}@^^&rbgI^w zsgbs{&a>g;`ZIEm-QADN(53?f1+e%<*NumqZYstJXX$=%i)f;19kR$2%@?flW%A2q4mh)DnZgkqcI4W{xUkAA5BM9!P2#a)c3Zm@8+ zVjx08jRQGG>GF#z<7;AdjfcPaJCE0v{=++6hUZK^?1=T0~5{#+EH=J#}+h^BA zw{-`zzdN*d!*+kArXaY5@P+LwSkAy^nb{W#Gl5sHXpzI>tYMdyKBQ5*O`8NF{VPP( z8(N$_4k=$O^17d;Sf`&1Aa-;81?Wj86%3QM*qW<}(24r=hvMBY=IGlyc-i{AR=)fN z{P@FSM#Jm=@!s1MOTyl^e%&@py{`C{mqJ1)GBO^LB{{79QxlDJg8P~gZm-pf`Bbh} zj@8w!Ma<=O^k8`_7q6lMtF5MxDkZoFS=K~h>~m(?|DCtBE2x=rBTuuum0 z`als3*8^AnQ`EY5(}a>%FAIgF?)!-~{CNguAE_vTeM`ODiDsgyA;nEnSa3ARNa*l^ z!sm{q7w2vBGjeYPq9yWqgUbNN;U?M_0?!MG`n2JH0UkQ6!F0sR-+%?`REjtHtQ1l| z^m3?4c|zThxLEwGZc|GnHy^OdgRsbrI2Dk8h<+_KrFn^9cg7;{aOnhRpTj*%F+E&i zv8Yy>RjgFP-1V#xW|%cq>I}?uQd&u@sF6)vRB?OGZcwEPAtEPXKg3v_vKOs$EbR9| zV6C;=c!mgfW4wR{Zr*-E;bB3odC&FM7rY7llYz-g?`^7tiR5#K)PQKCP&V3FreSo4 z%Kc^CefhVdw)~s4;-p%D8`pgkLlP_DM8;RiBZGkNAFf*c{eMp8IqYvthve9;-&?`> z`yIRq3?D8Rj}z`-KNw7Ma-i`dINaHhoe=XU&wgQzHRp(B-aHKFw@4BD6{pluPD@_5E>18a3p`&A5LmJdAc>48I`H|;~uv?k3e_k{re`9B9%MyZC<_3 zT2qfn*EOB>wiyr12U0P^mMWb5u2->t*1jn}XaA6!7wm8OuFp{Pzmt9lAruxM=}&RO z+LkgN@t?Cz=QhxMBgv2B1H5>D-l!zb23GWHetu>C&aVmn{*QPoaDiZLoz>OKd@?T2 za>}=*oYc}!aVGT1X->>l#&g_@rVmO@55);qZ@%v={SNwJJQA$1L1I@<*5}y$KY3Y} zFjWdvE%=}1v2AY%b`*|l?i4RTgW3{Za^ogzJl4WTX6YW27L7Vfada_kLiH4Zp{EW6 z{8N2Ewh%!QzDS8O`fVQftyL2DkXO>7wddb4I=x&{zb$g8WOcS_zfw7Mzusm%zPF`; z-S4D26Mi(r5jIn(OSOxsk@em~8dzvq-!h?zJSa6|e4LrOECtoCNg_a!F10T^ai4N) z132q$?rCoc6P(>^ItyKPoACqU zKcqb07?Yk+OMlG@3C*>3krj(yb`Y;1U{YzzSe}!M`<>%c{`^e#zsbt^Z&^oflu)yc z2^0@=5NZ^&?Js}fGu7qaClpUc`n_~$%A4_*v1!-+AmBuoj`RD|Z1?GqIAEtM=Iqa6 z&BQ>=r_+%cMKk)K;96JM^nYX}v-DFapwug-X|kjdrev&-$JvSU`aJz!T3`unSe*uzb%CmPiwUmj$=-DeNAtDq`l2>j zv6{Quwvl8q>B?BoS(u*w(vN9djrfqBj;ziEEE|E{N)5Hq(bchSq2QlHFpqoCqRNch z`D~dpaO`^2euXh!-~8vMIYX&&V=ArU0vNap!goTqfYs8*t3gLs+2Hbm1?~>o-l&%e`sW9gPlxjEcx2l4oHVY3m;*sI3q#w@YaooFczU0S5d`3{&-<~}1vmtr?t-M; z^6y4I=p^t!hz!@?!L+_ol@q==pRFdd*c6cd0Zs>N)qpG+ieKuh^SMx)xTOUbB_t8x zngrra>u<`>NRI(3vf~trkm5c3f{eCi$9IPN zDBg~-NtcF~wDpL5D2|VT8)Te-J>dyI^xpABBX~;jKNs+xfX5S~uZ@ZHgZ6c_{{jm5 z1Of{PaafEILK(d!JzU^J2$w>%Mpb2xK2NC>t@6|81Mp&0ggaLG+v-oa$7b|mGgtgkpRJ$~=E+g{lVjlTeWBf{*?{ad7TT!-5p{8{|n z8@8{U*Rb0p_PfFhm1&_w-(`Q_OO@ihUo?y#@W8!}p|7}P?Dd>(;mThC z>J9_W6y?00zA=1f^8>^3^;zVhZt?58Al~_t6!4MkqYHBT3O8D=p`T9+TDn@;#If!FTdS?VBU;v^x9Mk zD43ZkODZx~HbKD=Bob81kM6=)|Hb-q(tHBKBNY>IUM-`{Vgg5{g-V^va?WWgb7pkh z7Qp+9#*Y+?0Uv*xT8V~)^o^}XskOFJv@z84pRC@Ib=zxu#2Z;z z%@;H?L#*6diD=ksqt0l*EeJpVmq5l`+Hg*Uv}mOAqYTcJ1*Ep$>7R&KFW#H+v&P8gYxlAOYQ0Bd zL08D6hj@b0Acy0(r^Z>;@C!}Wp}zEq%kGoe|KG4%A0@R=|u z&*M03WKjYGCeWT+;JaRAN(C;=Y*pA5+KE{uyF830Q9VX5Pqaq!X)hsMm61guuELE~ z>BnRxOfqs26J8R$l4&J6#+*_osJCXOu@Ap+V>~OxSWRo(J)YIhm^tc2SN7c7t@qU< zVp4Te6Xc($=ES56>hd1^k8eoDgk7mz>F72z@Jw|#ZXAk!yiY@9f^-gXN-faSk9I9{K(1xdOGyfZ7l zP4{^9AjP=L*=Lvid9K8s7=sxNQAk2k*ICp>Ir;jEbb#nw22xZ~Q z#9FDomb%_PUX!V-=31qxOEOL+%vwTPJz!l}mB8q_4=Jtq-sjg8fOO0oWGa|7UDtM1 z9zQ#3YD`2~1MjW0w!>q%^kUyuncCQOk6`cD&G?MMMpPzcCR|>$=1<}Oer*7;0CuUVHG+bV@i{_m)L9-FWadN8I3Y__VfW?d8FSn8r zns0QXSrY5p4LG`Wu=?CUIzF8aYn|R`HGTCIMuw0Ig%Rf9FP=0xh&Za!Ri@fgqWf51 z{tlP;hT|PL1Pt>KJ(r-pTArjunXSFqil(!8dC4+HF_^u_yOR3x9`_?55Ay= zZy&E%SmEFR`D)Q7L?`;hoyd{WVAI5Q>s)MtFR3<2uSuo0G>Of3RjKKcGGOwaFJH~b znzeulg9@sqIMs>g*xp==4hr`*Il>Wi{5tR(L_Abs3mG4jgyw8ioOgqGF=g%dCbc@%% zS_6rj;I0em9pPkfIE62iU9k=!`t161$WLwR>Px z&t_xm`esQeffMG@c2#~yd(lEeJvyCRoxlAEatG2Z_3MxC$#30*D?gLE@x;Zp#}*>n zVhoWt9+6hM6#OcH)yz`*c!^4qq=PN1oL%-fTcKJ2wq+@2GA4(ZkJ8}DP~>#L0j%-! zWMz3D;Q+1WPDrvL zm2q@$wDC+auTf-WkZuk80rx96GLH_hX!7)7l)1w;?wuq=&zPVTIB=8C`Ds-`wAR0n zKmUNn^{xUD^>goBmWp?G<>j>vOaSX<74yEj_XnF9MPpZIid0%Me*Og{(kqE0?S$r` zn|*9e{sZzArOG`Z@6bNF)=^(JHL*SNPTfh(Qw+la)yqq2J`$E+4b7z(s!5bC66)KEYOME`el6 zga*Mjn8zANT+$?M-arx=Fn3q7filhlch?20TDXrQ>BHEJ6C~#Jxnj@|GM#J$BcGR< z-s@7jBXL`Ih6b_msztT{E@d(wzM^yiwL^4ZL*%1kWyuJeYoR}cr`sB1LIfWkKC7L4 z`~9QcUx2r?`7veCD`+XE6+aJH-_OtEq>cM1bYC$d4dXQ3eGxZj_>ddLz>ec_Ok+@G zhg$OZl)CoC^SwJ05;4f*od3o1nfxO0XAoAwqP(Zo&T`EuG>>sGr-^#WSMNaSvPNJn z64$FEiPqFCbXBeqV=ooombYd zDrx-Q&1b}`H1+Rf7MFq=8DLD$w`NF$-1#+#>=cZI_7Q*>6Ybf+TCdYTD#Ijp*qq@` zI!B?!B#3fFQVnRn1fzo=n#c~fSS7#++gc}g?X9`Xd@P+up#1lQjKteh+ZLP8i(J?5 z-Q!|+ik$_qH?@9|`XZAj%b!>|aM2--GRHMbgJ-eNlFv6dCxnumq`@=@ZF zZTbi0lkLP{W1T3=i89$n7nr|$*yERtv@eDlhDj*0?Q`d8cyO~O6%xLAGSWaL;6m_gfJ~f_%iBP^Q#)i)`ruNaw@*l_Eq6s!!h^?|L%;f9cwvBDeGawvCBY;ue-4lW!Tv zh|Mh_G-k0@4g}l!a<%k!fBekHErsUORrFpSWGbUx(|_6yuxcdlZ@|X2c$K&~(7g4@ zj#5)YDDWM)fv$4tT?VY;={^1ZeJVn9+soKzuuiB*(xzZSPPUc7V(u{ z0zW58ty1CR!ih+eF742gRcu{rGJPvTV10XhWOk#%`wb4R^dV%oDMgn8xfQN6u(dbm z8B-1&b(ai7NrReUK1sH3` zz-O4>5B;qpq4z$uJ}s9{{P#2-;~7qyIXKJ7sRE%;e=nN|Aa48%c4iZZ5LZn|rIKb* zZI?4F26M}@@Wsk0H%0j*fdKZ#&zJ@tv<|!;4xX&D3z9iR=_9g4@=3!AznGNP|G^P3_VEiF?!Csu40 z$6)$;Dw&eZ%a`?w<1I2*jRV5uox?Q0?>0^MRw)~5ejzJj!l%x+9&$D;7II^wD}z+> zx>o@$%wts%4;UwBAJyuRiZoBjk_`@*8``e>i=iXDM9cGh|s zo#QK4@9%!)o+-!G;$z|<%%q)2^PC5z$`XS$X4|5+7}oVn-b?;@Y37)Poo2l?3auV;y?ey+ z_;I1h*-{qO9IAux#=xKb^4){Qm^0lcH5CduB03BwqW3r#c$5!&USpuLqq(ES$;q{@w3o35}6xxa~4k-w|K?R?Xe&3FT4&)|K| z_D?=FZv0UdCPZ+Bt-n6WREfeC5oDPeew zdToOu7(gSN{Z=2P6SO1fGWLrAldC7zO=MD% z`pom0%rYzo5W9@tPHy~nhHEqaRtM!=VA>S5lVN?WrONvmM# znkXjbTY>3_uObg!nKUca^-qsO*u)U}(dF;caR})|E5MMU9PI6|DcnO~&+{4U_EURJ zF8o_E^vV>y$0nC*+3;WepWruFeH_*9M__1g;Gb{fVn z%W0Zb7e!Xrx}LPIl`TXauvqin`wpkB>=Wx;v^<4|-;WXJxAn8_G@brlXxE9cT9BZf z(gJCt--)(tvoUNWagNgaJ~#J?A;Vn_)yDhv-m}f)3`|uxEI<lV}S+i>}y*fr5ZFaQ3pt5PX@TIbng*zSvW zSg{9TO2)73+q;ICXbVLf>`g0}ru-?sYW-|T<9x+)LNN5;CzdApBmqc2xuDuZ1p1>N`~Fq9q89(t{k-?m z?h^ohcTf<09Ul7K`)yB+;MqflaNDXE-3|?G=d6Mj_wuB!Ur+vgM z_^FbH!di*zmA$|SR#wniMX(;sag3*5%H@_`*Q&ApZl z^11b?pfpgSAucIxD7x4wDry5FJw7tQuPOc*n^K6H?iUWlK;{>-oa4)`8C}U*VFpAQ z;~nPj8uE2?Ow7`0asW?5VF;p2iTd6X~^blrW2UpyNor$OlC3 z3=j|@h8nEiZw_q%H=ygQe+-`IJgeJRb)+ES(x{k|TW1e*eMy#_{8oCanpm{;leid! z<>Ql~QD=KM89w(u9T(*fEYGVO&0qQI7X**|WNhD)^thTFQ+*u%0e~Aw-DK{#RcAj+ zFmmExvknDf>MQ4SKN$1H@EN=fYTz&my8fI^E$~i}A%Bo~^N%KZcxP?xDao7_V+?nF;2E~JNt$gC)7?UAr&gIEcqOtu}D9B(%VlC_wtHzaCM z6tf~brTfxGdLjqp&W^aOtRX7 zzPu3q*~~|T<1&xR?dQR#d$Td@iy898w{N)U8qEsWO$rd%t z7`5OR06$&%leP{w3$tSG<`Gm#sX}PJi6IlG@Bm+n$gNiNfs%3q-=OG5OS@hD>W*=z zZi0U^sSaUm_4kL}inh*lExIx2+b#IP#3EITq~Nh(!Ka|0pfDdXu?Dt~830gJEuCcms?boI`&=;do06^`Q|@^O!7tc7MkztmBd6CIqB<(jsJ(~VkeCu%yypCbBB{DU z(fAi!Z}TWp^PAmjbi2AghB#8t?wdgNAO6_xnEG-xM_!uitC%f=RxwH60^l^!+o8mh zR)`3YH-okdc?|zWpc?BK{G2T1%x=QUeg5%LOI2nZjXzz+TzDr|a`)ZvQF^V&!=IEb z@BP-d{v_;3Z8IQaG*mK?Zq5kb)gfP^M$_;e%tWUbF1yLc$471*L(ODexea3XIRba} zP)L!?81j9L4W;Ukj0MwEs@#(!to6`_7WCQ59zp%BA3~5C6-D7{Cs4ihWGlb++u(5> zEOx>`?TPN2HF@pAUt1u^p9)W;KgVcua<0YXRnY|6hpy(HPr9au_NaJ(P4W{bOCcA* zF;*bcBsIrph)aRbq-wHwqD0GgDD*ix=ZO!EG!M?tq~N*W7u+eWOF*dnB-CB&8Q-q( zVvsHO?m_4N#x z>lu%sjZ5=vo!KvHt#4Y1bo5{GvAFr3hd1v`4Pw|@lWdzUv=7^K96a#E-jXIhcYy?{q|ui{1Igb-n1uf1B*B)(o6=1k`I!m>2HywqC+DnjZF@Xnnj< zCkO%j(34Rd*7eFTh#_d6SGO5nSZMjj*oAMLqTPh#w^<;o_uY>?NVunIV@J^*-FBbE zy!Q2MYPczFUlNof8vuY+%voSeIqIK+*UO|1!D?>!qxSP5l}?Y4f`^>;}?6v7=C2^yR(;sH(+RucUrUxYfuOq&ZN@pz%v$rm0)3O+egDQ&z(vw_2 zp^MMS#?aYRASXAB)%6^a4xTf$lpZkvrt8QRyI$;FO^2gE%&EPe3eM#ERR&oXGAzM_ z65@NdpBU;3luLpjhU0Kq0hw}BI7?dg%R_29TxTR6E3Ah-08t~ZaZQRVD}MdCDnkvp z`I#sBup;5-Z0gHY!I{sE$h%dco!u=F@8;X;<*CAFA}tnh#VFazJcD#C3N z)YlFtVRkfbr4jOENp+h~jl-Fz*q=Iq#MUUx&H{_iDSfNUo$*=hWETsAlkk;r-JcRg z>^ZvEx-~4Mv5knwNDSw3cU@W_l&TC&Dl(lEQ`KFSz7Th86}F^=PsUq-LfwrNEK5y_ z4MdEiq*~_mOBs1qU+x*b;d*mDY(7-~qwyIPDXq^JcijZzi&70neb1H z_F<@Q>qUNFbThL@$em82W12Uh9gtgP_rkYz%IXw~W6T7;?jxjuiy zKPV8aQIFbuG(o`j8Rf8^B&43^xXNcd#hLfBEXmk)idJ4->|YK9-vd96vl!WCl4unO z)~aV%B?=Ly`GCw0)#(MB*oc0C%L`;CuRp5`Hczp>Op=fNetT}LBl0W8RS@0@`bw)# zSN6eA`#e=}7v~TBQ>b96V;zn>0oQ@Sep!+cUQrej=U?!G!2dn^dtmGzxXEs6%fAr) z3AcFC9RKw7(3vDZbbK=*Yfk5`>#wr_sSN;!2OTrWY literal 0 HcmV?d00001 diff --git a/ctf/docs/grapple.jpg b/ctf/docs/grapple.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e6057fbbcfbe86e4cd76d96c015508c7b2c4378 GIT binary patch literal 17631 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 S%;46j=@lm@xmO)Mk^kB6Q~jj? literal 0 HcmV?d00001 diff --git a/ctf/docs/layout.jpg b/ctf/docs/layout.jpg new file mode 100644 index 0000000000000000000000000000000000000000..64bd9193c3d0cc2605bdbd7aaa197fafcdbbfa7f GIT binary patch literal 36882 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 XTXu>HL~#Gpe$SAV3Qw5G=*5 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

_6~aGY{QcQ;ey( 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 z zn9pkP0;;?d!0!n%FNx+7WLVIB&(;8D@h&9DS84Immq1Ixu1Ria z5ArWrkquxzQ2#Z8D3KttLEX1~CgowH&S<|I=wdviNV|%Uwq0ZULE>P2KW*wUZ~0UN+vPFOh#acmVqot6(Hy21%gp z1L8zcf z3!&=7zVU%6=lPto3oA482VZ9pcqia0EqodrnJag$GRclj<1PS<4Inp|uX=Kvuk*UX zL#=g}+-^0!L{5W$4+1=_3ZzA*@@1!@leX6j0E8Y0%qZ?Jo=xeujh%kIH05%7Y(V9zFXck+h|%q(!q2>`un7)Qi0o8-J9G_2h!8A zf@*i3{(0?bx1#=8=RXdr?z6Iw07V8+R*H%bxThf6mE7-;zcn@YeQG~vtoGj2hP%1V zmdB|0!e?pC2;-JB4lTU1051b2q+i%)S!lyZ5>EH-esE z5cyYA8rRwXZN&+4ZiQF*spG8yUQ5%@kXv@|p!R)B zWaUXQ2P!dpXlZGB$@e~hu#hkP*GaYRPBAzxwG3IYeLJl<1K+men;-|Ql0OE}O#W5J zqqo_0KorufR_pI4Uz+P0mLj&W#I@X1K9O**2&a^1oGEz^U9?~?b|j( zxY63KHxAQdIir(%{S(Jdy|6T8=TA?!FTD;~)W}^SMXr#b`|-%P&D;zC&LZOn0p4($ z$}BnB0qXe}kk_JCmcN%Sr+%HJes`&^-ktU-b^E(HpI|Zq53nA3N?7s-+GUp8|N8Wq z9Qa=Tqj44Db1B~J2%pbQzxTYDNtJawIpnpT8rxD0N$JQ@!0iwdYk=x zYdUFa0kbrK$*`z8wLCOE`KEKK@Two;qt}rKzQ{Za`I#2J*_WG(|LP5FK5)*ZKm|QW zb-{j6wfk^*GG2-0ZKvd_$_6M)zUHix<}vR@7sxmVur!`(?Mmg}XtKRDm$vFL_SfTb zBRhG@&GXB=-mPV;u6gF}hZi@_DejYbF=;{t8GIy~#SG1x3Ah9XoUr)dX7&yoxGdGY zYOjw=9{rf#II%t+of4ofn+6|tb_S2PJtEG{A_5!$HzVXVixAGzpGyDXBn< zxLM%hXH*1}55y`E^fb)sE!$}Q*j?o3zPF&m_o{1mff&WM+g%IY3q~l+^n?o39gw44 zX;SkA3f;7_FKbV4ekdCFK)J>CI7NcIT)g|zrQ)rh*4uuh{-WYXfR9b>h1$FDlO1%# zk}q#$eDP;5&LIYet7DUK?0>(|7gTx*-P$W=1ZPrW?OR7Ts4q7T#xpK-e%X+Gwm9z` zgq#{hi0}{YRi+LCTdfzpdUL;!Kgr#)8J`Imd-|!S*<*AnwD8%5&4{JA{)!xpPcG}U zzMZnCj#$OnO`Y2td{>tXw^kuDpJ_82olm}uxoq`=ft_PvUzNhXoeukM7q)vW?3VQr z_#`=v%nPRj)TWEcg~M4JgV$p=`e*8P55fRs`1g;R=KFx^x}AB{isC-OY#q|0yUAT8 z;ko(WfIv9U^YGn|ErPS#yyr4O79d^+D4Nx7%b&6n^TmBN4E|gN5q4Ra3%GTbasoq@ zEZ-56`^R6ce>iO&K%`Zf?I9K;k^10@T z=U@Lhzz)p-Zu&%Czp6CNua10EZSjk`l zXg0VljH1e-+7W1~^bB4s`AF;0t4IpJ2&iEk$R^UbNjC=Ba1bV5g`CxOp3-of!@dA$ z`$53^K;S{3u^65M;0YD_25Tom25BWfl7E{JR0!Hmg=4E)lCR4Ii?ZY>NshB7EI`H} zCCB0Pt1}>h393Rl^<*+wfuGEKoE&|SaI%6|_6KCmo{%iriK!?538$V5N{|iTy>>+u z^`0)Cl6Gtd4nIj}TJR$#sq$n1HVD{abr$)jaQ%&YaY_J8 zVAnMW#azzM$E9guni%+2o()c4m5E5UC3{WT4K#IxAm zHF2=oGg2$u6n6Nj__uJvNk<$}lxi|~MADt;pQ$R7)+M(`1je^Vj)f|>bRS;7vS5Ab zZPVv5C3{aHPE&Nur^S+h{@C9p2_Y4%wr_TtwldRb1uYq}OI5WIM8_n&DOJ{$pkl^Z z)!92NZ;h)#gjBU}O+yO57*yg@i9pyoZQ z7j-ne+C1ebOkkc3dAj>TF#8er>TymQFzY=TtTY z5G`*@C<=+H56lH$bTqpwN-FHmbGssWUexu|&$U?X9bq=03xyHS`{B9g&)>T!HD%}I z70v)0o3dyqEByODsGOiseu8LmX@XFZGs8-EvoYQfD(Fe+gO)dbGXcaK2Pvesm#qrE z>D9%_QfY+N3ks?=_1yt?u`>SWpZwIELYrJ!J{~fDdcpsE+2to&nSQW-!JAsVxvzuA zQZFPRr?1%(|6|~vTeBFl9bNS&bcR1pjPW$;ptWny^f+F$2ZnrSQAc)HI5wB z+d&Q>4aNC3^B*r#S`Lc6l+hd)jiQ>jzx18$UPft&^$1rp@%oB%={5Lwho=nP+<<>i zTc62OhEiqsuJ|d)$N7HcQfOJX7?y2WuCK`!dg8aPJ3zoGv>ojJ{8Sb9QpZ0_H1Ah> z;guh6Pg$#{+x;s9>|?+6NSTM}ZqVz%I{z^n_ + + + + +Quake II Capture The Flag User Manual + + + +

+ + + + + + + + + +
   
+ + + +

Quake + II Capture the Flag User Manual

+

Table + Of Contents

+


+
This document + Copyright ©1998 by id Software.
+
+

+
+

+

Introduction

+

Quake II Capture The Flag (Q2CTF) + is a multiplayer addon for Quake2 that features a simple + set of rules for team based play. It features five unique + maps and special powerups to enhance and make the + gameplay more exciting.

+

Q2CTF requires the full retail + version of Quake II installed in order to play. Once + installed, you simple need to connect to a Quake2 game + server that is running the Q2CTF addon.

+

Rules of the Game

+

Capture the Flag is a multiplayer + addon for Quake2 that features a simple set of rules for + team based play.

+

The basic rules are:

+
    +
  • When connecting to the server, + you join one of two teams: the Red team and the + Blue team
  • +
  • Each team has a base with a + flag positioned in it.
  • +
  • The object of the game is to + infiltrate the enemy base, take their flag and + bring it back to your base.
  • +
  • In order to successfully + complete a capture, you must be carrying the + enemy flag and touch your flag while carrying it + at your base.
  • +
+

When playing Q2CTF, there are + several different indicators on your Heads Up Display + (HUD) that show the status of the game. Learning to + interpret the information on this display is important in + learning how to play Q2CTF.

+

+

Joining a game

+

When you connect to a Q2CTF server, + you may be presented with the option as to which team you + join. You'll be present with a menu.

+
+ + + + +
Q2CTF Join MenuThis menu offers + you the ability to join either the Red and Blue + teams, select a chase camera (watch other players + as they play), see the credits for the Q2CTF + addon or Request that the server switch to Match + Mode.

Navigating + the menu assumes that you haven't change the + default keys in Quake2. The following keys are + used when navigating the menu:

+
    +
  • <]> is + used to move to the next menu selection
  • +
  • <[> is + used to move to the previous menu + selection
  • +
  • <ENTER> + is used to make a menu selection
  • +
  • <ESC> + will remove the menu (in this case, + you'll be left in an "observer" + mode and can freely move around the map, + but you can not interact with the game)
  • +
  • <TAB> + will recall the menu if you've cleared + it.
  • +
+
+

Note that if you + have change any of the key binds for those keys, the menu + may not work properly. The default bindings for these + keys are (from Quake2's default.cfg):

+

bind TAB inven
+ bind ENTER invuse
+ bind [ invprev
+ bind ] invnext
+ bind ESCAPE togglemenu

+

If you have changed those keys, you + will have to duplicate those bindings for other keys. For + example, if you wanted the up and down arrows to navigate + the menu, you could bind it like so:

+

bind UPARROW invprev
+ bind DOWNARROW invnext

+
+
Join Red Team
+
This joins you to the Red team + and places you into the game starting at the Red + base. You are immediately ready to go and start + playing.
+
 
+
Join Blue Team
+
This joins you to the Blue + team and places you into the game starting at the + Blue base. You are immediately ready to go and + start playing.
+
 
+
Chase Camera
+
This activates a chase camera + and lets you watch players as they play. The + camera will automatically be placed behind + someone as they are playing. If you wish to + change the camera to follow someone else, use the + <[> and <]> keys to change the player + you are trailing (same keys as the menu + selection). To get out of Chase Camera mode, hit + <TAB> to bring up the menu again and select + "Leave Chase Camera."
+
 
+
Credits
+
This shows a simple screen + showing the development credits for the Q2CTF + Addon.
+
 
+
Request Match
+
If the server has match + capability, this will begin an election that + players may vote on in order to switch the server + to match (competition mode).
+
 
+
+

+

Using the grapple

+

One of the exciting features of the + Q2CTF is a new weapon/tool called the Grapple. The + grapple lets you get to parts of the level that were + previously inaccessible and can be used as a great tool + to increase your mobility. It can also be used as a + weapon, but it only does the same damage as the blaster, + so it's not very effective.

+
+ + + + +
The grapple is a + fun addition to Q2CTF and learning how to use is + required for effective CTF play.

To use the grapple, you + must bind a key or mouse action to it. For + example, to bind it to you right mouse button, + you would use:

+

bind MOUSE2 + "use grapple"

+

This would cause + the grapple to be selected whenever the right + mouse button was hit.

+
+

You can use the + grapple in a few ways:

+
    +
  • Select the grapple, point it + where you want to go and press and HOLD the fire + button down. The grapple will launch and connect + to the spot you fired and once contact is made, + you will be pulled to the spot the grapple + connected. Once at your destination, let go of + the fire button to release the grapple.
  • +
  • You can also hang from the + ceiling or wall with the grapple. You would go + there by performing a normal grapple maneuver, + but once you get to your destination, do not + release the fire button. Instead, while holding + down the fire button, change to a different + weapon in midair. Once the change is completed, + let go of the fire button and you'll be left + hanging from the ceiling. When you want to drop + off the ceiling, switch back to the grapple and + make sure your fire button is not held down. + You'll be released and will fall from your + position.
  • +
+


+

+

Special Powerups

+

There are four special TECH + powerups in Q2CTF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ Disruptor Shield
+
+

This TECH + Powerup causes the holder to have a protective + shield and reduces the damage from all attackers + to half. Its an effective tool when attacking + enemy installations.

+
 
+
 
+
+

+ Power Amplifier
+
+

This TECH Powerup causes + the holder to power up all his attacks to double + the damage normally delivered. This an excellent + tool for defending your base installation.

+
  
+

+ Time Accelerator
+
+

This TECH Powerup + accelerates weapon time for the holder. All his + weapons will operate double speed in reload time, + switching, etc. This is also an excellent tool + for defense, since the holder can deliver twice + as much damage in the same amount of time.

+
+

+ AutoDoc
+
+

This TECH Powerup + automatically heals and increases a players + health and armor (up to a maximum of 150 for + each). The player will slow regain health and + armor over time. This is an excellent tool for + people carrying the enemy flag since the player + will constantly regain health and armor, making + him much tougher to kill and get the flag back + from.
+

+
  
+
+
 
+
 
+
+

+

Communicating With + Your Team

+

A very important part of any + multiplayer team based game is communication. Letting + your teammates know where you are and what you are doing + is an essential part of teamwork.

+

Q2CTF has a basic team + communication called "say_team" or + "messagemode2". It is like the regular + communication of Quake2, but you need to bind a key in + order to use it. I use the <R> key myself, since + its right beside the normal <T> key used for + communication. To bind it, use the following:

+

bind r + "messagemode2"

+

When you want to say a message to + your team, just hit <R> and type it in. This image + shows me telling what I'm doing to my team:

+

(Zoid): At base, defending

+

Note the parenthesis around my + name. This indicates it is a team message and only people + on your team saw it.

+

Now, normally I don't type a lot of + my team messages, I have several standard messages I use + to communicate with my teammates. They are bound to + several keys like so:

+

bind z "say_team Base + secure"
+ bind x "say_team Base overrun! Recover"
+ bind c "say_team Incoming!"
+ bind v "say_team I'm going offensive"
+ bind g "say_team Going to base"
+ bind b "say_team At base, defending"

+

So when I want to say "At + base, defending" to my teammates, I just hit my + <B> key to do so. This is much faster than typing + it out all the time.

+

Q2CTF also features some advanced + options for communicating to your teammates. This feature + is called "auto macros". It lets you configure + generic messages that will automatically be filled in as + necessary. Here's an example.

+

bind f "say_team I'm %L, + %H and %A, and have %T."

+

That looks kind of cryptic, but + here's what happens when I hit the F key I bound that + say_team to:

+

+

What this means is each of the % + macros in the say_team are automatically changed into + something specific to the situation. In this case, the %L + changes to a description of my current location, %H + changes to how much health I have, %A changes to indicate + how much armor I have and %T changes to indicate what + TECH Powerup I'm carrying.

+

Here is a table of all the + different % automacros and what they do.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+
Team Message Auto Macros
+
%LThis will be substituted + with your current location in the map. Some + common examples are:
+ "near the Red Flag"
+ "above the Red Armor"
+ "near the Blue Railgun"
  
%AThis will be substituted + with what ever armor you are current carrying. + Some common examples are:
+ "Power Shield with 100 cells"
+ "50 units of Yellow Armor"
+ "Power Shield with 154 cells and 108 units + of Red Armor"
+ "no armor"
  
%HThis will be substituted + with how much health you current have. Some + common examples are:
+ "106 health"
  
%TThis will be substituted + with the name of the TECH powerup you are + holding. Some common examples are:
+ "the Disruptor Shield"
+ "the Power Amplifier"
+ "no powerup"
  
%WThis is substituted with + the current weapon you are using. Some common + examples are:
+ "Railgun"
+ "Rocket Launcher"
+ "Grapple"
  
%NThis will be replaced with + a list of names of the people who are currently + in your visual range, or area. Some common + examples are:
+ "Zoid, Disruptor and Hellrot"
+ ">BC>Casey and >BC>Mutha"
+ "no one"
+

+

Scoring

+

Q2CTF features many different score + bonus based on actions that result in a flag capture, + defense of the flag and your flag carrier and other + bonuses.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Q2CTF Scoring
+
Fragging + enemy player One point
   
Fragging + emery player within your base Two points.
+
One Point for + the frag, one point for the base defense.
   
Fragging + enemy player within sight of your flag carrier Two points.
+
One point for + the frag, one point for defending your flag + carrier.
   
Fragging + enemy player who has hurt your flag carrier Three points.
+
One point for + the frag, two points for defending your flag + carrier against an aggressive enemy.
   
Fragging + enemy flag carrier Three points.
+
One point for + the frag, two points for fragging the enemy flag + carrier.
   
Returning + your flag (after enemy player has lost it) One point.
   
Getting an + assist for returning the flag (occurs if your + flag carrier captures within a few seconds of you + returning your flag). One point.
   
Getting an + assist for fragging the enemy flag carrier + (occurs if you flag carrier captures within a few + seconds of you fragging the enemy flag carrier) One point.
   
Capturing the + enemy flag (you are the flag carrier) 15 points.
+
Everyone else + on your team gets 10 points.
+

+

Elections

+

Elections are part of the new + competition mode for Q2CTF. They also can be used for + other features by users to change maps and other options. + Elections can occur during the following situations.

+
+
Switching from normal to match + mode
+
A user may make a selection + from the menu to request that the server switch + to match mode. If this selection is chosen, an + election will begin and run for twenty seconds. + If the election is won, the server will reset + into match mode; all players will be cleared and + placed at the join menu.
+
Changing map (warp)
+
If a user makes a map change + request (by using the "warp <map + name>" command) an election is started. + If the election is won, the game will enter the + intermission and then automatically change to the + new map.
+
Requesting to become an admin
+
A user can request to become + an administrator by typing 'admin' at the + console. An election will begin and if + successful, the user will gain access to the + admin menu. From here, the new admin can change + game settings, switch to match mode and warp to + different levels.
+
+

To vote on an election, simply type + "yes" in the console. Votes are anonymous. If + enough people have voted (its server configurable, the + needed amount of votes is displayed), the election will + be won and the action requested will happen.

+

Competition mode

+

Q2CTF features a + tournament/competition mode that allows organized setup + of matches between two teams. The competition mode (once + it has been activated by an admin or by an election) + works in three stages:

+
    +
  1. When competition mode is + activated, the game enters the setup phase. This + where the players pick their teams, discuss + strategies and prepare for the match. Players can + move around the level, but can't pick up any + items or damage any enemy players. If the server + isn't dedicated to match play, there is a timer + in this phase that determines how long players + have to set up their game. If they don't set it + up in the needed period of time, the server will + reset back to normal play. When the players have + established their teams, each player must enter + 'ready' to commit to the game. Once everyone has + committed, the game enters phase two.
  2. +
  3. In this phase, everyone has + committed and the game is about to begin. There + is usually a twenty second countdown before the + match actually starts. During this time, any + player may enter 'notready' at the console to + abort the countdown. After the countdown + completes, phase three begins.
  4. +
  5. At the point the match is + begin. Everyone is respawned (sometimes with a + small two or three second delay to even out the + spawns in the bases, if everyone respawned, + telefrags and overflows could occur) in their + base and the match begins. Players are also + assigned their ghost codes which can be used to + re-enter the game in case of a undesired + disconnection.
  6. +
+

Phase three continues until the + match is completed (the default is a twenty minute + match), after which the final scores are displayed and + the server resets back to phase one.

+

Ghost Codes

+

Ghost codes is a special code given + to every player in the game when the match starts. This + code is used to track the players statistics and other + information and is also used to allow a player to enter a + game in case he lost connection.

+

+

As you can see in the image above, + just after the match started I was assigned a ghost code. + If I lost connection during the match, I could reconnect + to the server while the match was in progress and type + "ghost 22632" in the console and I would be + automatically put back into the game with the same team, + score and statistics I had just before I lost my + connection.

+

Statistics

+

The CTF match code also keeps + statistics of the game in progress and keeps them around + after the match has completed. The statistics may be + accessed by typing "stats" into the console. + The look like the image below:

+

+

The statistics kept for each player + are:

+
    +
  • Score
    + How many points this player has accumulated.
  • +
  • Kills
    + How many frags this player has made.
  • +
  • Death (Deaths)
    + How many times this player has died.
  • +
  • BasDf (Base Defenses)
    + How many times this player got bonus points for + defending the base
  • +
  • CarDf (Carrier Defense)
    + How many times this player got bonus points for + defending his flag carrier
  • +
  • Effcy (Efficiency)
    + How efficient this player is when fighting other + players. It's calculated as (kills * 100) / + (kills + deaths)
  • +
+

+

New Console Commands

+

The following console commands have + been added for Q2CTF.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Q2CTF Console Commands
   
Console Command and syntax Description
   
team { + red | blue } Switches to the team the + player indicates
   
id Enables HUD identification + of the player you are looking at.
   
yes Vote yes on an election.
   
no Vote no on an election + (you must just abstain). A no vote counts against + the election percentage.
   
ready Commit to the match in + match setup
   
notready Cease commitment (will + abort match countdown)
   
ghost + ghost-code Re-enter the game after + losing connection in a match (requires ghost code + that was provided)
   
admin [ + password ] Request to become an admin + by election, or with password become admin + automatically if the password matches. Once one + obtains admin status, the admin command by itself + will bring up the admin menu.
   
stats Show the statistics of any + matches recently completed or in progress.
   
warp + level Warp to a new level. It + must be one of the levels listed in the warp_list + cvar (see Server Operator section).
   
boot + player-number Kick a player off the + server. The number of the player can be + determined with the playerlist command.
   
playerlist List out the player on a + server, their connect time, player number and + status (ready/notready, or admin).
   
observer
+                             
 Leave the current game and + become and observer (this resets your score to + zero).
+

+

User admin functions

+

An admin has several function he + may perform. A user can become an admin by entering + "admin" at the console and starting an election + or by typing the admin password at the console with + "admin <password>" if a password is set. + The server operator can set an admin password using the + admin_password cvar.

+

An admin can change settings, warp + to different maps and boot players from the server. Once + you become an admin, type "admin" again at the + console to access the admin menu.

+
+ + + + +
The admin menu + (show left) has just a couple options. Settings + allows you to change game settings (shown below) + . The second option changes depending on what + state the server is in:
+
    +
  • Switch to + match mode
    + This will switch the server from regular + play to match/competition mode.
  • +
  • Force start + match
    + If the server is in match mode and + players are in the phase where they are + setting up their teams, the admin can + force the match to start regardless of + the ready status of the players.
  • +
  • Cancel match
    + This will cancel the match that's + currently in progress.
  • +
+
+


+ The Settings menu has several options to allow the admin + to configure the server on the fly (some of these + settings are also applicable to normal play mode).

+
+ + + + + + + +
The menu + pictured on the left shows the different options + that an admin can change. They are:
    +
  • Match Len
    + This controls the length of the match. + The default is a twenty minute match + time. This can be changed while a + match is in progress. For example, if a + match started that was twenty minutes + long and two minutes had already passed + (so there was eighteen minutes remaining) + and the admin changed the match length to + ten minutes, the remaining time would + automatically be changed to eight + minutes. If the admin sets a match time + less than the amount of time already + passed, the match will immediately end.
  • +
  • Match Setup Len
    + This is the length of time that players + get to setup a match before the server + will reset back into normal play. This + setting can be changed on the fly like + Match Len (if a setup is in progress).
  • +
  • Match Start Len
    + This is the length of time before the + match actually begins after everyone had + committed by typing "ready" at + the console. It should be pretty short.
  • +
  • Instant Items
    + This is the dmflag that controls whether + powerups such as the Quad and + Invulnerability are instantly used or may + be carried and activated as desired.
  • +
  • Quad Drop
    + This is the dmflag that controls whether + the quad is dropped when a player is + fragged while carrying it.
  • +
  • Instant Weapons
    + This is a new option that allowed weapons + to be switched without put away and + activation animations. The weapons switch + instantly.
  • +
  • Match Lock
    + If enabled (the default) players can not + enter a match in progress (unless the + have a ghost code). If disabled, anyone + can enter a match while it's in progress.
  • +
+
+

When you have made + your changes, simply select Apply to activate them. + Whatever you changed will be broadcast to everyone in the + game.
+

+

Server Operator Information

+

A few notes for the server + operators.

+
    +
  • You can change maps on a + server with two commands, "map" and + "gamemap". Both take a map as a + parameter. They differ in CTF in that when a + "map" command is used, all player teams + are reset (and players must either select or be + assigned to new teams), where as the + "gamemap" command will retain the same + teams after the map change.
  • +
+

The following table lists the + various Cvars that can be configured by a server + operator. You can place them in a script file with + commands like "set competition 3" and then EXEC + that config file on server startup.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Q2CTF Specific CVar variables
+
VariableDefault ValueDescription
ctf1Enables CTF play. Normally + this should be enabled, but may be disabled to + allow deathmatch play with the Grapple and TECH + Powerups
ctf_forcejoinnoneThis may be set to + "blue" or "red" to force all + people joining a server to a specific team. This + may be handy when a player who has rcon access + wants to set up teams in a specific way.
competition0This variable + controls whether or not the server can be enabled + for match play. There are four legal values:
+
+ + + + + + + + + + + + + + + + +
0This value + indicates that match play is not + available on the server and can not be + enabled by players (even with voting).
+
1When this value is + set, the server defaults to normal play + (pick up CTF games) but can be changed to + match play mode by an admin or by an + election.
+
2This value is set + by the CTF game itself and should not be + set directly. It indicates that a server + has been voted into match play and will + revert if the match setup times out.
+
3This indicates + that a server is in dedicated + match play. The server stays in match + mode at all times and does not time out + and return to normal play.
+
+
matchlock1This value controls + whether or not players can join a match in + progress (without a ghost code). Its default + value of one indicates that players can not + join a match in progress.
+ This variable may be changed by and admin with + the admin settings menu.
electpercentage66This controls how many + people out of the players on a server have to + vote in order for an election to be one. The + default is 66% which means that two thirds of the + players must vote in order for the election to + succeed.
matchtime20Specifies the length of + time, in minutes, that the match will be played + for. Twenty minutes is the default standard match + time.
+ This variable may be changed by and admin with + the admin settings menu.
matchsetuptime10Specifics the length of + time, in minutes, that the setup phase before a + match must be started (by all players committing + by entering "ready" at the console). If + this time expires, the server will reset back + into normal play.
+ This variable may be changed by and admin with + the admin settings menu.
matchstarttime20This specifies, in + seconds, the amount of time after a match has + been committed (by all players entering + "ready" in their consoles) and the + actual start of the match.
+ This variable may be changed by and admin with + the admin settings menu.
admin_passwordnoneThis variable may be set + to a password that allows someone to grain admin + access without having to be elected. The admin + would just type "admin + <password>" to gain direct access to + the admin menu, assuming the password was + correct.
+ For example, "set admin_password bob" + in a start up config file for the server would + set the password to "bob" and any + player could become an admin by typing + "admin bob" at the console.
warp_list"q2ctf1 q2ctf2 q2ctf3 + q2ctf4 q2ctf5"This variable is a space + separated list of maps that may be warped to on + the server using the warp command. Any base map + name (bsp file name) can be specified. Admins and + players who start elections for warp requests + must specific a map in this list.
+
   
+
+ + + \ No newline at end of file diff --git a/ctf/docs/say_team.gif b/ctf/docs/say_team.gif new file mode 100644 index 0000000000000000000000000000000000000000..1e0f3405fc7413d0f9aca20fc96871990a53637c GIT binary patch literal 6717 zcmWktXEYlO6ipC9(;^{aq&0#d6bYfI5yWb0#;67nTdf$iM+gzr2x7FTU8A+TzZy-e z)z)ma)z+@jp}NP%*YBNo-j8?gIq%+k-X+>v8yNaM2Fe4z1O6`v1R^6NgGQtAcszkX zaCUa4QmJ%0oxxymIGm=Yrh$Qhd-v|m&(CjeZh8@r&PFo!co`xVZmJE(qoG(76r&)D zMv9_jL>1r=BoqP{1w$cVQ3wbE29e2u&dwBPdoqdWY)`N!ni5R$MtCe1tF4XsdlH3G zP*6Z3k#IO13WbV_isnYz)4hyBov{=m`e8Alo{nWu(1G>}n_Pu^3}kzx4BHDHNrXCM zA$SEa5(=uvqPS>eF$&I55KWMQghIg-D2OBqAV2^{5C9eez<>cLFaQY#z(D{}5P(i5 zgp!P?_IL^rOE%Re8DZ@4Xo5D%2%~^UBefOaXe3ktE-C|s{FUℜ^i0)`r@np++dE zHu8Ugf7SincR3TU(*y5u*fRq?_huHlMxJncZl~AxgcjF3r*ljr8E8rZ)ILy@K!F%J zgYottZ2}052Pt3xGAIC41^^KSfWUvvXOKwAL?YePG}Oq5ip6?iFwST+356mck$4#y z3>=OU6_tTNM8RM%2=o_U%%$fRho&>A3CUiObaH4YiAp7Ud6_yp8xe_EBO{EqHVTdY z-_gHzLLgu+0nawlW?<0?+6s|qBozhsLPAM0qC_~v2nyB~1*0G!888S60{qVg0EnbN z3=F+Zq4s!r)jK`A!hDuQHj5K(huU*V7&;!l`DA3I ziBr#rOsA5#&h`pF1%Us9G5?SMGhqha1GovhHrq^Hv;sl}cNUjMg_y$9`k_tQcUn&# zO8vPt*+8{O{$_Cd>~JPwge_+tL%ZLDBIas&7q~w6qp{)2CW%|qR;r^1Wn*K)J$uo_ z^Twtx2rmMHg)S(;co<<$ux+uHk*{Ww9!-cTAGal7N2gl!0c-QW;!0JOtd%l%C%j^4 ztkyPxeH= z7~R`b&aO+FGgt<@%dHLhkcHJe3JI2m_k98Kw)cl^bPNq7g`^Dc!)$~%annxJa-2Ak z9-Tpz^1R#Rk|RLY<1jQ57v%}@(eg^JM|W)8oet^hK?{+f;x{EW(EHqATN2@*-fu%5 zt&Dj5+K~XJw6%0J6ZrO-NYf|fm!Jy6Hfg`Jh6qHtn}z42NKb>jXF)|EYedaYPtQl3 z-<~a%jC3f13*hUtt@q;@wz0LwQr8z_&sD%e5)VODy{Oq;gDk)FE-@~pKnLz(VI1wJ zE_L?qSSk72pYN>9y(>JC4#FM?kwG|QwR(gowe%~~Mfto{iB+Ba{II1`+S)L)gC|jL zp?p^WdQ$C}+j|$xrFnA)*@(Gv(rKNs@@^hqxg=35skgiUB@!2|^sFe>@w7=iP=cwo zV>vtUWoz}_fe}(u73I`n;+<77syV7Czq*5kK(F zGi>xsQcgq-idj1@8wm{^VMrSTLM}S-xD<&u6SfgL>H58QkDjvTQV!rN^6F|k-zod? z`+wVQ&g+raJAnW6Yus|+mzOw^FKx2pGR0qD@aa%m)U>Kt^W5;W+iCCBPe_6~23_t@ z44`*_;pl`tTU*d1c~DJrP56yTVrj9PT!w^8zVO#+dk4i^zTJam^si*L7GN!MUG~rY z@*z2Gfr#aAsHRLoZy~ceLE&3#TJ6ub*qV-&vU5Q9FVi)XHen5V&<2oD;CxU)h#~N9 zJjHP*XdrGv*w=HXeAADs#me$ev9*5QYUFdXYFFdWAL$UDll60MlVMAH@K~&+pFkN9bzI*gMU7|;3QAY^=>FnW;Vj{^klkJT>olL!|U*BMowsMcS@X} zBAZ`M(A1`WDl^6uCtZ5*l?1$}Nz1DDhSCSuYu5P9)uJe8f0f+e7Rb$aegda5TUy+& znAok8?!I$@FI+t`%?*TkTzrlE>3VR*cP3B!Wg`Vru8J=yIhwI7@O_n2`7_P}Td_Aa zvm*$oT8%#GGHpZ>JT%D4UdYBd^FO$xby1_Z;7s^;;TnRLZb|O)mLkCJ{M?2<$8B!I zvqb5EjTRJEsEq@tQEf1F<(;YHx>mPvPd6OZ@Ghmzv6uQ}S?^XdW8xmi^IjBt3AT1g zlxSO({XSKchPyuX=`l=Iefm^2uSU}2W6;PHo8y?F^GIw~&+|Ip7YKjrE1JAR%}x+J zdqw8wur03oBE;{-+ve8=e&^u?&)vf+GB1 z7&|QW^X0iM_^I>WQE{7D4ek*SO2!AEy`%5eLZUq+VxE((qIOG)^e43jZ~7|6gxH;+ zEY2oX#khtl)!NgSg(I%{Dh9LGy?<_+CI&VH9~lk1pPc|Q-#tgmb(n~8G~?Y_T)QGU z+UhG%RT~sDq9e-i`7?sg$lW}8fFpkJyPQJ{CQq;!5LjR);w+|HtkB=3T+waUKA7h< zeWC}%c0Zmpbc(z(SZAbpg2l2=HrR;emc6pX2t0I+vCOgRd4J$~fM`8UHv-pfy?F6u zPEbRFhg7&&Ce``c#n~cNjZt>SxJS+Mc?a4-<~#^cpsjU!7@ucL+53H*(UYP0NVmwA zQ)(A~{CknGKf0q{45Xx@!xrogIwW~_B41}ouGi%If?}q;RsFlko}z`SqNd*ln>$T! zkD3zbpZPS4XOP(?e1>_Ky=$+RZK=D9C`1bgqcBAt#TNRR0rHj%UyKWlfqtEMGiegPKkf53tJaE57oDbGz-6l9tzK#fum*hf=mB zzhtks+`R@1ep(fXTd7tK6)Ptz^Khk6g*{SJD}88*bO$%wVd# z&WbK%{1DnnQVKCW+k9CCr|>-^{b;an?f2}mmf$lY_Z9_xsu&dVk5Gxu%e7IHNxu(Y zFcDl|`0}+k`et_GqlN8=v>k#&M5*Ieygb=5ADyB8tM~ymcI`lj{Hyri-9Z{`l|$oB zy=h1S$CvG6KA1bu@zAwRf5gLbs`k1-<@G9NglDZwn-FcU$@y_%(>>!~HF=bsiW{%G zTGEV6*YtO0RSz6EnthSFi;@Qt&KPwqcmGydBotK+Y&eNXhCWdUvW0^GD+0)_Ch=j+v~M-bpPY!T2VVb&*WNhdR#5A2_%hXocm zK~L@o7!N05iZPr4FKr?H>aRefJSQqP^hU3KUA+^sIXP~{>Bckf&3j5F?q?o<(@G~u zOgo3mU4cJ+W_P;h7$G3;;)<}YF5Gm^9uI;}t4h|E*`xfRK!8N6ySJPY@r>{apexNp?w%#ZT9-D41tUDpTX^UYbQ==f3*M&tXQ0{Tfy-nlb<9Rj*Ry2J zaUjSE{#EbJ58=}*Y*Czq(_5fZoH?n-whtHne8hrq^|Y~Gp)AJ^W*+XrO}Sjo^z@4x zN0A)~0`78d&s2rqYfY~tS@D)Ra<2+dq53g zKUNHE%j<3k$@vs;_$&x$qZ?1$V>{Df0IDaa-0AXLaLpRn(8j6oir4Vj&;co@1Ih&= z!}@`A4q~7{L*-Nx!|U4T!tS*Kv-n&~J@FID=kB$hzTT+Mn=5G3J2ug(xisxEIAeEI z(cm%+U=W|`%43=?C7Hx0{iB~2Cwy+bQfV z`iz~C91JNo&w$et&XRR9cqSrPwhhceq^82iZRKHpF5Ru> zBTpq=LgJnhDcd8=xxHqMX{l7xyUcf$%%vZ2n?IN;v?2dhhFjyTPgu7P67J4ZEd7_#@m>2RXu;q?+?1@rJ+!(nx9)`wwXS)SivLF#B#GOB`oB#L^$HG5*7uCzqe zu1Go6qi`P{_Is+LtI401Yhh9$Y4%j|$F!vNp#ASmnxzHovuE8Y7t~4j0Oja_)5QTA zR|3HKQCZdrXVe2}Pb~vmN<;MlBG)n$U}w2pa14`_;uN$nb4F4n;eFEyG*%^@$rnDr z&AS#c(^#C-db z2Sb1BnD?{<4arcXX%%u`fGsk1iV_x9(S<76mvk*-Z#}i3PQ{+qp(A1m2?EECpWQ!+ zhlWk-a6aOWDuAAiT!gWv)|p*w580}oh+^*bhA^GaEs5U(um8{oCX7W2DB2W! zimW2ar`km2`bG^jM4@*QPP~kI8-*0BY+KWgF6Fxn7rKnNhd$9CEtKzbWG^ABR_w^` z6zEHXh_TyHMS$n&6?s7_Mx`T6bXNlrd;3bD!jIk9$qj`&OqUgWg;Sj7vr;XcF)dx9 zJdP-D(wZ~RS9hU|_IYzKnPyVe?^f$|aEWFdsFD}_)ii6$0r>O!*^8Ev)+Z%GZ>->M zW@X*_gp2${jO;y2nk$a}aqmRRmz%%Mqj&7o1WG-;UX;44dmM$uC=G$V{bJB~+XJr2 zn4n}W_k)HT5-Rqg)!TG|Eczjug2{KfxhkBHOE->=y?ja2$m&?c{E!Peu3hk|TabY} zvC*<2?!uione8}q;2mnhl~=TQU{!pCV*iWSVm{?WI^I%8IwI_P!k*7;!o0*u=2;NK zJ&}4TAv_n?DLZlwKl(;+JgI=BNjRCHdS7=maeXQAnTRip4r+97ZLS2eR&TLqZHfeJ z2UKmv0+giRsc>l>)w3M2-#vA!4_v~Ans7tc-3n7>I}3{aZKUi$1!4W`5A@)MRoP8m z!bZz;|5VY!b+(1?yZYI`v^^7agBj#2T4wzqI;u7%{%I_jfqCe5%c^>wq6O8VH_9^Usb5Qe7fra1ny_esKD1p<|N%Qe+9ZX&Whbs;yk zRVd9=J?*JmS|Teg@;E;o{J8|o ztvjxPIE1zq&*S3*KED6>W-0DG~$#nLH-z0C+6L}-4oq%7xc7Ofo7S8{d#%M_`TRJ zYDM4L^KGe9zaD!3xdAsNTP*(ILw(1$O4(<>46j+CZOq{wj#9dvaA+k{35-rA(>U?1 z2}>b!JTq(9MW>2v?E^1Mk_na(!fsn(H=4nBf&eBir$FM{+A28a$kXG10R`@gdh1L{(=6uF#ilLYf|i=(JDK9vBD zT25*rH!Y6w50YKw4X~W3=RwkSI0f&*JJN$| zz&G_3Dw~v#N*KOSy|cz`JbqVFGHv1m0^qx$wWx*k5Pz z6TZZ6Gqp_#a=^8=@flsHZ3*Qdwy3QR@v|)~lzjMSm;BF{yTnr6h*Fax>8mDHd4AkC z;fSLlIy%CtE(ASIUpJsQRP(O?j9#N8HTR&tM5=fczh>xtr#Sz#Km#qq zIs7d-gGNH*KjkT7oy;PB!qo@_G{Mm8TwRtPgs4h%<*b1JJvCCtl&xo=Z0cG zY{D$Z2zkct78di565#HwlN31VXIeaMD_|;Hcc!uVDZkAN*!@=w9lCDm3V)`Q?{U~- z3wi7?dh)n&qwg-k^c%xe`Ll1*uxaYIl2V;AO~)rM{XmPWquEwN~)`9IgL8= zJatjJvi0AJbVB2!Js$(1uC(ncrHCJ{`Ki~kHr4(m$mNWex=((TQ}fMdef+nPT>MEDR;tfDuM)Dprd-ucriPuLfX&(I$DjS5O>HBq{*?Jm1rMqb^WsMgz;LkmtuKKc#aqq4-%S;*(m~S%;`mkUa8~mcmsqA&Q zjQ%D>+IPkp>7^e!TLn{oF!Aeb#iJV?-*21!(myjjHxjv^ zqIS3tV%8(>)7E|=$!?T(O+o MTDYkw4*(eb2eZb1{Qv*} literal 0 HcmV?d00001 diff --git a/ctf/docs/stats.jpg b/ctf/docs/stats.jpg new file mode 100644 index 0000000000000000000000000000000000000000..982c82d50f8ab9ea83f6735c42dd6464402b47c5 GIT binary patch literal 5558 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 SvN~@Zy}GV@XL?*5;Qj?l*$9sS literal 0 HcmV?d00001 diff --git a/ctf/docs/tech2.gif b/ctf/docs/tech2.gif new file mode 100644 index 0000000000000000000000000000000000000000..3d914426b86dd78eaf9af53075efda5ba2088f7e GIT binary patch literal 1344 zcmYjQeN5DL82)XUmaWEasnUd+HLPec-TbK)t9G+y)g4f=V&{rhy^#}7Xy#2$a+_68 zN64w20z1bE2i*L?!`z(TqM$L3Da#mh69ok4=Y;5KG%m|B6YTEx*WN$gKi)ja`#jJ4 zypEg?X3Q*{3?_rWL9#FmqbLeOD2k#i%et-`hGCkfWm(~HI2MbwwY7D2b`A^-#N%<7 zOzRS*ij*Rd4xSVtA+Q9;;E={y3TH@+CNL652@J(C1Va(0)1&KdU3F@TuF9(7kR75V z3WC7%Tw*25G7Ll0G)a;KLEt!U|F15Gfw~Dq!-0$e2?GKKI25obpiw{~07ro7l)ai{sG?gDoDN=-I8}r) z&q^F4LYiktND~Z+Qv{Zfkl^nT2q#adkdRn{r;`E`>Jszgx^mw1bcTbyZ7rR>w~lvo zM_Sqg(H3tgst3Xjp9S51LiOOX8M5a1=!mB!Le_%*v2AU>W{$m(O&1y_#VtE|){sB}EY= ziQ{<|!la`KJ24E6$YN09Edly@#s_JGC0#V3QMf{45`pqK%3=tGA_M}GYyj|?mp$Hg zx6$HqMRh%_YL&9$Pp}h3H_vMj$_yjY6eI~6$A|=_aLlZXcmg4pX=&ba+3k~@9zk<+ zvd)MK$xApCP#Pj64R8XW7yve)hcL05RNEM56gn$8r^)?y^KRRY+P2@eRoiClIA!+| z_Ib>XA$AZv@q>E?=+;1o1GESr#)2>fDhc4nfENKy02}~707w9y0KoIMos?p;Y$8{Q zP+{?qzul`wG|m*scy~ufEF86b0Yi)EYWhE~yp#mLoovaUn3M7XTz++2_K|Ncl&9d0 zQeeTyt?Ncnx$^^~YO@TSTXnV|pKjm!pzo-6_jgOPO0G^TIlKDV-k-jlkhfsW#HVlN z*23}q`v<2ENx@9>%Dd70x)ZCj_vM9q{w#Df^emjTeEYNezXmAr`Hi|MMo&OXFS(za z8q=x@nY+u1Kbd%?DX;LO|I#w)No6fBH{oc`i8yiQE1x5FMTgwMV*DWc!IIzuotUkPU z^}PONTNas*pJ$wmO2Z=$pV~D;zg_Rg23yG|mzv9~TTs>{|Lt~AH&1(OalnKqZzZf-r#;*5Y{~#q@ z%4k}(XKvFq{#M1C!w#n#l=fZQJiM(b|Ffd>p7f;;4mLSUj_w%SufVT*d!~7&EDl@i z7VjRt4&IsGnA=u7rsmVDOS-S@yqGmePMtHDQSU9?vwwMB-;!wcil+@xy~aNt87%F( zmi@=>`p0vdcH|Eh)h{f|A#R1_=O=<+pQ)&bgqWonE7rhw23M}nJcl$!q}dY!s3A{tFG7OuD;WE^Fa2w-sWxN`}yjXRcqGQmK{5pdbj4%8>bEwPzOsgA6C3P f>$TWsa^zOln!P=64gkWxGrRpY literal 0 HcmV?d00001 diff --git a/ctf/docs/tech3.gif b/ctf/docs/tech3.gif new file mode 100644 index 0000000000000000000000000000000000000000..29f313013525b335cbf743769268ca0459a28138 GIT binary patch literal 1595 zcmYjQeNYs282%a6+N`;w8*a7Ij;^%nCaYYu2ivqvYn-^jDH|-b(E+EP=Z0sVdX77F zCUI9Bf*^6(87Ja!>W!115mH%$Nx=vdj$%SoC!g2Hm{HLb+tFXWf4qOZ^US=@^SsY% z%FHmP7Owy+z#m|y5CkCzf?*h*=S5Le6h&24P1AfnUmy?&hr?}cZCzbmH*emIMx$1d zR0Kle2{VVASe$1tj>ZfW%8)2cpcIaf7=oiPhQKHSAuwdII26UENEX?wNTOsmi6&m) zIgVplLu@5YQxrv#B#z@4hM_2`|F3%dVo2kBHpU@Q(ID03Bj0xs2ducuj42$#Q!t4^ zA&w3*q@Tup6zV1rCkERvNJfE(0099U0t_&qVL-wFhX4uz%_2HwL6vx$nX{N!SujXE zBeJw$pm>I4DV!lO3P%YHiAjj@cW{`6#Uut3XpALi1jf|G=4Tc2Ma|I_IB>nKrR)08 z*^a(oOS?DJ;`E0UZ@}d8F*Y|QIZ)At2nx(g5Gz6q4^aj{&;TO4a-7v*P#8w0X^|v(f-vAXjiLmCpfC(WPz>)6YSn(H*QdJ6tS-&sbjqq~wpvY! zBACsbAQ)JdW|$dAV|F4492EHj0_)=#H%qw~Ql)V#iOB?N#t{L7Srn!bh=3ss0yAs? zaA`dbXS+>pv06il5|HF-(d>?~<9Qp)$_yh?6i*Tij*%#W#V7^BT6NIj^;?{*) zF2Um9WSc=$DBg^-0?KeO$v`*>Pz=Bb06HK?5xx(X+9*qiu~Zu@n&H2j_vv~_*WJ1< z={luH3H>^zUqtjUq#ppkxIsh(eKP1Ufff#gX%HYlH3r-Wa6-TWfC&Hw033i{0AN{N zpOK=|bSzh~P$5yjyWJ@TWrN1!(Y}t3a3JJ!c~v>6NQ)@G4Ek91K#Swq@=vtU`H>+1AeC0$d3NyY>am;!S@vHC zE>+O-g5}Fj*)#S{thn&NvD_Cs>*7&!hGpFM}{PnTkcawr6byE*muY|qnx3qx zNO5_lr8`eV{WkTlg^=bfx-x$EU8URyt#?`?oo}~`IXl0P_TPS?r~9AXS>C-h(`*La z5ZO>uhL&{%Y8x-lit7%@kcC|Bdo6X#`^yWPx(+3cPYykoWM5LaacW0mg7LefzwH;R z59WST;Lb7@?K%5#Vxdg0oPBlI!>?cKmhL%nUQwfm&(GU<>xocN>YU{Kd4jn$(!Dvk z_>Fn?6E`+RUM(#>mcMPZ-_yQEnDhP6o9_N&NA@lGplMxgNoz{>i}w4c-cHT+#AjE2 z_x$)kK3{{@z9xKA-jEK}#wBb|e=jqkzl?c9cn;T`7|?{-}~I8^`4m!YM3+dTfm^$X8?yS3Y;X_i-~ zCkEticOon6tE~IKS}(m+H@y3yy*Z7YmQ?Ys{;EAcJ*XC?etM#1ZQioe>#GV@URiQ* zmGR0^g==5*=i;)t&2f!je$I{?&u>n|fv5MrFAIzfHRO>^O<7~g=I}owFZlAd{h4Sa zzOA_S(Z%DtO1Rd>$zIjEBlqgM%*L{>a)vV!=UvDx{Cw`3OOtT_-j&^NX76K9)HhW< zRr@!Y)*e5+rRdxZk5WHZ$OaqwtpuZyz0oE#kqY^d+F j;{*Gwsj(j_nW}Z^0C4{Tq-}Xx literal 0 HcmV?d00001 diff --git a/ctf/docs/tech4.gif b/ctf/docs/tech4.gif new file mode 100644 index 0000000000000000000000000000000000000000..278cddae9302833f2e22a7debf9f0adbb1b86cae GIT binary patch literal 1434 zcmYjQe@qj16#q&{j(NIMj(F&)hn{%qIgioI6K~t$PAzas2gMz7n8FIHq=ss$cC5=9 zMJ-SrDngwF>QJoMDgKC5VGdoVs6;`TbVHG$C}6iRlf})scx(69-XHIemrwFOpU?X| zb?V9`N%;vN0sINVg&+t)5DdfcJTHo(EXzit(Q36i91fSu<@I{o+S)ohJMZ7W9}EWd zA}I@mmM1hEu4Zwb!8jUIQ7A*AG=Wk$LShJx!WaUh2!z0pPG^#3gRIp_8d)o9HEL1K z3p~ehEUOBwq-lzxNRq^H9K$daMV0?mms9juIfsEUX{n%_>U5CJ7NSazTQr!=Av^_> z806t-H$ysU+(Dr>0x@H-0fQtIhzJl6z#%{d0~!V-3~&gb5U}b*vm_X`yg|e1)T|_^ zv^*oSw4kDRhGZ$6Au$R^2@DBI2=O;@n2yD?3?|SRONIr8)P?5fWKFx()aj}UwD~#% z1Lrz=+&;hE<1;%wvfZV&I2eNs)0$AxfCw_oYav#I7#^ZjfS>_J00adP95UY_Nu?T% zRjoD)f|29&DwWJI5>1OF$rFSM$7vKL5Cnx`7=l80r`uZTG}|3UTdCe+)tSwb(Wueu z)v_#TG@Kx)Se9m(u%jV65d?ONe3igDIL5|O7KSv^xSqr$0@dJ%fWa&Z(+EVs5C(xT z8vrcUE|b}BF#7a*k1V^iQl+S|h1l`Dfn_Cz5h;o%2?obV6v0B2TwZIX+hlj@tq#dt zDjF<;&csOul_*oZ24@A7;b4-1a1x*xfDr%`Kn^2(53X&abRI@msnS_h|J}StQ9O!b zQxvVDP)d+c0+`Z{C|*dZ0%JDt*a&(g(4huC4tQzcB0wbuYzQzzKnH*t00saYfH44A zR#C!I6q*j@DikUt=(PFGTDPRK@_4YPqr>a+I4pLf=hwfiOWrjFTbQ!$i-8^z-ifEzxvf+ z>z3-O-cvI$Q%?Wr)>G-0smHp{PMKe`<>sgJg~J`QB3f20CvP75rhokOTZhj+8pFF@ z*L$0i^9CcelS`F{wM7vgYm%;3{-?3~LcnrjLQGjwYjfk;!rMaf)yS+&*QoEZ^xdVx zDN8fvUtd?$ShuxL@|P@rkp1KUd1zf3hJ!<2 z4cFvzSBs9Xihk!scKXPSx&22RD_Va%I-Jl~mD@daP2Hx398Z&VaeB?u+9Q3=sb7@1 zehU!w4fVHv@43*grRJTUzY#$mVr#{ngd{Vd{z;y;N9u`=c8lr#v}1 zdD+OXMKPIC4N_iy3Du(Cx$D}F{=`YMr=2L53tEg{_YB$x7RAplFW3`|+xYz9h5VNI a) game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +range + +returns the range catagorization of an entity reletive to self +0 melee range, will become hostile even if back is turned +1 visibility and infront, or visibility and show hostile +2 infront and show hostile +3 only triggered by damage +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +FindTarget + +Self is currently not attacking anything, so try to find a target + +Returns TRUE if an enemy was sighted + +When a player fires a missile, the point of impact becomes a fakeplayer so +that monsters that see the impact will respond as if they had seen the +player. + +To avoid spending too much time, only a single client (or fakeclient) is +checked each frame. This means multi player games will have slightly +slower noticing monsters. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/ctf/g_chase.c b/ctf/g_chase.c new file mode 100644 index 000000000..b57648e32 --- /dev/null +++ b/ctf/g_chase.c @@ -0,0 +1,157 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse) { + ent->client->chase_target = NULL; + return; + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); + + if ((!ent->client->showscores && !ent->client->menu && + !ent->client->showinventory && !ent->client->showhelp && + !(level.framenum & 31)) || ent->client->update_chase) { + char s[1024]; + + ent->client->update_chase = false; + sprintf(s, "xv 0 yb -68 string2 \"Chasing %s\"", + targ->client->pers.netname); + gi.WriteByte (svc_layout); + gi.WriteString (s); + gi.unicast(ent, false); + } + +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->solid != SOLID_NOT) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} diff --git a/ctf/g_cmds.c b/ctf/g_cmds.c new file mode 100644 index 000000000..2b9aaedd8 --- /dev/null +++ b/ctf/g_cmds.c @@ -0,0 +1,1066 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) { + PMenu_Next(ent); + return; + } else if (cl->chase_target) { + ChaseNext(ent); + return; + } +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + +//ZOID + if (cl->menu) { + PMenu_Prev(ent); + return; + } else if (cl->chase_target) { + ChasePrev(ent); + return; + } +//ZOID + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + +//ZOID--special case for tech powerups + if (Q_stricmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != NULL) { + it->drop (ent, it); + return; + } +//ZOID + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + +//ZOID + if (ent->client->menu) { + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } +//ZOID + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + +//ZOID + if (ctf->value && cl->resp.ctf_team == CTF_NOTEAM) { + CTFOpenJoinMenu(ent); + return; + } +//ZOID + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + +//ZOID + if (ent->client->menu) { + PMenu_Select(ent); + return; + } +//ZOID + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +//ZOID +/* +================= +Cmd_LastWeap_f +================= +*/ +void Cmd_LastWeap_f (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + cl->pers.lastweapon->use (ent, cl->pers.lastweapon); +} +//ZOID + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ +//ZOID + if (ent->solid == SOLID_NOT) + return; +//ZOID + + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); + ent->client->update_chase = true; +//ZOID +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +qboolean CheckFlood(edict_t *ent) +{ + int i; + gclient_t *cl; + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return true; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return true; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + return false; +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int j; + edict_t *other; + char *p; + char text[2048]; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (CheckFlood(ent)) + return; + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0 || Q_stricmp (cmd, "steam") == 0) + { + CTFSay_Team(ent, gi.args()); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); +//ZOID + else if (Q_stricmp (cmd, "team") == 0) + { + CTFTeam_f (ent); + } else if (Q_stricmp(cmd, "id") == 0) { + CTFID_f (ent); + } else if (Q_stricmp(cmd, "yes") == 0) { + CTFVoteYes(ent); + } else if (Q_stricmp(cmd, "no") == 0) { + CTFVoteNo(ent); + } else if (Q_stricmp(cmd, "ready") == 0) { + CTFReady(ent); + } else if (Q_stricmp(cmd, "notready") == 0) { + CTFNotReady(ent); + } else if (Q_stricmp(cmd, "ghost") == 0) { + CTFGhost(ent); + } else if (Q_stricmp(cmd, "admin") == 0) { + CTFAdmin(ent); + } else if (Q_stricmp(cmd, "stats") == 0) { + CTFStats(ent); + } else if (Q_stricmp(cmd, "warp") == 0) { + CTFWarp(ent); + } else if (Q_stricmp(cmd, "boot") == 0) { + CTFBoot(ent); + } else if (Q_stricmp(cmd, "playerlist") == 0) { + CTFPlayerList(ent); + } else if (Q_stricmp(cmd, "observer") == 0) { + CTFObserver(ent); + } +//ZOID + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/ctf/g_combat.c b/ctf/g_combat.c new file mode 100644 index 000000000..d0f83085b --- /dev/null +++ b/ctf/g_combat.c @@ -0,0 +1,596 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 1; // power armor is weaker in CTF + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + else + // otherwise get mad at whoever they are mad at (help our buddy) + { + if (targ->enemy) + if (targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ +//ZOID + if (ctf->value && targ->client && attacker->client) + if (targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker) + return true; +//ZOID + + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + +//ZOID +//strength tech + damage = CTFApplyStrength(attacker, damage); +//ZOID + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + +//ZOID +//team armor protect + if (ctf->value && targ->client && attacker->client && + targ->client->resp.ctf_team == attacker->client->resp.ctf_team && + targ != attacker && ((int)dmflags->value & DF_ARMOR_PROTECT)) { + psave = asave = 0; + } else { +//ZOID + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + } + + //treat cheat/powerup savings the same as armor + asave += save; + +//ZOID +//resistance tech + take = CTFApplyResistance(targ, take); +//ZOID + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +//ZOID + CTFCheckHurtCarrier(targ, attacker); +//ZOID + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + if (!CTFMatchSetup()) + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take) && !CTFMatchSetup()) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/ctf/g_ctf.c b/ctf/g_ctf.c new file mode 100644 index 000000000..1fcf0d596 --- /dev/null +++ b/ctf/g_ctf.c @@ -0,0 +1,4016 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" +#include "m_player.h" + +typedef enum match_s { + MATCH_NONE, + MATCH_SETUP, + MATCH_PREGAME, + MATCH_GAME, + MATCH_POST +} match_t; + +typedef enum { + ELECT_NONE, + ELECT_MATCH, + ELECT_ADMIN, + ELECT_MAP +} elect_t; + +typedef struct ctfgame_s +{ + int team1, team2; + int total1, total2; // these are only set when going into intermission! + float last_flag_capture; + int last_capture_team; + + match_t match; // match state + float matchtime; // time for match start/end (depends on state) + int lasttime; // last time update + + elect_t election; // election type + edict_t *etarget; // for admin election, who's being elected + char elevel[32]; // for map election, target level + int evotes; // votes so far + int needvotes; // votes needed + float electtime; // remaining time until election times out + char emsg[256]; // election name + + + ghost_t ghosts[MAX_CLIENTS]; // ghost codes +} ctfgame_t; + +ctfgame_t ctfgame; + +cvar_t *ctf; +cvar_t *ctf_forcejoin; + +cvar_t *competition; +cvar_t *matchlock; +cvar_t *electpercentage; +cvar_t *matchtime; +cvar_t *matchsetuptime; +cvar_t *matchstarttime; +cvar_t *admin_password; +cvar_t *warp_list; + +char *ctf_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " + "xv 246 " + "num 2 10 " + "xv 296 " + "pic 9 " +"endif " + +// help / weapon icon +"if 11 " + "xv 148 " + "pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14 " + +//tech +"yb -129 " +"if 26 " + "xr -26 " + "pic 26 " +"endif " + +// red team +"yb -102 " +"if 17 " + "xr -26 " + "pic 17 " +"endif " +"xr -62 " +"num 2 18 " +//joined overlay +"if 22 " + "yb -104 " + "xr -28 " + "pic 22 " +"endif " + +// blue team +"yb -75 " +"if 19 " + "xr -26 " + "pic 19 " +"endif " +"xr -62 " +"num 2 20 " +"if 23 " + "yb -77 " + "xr -28 " + "pic 23 " +"endif " + +// have flag graph +"if 21 " + "yt 26 " + "xr -24 " + "pic 21 " +"endif " + +// id view state +"if 27 " + "xv 0 " + "yb -58 " + "string \"Viewing\" " + "xv 64 " + "stat_string 27 " +"endif " + +"if 28 " + "xl 0 " + "yb -78 " + "stat_string 28 " +"endif " +; + +static char *tnames[] = { + "item_tech1", "item_tech2", "item_tech3", "item_tech4", + NULL +}; + +void stuffcmd(edict_t *ent, char *s) +{ + gi.WriteByte (11); + gi.WriteString (s); + gi.unicast (ent, true); +} + +/*--------------------------------------------------------------------------*/ + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; +#if 0 + if (from->solid == SOLID_NOT) + continue; +#endif + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + +static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs) +{ + VectorAdd(org, mins, p[0]); + VectorCopy(p[0], p[1]); + p[1][0] -= mins[0]; + VectorCopy(p[0], p[2]); + p[2][1] -= mins[1]; + VectorCopy(p[0], p[3]); + p[3][0] -= mins[0]; + p[3][1] -= mins[1]; + VectorAdd(org, maxs, p[4]); + VectorCopy(p[4], p[5]); + p[5][0] -= maxs[0]; + VectorCopy(p[0], p[6]); + p[6][1] -= maxs[1]; + VectorCopy(p[0], p[7]); + p[7][0] -= maxs[0]; + p[7][1] -= maxs[1]; +} + +static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor) +{ + trace_t trace; + vec3_t targpoints[8]; + int i; + vec3_t viewpoint; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + return false; // bmodels not supported + + loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs); + + VectorCopy(inflictor->s.origin, viewpoint); + viewpoint[2] += inflictor->viewheight; + + for (i = 0; i < 8; i++) { + trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + } + + return false; +} + +/*--------------------------------------------------------------------------*/ + +static gitem_t *flag1_item; +static gitem_t *flag2_item; + +void CTFSpawn(void) +{ + if (!flag1_item) + flag1_item = FindItemByClassname("item_flag_team1"); + if (!flag2_item) + flag2_item = FindItemByClassname("item_flag_team2"); + memset(&ctfgame, 0, sizeof(ctfgame)); + CTFSetupTechSpawn(); + + if (competition->value > 1) { + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } +} + +void CTFInit(void) +{ + ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO); + ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0); + competition = gi.cvar("competition", "0", CVAR_SERVERINFO); + matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO); + electpercentage = gi.cvar("electpercentage", "66", 0); + matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO); + matchsetuptime = gi.cvar("matchsetuptime", "10", 0); + matchstarttime = gi.cvar("matchstarttime", "20", 0); + admin_password = gi.cvar("admin_password", "", 0); + warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", 0); +} + +/*--------------------------------------------------------------------------*/ + +char *CTFTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "RED"; + case CTF_TEAM2: + return "BLUE"; + } + return "UKNOWN"; +} + +char *CTFOtherTeamName(int team) +{ + switch (team) { + case CTF_TEAM1: + return "BLUE"; + case CTF_TEAM2: + return "RED"; + } + return "UKNOWN"; +} + +int CTFOtherTeam(int team) +{ + switch (team) { + case CTF_TEAM1: + return CTF_TEAM2; + case CTF_TEAM2: + return CTF_TEAM1; + } + return -1; // invalid value +} + +/*--------------------------------------------------------------------------*/ + +edict_t *SelectRandomDeathmatchSpawnPoint (void); +edict_t *SelectFarthestDeathmatchSpawnPoint (void); +float PlayersRangeFromSpot (edict_t *spot); + +void CTFAssignSkin(edict_t *ent, char *s) +{ + int playernum = ent-g_edicts-1; + char *p; + char t[64]; + + Com_sprintf(t, sizeof(t), "%s", s); + + if ((p = strrchr(t, '/')) != NULL) + p[1] = 0; + else + strcpy(t, "male/"); + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s", + ent->client->pers.netname, t, CTF_TEAM1_SKIN) ); + break; + case CTF_TEAM2: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) ); + break; + default: + gi.configstring (CS_PLAYERSKINS+playernum, + va("%s\\%s", ent->client->pers.netname, s) ); + break; + } +// gi.cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname); +} + +void CTFAssignTeam(gclient_t *who) +{ + edict_t *player; + int i; + int team1count = 0, team2count = 0; + + who->resp.ctf_state = 0; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) { + who->resp.ctf_team = CTF_NOTEAM; + return; + } + + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + + if (!player->inuse || player->client == who) + continue; + + switch (player->client->resp.ctf_team) { + case CTF_TEAM1: + team1count++; + break; + case CTF_TEAM2: + team2count++; + } + } + if (team1count < team2count) + who->resp.ctf_team = CTF_TEAM1; + else if (team2count < team1count) + who->resp.ctf_team = CTF_TEAM2; + else if (rand() & 1) + who->resp.ctf_team = CTF_TEAM1; + else + who->resp.ctf_team = CTF_TEAM2; +} + +/* +================ +SelectCTFSpawnPoint + +go to a ctf point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectCTFSpawnPoint (edict_t *ent) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + char *cname; + + if (ent->client->resp.ctf_state) + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); + + ent->client->resp.ctf_state++; + + switch (ent->client->resp.ctf_team) { + case CTF_TEAM1: + cname = "info_player_team1"; + break; + case CTF_TEAM2: + cname = "info_player_team2"; + break; + default: + return SelectRandomDeathmatchSpawnPoint(); + } + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return SelectRandomDeathmatchSpawnPoint(); + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), cname); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/*------------------------------------------------------------------------*/ +/* +CTFFragBonuses + +Calculate the bonuses for flag defense, flag carrier defense, etc. +Note that bonuses are not cumaltive. You get one, they are in importance +order. +*/ +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker) +{ + int i; + edict_t *ent; + gitem_t *flag_item, *enemy_flag_item; + int otherteam; + edict_t *flag, *carrier; + char *c; + vec3_t v1, v2; + + if (targ->client && attacker->client) { + if (attacker->client->resp.ghost) + if (attacker != targ) + attacker->client->resp.ghost->kills++; + if (targ->client->resp.ghost) + targ->client->resp.ghost->deaths++; + } + + // no bonus for fragging yourself + if (!targ->client || !attacker->client || targ == attacker) + return; + + otherteam = CTFOtherTeam(targ->client->resp.ctf_team); + if (otherteam < 0) + return; // whoever died isn't on a team + + // same team, if the flag at base, check to he has the enemy flag + if (targ->client->resp.ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + // did the attacker frag the flag carrier? + if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + attacker->client->resp.ctf_lastfraggedcarrier = level.time; + attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS; + gi.cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n", + CTF_FRAG_CARRIER_BONUS); + + // the target had the flag, clear the hurt carrier + // field on the other team + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (ent->inuse && ent->client->resp.ctf_team == otherteam) + ent->client->resp.ctf_lasthurtcarrier = 0; + } + return; + } + + if (targ->client->resp.ctf_lasthurtcarrier && + level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT && + !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) { + // attacker is on the same team as the flag carrier and + // fragged a guy who hurt our flag carrier + attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + + // flag and flag carrier area defense bonuses + + // we have to find the flag and carrier entities + + // find the flag + switch (attacker->client->resp.ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + flag = NULL; + while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) { + if (!(flag->spawnflags & DROPPED_ITEM)) + break; + } + + if (!flag) + return; // can't find attacker's flag + + // find attacker's team's flag carrier + for (i = 1; i <= maxclients->value; i++) { + carrier = g_edicts + i; + if (carrier->inuse && + carrier->client->pers.inventory[ITEM_INDEX(flag_item)]) + break; + carrier = NULL; + } + + // ok we have the attackers flag and a pointer to the carrier + + // check to see if we are defending the base's flag + VectorSubtract(targ->s.origin, flag->s.origin, v1); + VectorSubtract(attacker->s.origin, flag->s.origin, v2); + + if ((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS || + VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS || + loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) && + attacker->client->resp.ctf_team != targ->client->resp.ctf_team) { + // we defended the base flag + attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS; + if (flag->solid == SOLID_NOT) + gi.bprintf(PRINT_MEDIUM, "%s defends the %s base.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + else + gi.bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->basedef++; + return; + } + + if (carrier && carrier != attacker) { + VectorSubtract(targ->s.origin, carrier->s.origin, v1); + VectorSubtract(attacker->s.origin, carrier->s.origin, v1); + + if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS || + VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS || + loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) { + attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS; + gi.bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n", + attacker->client->pers.netname, + CTFTeamName(attacker->client->resp.ctf_team)); + if (attacker->client->resp.ghost) + attacker->client->resp.ghost->carrierdef++; + return; + } + } +} + +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker) +{ + gitem_t *flag_item; + + if (!targ->client || !attacker->client) + return; + + if (targ->client->resp.ctf_team == CTF_TEAM1) + flag_item = flag2_item; + else + flag_item = flag1_item; + + if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] && + targ->client->resp.ctf_team != attacker->client->resp.ctf_team) + attacker->client->resp.ctf_lasthurtcarrier = level.time; +} + + +/*------------------------------------------------------------------------*/ + +void CTFResetFlag(int ctf_team) +{ + char *c; + edict_t *ent; + + switch (ctf_team) { + case CTF_TEAM1: + c = "item_flag_team1"; + break; + case CTF_TEAM2: + c = "item_flag_team2"; + break; + default: + return; + } + + ent = NULL; + while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) { + if (ent->spawnflags & DROPPED_ITEM) + G_FreeEdict(ent); + else { + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity(ent); + ent->s.event = EV_ITEM_RESPAWN; + } + } +} + +void CTFResetFlags(void) +{ + CTFResetFlag(CTF_TEAM1); + CTFResetFlag(CTF_TEAM2); +} + +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other) +{ + int ctf_team; + int i; + edict_t *player; + gitem_t *flag_item, *enemy_flag_item; + + // figure out what team this flag is + if (strcmp(ent->classname, "item_flag_team1") == 0) + ctf_team = CTF_TEAM1; + else if (strcmp(ent->classname, "item_flag_team2") == 0) + ctf_team = CTF_TEAM2; + else { + gi.cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n"); + return false; + } + + // same team, if the flag at base, check to he has the enemy flag + if (ctf_team == CTF_TEAM1) { + flag_item = flag1_item; + enemy_flag_item = flag2_item; + } else { + flag_item = flag2_item; + enemy_flag_item = flag1_item; + } + + if (ctf_team == other->client->resp.ctf_team) { + + if (!(ent->spawnflags & DROPPED_ITEM)) { + // the flag is at home base. if the player has the enemy + // flag, he's just won! + + if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) { + gi.bprintf(PRINT_HIGH, "%s captured the %s flag!\n", + other->client->pers.netname, CTFOtherTeamName(ctf_team)); + other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0; + + ctfgame.last_flag_capture = level.time; + ctfgame.last_capture_team = ctf_team; + if (ctf_team == CTF_TEAM1) + ctfgame.team1++; + else + ctfgame.team2++; + + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0); + + // other gets another 10 frag bonus + other->client->resp.score += CTF_CAPTURE_BONUS; + if (other->client->resp.ghost) + other->client->resp.ghost->caps++; + + // Ok, let's do the player loop, hand out the bonuses + for (i = 1; i <= maxclients->value; i++) { + player = &g_edicts[i]; + if (!player->inuse) + continue; + + if (player->client->resp.ctf_team != other->client->resp.ctf_team) + player->client->resp.ctf_lasthurtcarrier = -5; + else if (player->client->resp.ctf_team == other->client->resp.ctf_team) { + if (player != other) + player->client->resp.score += CTF_TEAM_BONUS; + // award extra points for capture assists + if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname); + player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS; + } + if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) { + gi.bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname); + player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS; + } + } + } + + CTFResetFlags(); + return false; + } + return false; // its at home base already + } + // hey, its not home. return it by teleporting it back + gi.bprintf(PRINT_HIGH, "%s returned the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_RECOVERY_BONUS; + other->client->resp.ctf_lastreturnedflag = level.time; + gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0); + //CTFResetFlag will remove this entity! We must return false + CTFResetFlag(ctf_team); + return false; + } + + // hey, its not our flag, pick it up + gi.bprintf(PRINT_HIGH, "%s got the %s flag!\n", + other->client->pers.netname, CTFTeamName(ctf_team)); + other->client->resp.score += CTF_FLAG_BONUS; + + other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1; + other->client->resp.ctf_flagsince = level.time; + + // pick up the flag + // if it's not a dropped flag, we just make is disappear + // if it's dropped, it will be removed by the pickup caller + if (!(ent->spawnflags & DROPPED_ITEM)) { + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + return true; +} + +static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + //owner (who dropped us) can't touch for two secs + if (other == ent->owner && + ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void CTFDropFlagThink(edict_t *ent) +{ + // auto return the flag + // reset flag will remove ourselves + if (strcmp(ent->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + } else if (strcmp(ent->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM2)); + } +} + +// Called from PlayerDie, to drop the flag from a dying player +void CTFDeadDropFlag(edict_t *self) +{ + edict_t *dropped = NULL; + + if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + dropped = Drop_Item(self, flag1_item); + self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM1)); + } else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + dropped = Drop_Item(self, flag2_item); + self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0; + gi.bprintf(PRINT_HIGH, "%s lost the %s flag!\n", + self->client->pers.netname, CTFTeamName(CTF_TEAM2)); + } + + if (dropped) { + dropped->think = CTFDropFlagThink; + dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT; + dropped->touch = CTFDropFlagTouch; + } +} + +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item) +{ + if (rand() & 1) + gi.cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n"); + else + gi.cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n"); + return false; +} + +static void CTFFlagThink(edict_t *ent) +{ + if (ent->solid != SOLID_NOT) + ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16); + ent->nextthink = level.time + FRAMETIME; +} + + +void CTFFlagSetup (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + ent->think = CTFFlagThink; +} + +void CTFEffects(edict_t *player) +{ + player->s.effects &= ~(EF_FLAG1 | EF_FLAG2); + if (player->health > 0) { + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) { + player->s.effects |= EF_FLAG1; + } + if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) { + player->s.effects |= EF_FLAG2; + } + } + + if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag1.md2"); + else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) + player->s.modelindex3 = gi.modelindex("players/male/flag2.md2"); + else + player->s.modelindex3 = 0; +} + +// called when we enter the intermission +void CTFCalcScores(void) +{ + int i; + + ctfgame.total1 = ctfgame.total2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + ctfgame.total1 += game.clients[i].resp.score; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + ctfgame.total2 += game.clients[i].resp.score; + } +} + +void CTFID_f (edict_t *ent) +{ + if (ent->client->resp.id_state) { + gi.cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n"); + ent->client->resp.id_state = false; + } else { + gi.cprintf(ent, PRINT_HIGH, "Activating player identication display.\n"); + ent->client->resp.id_state = true; + } +} + +static void CTFSetIDView(edict_t *ent) +{ + vec3_t forward, dir; + trace_t tr; + edict_t *who, *best; + float bd = 0, d; + int i; + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 1024, forward); + VectorAdd(ent->s.origin, forward, forward); + tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID); + if (tr.fraction < 1 && tr.ent && tr.ent->client) { + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_PLAYERSKINS + (ent - g_edicts - 1); + return; + } + + AngleVectors(ent->client->v_angle, forward, NULL, NULL); + best = NULL; + for (i = 1; i <= maxclients->value; i++) { + who = g_edicts + i; + if (!who->inuse || who->solid == SOLID_NOT) + continue; + VectorSubtract(who->s.origin, ent->s.origin, dir); + VectorNormalize(dir); + d = DotProduct(forward, dir); + if (d > bd && loc_CanSee(ent, who)) { + bd = d; + best = who; + } + } + if (bd > 0.90) + ent->client->ps.stats[STAT_CTF_ID_VIEW] = + CS_PLAYERSKINS + (best - g_edicts - 1); +} + +void SetCTFStats(edict_t *ent) +{ + gitem_t *tech; + int i; + int p1, p2; + edict_t *e; + + if (ctfgame.match > MATCH_NONE) + ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH; + else + ent->client->ps.stats[STAT_CTF_MATCH] = 0; + + //ghosting + if (ent->client->resp.ghost) { + ent->client->resp.ghost->score = ent->client->resp.score; + strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname); + ent->client->resp.ghost->number = ent->s.number; + } + + // logo headers for the frag display + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = gi.imageindex ("ctfsb1"); + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = gi.imageindex ("ctfsb2"); + + // if during intermission, we must blink the team header of the winning team + if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second + // note that ctfgame.total[12] is set when we go to intermission + if (ctfgame.team1 > ctfgame.team2) + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.team2 > ctfgame.team1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + else if (ctfgame.total2 > ctfgame.total1) + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + else { // tie game! + ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0; + ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0; + } + } + + // tech icon + i = 0; + ent->client->ps.stats[STAT_CTF_TECH] = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon); + break; + } + i++; + } + + // figure out what icon to display for team logos + // three states: + // flag at base + // flag taken + // flag dropped + p1 = gi.imageindex ("i_ctf1"); + e = G_Find(NULL, FOFS(classname), "item_flag_team1"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p1 = gi.imageindex ("i_ctf1d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) { + // enemy has it + p1 = gi.imageindex ("i_ctf1t"); + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p1 = gi.imageindex ("i_ctf1d"); // must be dropped + } + p2 = gi.imageindex ("i_ctf2"); + e = G_Find(NULL, FOFS(classname), "item_flag_team2"); + if (e != NULL) { + if (e->solid == SOLID_NOT) { + int i; + + // not at base + // check if on player + p2 = gi.imageindex ("i_ctf2d"); // default to dropped + for (i = 1; i <= maxclients->value; i++) + if (g_edicts[i].inuse && + g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) { + // enemy has it + p2 = gi.imageindex ("i_ctf2t"); + break; + } + } else if (e->spawnflags & DROPPED_ITEM) + p2 = gi.imageindex ("i_ctf2d"); // must be dropped + } + + + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + + if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) { + if (ctfgame.last_capture_team == CTF_TEAM1) + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1; + else + ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0; + else + if (level.framenum & 8) + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2; + else + ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0; + } + + ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1; + ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2; + + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1 && + ent->client->pers.inventory[ITEM_INDEX(flag2_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf2"); + + else if (ent->client->resp.ctf_team == CTF_TEAM2 && + ent->client->pers.inventory[ITEM_INDEX(flag1_item)] && + (level.framenum & 8)) + ent->client->ps.stats[STAT_CTF_FLAG_PIC] = gi.imageindex ("i_ctf1"); + + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0; + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0; + if (ent->client->resp.ctf_team == CTF_TEAM1) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = gi.imageindex ("i_ctfj"); + else if (ent->client->resp.ctf_team == CTF_TEAM2) + ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = gi.imageindex ("i_ctfj"); + + ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0; + if (ent->client->resp.id_state) + CTFSetIDView(ent); +} + +/*------------------------------------------------------------------------*/ + +/*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32) +potential team1 spawning position for ctf games +*/ +void SP_info_player_team1(edict_t *self) +{ +} + +/*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32) +potential team2 spawning position for ctf games +*/ +void SP_info_player_team2(edict_t *self) +{ +} + + +/*------------------------------------------------------------------------*/ +/* GRAPPLE */ +/*------------------------------------------------------------------------*/ + +// ent is player +void CTFPlayerResetGrapple(edict_t *ent) +{ + if (ent->client && ent->client->ctf_grapple) + CTFResetGrapple(ent->client->ctf_grapple); +} + +// self is grapple, not player +void CTFResetGrapple(edict_t *self) +{ + if (self->owner->client->ctf_grapple) { + float volume = 1.0; + gclient_t *cl; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0); + cl = self->owner->client; + cl->ctf_grapple = NULL; + cl->ctf_grapplereleasetime = level.time; + cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + G_FreeEdict(self); + } +} + +void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + float volume = 1.0; + + if (other == self->owner) + return; + + if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + CTFResetGrapple(self); + return; + } + + VectorCopy(vec3_origin, self->velocity); + + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) { + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE); + CTFResetGrapple(self); + return; + } + + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook + self->enemy = other; + + self->solid = SOLID_NOT; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPARKS); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +// draw beam between grapple and self +void CTFGrappleDrawCable(edict_t *self) +{ + vec3_t offset, start, end, f, r; + vec3_t dir; + float distance; + + AngleVectors (self->owner->client->v_angle, f, r, NULL); + VectorSet(offset, 16, 16, self->owner->viewheight-8); + P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start); + + VectorSubtract(start, self->owner->s.origin, offset); + + VectorSubtract (start, self->s.origin, dir); + distance = VectorLength(dir); + // don't draw cable if close + if (distance < 64) + return; + +#if 0 + if (distance > 256) + return; + + // check for min/max pitch + vectoangles (dir, angles); + if (angles[0] < -180) + angles[0] += 360; + if (fabs(angles[0]) > 45) + return; + + trace_t tr; //!! + + tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT); + if (tr.ent != self) { + CTFResetGrapple(self); + return; + } +#endif + + // adjust start for beam origin being in middle of a segment +// VectorMA (start, 8, f, start); + + VectorCopy (self->s.origin, end); + // adjust end z for end spot since the monster is currently dead +// end[2] = self->absmin[2] + self->size[2] / 2; + + gi.WriteByte (svc_temp_entity); +#if 1 //def USE_GRAPPLE_CABLE + gi.WriteByte (TE_GRAPPLE_CABLE); + gi.WriteShort (self->owner - g_edicts); + gi.WritePosition (self->owner->s.origin); + gi.WritePosition (end); + gi.WritePosition (offset); +#else + gi.WriteByte (TE_MEDIC_CABLE_ATTACK); + gi.WriteShort (self - g_edicts); + gi.WritePosition (end); + gi.WritePosition (start); +#endif + gi.multicast (self->s.origin, MULTICAST_PVS); +} + +void SV_AddGravity (edict_t *ent); + +// pull the player toward the grapple +void CTFGrapplePull(edict_t *self) +{ + vec3_t hookdir, v; + float vlen; + + if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 && + !self->owner->client->newweapon && + self->owner->client->weaponstate != WEAPON_FIRING && + self->owner->client->weaponstate != WEAPON_ACTIVATING) { + CTFResetGrapple(self); + return; + } + + if (self->enemy) { + if (self->enemy->solid == SOLID_NOT) { + CTFResetGrapple(self); + return; + } + if (self->enemy->solid == SOLID_BBOX) { + VectorScale(self->enemy->size, 0.5, v); + VectorAdd(v, self->enemy->s.origin, v); + VectorAdd(v, self->enemy->mins, self->s.origin); + gi.linkentity (self); + } else + VectorCopy(self->enemy->velocity, self->velocity); + if (self->enemy->takedamage && + !CheckTeamDamage (self->enemy, self->owner)) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE); + gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0); + } + if (self->enemy->deadflag) { // he died + CTFResetGrapple(self); + return; + } + } + + CTFGrappleDrawCable(self); + + if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + // pull player toward grapple + // this causes icky stuff with prediction, we need to extend + // the prediction layer to include two new fields in the player + // move stuff: a point and a velocity. The client should add + // that velociy in the direction of the point + vec3_t forward, up; + + AngleVectors (self->owner->client->v_angle, forward, NULL, up); + VectorCopy(self->owner->s.origin, v); + v[2] += self->owner->viewheight; + VectorSubtract (self->s.origin, v, hookdir); + + vlen = VectorLength(hookdir); + + if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL && + vlen < 64) { + float volume = 1.0; + + if (self->owner->client->silencer_shots) + volume = 0.2; + + self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0); + self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG; + } + + VectorNormalize (hookdir); + VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir); + VectorCopy(hookdir, self->owner->velocity); + SV_AddGravity(self->owner); + } +} + +void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect) +{ + edict_t *grapple; + trace_t tr; + + VectorNormalize (dir); + + grapple = G_Spawn(); + VectorCopy (start, grapple->s.origin); + VectorCopy (start, grapple->s.old_origin); + vectoangles (dir, grapple->s.angles); + VectorScale (dir, speed, grapple->velocity); + grapple->movetype = MOVETYPE_FLYMISSILE; + grapple->clipmask = MASK_SHOT; + grapple->solid = SOLID_BBOX; + grapple->s.effects |= effect; + VectorClear (grapple->mins); + VectorClear (grapple->maxs); + grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2"); +// grapple->s.sound = gi.soundindex ("misc/lasfly.wav"); + grapple->owner = self; + grapple->touch = CTFGrappleTouch; +// grapple->nextthink = level.time + FRAMETIME; +// grapple->think = CTFGrappleThink; + grapple->dmg = damage; + self->client->ctf_grapple = grapple; + self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook + gi.linkentity (grapple); + + tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (grapple->s.origin, -10, dir, grapple->s.origin); + grapple->touch (grapple, tr.ent, NULL, NULL); + } +} + +void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect) +{ + vec3_t forward, right; + vec3_t start; + vec3_t offset; + float volume = 1.0; + + if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) + return; // it's already out + + AngleVectors (ent->client->v_angle, forward, right, NULL); +// VectorSet(offset, 24, 16, ent->viewheight-8+2); + VectorSet(offset, 24, 8, ent->viewheight-8+2); + VectorAdd (offset, g_offset, offset); + P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start); + + VectorScale (forward, -2, ent->client->kick_origin); + ent->client->kick_angles[0] = -1; + + if (ent->client->silencer_shots) + volume = 0.2; + + gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0); + CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect); + +#if 0 + // send muzzle flash + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_BLASTER); + gi.multicast (ent->s.origin, MULTICAST_PVS); +#endif + + PlayerNoise(ent, start, PNOISE_WEAPON); +} + + +void CTFWeapon_Grapple_Fire (edict_t *ent) +{ + int damage; + + damage = 10; + CTFGrappleFire (ent, vec3_origin, damage, 0); + ent->client->ps.gunframe++; +} + +void CTFWeapon_Grapple (edict_t *ent) +{ + static int pause_frames[] = {10, 18, 27, 0}; + static int fire_frames[] = {6, 0}; + int prevstate; + + // if the the attack button is still down, stay in the firing frame + if ((ent->client->buttons & BUTTON_ATTACK) && + ent->client->weaponstate == WEAPON_FIRING && + ent->client->ctf_grapple) + ent->client->ps.gunframe = 9; + + if (!(ent->client->buttons & BUTTON_ATTACK) && + ent->client->ctf_grapple) { + CTFResetGrapple(ent->client->ctf_grapple); + if (ent->client->weaponstate == WEAPON_FIRING) + ent->client->weaponstate = WEAPON_READY; + } + + + if (ent->client->newweapon && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY && + ent->client->weaponstate == WEAPON_FIRING) { + // he wants to change weapons while grappled + ent->client->weaponstate = WEAPON_DROPPING; + ent->client->ps.gunframe = 32; + } + + prevstate = ent->client->weaponstate; + Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames, + CTFWeapon_Grapple_Fire); + + // if we just switched back to grapple, immediately go to fire frame + if (prevstate == WEAPON_ACTIVATING && + ent->client->weaponstate == WEAPON_READY && + ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) { + if (!(ent->client->buttons & BUTTON_ATTACK)) + ent->client->ps.gunframe = 9; + else + ent->client->ps.gunframe = 5; + ent->client->weaponstate = WEAPON_FIRING; + } +} + +void CTFTeam_f (edict_t *ent) +{ + char *t, *s; + int desired_team; + + t = gi.args(); + if (!*t) { + gi.cprintf(ent, PRINT_HIGH, "You are on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + + if (ctfgame.match > MATCH_SETUP) { + gi.cprintf(ent, PRINT_HIGH, "Can't change teams in a match.\n"); + return; + } + + if (Q_stricmp(t, "red") == 0) + desired_team = CTF_TEAM1; + else if (Q_stricmp(t, "blue") == 0) + desired_team = CTF_TEAM2; + else { + gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t); + return; + } + + if (ent->client->resp.ctf_team == desired_team) { + gi.cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n", + CTFTeamName(ent->client->resp.ctf_team)); + return; + } + +//// + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + if (ent->solid == SOLID_NOT) { // spectator + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + return; + } + + ent->health = 0; + player_die (ent, ent, ent, 100000, vec3_origin); + // don't even bother waiting for death frames + ent->deadflag = DEAD_DEAD; + respawn (ent); + + ent->client->resp.score = 0; + + gi.bprintf(PRINT_HIGH, "%s changed to the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); +} + +/* +================== +CTFScoreboardMessage +================== +*/ +void CTFScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int len; + int i, j, k, n; + int sorted[2][MAX_CLIENTS]; + int sortedscores[2][MAX_CLIENTS]; + int score, total[2], totalscore[2]; + int last[2]; + gclient_t *cl; + edict_t *cl_ent; + int team; + int maxsize = 1000; + + // sort the clients by team and score + total[0] = total[1] = 0; + last[0] = last[1] = 0; + totalscore[0] = totalscore[1] = 0; + for (i=0 ; iinuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + team = 0; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + team = 1; + else + continue; // unknown team? + + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[team][j]) + break; + } + for (k=total[team] ; k>j ; k--) + { + sorted[team][k] = sorted[team][k-1]; + sortedscores[team][k] = sortedscores[team][k-1]; + } + sorted[team][j] = i; + sortedscores[team][j] = score; + totalscore[team] += score; + total[team]++; + } + + // print level name and exit rules + // add the clients in sorted order + *string = 0; + len = 0; + + // team one + sprintf(string, "if 24 xv 8 yv 8 pic 24 endif " + "xv 40 yv 28 string \"%4d/%-3d\" " + "xv 98 yv 12 num 2 18 " + "if 25 xv 168 yv 8 pic 25 endif " + "xv 200 yv 28 string \"%4d/%-3d\" " + "xv 256 yv 12 num 2 20 ", + totalscore[0], total[0], + totalscore[1], total[1]); + len = strlen(string); + + for (i=0 ; i<16 ; i++) + { + if (i >= total[0] && i >= total[1]) + break; // we're done + +#if 0 //ndef NEW_SCORE + // set up y + sprintf(entry, "yv %d ", 42 + i * 8); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } +#else + *entry = 0; +#endif + + // left side + if (i < total[0]) { + cl = &game.clients[sorted[0][i]]; + cl_ent = g_edicts + 1 + sorted[0][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 0 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + strcat(entry, "xv 56 picn sbfctf2 "); +#else + sprintf(entry+strlen(entry), + "ctf 0 %d %d %d %d ", + 42 + i * 8, + sorted[0][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)]) + sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ", + 42 + i * 8); +#endif + + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[0] = i; + } + } + + // right side + if (i < total[1]) { + cl = &game.clients[sorted[1][i]]; + cl_ent = g_edicts + 1 + sorted[1][i]; + +#if 0 //ndef NEW_SCORE + sprintf(entry+strlen(entry), + "xv 160 %s \"%3d %3d %-12.12s\" ", + (cl_ent == ent) ? "string2" : "string", + cl->resp.score, + (cl->ping > 999) ? 999 : cl->ping, + cl->pers.netname); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + strcat(entry, "xv 216 picn sbfctf1 "); + +#else + + sprintf(entry+strlen(entry), + "ctf 160 %d %d %d %d ", + 42 + i * 8, + sorted[1][i], + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + + if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)]) + sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ", + 42 + i * 8); +#endif + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + last[1] = i; + } + } + } + + // put in spectators if we have enough room + if (last[0] > last[1]) + j = last[0]; + else + j = last[1]; + j = (j + 2) * 8 + 42; + + k = n = 0; + if (maxsize - len > 50) { + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + cl = &game.clients[i]; + if (!cl_ent->inuse || + cl_ent->solid != SOLID_NOT || + cl_ent->client->resp.ctf_team != CTF_NOTEAM) + continue; + + if (!k) { + k = 1; + sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j); + strcat(string, entry); + len = strlen(string); + j += 8; + } + + sprintf(entry+strlen(entry), + "ctf %d %d %d %d %d ", + (n & 1) ? 160 : 0, // x + j, // y + i, // playernum + cl->resp.score, + cl->ping > 999 ? 999 : cl->ping); + if (maxsize - len > strlen(entry)) { + strcat(string, entry); + len = strlen(string); + } + + if (n & 1) + j += 8; + n++; + } + } + + if (total[0] - last[0] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ", + 42 + (last[0]+1)*8, total[0] - last[0] - 1); + if (total[1] - last[1] > 1) // couldn't fit everyone + sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ", + 42 + (last[1]+1)*8, total[1] - last[1] - 1); + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +/*------------------------------------------------------------------------*/ +/* TECH */ +/*------------------------------------------------------------------------*/ + +void CTFHasTech(edict_t *who) +{ + if (level.time - who->client->ctf_lasttechmsg > 2) { + gi.centerprintf(who, "You already have a TECH powerup."); + who->client->ctf_lasttechmsg = level.time; + } +} + +gitem_t *CTFWhat_Tech(edict_t *ent) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return tech; + } + i++; + } + return NULL; +} + +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other) +{ + gitem_t *tech; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + other->client->pers.inventory[ITEM_INDEX(tech)]) { + CTFHasTech(other); + return false; // has this one + } + i++; + } + + // client only gets one tech + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->ctf_regentime = level.time; + return true; +} + +static void SpawnTech(gitem_t *item, edict_t *spot); + +static edict_t *FindTechSpawn(void) +{ + edict_t *spot = NULL; + int i = rand() % 16; + + while (i--) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (!spot) + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + return spot; +} + +static void TechThink(edict_t *tech) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) { + SpawnTech(tech->item, spot); + G_FreeEdict(tech); + } else { + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + } +} + +void CTFDrop_Tech(edict_t *ent, gitem_t *item) +{ + edict_t *tech; + + tech = Drop_Item(ent, item); + tech->nextthink = level.time + CTF_TECH_TIMEOUT; + tech->think = TechThink; + ent->client->pers.inventory[ITEM_INDEX(item)] = 0; +} + +void CTFDeadDropTech(edict_t *ent) +{ + gitem_t *tech; + edict_t *dropped; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + dropped = Drop_Item(ent, tech); + // hack the velocity to make it bounce random + dropped->velocity[0] = (rand() % 600) - 300; + dropped->velocity[1] = (rand() % 600) - 300; + dropped->nextthink = level.time + CTF_TECH_TIMEOUT; + dropped->think = TechThink; + dropped->owner = NULL; + ent->client->pers.inventory[ITEM_INDEX(tech)] = 0; + } + i++; + } +} + +static void SpawnTech(gitem_t *item, edict_t *spot) +{ + edict_t *ent; + vec3_t forward, right; + vec3_t angles; + + ent = G_Spawn(); + + ent->classname = item->classname; + ent->item = item; + ent->spawnflags = DROPPED_ITEM; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + VectorSet (ent->mins, -15, -15, -15); + VectorSet (ent->maxs, 15, 15, 15); + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + ent->owner = ent; + + angles[0] = 0; + angles[1] = rand() % 360; + angles[2] = 0; + + AngleVectors (angles, forward, right, NULL); + VectorCopy (spot->s.origin, ent->s.origin); + ent->s.origin[2] += 16; + VectorScale (forward, 100, ent->velocity); + ent->velocity[2] = 300; + + ent->nextthink = level.time + CTF_TECH_TIMEOUT; + ent->think = TechThink; + + gi.linkentity (ent); +} + +static void SpawnTechs(edict_t *ent) +{ + gitem_t *tech; + edict_t *spot; + int i; + + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + (spot = FindTechSpawn()) != NULL) + SpawnTech(tech, spot); + i++; + } + if (ent) + G_FreeEdict(ent); +} + +// frees the passed edict! +void CTFRespawnTech(edict_t *ent) +{ + edict_t *spot; + + if ((spot = FindTechSpawn()) != NULL) + SpawnTech(ent->item, spot); + G_FreeEdict(ent); +} + +void CTFSetupTechSpawn(void) +{ + edict_t *ent; + + if (((int)dmflags->value & DF_CTF_NO_TECH)) + return; + + ent = G_Spawn(); + ent->nextthink = level.time + 2; + ent->think = SpawnTechs; +} + +void CTFResetTech(void) +{ + edict_t *ent; + int i; + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) { + if (ent->inuse) + if (ent->item && (ent->item->flags & IT_TECH)) + G_FreeEdict(ent); + } + SpawnTechs(NULL); +} + +int CTFApplyResistance(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech1"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + // make noise + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0); + return dmg / 2; + } + return dmg; +} + +int CTFApplyStrength(edict_t *ent, int dmg) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) { + return dmg * 2; + } + return dmg; +} + +qboolean CTFApplyStrengthSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech2"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) { + if (ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + if (ent->client->quad_framenum > level.framenum) + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0); + else + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0); + } + return true; + } + return false; +} + + +qboolean CTFApplyHaste(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +void CTFApplyHasteSound(edict_t *ent) +{ + static gitem_t *tech = NULL; + float volume = 1.0; + + if (ent->client && ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech3"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)] && + ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0); + } +} + +void CTFApplyRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + qboolean noise = false; + gclient_t *client; + int index; + float volume = 1.0; + + client = ent->client; + if (!client) + return; + + if (ent->client->silencer_shots) + volume = 0.2; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && client->pers.inventory[ITEM_INDEX(tech)]) { + if (client->ctf_regentime < level.time) { + client->ctf_regentime = level.time; + if (ent->health < 150) { + ent->health += 5; + if (ent->health > 150) + ent->health = 150; + client->ctf_regentime += 0.5; + noise = true; + } + index = ArmorIndex (ent); + if (index && client->pers.inventory[index] < 150) { + client->pers.inventory[index] += 5; + if (client->pers.inventory[index] > 150) + client->pers.inventory[index] = 150; + client->ctf_regentime += 0.5; + noise = true; + } + } + if (noise && ent->client->ctf_techsndtime < level.time) { + ent->client->ctf_techsndtime = level.time + 1; + gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0); + } + } +} + +qboolean CTFHasRegeneration(edict_t *ent) +{ + static gitem_t *tech = NULL; + + if (!tech) + tech = FindItemByClassname("item_tech4"); + if (tech && ent->client && + ent->client->pers.inventory[ITEM_INDEX(tech)]) + return true; + return false; +} + +/* +====================================================================== + +SAY_TEAM + +====================================================================== +*/ + +// This array is in 'importance order', it indicates what items are +// more important when reporting their names. +struct { + char *classname; + int priority; +} loc_names[] = +{ + { "item_flag_team1", 1 }, + { "item_flag_team2", 1 }, + { "item_quad", 2 }, + { "item_invulnerability", 2 }, + { "weapon_bfg", 3 }, + { "weapon_railgun", 4 }, + { "weapon_rocketlauncher", 4 }, + { "weapon_hyperblaster", 4 }, + { "weapon_chaingun", 4 }, + { "weapon_grenadelauncher", 4 }, + { "weapon_machinegun", 4 }, + { "weapon_supershotgun", 4 }, + { "weapon_shotgun", 4 }, + { "item_power_screen", 5 }, + { "item_power_shield", 5 }, + { "item_armor_body", 6 }, + { "item_armor_combat", 6 }, + { "item_armor_jacket", 6 }, + { "item_silencer", 7 }, + { "item_breather", 7 }, + { "item_enviro", 7 }, + { "item_adrenaline", 7 }, + { "item_bandolier", 8 }, + { "item_pack", 8 }, + { NULL, 0 } +}; + + +static void CTFSay_Team_Location(edict_t *who, char *buf) +{ + edict_t *what = NULL; + edict_t *hot = NULL; + float hotdist = 999999, newdist; + vec3_t v; + int hotindex = 999; + int i; + gitem_t *item; + int nearteam = -1; + edict_t *flag1, *flag2; + qboolean hotsee = false; + qboolean cansee; + + while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) { + // find what in loc_classnames + for (i = 0; loc_names[i].classname; i++) + if (strcmp(what->classname, loc_names[i].classname) == 0) + break; + if (!loc_names[i].classname) + continue; + // something we can see get priority over something we can't + cansee = loc_CanSee(what, who); + if (cansee && !hotsee) { + hotsee = true; + hotindex = loc_names[i].priority; + hot = what; + VectorSubtract(what->s.origin, who->s.origin, v); + hotdist = VectorLength(v); + continue; + } + // if we can't see this, but we have something we can see, skip it + if (hotsee && !cansee) + continue; + if (hotsee && hotindex < loc_names[i].priority) + continue; + VectorSubtract(what->s.origin, who->s.origin, v); + newdist = VectorLength(v); + if (newdist < hotdist || + (cansee && loc_names[i].priority < hotindex)) { + hot = what; + hotdist = newdist; + hotindex = i; + hotsee = loc_CanSee(hot, who); + } + } + + if (!hot) { + strcpy(buf, "nowhere"); + return; + } + + // we now have the closest item + // see if there's more than one in the map, if so + // we need to determine what team is closest + what = NULL; + while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) { + if (what == hot) + continue; + // if we are here, there is more than one, find out if hot + // is closer to red flag or blue flag + if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL && + (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) { + VectorSubtract(hot->s.origin, flag1->s.origin, v); + hotdist = VectorLength(v); + VectorSubtract(hot->s.origin, flag2->s.origin, v); + newdist = VectorLength(v); + if (hotdist < newdist) + nearteam = CTF_TEAM1; + else if (hotdist > newdist) + nearteam = CTF_TEAM2; + } + break; + } + + if ((item = FindItemByClassname(hot->classname)) == NULL) { + strcpy(buf, "nowhere"); + return; + } + + // in water? + if (who->waterlevel) + strcpy(buf, "in the water "); + else + *buf = 0; + + // near or above + VectorSubtract(who->s.origin, hot->s.origin, v); + if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1])) + if (v[2] > 0) + strcat(buf, "above "); + else + strcat(buf, "below "); + else + strcat(buf, "near "); + + if (nearteam == CTF_TEAM1) + strcat(buf, "the red "); + else if (nearteam == CTF_TEAM2) + strcat(buf, "the blue "); + else + strcat(buf, "the "); + + strcat(buf, item->pickup_name); +} + +static void CTFSay_Team_Armor(edict_t *who, char *buf) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + *buf = 0; + + power_armor_type = PowerArmorType (who); + if (power_armor_type) + { + cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells) + sprintf(buf+strlen(buf), "%s with %i cells ", + (power_armor_type == POWER_ARMOR_SCREEN) ? + "Power Screen" : "Power Shield", cells); + } + + index = ArmorIndex (who); + if (index) + { + item = GetItemByIndex (index); + if (item) { + if (*buf) + strcat(buf, "and "); + sprintf(buf+strlen(buf), "%i units of %s", + who->client->pers.inventory[index], item->pickup_name); + } + } + + if (!*buf) + strcpy(buf, "no armor"); +} + +static void CTFSay_Team_Health(edict_t *who, char *buf) +{ + if (who->health <= 0) + strcpy(buf, "dead"); + else + sprintf(buf, "%i health", who->health); +} + +static void CTFSay_Team_Tech(edict_t *who, char *buf) +{ + gitem_t *tech; + int i; + + // see if the player has a tech powerup + i = 0; + while (tnames[i]) { + if ((tech = FindItemByClassname(tnames[i])) != NULL && + who->client->pers.inventory[ITEM_INDEX(tech)]) { + sprintf(buf, "the %s", tech->pickup_name); + return; + } + i++; + } + strcpy(buf, "no powerup"); +} + +static void CTFSay_Team_Weapon(edict_t *who, char *buf) +{ + if (who->client->pers.weapon) + strcpy(buf, who->client->pers.weapon->pickup_name); + else + strcpy(buf, "none"); +} + +static void CTFSay_Team_Sight(edict_t *who, char *buf) +{ + int i; + edict_t *targ; + int n = 0; + char s[1024]; + char s2[1024]; + + *s = *s2 = 0; + for (i = 1; i <= maxclients->value; i++) { + targ = g_edicts + i; + if (!targ->inuse || + targ == who || + !loc_CanSee(targ, who)) + continue; + if (*s2) { + if (strlen(s) + strlen(s2) + 3 < sizeof(s)) { + if (n) + strcat(s, ", "); + strcat(s, s2); + *s2 = 0; + } + n++; + } + strcpy(s2, targ->client->pers.netname); + } + if (*s2) { + if (strlen(s) + strlen(s2) + 6 < sizeof(s)) { + if (n) + strcat(s, " and "); + strcat(s, s2); + } + strcpy(buf, s); + } else + strcpy(buf, "no one"); +} + +void CTFSay_Team(edict_t *who, char *msg) +{ + char outmsg[1024]; + char buf[1024]; + int i; + char *p; + edict_t *cl_ent; + + if (CheckFlood(who)) + return; + + outmsg[0] = 0; + + if (*msg == '\"') { + msg[strlen(msg) - 1] = 0; + msg++; + } + + for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 1; msg++) { + if (*msg == '%') { + switch (*++msg) { + case 'l' : + case 'L' : + CTFSay_Team_Location(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'a' : + case 'A' : + CTFSay_Team_Armor(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'h' : + case 'H' : + CTFSay_Team_Health(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 't' : + case 'T' : + CTFSay_Team_Tech(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + case 'w' : + case 'W' : + CTFSay_Team_Weapon(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + case 'n' : + case 'N' : + CTFSay_Team_Sight(who, buf); + strcpy(p, buf); + p += strlen(buf); + break; + + default : + *p++ = *msg; + } + } else + *p++ = *msg; + } + *p = 0; + + for (i = 0; i < maxclients->value; i++) { + cl_ent = g_edicts + 1 + i; + if (!cl_ent->inuse) + continue; + if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team) + gi.cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n", + who->client->pers.netname, outmsg); + } +} + +/*-----------------------------------------------------------------------*/ +/*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2 +The origin is the bottom of the banner. +The banner is 248 tall. +*/ +static void misc_ctf_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_ctf_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2 +The origin is the bottom of the banner. +The banner is 124 tall. +*/ +void SP_misc_ctf_small_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2"); + if (ent->spawnflags & 1) // team2 + ent->s.skinnum = 1; + + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_ctf_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*-----------------------------------------------------------------------*/ + +static void SetLevelName(pmenu_t *p) +{ + static char levelname[33]; + + levelname[0] = '*'; + if (g_edicts[0].message) + strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2); + else + strncpy(levelname+1, level.mapname, sizeof(levelname) - 2); + levelname[sizeof(levelname) - 1] = 0; + p->text = levelname; +} + + +/*-----------------------------------------------------------------------*/ + + +/* ELECTIONS */ + +qboolean CTFBeginElection(edict_t *ent, elect_t type, char *msg) +{ + int i; + int count; + edict_t *e; + + if (electpercentage->value == 0) { + gi.cprintf(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n"); + return false; + } + + + if (ctfgame.election != ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "Election already in progress.\n"); + return false; + } + + // clear votes + count = 0; + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + e->client->resp.voted = false; + if (e->inuse) + count++; + } + + if (count < 2) { + gi.cprintf(ent, PRINT_HIGH, "Not enough players for election.\n"); + return false; + } + + ctfgame.etarget = ent; + ctfgame.election = type; + ctfgame.evotes = 0; + ctfgame.needvotes = (count * electpercentage->value) / 100; + ctfgame.electtime = level.time + 20; // twenty seconds for election + strncpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg) - 1); + + // tell everyone + gi.bprintf(PRINT_CHAT, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_HIGH, "Type YES or NO to vote on this request.\n"); + gi.bprintf(PRINT_HIGH, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); + + return true; +} + +void DoRespawn (edict_t *ent); + +void CTFResetAllPlayers(void) +{ + int i; + edict_t *ent; + + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + if (ent->client->menu) + PMenu_Close(ent); + + CTFPlayerResetGrapple(ent); + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->resp.ready = false; + + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + } + + // reset the level + CTFResetTech(); + CTFResetFlags(); + + for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) { + if (ent->inuse && !ent->client) { + if (ent->solid == SOLID_NOT && ent->think == DoRespawn && + ent->nextthink >= level.time) { + ent->nextthink = 0; + DoRespawn(ent); + } + } + } + if (ctfgame.match == MATCH_SETUP) + ctfgame.matchtime = level.time + matchsetuptime->value * 60; +} + +void CTFAssignGhost(edict_t *ent) +{ + int ghost, i; + + for (ghost = 0; ghost < MAX_CLIENTS; ghost++) + if (!ctfgame.ghosts[ghost].code) + break; + if (ghost == MAX_CLIENTS) + return; + ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team; + ctfgame.ghosts[ghost].score = 0; + for (;;) { + ctfgame.ghosts[ghost].code = 10000 + (rand() % 90000); + for (i = 0; i < MAX_CLIENTS; i++) + if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code) + break; + if (i == MAX_CLIENTS) + break; + } + ctfgame.ghosts[ghost].ent = ent; + strcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname); + ent->client->resp.ghost = ctfgame.ghosts + ghost; + gi.cprintf(ent, PRINT_CHAT, "Your ghost code is **** %d ****\n", ctfgame.ghosts[ghost].code); + gi.cprintf(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score " + "intact by typing \"ghost %d\".\n", ctfgame.ghosts[ghost].code); +} + +// start a match +void CTFStartMatch(void) +{ + int i; + edict_t *ent; + int ghost = 0; + + ctfgame.match = MATCH_GAME; + ctfgame.matchtime = level.time + matchtime->value * 60; + + ctfgame.team1 = ctfgame.team2 = 0; + + memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts)); + + for (i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + + ent->client->resp.score = 0; + ent->client->resp.ctf_state = 0; + ent->client->resp.ghost = NULL; + + gi.centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************"); + + if (ent->client->resp.ctf_team != CTF_NOTEAM) { + // make up a ghost code + CTFAssignGhost(ent); + CTFPlayerResetGrapple(ent); + ent->svflags = SVF_NOCLIENT; + ent->flags &= ~FL_GODMODE; + + ent->client->respawn_time = level.time + 1.0 + ((rand()%30)/10.0); + ent->client->ps.pmove.pm_type = PM_DEAD; + ent->client->anim_priority = ANIM_DEATH; + ent->s.frame = FRAME_death308-1; + ent->client->anim_end = FRAME_death308; + ent->deadflag = DEAD_DEAD; + ent->movetype = MOVETYPE_NOCLIP; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + } + } +} + +void CTFEndMatch(void) +{ + ctfgame.match = MATCH_POST; + gi.bprintf(PRINT_CHAT, "MATCH COMPLETED!\n"); + + CTFCalcScores(); + + gi.bprintf(PRINT_HIGH, "RED TEAM: %d captures, %d points\n", + ctfgame.team1, ctfgame.total1); + gi.bprintf(PRINT_HIGH, "BLUE TEAM: %d captures, %d points\n", + ctfgame.team2, ctfgame.total2); + + if (ctfgame.team1 > ctfgame.team2) + gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d CAPTURES!\n", + ctfgame.team1 - ctfgame.team2); + else if (ctfgame.team2 > ctfgame.team1) + gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d CAPTURES!\n", + ctfgame.team2 - ctfgame.team1); + else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker + gi.bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d POINTS!\n", + ctfgame.total1 - ctfgame.total2); + else if (ctfgame.total2 > ctfgame.total1) + gi.bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d POINTS!\n", + ctfgame.total2 - ctfgame.total1); + else + gi.bprintf(PRINT_CHAT, "TIE GAME!\n"); + + EndDMLevel(); +} + +qboolean CTFNextMap(void) +{ + if (ctfgame.match == MATCH_POST) { + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + return true; + } + return false; +} + +void CTFWinElection(void) +{ + switch (ctfgame.election) { + case ELECT_MATCH : + // reset into match mode + if (competition->value < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + break; + + case ELECT_ADMIN : + ctfgame.etarget->client->resp.admin = true; + gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ctfgame.etarget->client->pers.netname); + gi.cprintf(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + break; + + case ELECT_MAP : + gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", + ctfgame.etarget->client->pers.netname, ctfgame.elevel); + strncpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap) - 1); + EndDMLevel(); + break; + } + ctfgame.election = ELECT_NONE; +} + +void CTFVoteYes(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) { + gi.cprintf(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) { + gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + ctfgame.evotes++; + if (ctfgame.evotes == ctfgame.needvotes) { + // the election has been won + CTFWinElection(); + return; + } + gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); +} + +void CTFVoteNo(edict_t *ent) +{ + if (ctfgame.election == ELECT_NONE) { + gi.cprintf(ent, PRINT_HIGH, "No election is in progress.\n"); + return; + } + if (ent->client->resp.voted) { + gi.cprintf(ent, PRINT_HIGH, "You already voted.\n"); + return; + } + if (ctfgame.etarget == ent) { + gi.cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n"); + return; + } + + ent->client->resp.voted = true; + + gi.bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg); + gi.bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes, + (int)(ctfgame.electtime - level.time)); +} + +void CTFReady(edict_t *ent) +{ + int i, j; + edict_t *e; + int t1, t2; + + if (ent->client->resp.ctf_team == CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP) { + gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (ent->client->resp.ready) { + gi.cprintf(ent, PRINT_HIGH, "You have already commited.\n"); + return; + } + + ent->client->resp.ready = true; + gi.bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname); + + t1 = t2 = 0; + for (j = 0, i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (!e->inuse) + continue; + if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready) + j++; + if (e->client->resp.ctf_team == CTF_TEAM1) + t1++; + else if (e->client->resp.ctf_team == CTF_TEAM2) + t2++; + } + if (!j && t1 && t2) { + // everyone has commited + gi.bprintf(PRINT_CHAT, "All players have commited. Match starting\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + matchstarttime->value; + } +} + +void CTFNotReady(edict_t *ent) +{ + if (ent->client->resp.ctf_team == CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "Pick a team first (hit for menu)\n"); + return; + } + + if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) { + gi.cprintf(ent, PRINT_HIGH, "A match is not being setup.\n"); + return; + } + + if (!ent->client->resp.ready) { + gi.cprintf(ent, PRINT_HIGH, "You haven't commited.\n"); + return; + } + + ent->client->resp.ready = false; + gi.bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname); + + if (ctfgame.match == MATCH_PREGAME) { + gi.bprintf(PRINT_CHAT, "Match halted.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } +} + +void CTFGhost(edict_t *ent) +{ + int i; + int n; + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Usage: ghost \n"); + return; + } + + if (ent->client->resp.ctf_team != CTF_NOTEAM) { + gi.cprintf(ent, PRINT_HIGH, "You are already in the game.\n"); + return; + } + if (ctfgame.match != MATCH_GAME) { + gi.cprintf(ent, PRINT_HIGH, "No match is in progress.\n"); + return; + } + + n = atoi(gi.argv(1)); + + for (i = 0; i < MAX_CLIENTS; i++) { + if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) { + gi.cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n"); + ctfgame.ghosts[i].ent->client->resp.ghost = NULL; + ent->client->resp.ctf_team = ctfgame.ghosts[i].team; + ent->client->resp.ghost = ctfgame.ghosts + i; + ent->client->resp.score = ctfgame.ghosts[i].score; + ent->client->resp.ctf_state = 0; + ctfgame.ghosts[i].ent = ent; + ent->svflags = 0; + ent->flags &= ~FL_GODMODE; + PutClientInServer(ent); + gi.bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n", + ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team)); + return; + } + } + gi.cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n"); +} + +qboolean CTFMatchSetup(void) +{ + if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) + return true; + return false; +} + +qboolean CTFMatchOn(void) +{ + if (ctfgame.match == MATCH_GAME) + return true; + return false; +} + + +/*-----------------------------------------------------------------------*/ + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p); +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p); +void CTFCredits(edict_t *ent, pmenuhnd_t *p); +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p); +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p); + +pmenu_t creditsmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "*Programming", PMENU_ALIGN_CENTER, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL }, + { "*Level Design", PMENU_ALIGN_CENTER, NULL }, + { "Christian Antkow", PMENU_ALIGN_CENTER, NULL }, + { "Tim Willits", PMENU_ALIGN_CENTER, NULL }, + { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL }, + { "*Art", PMENU_ALIGN_CENTER, NULL }, + { "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, NULL }, + { "Kevin Cloud", PMENU_ALIGN_CENTER, NULL }, + { "*Sound", PMENU_ALIGN_CENTER, NULL }, + { "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, NULL }, + { "*Original CTF Art Design", PMENU_ALIGN_CENTER, NULL }, + { "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain } +}; + +static const int jmenu_level = 2; +static const int jmenu_match = 3; +static const int jmenu_red = 5; +static const int jmenu_blue = 7; +static const int jmenu_chase = 9; +static const int jmenu_reqmatch = 11; + +pmenu_t joinmenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Join Red Team", PMENU_ALIGN_LEFT, CTFJoinTeam1 }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Join Blue Team", PMENU_ALIGN_LEFT, CTFJoinTeam2 }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Chase Camera", PMENU_ALIGN_LEFT, CTFChaseCam }, + { "Credits", PMENU_ALIGN_LEFT, CTFCredits }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL }, + { "ENTER to select", PMENU_ALIGN_LEFT, NULL }, + { "ESC to Exit Menu", PMENU_ALIGN_LEFT, NULL }, + { "(TAB to Return)", PMENU_ALIGN_LEFT, NULL }, + { "v" CTF_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL }, +}; + +pmenu_t nochasemenu[] = { + { "*Quake II", PMENU_ALIGN_CENTER, NULL }, + { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "No one to chase", PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain } +}; + +void CTFJoinTeam(edict_t *ent, int desired_team) +{ + char *s; + + PMenu_Close(ent); + + ent->svflags &= ~SVF_NOCLIENT; + ent->client->resp.ctf_team = desired_team; + ent->client->resp.ctf_state = 0; + s = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + CTFAssignSkin(ent, s); + + // assign a ghost if we are in match mode + if (ctfgame.match == MATCH_GAME) { + if (ent->client->resp.ghost) + ent->client->resp.ghost->code = 0; + ent->client->resp.ghost = NULL; + CTFAssignGhost(ent); + } + + PutClientInServer (ent); + // add a teleportation effect + ent->s.event = EV_PLAYER_TELEPORT; + // hold in place briefly + ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + ent->client->ps.pmove.pm_time = 14; + gi.bprintf(PRINT_HIGH, "%s joined the %s team.\n", + ent->client->pers.netname, CTFTeamName(desired_team)); + + if (ctfgame.match == MATCH_SETUP) { + gi.centerprintf(ent, "***********************\n" + "Type \"ready\" in console\n" + "to ready up.\n" + "***********************"); + } +} + +void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM1); +} + +void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p) +{ + CTFJoinTeam(ent, CTF_TEAM2); +} + +void CTFChaseCam(edict_t *ent, pmenuhnd_t *p) +{ + int i; + edict_t *e; + + if (ent->client->chase_target) { + ent->client->chase_target = NULL; + PMenu_Close(ent); + return; + } + + for (i = 1; i <= maxclients->value; i++) { + e = g_edicts + i; + if (e->inuse && e->solid != SOLID_NOT) { + ent->client->chase_target = e; + PMenu_Close(ent); + ent->client->update_chase = true; + return; + } + } + + SetLevelName(nochasemenu + jmenu_level); + + PMenu_Close(ent); + PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), NULL); +} + +void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + CTFOpenJoinMenu(ent); +} + +void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p) +{ + char text[1024]; + + PMenu_Close(ent); + + sprintf(text, "%s has requested to switch to competition mode.", + ent->client->pers.netname); + CTFBeginElection(ent, ELECT_MATCH, text); +} + +void DeathmatchScoreboard (edict_t *ent); + +void CTFShowScores(edict_t *ent, pmenu_t *p) +{ + PMenu_Close(ent); + + ent->client->showscores = true; + ent->client->showinventory = false; + DeathmatchScoreboard (ent); +} + +int CTFUpdateJoinMenu(edict_t *ent) +{ + static char team1players[32]; + static char team2players[32]; + int num1, num2, i; + + if (ctfgame.match >= MATCH_PREGAME && matchlock->value) { + joinmenu[jmenu_red].text = "MATCH IS LOCKED"; + joinmenu[jmenu_red].SelectFunc = NULL; + joinmenu[jmenu_blue].text = " (entry is not permitted)"; + joinmenu[jmenu_blue].SelectFunc = NULL; + } else { + if (ctfgame.match >= MATCH_PREGAME) { + joinmenu[jmenu_red].text = "Join Red MATCH Team"; + joinmenu[jmenu_blue].text = "Join Blue MATCH Team"; + } else { + joinmenu[jmenu_red].text = "Join Red Team"; + joinmenu[jmenu_blue].text = "Join Blue Team"; + } + joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1; + joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2; + } + + if (ctf_forcejoin->string && *ctf_forcejoin->string) { + if (stricmp(ctf_forcejoin->string, "red") == 0) { + joinmenu[jmenu_blue].text = NULL; + joinmenu[jmenu_blue].SelectFunc = NULL; + } else if (stricmp(ctf_forcejoin->string, "blue") == 0) { + joinmenu[jmenu_red].text = NULL; + joinmenu[jmenu_red].SelectFunc = NULL; + } + } + + if (ent->client->chase_target) + joinmenu[jmenu_chase].text = "Leave Chase Camera"; + else + joinmenu[jmenu_chase].text = "Chase Camera"; + + SetLevelName(joinmenu + jmenu_level); + + num1 = num2 = 0; + for (i = 0; i < maxclients->value; i++) { + if (!g_edicts[i+1].inuse) + continue; + if (game.clients[i].resp.ctf_team == CTF_TEAM1) + num1++; + else if (game.clients[i].resp.ctf_team == CTF_TEAM2) + num2++; + } + + sprintf(team1players, " (%d players)", num1); + sprintf(team2players, " (%d players)", num2); + + switch (ctfgame.match) { + case MATCH_NONE : + joinmenu[jmenu_match].text = NULL; + break; + + case MATCH_SETUP : + joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS"; + break; + + case MATCH_PREGAME : + joinmenu[jmenu_match].text = "*MATCH STARTING"; + break; + + case MATCH_GAME : + joinmenu[jmenu_match].text = "*MATCH IN PROGRESS"; + break; + } + + if (joinmenu[jmenu_red].text) + joinmenu[jmenu_red+1].text = team1players; + else + joinmenu[jmenu_red+1].text = NULL; + if (joinmenu[jmenu_blue].text) + joinmenu[jmenu_blue+1].text = team2players; + else + joinmenu[jmenu_blue+1].text = NULL; + + joinmenu[jmenu_reqmatch].text = NULL; + joinmenu[jmenu_reqmatch].SelectFunc = NULL; + if (competition->value && ctfgame.match < MATCH_SETUP) { + joinmenu[jmenu_reqmatch].text = "Request Match"; + joinmenu[jmenu_reqmatch].SelectFunc = CTFRequestMatch; + } + + if (num1 > num2) + return CTF_TEAM1; + else if (num2 > num1) + return CTF_TEAM2; + return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2; +} + +void CTFOpenJoinMenu(edict_t *ent) +{ + int team; + + team = CTFUpdateJoinMenu(ent); + if (ent->client->chase_target) + team = 8; + else if (team == CTF_TEAM1) + team = 4; + else + team = 6; + PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL); +} + +void CTFCredits(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL); +} + +qboolean CTFStartClient(edict_t *ent) +{ + if (ent->client->resp.ctf_team != CTF_NOTEAM) + return false; + + if (!((int)dmflags->value & DF_CTF_FORCEJOIN) || ctfgame.match >= MATCH_SETUP) { + // start as 'observer' + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + gi.linkentity (ent); + + CTFOpenJoinMenu(ent); + return true; + } + return false; +} + +void CTFObserver(edict_t *ent) +{ + // start as 'observer' + if (ent->movetype == MOVETYPE_NOCLIP) { + gi.cprintf(ent, PRINT_HIGH, "You are already an observer.\n"); + return; + } + + CTFPlayerResetGrapple(ent); + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); + + ent->movetype = MOVETYPE_NOCLIP; + ent->solid = SOLID_NOT; + ent->svflags |= SVF_NOCLIENT; + ent->client->resp.ctf_team = CTF_NOTEAM; + ent->client->ps.gunindex = 0; + ent->client->resp.score = 0; + gi.linkentity (ent); + CTFOpenJoinMenu(ent); +} + +qboolean CTFInMatch(void) +{ + if (ctfgame.match > MATCH_NONE) + return true; + return false; +} + +qboolean CTFCheckRules(void) +{ + int t; + int i, j; + char text[64]; + edict_t *ent; + + if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) { + gi.bprintf(PRINT_CHAT, "Election timed out and has been cancelled.\n"); + ctfgame.election = ELECT_NONE; + } + + if (ctfgame.match != MATCH_NONE) { + t = ctfgame.matchtime - level.time; + + if (t <= 0) { // time ended on something + switch (ctfgame.match) { + case MATCH_SETUP : + // go back to normal mode + if (competition->value < 3) { + ctfgame.match = MATCH_NONE; + gi.cvar_set("competition", "1"); + CTFResetAllPlayers(); + } else { + // reset the time + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + } + return false; + + case MATCH_PREGAME : + // match started! + CTFStartMatch(); + return false; + + case MATCH_GAME : + // match ended! + CTFEndMatch(); + return false; + } + } + + if (t == ctfgame.lasttime) + return false; + + ctfgame.lasttime = t; + + switch (ctfgame.match) { + case MATCH_SETUP : + for (j = 0, i = 1; i <= maxclients->value; i++) { + ent = g_edicts + i; + if (!ent->inuse) + continue; + if (ent->client->resp.ctf_team != CTF_NOTEAM && + !ent->client->resp.ready) + j++; + } + + if (competition->value < 3) + sprintf(text, "%02d:%02d SETUP: %d not ready", + t / 60, t % 60, j); + else + sprintf(text, "SETUP: %d not ready", j); + + gi.configstring (CONFIG_CTF_MATCH, text); + break; + + + case MATCH_PREGAME : + sprintf(text, "%02d:%02d UNTIL START", + t / 60, t % 60); + gi.configstring (CONFIG_CTF_MATCH, text); + break; + + case MATCH_GAME: + sprintf(text, "%02d:%02d MATCH", + t / 60, t % 60); + gi.configstring (CONFIG_CTF_MATCH, text); + break; + } + return false; + } + + if (capturelimit->value && + (ctfgame.team1 >= capturelimit->value || + ctfgame.team2 >= capturelimit->value)) { + gi.bprintf (PRINT_HIGH, "Capturelimit hit.\n"); + return true; + } + return false; +} + +/*-------------------------------------------------------------------------- + * just here to help old map conversions + *--------------------------------------------------------------------------*/ + +static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + vec3_t forward; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); +// other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->enemy->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + other->s.angles[PITCH] = 0; + other->s.angles[YAW] = dest->s.angles[YAW]; + other->s.angles[ROLL] = 0; + VectorCopy (dest->s.angles, other->client->ps.viewangles); + VectorCopy (dest->s.angles, other->client->v_angle); + + // give a little forward velocity + AngleVectors (other->client->v_angle, forward, NULL, NULL); + VectorScale(forward, 200, other->velocity); + + // kill anything at the destination + if (!KillBox (other)) + { + } + + gi.linkentity (other); +} + +/*QUAKED trigger_teleport (0.5 0.5 0.5) ? +Players touching this will be teleported +*/ +void SP_trigger_teleport (edict_t *ent) +{ + edict_t *s; + int i; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + ent->touch = old_teleporter_touch; + gi.setmodel (ent, ent->model); + gi.linkentity (ent); + + // noise maker and splash effect dude + s = G_Spawn(); + ent->enemy = s; + for (i = 0; i < 3; i++) + s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2; + s->s.sound = gi.soundindex ("world/hum1.wav"); + gi.linkentity(s); + +} + +/*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) +Point trigger_teleports at these. +*/ +void SP_info_teleport_destination (edict_t *ent) +{ + ent->s.origin[2] += 16; +} + +/*----------------------------------------------------------------------------------*/ +/* ADMIN */ + +typedef struct admin_settings_s { + int matchlen; + int matchsetuplen; + int matchstartlen; + qboolean weaponsstay; + qboolean instantitems; + qboolean quaddrop; + qboolean instantweap; + qboolean matchlock; +} admin_settings_t; + +#define SETMENU_SIZE (7 + 5) + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu); +void CTFOpenAdminMenu(edict_t *ent); + +void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + char st[80]; + int i; + + if (settings->matchlen != matchtime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match length to %d minutes.\n", + ent->client->pers.netname, settings->matchlen); + if (ctfgame.match == MATCH_GAME) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchtime->value*60) + settings->matchlen*60; + } + sprintf(st, "%d", settings->matchlen); + gi.cvar_set("matchtime", st); + } + + if (settings->matchsetuplen != matchsetuptime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match setup time to %d minutes.\n", + ent->client->pers.netname, settings->matchsetuplen); + if (ctfgame.match == MATCH_SETUP) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchsetuptime->value*60) + settings->matchsetuplen*60; + } + sprintf(st, "%d", settings->matchsetuplen); + gi.cvar_set("matchsetuptime", st); + } + + if (settings->matchstartlen != matchstarttime->value) { + gi.bprintf(PRINT_HIGH, "%s changed the match start time to %d seconds.\n", + ent->client->pers.netname, settings->matchstartlen); + if (ctfgame.match == MATCH_PREGAME) { + // in the middle of a match, change it on the fly + ctfgame.matchtime = (ctfgame.matchtime - matchstarttime->value) + settings->matchstartlen; + } + sprintf(st, "%d", settings->matchstartlen); + gi.cvar_set("matchstarttime", st); + } + + if (settings->weaponsstay != !!((int)dmflags->value & DF_WEAPONS_STAY)) { + gi.bprintf(PRINT_HIGH, "%s turned %s weapons stay.\n", + ent->client->pers.netname, settings->weaponsstay ? "on" : "off"); + i = (int)dmflags->value; + if (settings->weaponsstay) + i |= DF_WEAPONS_STAY; + else + i &= ~DF_WEAPONS_STAY; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->instantitems != !!((int)dmflags->value & DF_INSTANT_ITEMS)) { + gi.bprintf(PRINT_HIGH, "%s turned %s instant items.\n", + ent->client->pers.netname, settings->instantitems ? "on" : "off"); + i = (int)dmflags->value; + if (settings->instantitems) + i |= DF_INSTANT_ITEMS; + else + i &= ~DF_INSTANT_ITEMS; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->quaddrop != !!((int)dmflags->value & DF_QUAD_DROP)) { + gi.bprintf(PRINT_HIGH, "%s turned %s quad drop.\n", + ent->client->pers.netname, settings->quaddrop ? "on" : "off"); + i = (int)dmflags->value; + if (settings->quaddrop) + i |= DF_QUAD_DROP; + else + i &= ~DF_QUAD_DROP; + sprintf(st, "%d", i); + gi.cvar_set("dmflags", st); + } + + if (settings->instantweap != !!((int)instantweap->value)) { + gi.bprintf(PRINT_HIGH, "%s turned %s instant weapons.\n", + ent->client->pers.netname, settings->instantweap ? "on" : "off"); + sprintf(st, "%d", (int)settings->instantweap); + gi.cvar_set("instantweap", st); + } + + if (settings->matchlock != !!((int)matchlock->value)) { + gi.bprintf(PRINT_HIGH, "%s turned %s match lock.\n", + ent->client->pers.netname, settings->matchlock ? "on" : "off"); + sprintf(st, "%d", (int)settings->matchlock); + gi.cvar_set("matchlock", st); + } + + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + PMenu_Close(ent); + CTFOpenAdminMenu(ent); +} + +void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchlen = (settings->matchlen % 60) + 5; + if (settings->matchlen < 5) + settings->matchlen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchsetuplen = (settings->matchsetuplen % 60) + 5; + if (settings->matchsetuplen < 5) + settings->matchsetuplen = 5; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchstartlen = (settings->matchstartlen % 600) + 10; + if (settings->matchstartlen < 20) + settings->matchstartlen = 20; + + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->weaponsstay = !settings->weaponsstay; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->instantitems = !settings->instantitems; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->quaddrop = !settings->quaddrop; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->instantweap = !settings->instantweap; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings = p->arg; + + settings->matchlock = !settings->matchlock; + CTFAdmin_UpdateSettings(ent, p); +} + +void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu) +{ + int i = 2; + char text[64]; + admin_settings_t *settings = setmenu->arg; + + sprintf(text, "Match Len: %2d mins", settings->matchlen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen); + i++; + + sprintf(text, "Match Setup Len: %2d mins", settings->matchsetuplen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen); + i++; + + sprintf(text, "Match Start Len: %2d secs", settings->matchstartlen); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen); + i++; + + sprintf(text, "Weapons Stay: %s", settings->weaponsstay ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay); + i++; + + sprintf(text, "Instant Items: %s", settings->instantitems ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems); + i++; + + sprintf(text, "Quad Drop: %s", settings->quaddrop ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop); + i++; + + sprintf(text, "Instant Weapons: %s", settings->instantweap ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap); + i++; + + sprintf(text, "Match Lock: %s", settings->matchlock ? "Yes" : "No"); + PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock); + i++; + + PMenu_Update(ent); +} + +pmenu_t def_setmenu[] = { + { "*Settings Menu", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchlen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchsetuplen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchstartlen; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean weaponsstay; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantitems; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean quaddrop; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantweap; + { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean matchlock; + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Apply", PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel } +}; + +void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p) +{ + admin_settings_t *settings; + pmenuhnd_t *menu; + + PMenu_Close(ent); + + settings = malloc(sizeof(*settings)); + + settings->matchlen = matchtime->value; + settings->matchsetuplen = matchsetuptime->value; + settings->matchstartlen = matchstarttime->value; + settings->weaponsstay = !!((int)dmflags->value & DF_WEAPONS_STAY); + settings->instantitems = !!((int)dmflags->value & DF_INSTANT_ITEMS); + settings->quaddrop = !!((int)dmflags->value & DF_QUAD_DROP); + settings->instantweap = instantweap->value != 0; + settings->matchlock = matchlock->value != 0; + + menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings); + CTFAdmin_UpdateSettings(ent, menu); +} + +void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match == MATCH_SETUP) { + gi.bprintf(PRINT_CHAT, "Match has been forced to start.\n"); + ctfgame.match = MATCH_PREGAME; + ctfgame.matchtime = level.time + matchstarttime->value; + } else if (ctfgame.match == MATCH_GAME) { + gi.bprintf(PRINT_CHAT, "Match has been forced to terminate.\n"); + ctfgame.match = MATCH_SETUP; + ctfgame.matchtime = level.time + matchsetuptime->value * 60; + CTFResetAllPlayers(); + } +} + +void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); + + if (ctfgame.match != MATCH_SETUP) { + if (competition->value < 3) + gi.cvar_set("competition", "2"); + ctfgame.match = MATCH_SETUP; + CTFResetAllPlayers(); + } +} + +void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p) +{ + PMenu_Close(ent); +} + + +pmenu_t adminmenu[] = { + { "*Administration Menu", PMENU_ALIGN_CENTER, NULL }, + { NULL, PMENU_ALIGN_CENTER, NULL }, // blank + { "Settings", PMENU_ALIGN_LEFT, CTFAdmin_Settings }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { NULL, PMENU_ALIGN_LEFT, NULL }, + { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_Cancel }, + { NULL, PMENU_ALIGN_CENTER, NULL }, +}; + +void CTFOpenAdminMenu(edict_t *ent) +{ + adminmenu[3].text = NULL; + adminmenu[3].SelectFunc = NULL; + if (ctfgame.match == MATCH_SETUP) { + adminmenu[3].text = "Force start match"; + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + } else if (ctfgame.match == MATCH_GAME) { + adminmenu[3].text = "Cancel match"; + adminmenu[3].SelectFunc = CTFAdmin_MatchSet; + } else if (ctfgame.match == MATCH_NONE && competition->value) { + adminmenu[3].text = "Switch to match mode"; + adminmenu[3].SelectFunc = CTFAdmin_MatchMode; + } + +// if (ent->client->menu) +// PMenu_Close(ent->client->menu); + + PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), NULL); +} + +void CTFAdmin(edict_t *ent) +{ + char text[1024]; + + if (gi.argc() > 1 && admin_password->string && *admin_password->string && + !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) { + ent->client->resp.admin = true; + gi.bprintf(PRINT_HIGH, "%s has become an admin.\n", ent->client->pers.netname); + gi.cprintf(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n"); + } + + if (!ent->client->resp.admin) { + sprintf(text, "%s has requested admin rights.", + ent->client->pers.netname); + CTFBeginElection(ent, ELECT_ADMIN, text); + return; + } + + if (ent->client->menu) + PMenu_Close(ent); + + CTFOpenAdminMenu(ent); +} + +/*----------------------------------------------------------------*/ + +void CTFStats(edict_t *ent) +{ + int i, e; + ghost_t *g; + char st[80]; + char text[1400]; + edict_t *e2; + + *text = 0; + if (ctfgame.match == MATCH_SETUP) { + for (i = 1; i <= maxclients->value; i++) { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) { + sprintf(st, "%s is not ready.\n", e2->client->pers.netname); + if (strlen(text) + strlen(st) < sizeof(text) - 50) + strcat(text, st); + } + } + } + + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) + if (g->ent) + break; + + if (i == MAX_CLIENTS) { + if (*text) + gi.cprintf(ent, PRINT_HIGH, "%s", text); + gi.cprintf(ent, PRINT_HIGH, "No statistics available.\n"); + return; + } + + strcat(text, " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n"); + + for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) { + if (!*g->netname) + continue; + + if (g->deaths + g->kills == 0) + e = 50; + else + e = g->kills * 100 / (g->kills + g->deaths); + sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n", + g->number, + g->netname, + g->score, + g->kills, + g->deaths, + g->basedef, + g->carrierdef, + e); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + +void CTFPlayerList(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + + *text = 0; + if (ctfgame.match == MATCH_SETUP) { + for (i = 1; i <= maxclients->value; i++) { + e2 = g_edicts + i; + if (!e2->inuse) + continue; + if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) { + sprintf(st, "%s is not ready.\n", e2->client->pers.netname); + if (strlen(text) + strlen(st) < sizeof(text) - 50) + strcat(text, st); + } + } + } + + // number, name, connect time, ping, score, admin + + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + sprintf(st, "%3d %-16.16s %02d:%02d %4d %3d%s%s\n", + i + 1, + e2->client->pers.netname, + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ? + (e2->client->resp.ready ? " (ready)" : " (notready)") : "", + e2->client->resp.admin ? " (admin)" : ""); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +void CTFWarp(edict_t *ent) +{ + char text[1024]; + char *mlist, *token; + static const char *seps = " \t\n\r"; + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Where do you want to warp to?\n"); + gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string); + return; + } + + mlist = strdup(warp_list->string); + + token = strtok(mlist, seps); + while (token != NULL) { + if (Q_stricmp(token, gi.argv(1)) == 0) + break; + token = strtok(NULL, seps); + } + + if (token == NULL) { + gi.cprintf(ent, PRINT_HIGH, "Unknown CTF level.\n"); + gi.cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string); + free(mlist); + return; + } + + free(mlist); + + + if (ent->client->resp.admin) { + gi.bprintf(PRINT_HIGH, "%s is warping to level %s.\n", + ent->client->pers.netname, gi.argv(1)); + strncpy(level.forcemap, gi.argv(1), sizeof(level.forcemap) - 1); + EndDMLevel(); + return; + } + + sprintf(text, "%s has requested warping to level %s.", + ent->client->pers.netname, gi.argv(1)); + if (CTFBeginElection(ent, ELECT_MAP, text)) + strncpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel) - 1); +} + +void CTFBoot(edict_t *ent) +{ + int i; + edict_t *targ; + char text[80]; + + if (!ent->client->resp.admin) { + gi.cprintf(ent, PRINT_HIGH, "You are not an admin.\n"); + return; + } + + if (gi.argc() < 2) { + gi.cprintf(ent, PRINT_HIGH, "Who do you want to kick?\n"); + return; + } + + if (*gi.argv(1) < '0' && *gi.argv(1) > '9') { + gi.cprintf(ent, PRINT_HIGH, "Specify the player number to kick.\n"); + return; + } + + i = atoi(gi.argv(1)); + if (i < 1 || i > maxclients->value) { + gi.cprintf(ent, PRINT_HIGH, "Invalid player number.\n"); + return; + } + + targ = g_edicts + i; + if (!targ->inuse) { + gi.cprintf(ent, PRINT_HIGH, "That player number is not connected.\n"); + return; + } + + sprintf(text, "kick %d\n", i - 1); + gi.AddCommandString(text); +} + + + diff --git a/ctf/g_ctf.h b/ctf/g_ctf.h new file mode 100644 index 000000000..5ab400dc0 --- /dev/null +++ b/ctf/g_ctf.h @@ -0,0 +1,185 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#define CTF_VERSION 1.09b +#define CTF_VSTRING2(x) #x +#define CTF_VSTRING(x) CTF_VSTRING2(x) +#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION) + +#define STAT_CTF_TEAM1_PIC 17 +#define STAT_CTF_TEAM1_CAPS 18 +#define STAT_CTF_TEAM2_PIC 19 +#define STAT_CTF_TEAM2_CAPS 20 +#define STAT_CTF_FLAG_PIC 21 +#define STAT_CTF_JOINED_TEAM1_PIC 22 +#define STAT_CTF_JOINED_TEAM2_PIC 23 +#define STAT_CTF_TEAM1_HEADER 24 +#define STAT_CTF_TEAM2_HEADER 25 +#define STAT_CTF_TECH 26 +#define STAT_CTF_ID_VIEW 27 +#define STAT_CTF_MATCH 28 + +#define CONFIG_CTF_MATCH (CS_MAXCLIENTS-1) + +typedef enum { + CTF_NOTEAM, + CTF_TEAM1, + CTF_TEAM2 +} ctfteam_t; + +typedef enum { + CTF_GRAPPLE_STATE_FLY, + CTF_GRAPPLE_STATE_PULL, + CTF_GRAPPLE_STATE_HANG +} ctfgrapplestate_t; + +typedef struct ghost_s { + char netname[16]; + int number; + + // stats + int deaths; + int kills; + int caps; + int basedef; + int carrierdef; + + int code; // ghost code + int team; // team + int score; // frags at time of disconnect + edict_t *ent; +} ghost_t; + +extern cvar_t *ctf; + +#define CTF_TEAM1_SKIN "ctf_r" +#define CTF_TEAM2_SKIN "ctf_b" + +#define DF_CTF_FORCEJOIN 131072 +#define DF_ARMOR_PROTECT 262144 +#define DF_CTF_NO_TECH 524288 + +#define CTF_CAPTURE_BONUS 15 // what you get for capture +#define CTF_TEAM_BONUS 10 // what your team gets for capture +#define CTF_RECOVERY_BONUS 1 // what you get for recovery +#define CTF_FLAG_BONUS 0 // what you get for picking up enemy flag +#define CTF_FRAG_CARRIER_BONUS 2 // what you get for fragging enemy flag carrier +#define CTF_FLAG_RETURN_TIME 40 // seconds until auto return + +#define CTF_CARRIER_DANGER_PROTECT_BONUS 2 // bonus for fraggin someone who has recently hurt your flag carrier +#define CTF_CARRIER_PROTECT_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag carrier +#define CTF_FLAG_DEFENSE_BONUS 1 // bonus for fraggin someone while either you or your target are near your flag +#define CTF_RETURN_FLAG_ASSIST_BONUS 1 // awarded for returning a flag that causes a capture to happen almost immediately +#define CTF_FRAG_CARRIER_ASSIST_BONUS 2 // award for fragging a flag carrier if a capture happens almost immediately + +#define CTF_TARGET_PROTECT_RADIUS 400 // the radius around an object being defended where a target will be worth extra frags +#define CTF_ATTACKER_PROTECT_RADIUS 400 // the radius around an object being defended where an attacker will get extra frags when making kills + +#define CTF_CARRIER_DANGER_PROTECT_TIMEOUT 8 +#define CTF_FRAG_CARRIER_ASSIST_TIMEOUT 10 +#define CTF_RETURN_FLAG_ASSIST_TIMEOUT 10 + +#define CTF_AUTO_FLAG_RETURN_TIMEOUT 30 // number of seconds before dropped flag auto-returns + +#define CTF_TECH_TIMEOUT 60 // seconds before techs spawn again + +#define CTF_GRAPPLE_SPEED 650 // speed of grapple in flight +#define CTF_GRAPPLE_PULL_SPEED 650 // speed player is pulled at + +void CTFInit(void); +void CTFSpawn(void); + +void SP_info_player_team1(edict_t *self); +void SP_info_player_team2(edict_t *self); + +char *CTFTeamName(int team); +char *CTFOtherTeamName(int team); +void CTFAssignSkin(edict_t *ent, char *s); +void CTFAssignTeam(gclient_t *who); +edict_t *SelectCTFSpawnPoint (edict_t *ent); +qboolean CTFPickup_Flag(edict_t *ent, edict_t *other); +qboolean CTFDrop_Flag(edict_t *ent, gitem_t *item); +void CTFEffects(edict_t *player); +void CTFCalcScores(void); +void SetCTFStats(edict_t *ent); +void CTFDeadDropFlag(edict_t *self); +void CTFScoreboardMessage (edict_t *ent, edict_t *killer); +void CTFTeam_f (edict_t *ent); +void CTFID_f (edict_t *ent); +void CTFSay_Team(edict_t *who, char *msg); +void CTFFlagSetup (edict_t *ent); +void CTFResetFlag(int ctf_team); +void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker); +void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker); + +// GRAPPLE +void CTFWeapon_Grapple (edict_t *ent); +void CTFPlayerResetGrapple(edict_t *ent); +void CTFGrapplePull(edict_t *self); +void CTFResetGrapple(edict_t *self); + +//TECH +gitem_t *CTFWhat_Tech(edict_t *ent); +qboolean CTFPickup_Tech (edict_t *ent, edict_t *other); +void CTFDrop_Tech(edict_t *ent, gitem_t *item); +void CTFDeadDropTech(edict_t *ent); +void CTFSetupTechSpawn(void); +int CTFApplyResistance(edict_t *ent, int dmg); +int CTFApplyStrength(edict_t *ent, int dmg); +qboolean CTFApplyStrengthSound(edict_t *ent); +qboolean CTFApplyHaste(edict_t *ent); +void CTFApplyHasteSound(edict_t *ent); +void CTFApplyRegeneration(edict_t *ent); +qboolean CTFHasRegeneration(edict_t *ent); +void CTFRespawnTech(edict_t *ent); +void CTFResetTech(void); + +void CTFOpenJoinMenu(edict_t *ent); +qboolean CTFStartClient(edict_t *ent); +void CTFVoteYes(edict_t *ent); +void CTFVoteNo(edict_t *ent); +void CTFReady(edict_t *ent); +void CTFNotReady(edict_t *ent); +qboolean CTFNextMap(void); +qboolean CTFMatchSetup(void); +qboolean CTFMatchOn(void); +void CTFGhost(edict_t *ent); +void CTFAdmin(edict_t *ent); +qboolean CTFInMatch(void); +void CTFStats(edict_t *ent); +void CTFWarp(edict_t *ent); +void CTFBoot(edict_t *ent); +void CTFPlayerList(edict_t *ent); + +qboolean CTFCheckRules(void); + +void SP_misc_ctf_banner (edict_t *ent); +void SP_misc_ctf_small_banner (edict_t *ent); + +extern char *ctf_statusbar; + +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); + +void CTFObserver(edict_t *ent); + +void SP_trigger_teleport (edict_t *ent); +void SP_info_teleport_destination (edict_t *ent); diff --git a/ctf/g_func.c b/ctf/g_func.c new file mode 100644 index 000000000..70365a01d --- /dev/null +++ b/ctf/g_func.c @@ -0,0 +1,2047 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + allready targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/ctf/g_items.c b/ctf/g_items.c new file mode 100644 index 000000000..56fe2e687 --- /dev/null +++ b/ctf/g_items.c @@ -0,0 +1,2446 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + +//ZOID +//in ctf, when we are weapons stay, only the master of a team of weapons +//is spawned + if (ctf->value && + ((int)dmflags->value & DF_WEAPONS_STAY) && + master->item && (master->item->flags & IT_WEAPON)) + ent = master; + else { +//ZOID + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health +//ZOID + && !CTFHasRegeneration(self->owner) +//ZOID + ) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + +//ZOID + if (other->health >= 250 && ent->count > 25) + return false; +//ZOID + + other->health += ent->count; + +//ZOID + if (other->health > 250 && ent->count > 25) + other->health = 250; +//ZOID + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + +//ZOID + if ((ent->style & HEALTH_TIMED) + && !CTFHasRegeneration(other) +//ZOID + ) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + if (CTFMatchSetup()) + return; // can't pick stuff up right now + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + +//ZOID +//Don't spawn the flags unless enabled + if (!ctf->value && + (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0)) { + G_FreeEdict(ent); + return; + } +//ZOID + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); + +//ZOID +//flags are server animated and have special handling + if (strcmp(ent->classname, "item_flag_team1") == 0 || + strcmp(ent->classname, "item_flag_team2") == 0) { + ent->think = CTFFlagSetup; + } +//ZOID + +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_grapple (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_grapple", + NULL, + Use_Weapon, + NULL, + CTFWeapon_Grapple, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/grapple/tris.md2", +/* icon */ "w_grapple", +/* pickup */ "Grapple", + 0, + 0, + NULL, + IT_WEAPON, + WEAP_GRAPPLE, + NULL, + 0, +/* precache */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav" + }, + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + +#if 0 +//ZOID +/*QUAKED weapon_laser (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_laser", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Laser, + "misc/w_pkup.wav", + "models/weapons/g_laser/tris.md2", EF_ROTATE, + "models/weapons/v_laser/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "Flashlight Laser", + 0, + 1, + "Cells", + IT_WEAPON, + 0, + NULL, + 0, +/* precache */ "" + }, +#endif + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + + +//ZOID +/*QUAKED item_flag_team1 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team1", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag1.md2", EF_FLAG1, + NULL, +/* icon */ "i_ctf1", +/* pickup */ "Red Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/*QUAKED item_flag_team2 (1 0.2 0) (-16 -16 -24) (16 16 32) +*/ + { + "item_flag_team2", + CTFPickup_Flag, + NULL, + CTFDrop_Flag, //Should this be null if we don't want players to drop it manually? + NULL, + "ctf/flagtk.wav", + "players/male/flag2.md2", EF_FLAG2, + NULL, +/* icon */ "i_ctf2", +/* pickup */ "Blue Flag", +/* width */ 2, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "ctf/flagcap.wav" + }, + +/* Resistance Tech */ + { + "item_tech1", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/resistance/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech1", +/* pickup */ "Disruptor Shield", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech1.wav" + }, + +/* Strength Tech */ + { + "item_tech2", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/strength/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech2", +/* pickup */ "Power Amplifier", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech2.wav ctf/tech2x.wav" + }, + +/* Haste Tech */ + { + "item_tech3", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/haste/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech3", +/* pickup */ "Time Accel", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech3.wav" + }, + +/* Regeneration Tech */ + { + "item_tech4", + CTFPickup_Tech, + NULL, + CTFDrop_Tech, //Should this be null if we don't want players to drop it manually? + NULL, + "items/pkup.wav", + "models/ctf/regeneration/tris.md2", EF_ROTATE, + NULL, +/* icon */ "tech4", +/* pickup */ "AutoDoc", +/* width */ 2, + 0, + NULL, + IT_TECH, + 0, + NULL, + 0, +/* precache */ "ctf/tech4.wav" + }, + +//ZOID + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/ctf/g_local.h b/ctf/g_local.h new file mode 100644 index 000000000..8e88222c6 --- /dev/null +++ b/ctf/g_local.h @@ -0,0 +1,1145 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +//ZOID +#include "p_menu.h" +//ZOID + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 +//ZOID +#define IT_TECH 64 +//ZOID + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 +#define WEAP_GRAPPLE 12 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + char forcemap[MAX_QPATH]; // go here + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_GRAPPLE 34 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +//ZOID +extern cvar_t *capturelimit; +extern cvar_t *instantweap; +//ZOID +extern cvar_t *password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +//ZOID +extern qboolean is_quad; +//ZOID + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +qboolean CheckFlood(edict_t *ent); +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); +void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +void Weapon_Generic (edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int FRAME_DEACTIVATE_LAST, int *pause_frames, int *fire_frames, void (*fire)(edict_t *ent)); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); +void EndDMLevel (void); + + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + qboolean powerArmorActive; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc +//ZOID + int ctf_team; // CTF team + int ctf_state; + float ctf_lasthurtcarrier; + float ctf_lastreturnedflag; + float ctf_flagsince; + float ctf_lastfraggedcarrier; + qboolean id_state; + qboolean voted; // for elections + qboolean ready; + qboolean admin; + struct ghost_s *ghost; // for ghost codes +//ZOID + vec3_t cmd_angles; // angles sent over in the last command + int game_helpchanged; + int helpchanged; +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat +//ZOID + qboolean inmenu; // in menu + pmenuhnd_t *menu; // current menu +//ZOID + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + +//ZOID + void *ctf_grapple; // entity of grapple + int ctf_grapplestate; // true if pulling + float ctf_grapplereleasetime; // time of grapple release + float ctf_regentime; // regen tech + float ctf_techsndtime; + float ctf_lasttechmsg; + edict_t *chase_target; + qboolean update_chase; + float menutime; // time to update menu + qboolean menudirty; +//ZOID +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; +}; + +//ZOID +#include "g_ctf.h" +//ZOID + diff --git a/ctf/g_main.c b/ctf/g_main.c new file mode 100644 index 000000000..db734c011 --- /dev/null +++ b/ctf/g_main.c @@ -0,0 +1,427 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +//ZOID +cvar_t *capturelimit; +cvar_t *instantweap; +//ZOID +cvar_t *password; +cvar_t *maxclients; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + if (*level.forcemap) { + BeginIntermission (CreateTargetChangeLevel (level.forcemap) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + +//ZOID + if (ctf->value && CTFCheckRules()) { + EndDMLevel (); + return; + } + if (CTFInMatch()) + return; // no checking in match mode +//ZOID + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + level.exitintermission = 0; + level.intermissiontime = 0; + + if (CTFNextMap()) + return; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + ClientEndServerFrames (); + + level.changemap = NULL; + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/ctf/g_misc.c b/ctf/g_misc.c new file mode 100644 index 000000000..ba130ffd9 --- /dev/null +++ b/ctf/g_misc.c @@ -0,0 +1,1909 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_misc.c + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // allways start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ +//ZOID + //flags are important + if (strcmp(self->classname, "item_flag_team1") == 0) { + CTFResetFlag(CTF_TEAM1); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + if (strcmp(self->classname, "item_flag_team2") == 0) { + CTFResetFlag(CTF_TEAM2); // this will free self! + gi.bprintf(PRINT_HIGH, "The %s flag has returned!\n", + CTFTeamName(CTF_TEAM1)); + return; + } + // techs are important too + if (self->item && (self->item->flags & IT_TECH)) { + CTFRespawnTech(self); // this frees self! + return; + } +//ZOID + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +static int robotron[4]; + +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; +// ent->s.frame = (ent->s.frame + 1) % 9; + ent->nextthink = level.time + FRAMETIME; +// return; + + if (ent->spawnflags) + { + if (ent->s.frame == 0) + { + ent->spawnflags = (ent->spawnflags + 1) % 4 + 1; + ent->s.modelindex = robotron[ent->spawnflags - 1]; + } + } +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); +// ent->s.modelindex = gi.modelindex ("models/player_y/tris.md2"); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + +//ZOID + CTFPlayerResetGrapple(other); +//ZOID + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + diff --git a/ctf/g_monster.c b/ctf/g_monster.c new file mode 100644 index 000000000..c7b954b6d --- /dev/null +++ b/ctf/g_monster.c @@ -0,0 +1,740 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} diff --git a/ctf/g_phys.c b/ctf/g_phys.c new file mode 100644 index 000000000..6de9e36b5 --- /dev/null +++ b/ctf/g_phys.c @@ -0,0 +1,959 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +} diff --git a/ctf/g_save.c b/ctf/g_save.c new file mode 100644 index 000000000..e63a9f903 --- /dev/null +++ b/ctf/g_save.c @@ -0,0 +1,743 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "g_local.h" + +field_t fields[] = { + {"classname", FOFS(classname), F_LSTRING}, + {"origin", FOFS(s.origin), F_VECTOR}, + {"model", FOFS(model), F_LSTRING}, + {"spawnflags", FOFS(spawnflags), F_INT}, + {"speed", FOFS(speed), F_FLOAT}, + {"accel", FOFS(accel), F_FLOAT}, + {"decel", FOFS(decel), F_FLOAT}, + {"target", FOFS(target), F_LSTRING}, + {"targetname", FOFS(targetname), F_LSTRING}, + {"pathtarget", FOFS(pathtarget), F_LSTRING}, + {"deathtarget", FOFS(deathtarget), F_LSTRING}, + {"killtarget", FOFS(killtarget), F_LSTRING}, + {"combattarget", FOFS(combattarget), F_LSTRING}, + {"message", FOFS(message), F_LSTRING}, + {"team", FOFS(team), F_LSTRING}, + {"wait", FOFS(wait), F_FLOAT}, + {"delay", FOFS(delay), F_FLOAT}, + {"random", FOFS(random), F_FLOAT}, + {"move_origin", FOFS(move_origin), F_VECTOR}, + {"move_angles", FOFS(move_angles), F_VECTOR}, + {"style", FOFS(style), F_INT}, + {"count", FOFS(count), F_INT}, + {"health", FOFS(health), F_INT}, + {"sounds", FOFS(sounds), F_INT}, + {"light", 0, F_IGNORE}, + {"dmg", FOFS(dmg), F_INT}, + {"angles", FOFS(s.angles), F_VECTOR}, + {"angle", FOFS(s.angles), F_ANGLEHACK}, + {"mass", FOFS(mass), F_INT}, + {"volume", FOFS(volume), F_FLOAT}, + {"attenuation", FOFS(attenuation), F_FLOAT}, + {"map", FOFS(map), F_LSTRING}, + + // temp spawn vars -- only valid when the spawn function is called + {"lip", STOFS(lip), F_INT, FFL_SPAWNTEMP}, + {"distance", STOFS(distance), F_INT, FFL_SPAWNTEMP}, + {"height", STOFS(height), F_INT, FFL_SPAWNTEMP}, + {"noise", STOFS(noise), F_LSTRING, FFL_SPAWNTEMP}, + {"pausetime", STOFS(pausetime), F_FLOAT, FFL_SPAWNTEMP}, + {"item", STOFS(item), F_LSTRING, FFL_SPAWNTEMP}, + {"gravity", STOFS(gravity), F_LSTRING, FFL_SPAWNTEMP}, + {"sky", STOFS(sky), F_LSTRING, FFL_SPAWNTEMP}, + {"skyrotate", STOFS(skyrotate), F_FLOAT, FFL_SPAWNTEMP}, + {"skyaxis", STOFS(skyaxis), F_VECTOR, FFL_SPAWNTEMP}, + {"minyaw", STOFS(minyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"maxyaw", STOFS(maxyaw), F_FLOAT, FFL_SPAWNTEMP}, + {"minpitch", STOFS(minpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"maxpitch", STOFS(maxpitch), F_FLOAT, FFL_SPAWNTEMP}, + {"nextmap", STOFS(nextmap), F_LSTRING, FFL_SPAWNTEMP} +}; + +// -------- just for savegames ---------- +// all pointer fields should be listed here, or savegames +// won't work properly (they will crash and burn). +// this wasn't just tacked on to the fields array, because +// these don't need names, we wouldn't want map fields using +// some of these, and if one were accidentally present twice +// it would double swizzle (fuck) the pointer. + +field_t savefields[] = +{ + {"", FOFS(classname), F_LSTRING}, + {"", FOFS(target), F_LSTRING}, + {"", FOFS(targetname), F_LSTRING}, + {"", FOFS(killtarget), F_LSTRING}, + {"", FOFS(team), F_LSTRING}, + {"", FOFS(pathtarget), F_LSTRING}, + {"", FOFS(deathtarget), F_LSTRING}, + {"", FOFS(combattarget), F_LSTRING}, + {"", FOFS(model), F_LSTRING}, + {"", FOFS(map), F_LSTRING}, + {"", FOFS(message), F_LSTRING}, + + {"", FOFS(client), F_CLIENT}, + {"", FOFS(item), F_ITEM}, + + {"", FOFS(goalentity), F_EDICT}, + {"", FOFS(movetarget), F_EDICT}, + {"", FOFS(enemy), F_EDICT}, + {"", FOFS(oldenemy), F_EDICT}, + {"", FOFS(activator), F_EDICT}, + {"", FOFS(groundentity), F_EDICT}, + {"", FOFS(teamchain), F_EDICT}, + {"", FOFS(teammaster), F_EDICT}, + {"", FOFS(owner), F_EDICT}, + {"", FOFS(mynoise), F_EDICT}, + {"", FOFS(mynoise2), F_EDICT}, + {"", FOFS(target_ent), F_EDICT}, + {"", FOFS(chain), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t levelfields[] = +{ + {"", LLOFS(changemap), F_LSTRING}, + + {"", LLOFS(sight_client), F_EDICT}, + {"", LLOFS(sight_entity), F_EDICT}, + {"", LLOFS(sound_entity), F_EDICT}, + {"", LLOFS(sound2_entity), F_EDICT}, + + {NULL, 0, F_INT} +}; + +field_t clientfields[] = +{ + {"", CLOFS(pers.weapon), F_ITEM}, + {"", CLOFS(pers.lastweapon), F_ITEM}, + {"", CLOFS(newweapon), F_ITEM}, + + {NULL, 0, F_INT} +}; + +/* +============ +InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is started or a save game +is loaded. +============ +*/ +void InitGame (void) +{ + gi.dprintf ("==== InitGame ====\n"); + + gun_x = gi.cvar ("gun_x", "0", 0); + gun_y = gi.cvar ("gun_y", "0", 0); + gun_z = gi.cvar ("gun_z", "0", 0); + + //FIXME: sv_ prefix is wrong for these + sv_rollspeed = gi.cvar ("sv_rollspeed", "200", 0); + sv_rollangle = gi.cvar ("sv_rollangle", "2", 0); + sv_maxvelocity = gi.cvar ("sv_maxvelocity", "2000", 0); + sv_gravity = gi.cvar ("sv_gravity", "800", 0); + + // noset vars + dedicated = gi.cvar ("dedicated", "0", CVAR_NOSET); + + // latched vars + sv_cheats = gi.cvar ("cheats", "0", CVAR_SERVERINFO|CVAR_LATCH); + gi.cvar ("gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_LATCH); + gi.cvar ("gamedate", __DATE__ , CVAR_SERVERINFO | CVAR_LATCH); + + maxclients = gi.cvar ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); + deathmatch = gi.cvar ("deathmatch", "0", CVAR_LATCH); + coop = gi.cvar ("coop", "0", CVAR_LATCH); + skill = gi.cvar ("skill", "1", CVAR_LATCH); + maxentities = gi.cvar ("maxentities", "1024", CVAR_LATCH); + +//ZOID +//This game.dll only supports deathmatch + if (!deathmatch->value) { + gi.dprintf("Forcing deathmatch."); + gi.cvar_set("deathmatch", "1"); + } + //force coop off + if (coop->value) + gi.cvar_set("coop", "0"); +//ZOID + + + // change anytime vars + dmflags = gi.cvar ("dmflags", "0", CVAR_SERVERINFO); + fraglimit = gi.cvar ("fraglimit", "0", CVAR_SERVERINFO); + timelimit = gi.cvar ("timelimit", "0", CVAR_SERVERINFO); +//ZOID + capturelimit = gi.cvar ("capturelimit", "0", CVAR_SERVERINFO); + instantweap = gi.cvar ("instantweap", "0", CVAR_SERVERINFO); +//ZOID + password = gi.cvar ("password", "", CVAR_USERINFO); + + g_select_empty = gi.cvar ("g_select_empty", "0", CVAR_ARCHIVE); + + run_pitch = gi.cvar ("run_pitch", "0.002", 0); + run_roll = gi.cvar ("run_roll", "0.005", 0); + bob_up = gi.cvar ("bob_up", "0.005", 0); + bob_pitch = gi.cvar ("bob_pitch", "0.002", 0); + bob_roll = gi.cvar ("bob_roll", "0.002", 0); + + // flood control + flood_msgs = gi.cvar ("flood_msgs", "4", 0); + flood_persecond = gi.cvar ("flood_persecond", "4", 0); + flood_waitdelay = gi.cvar ("flood_waitdelay", "10", 0); + + // dm map list + sv_maplist = gi.cvar ("sv_maplist", "", 0); + + // items + InitItems (); + + Com_sprintf (game.helpmessage1, sizeof(game.helpmessage1), ""); + + Com_sprintf (game.helpmessage2, sizeof(game.helpmessage2), ""); + + // initialize all entities for this game + game.maxentities = maxentities->value; + g_edicts = gi.TagMalloc (game.maxentities * sizeof(g_edicts[0]), TAG_GAME); + globals.edicts = g_edicts; + globals.max_edicts = game.maxentities; + + // initialize all clients for this game + game.maxclients = maxclients->value; + game.clients = gi.TagMalloc (game.maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_edicts = game.maxclients+1; + +//ZOID + CTFInit(); +//ZOID +} + +//========================================================= + +void WriteField1 (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + len = strlen(*(char **)p) + 1; + else + len = 0; + *(int *)p = len; + break; + case F_EDICT: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(edict_t **)p - g_edicts; + *(int *)p = index; + break; + case F_CLIENT: + if ( *(gclient_t **)p == NULL) + index = -1; + else + index = *(gclient_t **)p - game.clients; + *(int *)p = index; + break; + case F_ITEM: + if ( *(edict_t **)p == NULL) + index = -1; + else + index = *(gitem_t **)p - itemlist; + *(int *)p = index; + break; + + default: + gi.error ("WriteEdict: unknown field type"); + } +} + +void WriteField2 (FILE *f, field_t *field, byte *base) +{ + int len; + void *p; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_LSTRING: + case F_GSTRING: + if ( *(char **)p ) + { + len = strlen(*(char **)p) + 1; + fwrite (*(char **)p, len, 1, f); + } + break; + } +} + +void ReadField (FILE *f, field_t *field, byte *base) +{ + void *p; + int len; + int index; + + p = (void *)(base + field->ofs); + switch (field->type) + { + case F_INT: + case F_FLOAT: + case F_ANGLEHACK: + case F_VECTOR: + case F_IGNORE: + break; + + case F_LSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_LEVEL); + fread (*(char **)p, len, 1, f); + } + break; + case F_GSTRING: + len = *(int *)p; + if (!len) + *(char **)p = NULL; + else + { + *(char **)p = gi.TagMalloc (len, TAG_GAME); + fread (*(char **)p, len, 1, f); + } + break; + case F_EDICT: + index = *(int *)p; + if ( index == -1 ) + *(edict_t **)p = NULL; + else + *(edict_t **)p = &g_edicts[index]; + break; + case F_CLIENT: + index = *(int *)p; + if ( index == -1 ) + *(gclient_t **)p = NULL; + else + *(gclient_t **)p = &game.clients[index]; + break; + case F_ITEM: + index = *(int *)p; + if ( index == -1 ) + *(gitem_t **)p = NULL; + else + *(gitem_t **)p = &itemlist[index]; + break; + + default: + gi.error ("ReadEdict: unknown field type"); + } +} + +//========================================================= + +/* +============== +WriteClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteClient (FILE *f, gclient_t *client) +{ + field_t *field; + gclient_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = *client; + + // change the pointers to lengths or indexes + for (field=clientfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=clientfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)client); + } +} + +/* +============== +ReadClient + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadClient (FILE *f, gclient_t *client) +{ + field_t *field; + + fread (client, sizeof(*client), 1, f); + + for (field=clientfields ; field->name ; field++) + { + ReadField (f, field, (byte *)client); + } +} + +/* +============ +WriteGame + +This will be called whenever the game goes to a new level, +and when the user explicitly saves the game. + +Game information include cross level data, like multi level +triggers, help computer info, and all client states. + +A single player death will automatically restore from the +last save position. +============ +*/ +void WriteGame (char *filename, qboolean autosave) +{ + FILE *f; + int i; + char str[16]; + + if (!autosave) + SaveClientData (); + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + memset (str, 0, sizeof(str)); + strcpy (str, __DATE__); + fwrite (str, sizeof(str), 1, f); + + game.autosaved = autosave; + fwrite (&game, sizeof(game), 1, f); + game.autosaved = false; + + for (i=0 ; iname ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=savefields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)ent); + } + +} + +/* +============== +WriteLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void WriteLevelLocals (FILE *f) +{ + field_t *field; + level_locals_t temp; + + // all of the ints, floats, and vectors stay as they are + temp = level; + + // change the pointers to lengths or indexes + for (field=levelfields ; field->name ; field++) + { + WriteField1 (f, field, (byte *)&temp); + } + + // write the block + fwrite (&temp, sizeof(temp), 1, f); + + // now write any allocated data following the edict + for (field=levelfields ; field->name ; field++) + { + WriteField2 (f, field, (byte *)&level); + } +} + + +/* +============== +ReadEdict + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadEdict (FILE *f, edict_t *ent) +{ + field_t *field; + + fread (ent, sizeof(*ent), 1, f); + + for (field=savefields ; field->name ; field++) + { + ReadField (f, field, (byte *)ent); + } +} + +/* +============== +ReadLevelLocals + +All pointer variables (except function pointers) must be handled specially. +============== +*/ +void ReadLevelLocals (FILE *f) +{ + field_t *field; + + fread (&level, sizeof(level), 1, f); + + for (field=levelfields ; field->name ; field++) + { + ReadField (f, field, (byte *)&level); + } +} + +/* +================= +WriteLevel + +================= +*/ +void WriteLevel (char *filename) +{ + int i; + edict_t *ent; + FILE *f; + void *base; + + f = fopen (filename, "wb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // write out edict size for checking + i = sizeof(edict_t); + fwrite (&i, sizeof(i), 1, f); + + // write out a function pointer for checking + base = (void *)InitGame; + fwrite (&base, sizeof(base), 1, f); + + // write out level_locals_t + WriteLevelLocals (f); + + // write out all the entities + for (i=0 ; iinuse) + continue; + fwrite (&i, sizeof(i), 1, f); + WriteEdict (f, ent); + } + i = -1; + fwrite (&i, sizeof(i), 1, f); + + fclose (f); +} + + +/* +================= +ReadLevel + +SpawnEntities will allready have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines +set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +void ReadLevel (char *filename) +{ + int entnum; + FILE *f; + int i; + void *base; + edict_t *ent; + + f = fopen (filename, "rb"); + if (!f) + gi.error ("Couldn't open %s", filename); + + // free any dynamic memory allocated by loading the level + // base state + gi.FreeTags (TAG_LEVEL); + + // wipe all the entities + memset (g_edicts, 0, game.maxentities*sizeof(g_edicts[0])); + globals.num_edicts = maxclients->value+1; + + // check edict size + fread (&i, sizeof(i), 1, f); + if (i != sizeof(edict_t)) + { + fclose (f); + gi.error ("ReadLevel: mismatched edict size"); + } + + // check function pointer base address + fread (&base, sizeof(base), 1, f); + if (base != (void *)InitGame) + { + fclose (f); + gi.error ("ReadLevel: function pointers have moved"); + } + + // load the level locals + ReadLevelLocals (f); + + // load all the entities + while (1) + { + if (fread (&entnum, sizeof(entnum), 1, f) != 1) + { + fclose (f); + gi.error ("ReadLevel: failed to read entnum"); + } + if (entnum == -1) + break; + if (entnum >= globals.num_edicts) + globals.num_edicts = entnum+1; + + ent = &g_edicts[entnum]; + ReadEdict (f, ent); + + // let the server rebuild world links for this ent + memset (&ent->area, 0, sizeof(ent->area)); + gi.linkentity (ent); + } + + fclose (f); + + // mark all clients as unconnected + for (i=0 ; ivalue ; i++) + { + ent = &g_edicts[i+1]; + ent->client = game.clients + i; + ent->client->pers.connected = false; + } + + // do any load time things at this point + for (i=0 ; iinuse) + continue; + + // fire any cross-level triggers + if (ent->classname) + if (strcmp(ent->classname, "target_crosslevel_target") == 0) + ent->nextthink = level.time + ent->delay; + } +} + diff --git a/ctf/g_spawn.c b/ctf/g_spawn.c new file mode 100644 index 000000000..7ebad562e --- /dev/null +++ b/ctf/g_spawn.c @@ -0,0 +1,998 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "g_local.h" + +typedef struct +{ + char *name; + void (*spawn)(edict_t *ent); +} spawn_t; + + +void SP_item_health (edict_t *self); +void SP_item_health_small (edict_t *self); +void SP_item_health_large (edict_t *self); +void SP_item_health_mega (edict_t *self); + +void SP_info_player_start (edict_t *ent); +void SP_info_player_deathmatch (edict_t *ent); +void SP_info_player_coop (edict_t *ent); +void SP_info_player_intermission (edict_t *ent); + +void SP_func_plat (edict_t *ent); +void SP_func_rotating (edict_t *ent); +void SP_func_button (edict_t *ent); +void SP_func_door (edict_t *ent); +void SP_func_door_secret (edict_t *ent); +void SP_func_door_rotating (edict_t *ent); +void SP_func_water (edict_t *ent); +void SP_func_train (edict_t *ent); +void SP_func_conveyor (edict_t *self); +void SP_func_wall (edict_t *self); +void SP_func_object (edict_t *self); +void SP_func_explosive (edict_t *self); +void SP_func_timer (edict_t *self); +void SP_func_areaportal (edict_t *ent); +void SP_func_clock (edict_t *ent); +void SP_func_killbox (edict_t *ent); + +void SP_trigger_always (edict_t *ent); +void SP_trigger_once (edict_t *ent); +void SP_trigger_multiple (edict_t *ent); +void SP_trigger_relay (edict_t *ent); +void SP_trigger_push (edict_t *ent); +void SP_trigger_hurt (edict_t *ent); +void SP_trigger_key (edict_t *ent); +void SP_trigger_counter (edict_t *ent); +void SP_trigger_elevator (edict_t *ent); +void SP_trigger_gravity (edict_t *ent); +void SP_trigger_monsterjump (edict_t *ent); + +void SP_target_temp_entity (edict_t *ent); +void SP_target_speaker (edict_t *ent); +void SP_target_explosion (edict_t *ent); +void SP_target_changelevel (edict_t *ent); +void SP_target_secret (edict_t *ent); +void SP_target_goal (edict_t *ent); +void SP_target_splash (edict_t *ent); +void SP_target_spawner (edict_t *ent); +void SP_target_blaster (edict_t *ent); +void SP_target_crosslevel_trigger (edict_t *ent); +void SP_target_crosslevel_target (edict_t *ent); +void SP_target_laser (edict_t *self); +void SP_target_help (edict_t *ent); +void SP_target_actor (edict_t *ent); +void SP_target_lightramp (edict_t *self); +void SP_target_earthquake (edict_t *ent); +void SP_target_character (edict_t *ent); +void SP_target_string (edict_t *ent); + +void SP_worldspawn (edict_t *ent); +void SP_viewthing (edict_t *ent); + +void SP_light (edict_t *self); +void SP_light_mine1 (edict_t *ent); +void SP_light_mine2 (edict_t *ent); +void SP_info_null (edict_t *self); +void SP_info_notnull (edict_t *self); +void SP_path_corner (edict_t *self); +void SP_point_combat (edict_t *self); + +void SP_misc_explobox (edict_t *self); +void SP_misc_banner (edict_t *self); +void SP_misc_satellite_dish (edict_t *self); +void SP_misc_actor (edict_t *self); +void SP_misc_gib_arm (edict_t *self); +void SP_misc_gib_leg (edict_t *self); +void SP_misc_gib_head (edict_t *self); +void SP_misc_insane (edict_t *self); +void SP_misc_deadsoldier (edict_t *self); +void SP_misc_viper (edict_t *self); +void SP_misc_viper_bomb (edict_t *self); +void SP_misc_bigviper (edict_t *self); +void SP_misc_strogg_ship (edict_t *self); +void SP_misc_teleporter (edict_t *self); +void SP_misc_teleporter_dest (edict_t *self); +void SP_misc_blackhole (edict_t *self); +void SP_misc_eastertank (edict_t *self); +void SP_misc_easterchick (edict_t *self); +void SP_misc_easterchick2 (edict_t *self); + +void SP_monster_berserk (edict_t *self); +void SP_monster_gladiator (edict_t *self); +void SP_monster_gunner (edict_t *self); +void SP_monster_infantry (edict_t *self); +void SP_monster_soldier_light (edict_t *self); +void SP_monster_soldier (edict_t *self); +void SP_monster_soldier_ss (edict_t *self); +void SP_monster_tank (edict_t *self); +void SP_monster_medic (edict_t *self); +void SP_monster_flipper (edict_t *self); +void SP_monster_chick (edict_t *self); +void SP_monster_parasite (edict_t *self); +void SP_monster_flyer (edict_t *self); +void SP_monster_brain (edict_t *self); +void SP_monster_floater (edict_t *self); +void SP_monster_hover (edict_t *self); +void SP_monster_mutant (edict_t *self); +void SP_monster_supertank (edict_t *self); +void SP_monster_boss2 (edict_t *self); +void SP_monster_jorg (edict_t *self); +void SP_monster_boss3_stand (edict_t *self); + +void SP_monster_commander_body (edict_t *self); + +void SP_turret_breach (edict_t *self); +void SP_turret_base (edict_t *self); +void SP_turret_driver (edict_t *self); + + +spawn_t spawns[] = { + {"item_health", SP_item_health}, + {"item_health_small", SP_item_health_small}, + {"item_health_large", SP_item_health_large}, + {"item_health_mega", SP_item_health_mega}, + + {"info_player_start", SP_info_player_start}, + {"info_player_deathmatch", SP_info_player_deathmatch}, + {"info_player_coop", SP_info_player_coop}, + {"info_player_intermission", SP_info_player_intermission}, +//ZOID + {"info_player_team1", SP_info_player_team1}, + {"info_player_team2", SP_info_player_team2}, +//ZOID + + {"func_plat", SP_func_plat}, + {"func_button", SP_func_button}, + {"func_door", SP_func_door}, + {"func_door_secret", SP_func_door_secret}, + {"func_door_rotating", SP_func_door_rotating}, + {"func_rotating", SP_func_rotating}, + {"func_train", SP_func_train}, + {"func_water", SP_func_water}, + {"func_conveyor", SP_func_conveyor}, + {"func_areaportal", SP_func_areaportal}, + {"func_clock", SP_func_clock}, + {"func_wall", SP_func_wall}, + {"func_object", SP_func_object}, + {"func_timer", SP_func_timer}, + {"func_explosive", SP_func_explosive}, + {"func_killbox", SP_func_killbox}, + + {"trigger_always", SP_trigger_always}, + {"trigger_once", SP_trigger_once}, + {"trigger_multiple", SP_trigger_multiple}, + {"trigger_relay", SP_trigger_relay}, + {"trigger_push", SP_trigger_push}, + {"trigger_hurt", SP_trigger_hurt}, + {"trigger_key", SP_trigger_key}, + {"trigger_counter", SP_trigger_counter}, + {"trigger_elevator", SP_trigger_elevator}, + {"trigger_gravity", SP_trigger_gravity}, + {"trigger_monsterjump", SP_trigger_monsterjump}, + + {"target_temp_entity", SP_target_temp_entity}, + {"target_speaker", SP_target_speaker}, + {"target_explosion", SP_target_explosion}, + {"target_changelevel", SP_target_changelevel}, + {"target_secret", SP_target_secret}, + {"target_goal", SP_target_goal}, + {"target_splash", SP_target_splash}, + {"target_spawner", SP_target_spawner}, + {"target_blaster", SP_target_blaster}, + {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, + {"target_crosslevel_target", SP_target_crosslevel_target}, + {"target_laser", SP_target_laser}, + {"target_help", SP_target_help}, +#if 0 // remove monster code + {"target_actor", SP_target_actor}, +#endif + {"target_lightramp", SP_target_lightramp}, + {"target_earthquake", SP_target_earthquake}, + {"target_character", SP_target_character}, + {"target_string", SP_target_string}, + + {"worldspawn", SP_worldspawn}, + {"viewthing", SP_viewthing}, + + {"light", SP_light}, + {"light_mine1", SP_light_mine1}, + {"light_mine2", SP_light_mine2}, + {"info_null", SP_info_null}, + {"func_group", SP_info_null}, + {"info_notnull", SP_info_notnull}, + {"path_corner", SP_path_corner}, + {"point_combat", SP_point_combat}, + + {"misc_explobox", SP_misc_explobox}, + {"misc_banner", SP_misc_banner}, +//ZOID + {"misc_ctf_banner", SP_misc_ctf_banner}, + {"misc_ctf_small_banner", SP_misc_ctf_small_banner}, +//ZOID + {"misc_satellite_dish", SP_misc_satellite_dish}, +#if 0 // remove monster code + {"misc_actor", SP_misc_actor}, +#endif + {"misc_gib_arm", SP_misc_gib_arm}, + {"misc_gib_leg", SP_misc_gib_leg}, + {"misc_gib_head", SP_misc_gib_head}, +#if 0 // remove monster code + {"misc_insane", SP_misc_insane}, +#endif + {"misc_deadsoldier", SP_misc_deadsoldier}, + {"misc_viper", SP_misc_viper}, + {"misc_viper_bomb", SP_misc_viper_bomb}, + {"misc_bigviper", SP_misc_bigviper}, + {"misc_strogg_ship", SP_misc_strogg_ship}, + {"misc_teleporter", SP_misc_teleporter}, + {"misc_teleporter_dest", SP_misc_teleporter_dest}, +//ZOID + {"trigger_teleport", SP_trigger_teleport}, + {"info_teleport_destination", SP_info_teleport_destination}, +//ZOID + {"misc_blackhole", SP_misc_blackhole}, + {"misc_eastertank", SP_misc_eastertank}, + {"misc_easterchick", SP_misc_easterchick}, + {"misc_easterchick2", SP_misc_easterchick2}, + +#if 0 // remove monster code + {"monster_berserk", SP_monster_berserk}, + {"monster_gladiator", SP_monster_gladiator}, + {"monster_gunner", SP_monster_gunner}, + {"monster_infantry", SP_monster_infantry}, + {"monster_soldier_light", SP_monster_soldier_light}, + {"monster_soldier", SP_monster_soldier}, + {"monster_soldier_ss", SP_monster_soldier_ss}, + {"monster_tank", SP_monster_tank}, + {"monster_tank_commander", SP_monster_tank}, + {"monster_medic", SP_monster_medic}, + {"monster_flipper", SP_monster_flipper}, + {"monster_chick", SP_monster_chick}, + {"monster_parasite", SP_monster_parasite}, + {"monster_flyer", SP_monster_flyer}, + {"monster_brain", SP_monster_brain}, + {"monster_floater", SP_monster_floater}, + {"monster_hover", SP_monster_hover}, + {"monster_mutant", SP_monster_mutant}, + {"monster_supertank", SP_monster_supertank}, + {"monster_boss2", SP_monster_boss2}, + {"monster_boss3_stand", SP_monster_boss3_stand}, + {"monster_jorg", SP_monster_jorg}, + + {"monster_commander_body", SP_monster_commander_body}, + + {"turret_breach", SP_turret_breach}, + {"turret_base", SP_turret_base}, + {"turret_driver", SP_turret_driver}, +#endif + + {NULL, NULL} +}; + +/* +=============== +ED_CallSpawn + +Finds the spawn function for the entity and calls it +=============== +*/ +void ED_CallSpawn (edict_t *ent) +{ + spawn_t *s; + gitem_t *item; + int i; + + if (!ent->classname) + { + gi.dprintf ("ED_CallSpawn: NULL classname\n"); + return; + } + + // check item spawn functions + for (i=0,item=itemlist ; iclassname) + continue; + if (!strcmp(item->classname, ent->classname)) + { // found it + SpawnItem (ent, item); + return; + } + } + + // check normal spawn functions + for (s=spawns ; s->name ; s++) + { + if (!strcmp(s->name, ent->classname)) + { // found it + s->spawn (ent); + return; + } + } + gi.dprintf ("%s doesn't have a spawn function\n", ent->classname); +} + +/* +============= +ED_NewString +============= +*/ +char *ED_NewString (char *string) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = gi.TagMalloc (l, TAG_LEVEL); + + new_p = newb; + + for (i=0 ; i< l ; i++) + { + if (string[i] == '\\' && i < l-1) + { + i++; + if (string[i] == 'n') + *new_p++ = '\n'; + else + *new_p++ = '\\'; + } + else + *new_p++ = string[i]; + } + + return newb; +} + + + + +/* +=============== +ED_ParseField + +Takes a key/value pair and sets the binary values +in an edict +=============== +*/ +void ED_ParseField (char *key, char *value, edict_t *ent) +{ + field_t *f; + byte *b; + float v; + vec3_t vec; + + for (f=fields ; f->name ; f++) + { + if (!Q_stricmp(f->name, key)) + { // found it + if (f->flags & FFL_SPAWNTEMP) + b = (byte *)&st; + else + b = (byte *)ent; + + switch (f->type) + { + case F_LSTRING: + *(char **)(b+f->ofs) = ED_NewString (value); + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; + case F_IGNORE: + break; + } + return; + } + } + gi.dprintf ("%s is not a field\n", key); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +char *ED_ParseEdict (char *data, edict_t *ent) +{ + qboolean init; + char keyname[256]; + char *com_token; + + init = false; + memset (&st, 0, sizeof(st)); + +// go through all the dictionary pairs + while (1) + { + // parse key + com_token = COM_Parse (&data); + if (com_token[0] == '}') + break; + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + strncpy (keyname, com_token, sizeof(keyname)-1); + + // parse value + com_token = COM_Parse (&data); + if (!data) + gi.error ("ED_ParseEntity: EOF without closing brace"); + + if (com_token[0] == '}') + gi.error ("ED_ParseEntity: closing brace without data"); + + init = true; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if (keyname[0] == '_') + continue; + + ED_ParseField (keyname, com_token, ent); + } + + if (!init) + memset (ent, 0, sizeof(*ent)); + + return data; +} + + +/* +================ +G_FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void G_FindTeams (void) +{ + edict_t *e, *e2, *chain; + int i, j; + int c, c2; + + c = 0; + c2 = 0; + for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++) + { + if (!e->inuse) + continue; + if (!e->team) + continue; + if (e->flags & FL_TEAMSLAVE) + continue; + chain = e; + e->teammaster = e; + c++; + c2++; + for (j=i+1, e2=e+1 ; j < globals.num_edicts ; j++,e2++) + { + if (!e2->inuse) + continue; + if (!e2->team) + continue; + if (e2->flags & FL_TEAMSLAVE) + continue; + if (!strcmp(e->team, e2->team)) + { + c2++; + chain->teamchain = e2; + e2->teammaster = e; + chain = e2; + e2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.dprintf ("%i teams with %i entities\n", c, c2); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SpawnEntities (char *mapname, char *entities, char *spawnpoint) +{ + edict_t *ent; + int inhibit; + char *com_token; + int i; + float skill_level; + + skill_level = floor (skill->value); + if (skill_level < 0) + skill_level = 0; + if (skill_level > 3) + skill_level = 3; + if (skill->value != skill_level) + gi.cvar_forceset("skill", va("%f", skill_level)); + + SaveClientData (); + + gi.FreeTags (TAG_LEVEL); + + memset (&level, 0, sizeof(level)); + memset (g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + + strncpy (level.mapname, mapname, sizeof(level.mapname)-1); + strncpy (game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)-1); + + // set client fields on player ents + for (i=0 ; iclassname, "trigger_once") && !stricmp(ent->model, "*27")) + ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; + + // remove things (except the world) from different skill levels or deathmatch + if (ent != g_edicts) + { + if (deathmatch->value) + { + if ( ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + else + { + if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ + ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) + ) + { + G_FreeEdict (ent); + inhibit++; + continue; + } + } + + ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY|SPAWNFLAG_NOT_MEDIUM|SPAWNFLAG_NOT_HARD|SPAWNFLAG_NOT_COOP|SPAWNFLAG_NOT_DEATHMATCH); + } + + ED_CallSpawn (ent); + } + + gi.dprintf ("%i entities inhibited\n", inhibit); + + G_FindTeams (); + + PlayerTrail_Init (); + +//ZOID + CTFSpawn(); +//ZOID +} + + +//=================================================================== + +#if 0 + // cursor positioning + xl + xr + yb + yt + xv + yv + + // drawing + statpic + pic + num + string + + // control + if + ifeq + ifbit + endif + +#endif + +char *single_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 262 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " +; + +char *dm_statusbar = +"yb -24 " + +// health +"xv 0 " +"hnum " +"xv 50 " +"pic 0 " + +// ammo +"if 2 " +" xv 100 " +" anum " +" xv 150 " +" pic 2 " +"endif " + +// armor +"if 4 " +" xv 200 " +" rnum " +" xv 250 " +" pic 4 " +"endif " + +// selected item +"if 6 " +" xv 296 " +" pic 6 " +"endif " + +"yb -50 " + +// picked up item +"if 7 " +" xv 0 " +" pic 7 " +" xv 26 " +" yb -42 " +" stat_string 8 " +" yb -50 " +"endif " + +// timer +"if 9 " +" xv 246 " +" num 2 10 " +" xv 296 " +" pic 9 " +"endif " + +// help / weapon icon +"if 11 " +" xv 148 " +" pic 11 " +"endif " + +// frags +"xr -50 " +"yt 2 " +"num 3 14" +; + + +/*QUAKED worldspawn (0 0 0) ? + +Only used for the world. +"sky" environment map name +"skyaxis" vector axis for rotating sky +"skyrotate" speed of rotation in degrees/second +"sounds" music cd track number +"gravity" 800 is default gravity +"message" text to print at user logon +*/ +void SP_worldspawn (edict_t *ent) +{ + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + ent->inuse = true; // since the world doesn't use G_Spawn() + ent->s.modelindex = 1; // world model is always index 1 + + //--------------- + + // reserve some spots for dead player bodies for coop / deathmatch + InitBodyQue (); + + // set configstrings for items + SetItemNames (); + + if (st.nextmap) + strcpy (level.nextmap, st.nextmap); + + // make some data visible to the server + + if (ent->message && ent->message[0]) + { + gi.configstring (CS_NAME, ent->message); + strncpy (level.level_name, ent->message, sizeof(level.level_name)); + } + else + strncpy (level.level_name, level.mapname, sizeof(level.level_name)); + + if (st.sky && st.sky[0]) + gi.configstring (CS_SKY, st.sky); + else + gi.configstring (CS_SKY, "unit1_"); + + gi.configstring (CS_SKYROTATE, va("%f", st.skyrotate) ); + + gi.configstring (CS_SKYAXIS, va("%f %f %f", + st.skyaxis[0], st.skyaxis[1], st.skyaxis[2]) ); + + gi.configstring (CS_CDTRACK, va("%i", ent->sounds) ); + + gi.configstring (CS_MAXCLIENTS, va("%i", (int)(maxclients->value) ) ); + + // status bar program + if (deathmatch->value) +//ZOID + if (ctf->value) { + gi.configstring (CS_STATUSBAR, ctf_statusbar); + //precaches + gi.imageindex("i_ctf1"); + gi.imageindex("i_ctf2"); + gi.imageindex("i_ctf1d"); + gi.imageindex("i_ctf2d"); + gi.imageindex("i_ctf1t"); + gi.imageindex("i_ctf2t"); + gi.imageindex("i_ctfj"); + } else +//ZOID + gi.configstring (CS_STATUSBAR, dm_statusbar); + else + gi.configstring (CS_STATUSBAR, single_statusbar); + + //--------------- + + + // help icon for statusbar + gi.imageindex ("i_help"); + level.pic_health = gi.imageindex ("i_health"); + gi.imageindex ("help"); + gi.imageindex ("field_3"); + + if (!st.gravity) + gi.cvar_set("sv_gravity", "800"); + else + gi.cvar_set("sv_gravity", st.gravity); + + snd_fry = gi.soundindex ("player/fry.wav"); // standing in lava / slime + + PrecacheItem (FindItem ("Blaster")); + + gi.soundindex ("player/lava1.wav"); + gi.soundindex ("player/lava2.wav"); + + gi.soundindex ("misc/pc_up.wav"); + gi.soundindex ("misc/talk1.wav"); + + gi.soundindex ("misc/udeath.wav"); + + // gibs + gi.soundindex ("items/respawn1.wav"); + + // sexed sounds + gi.soundindex ("*death1.wav"); + gi.soundindex ("*death2.wav"); + gi.soundindex ("*death3.wav"); + gi.soundindex ("*death4.wav"); + gi.soundindex ("*fall1.wav"); + gi.soundindex ("*fall2.wav"); + gi.soundindex ("*gurp1.wav"); // drowning damage + gi.soundindex ("*gurp2.wav"); + gi.soundindex ("*jump1.wav"); // player jump + gi.soundindex ("*pain25_1.wav"); + gi.soundindex ("*pain25_2.wav"); + gi.soundindex ("*pain50_1.wav"); + gi.soundindex ("*pain50_2.wav"); + gi.soundindex ("*pain75_1.wav"); + gi.soundindex ("*pain75_2.wav"); + gi.soundindex ("*pain100_1.wav"); + gi.soundindex ("*pain100_2.wav"); + +#if 0 //DISABLED + // sexed models + // THIS ORDER MUST MATCH THE DEFINES IN g_local.h + // you can add more, max 15 + gi.modelindex ("#w_blaster.md2"); + gi.modelindex ("#w_shotgun.md2"); + gi.modelindex ("#w_sshotgun.md2"); + gi.modelindex ("#w_machinegun.md2"); + gi.modelindex ("#w_chaingun.md2"); + gi.modelindex ("#a_grenades.md2"); + gi.modelindex ("#w_glauncher.md2"); + gi.modelindex ("#w_rlauncher.md2"); + gi.modelindex ("#w_hyperblaster.md2"); + gi.modelindex ("#w_railgun.md2"); + gi.modelindex ("#w_bfg.md2"); + gi.modelindex ("#w_grapple.md2"); +#endif + + //------------------- + + gi.soundindex ("player/gasp1.wav"); // gasping for air + gi.soundindex ("player/gasp2.wav"); // head breaking surface, not gasping + + gi.soundindex ("player/watr_in.wav"); // feet hitting water + gi.soundindex ("player/watr_out.wav"); // feet leaving water + + gi.soundindex ("player/watr_un.wav"); // head going underwater + + gi.soundindex ("player/u_breath1.wav"); + gi.soundindex ("player/u_breath2.wav"); + + gi.soundindex ("items/pkup.wav"); // bonus item pickup + gi.soundindex ("world/land.wav"); // landing thud + gi.soundindex ("misc/h2ohit1.wav"); // landing splash + + gi.soundindex ("items/damage.wav"); + gi.soundindex ("items/protect.wav"); + gi.soundindex ("items/protect4.wav"); + gi.soundindex ("weapons/noammo.wav"); + + gi.soundindex ("infantry/inflies1.wav"); + + sm_meat_index = gi.modelindex ("models/objects/gibs/sm_meat/tris.md2"); + gi.modelindex ("models/objects/gibs/arm/tris.md2"); + gi.modelindex ("models/objects/gibs/bone/tris.md2"); + gi.modelindex ("models/objects/gibs/bone2/tris.md2"); + gi.modelindex ("models/objects/gibs/chest/tris.md2"); + gi.modelindex ("models/objects/gibs/skull/tris.md2"); + gi.modelindex ("models/objects/gibs/head2/tris.md2"); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is doublebright. +// + + // 0 normal + gi.configstring(CS_LIGHTS+0, "m"); + + // 1 FLICKER (first variety) + gi.configstring(CS_LIGHTS+1, "mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + gi.configstring(CS_LIGHTS+2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + gi.configstring(CS_LIGHTS+3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + gi.configstring(CS_LIGHTS+4, "mamamamamama"); + + // 5 GENTLE PULSE 1 + gi.configstring(CS_LIGHTS+5,"jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + gi.configstring(CS_LIGHTS+6, "nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + gi.configstring(CS_LIGHTS+7, "mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + gi.configstring(CS_LIGHTS+8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + gi.configstring(CS_LIGHTS+9, "aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + gi.configstring(CS_LIGHTS+10, "mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + gi.configstring(CS_LIGHTS+11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + gi.configstring(CS_LIGHTS+63, "a"); +} + diff --git a/ctf/g_svcmds.c b/ctf/g_svcmds.c new file mode 100644 index 000000000..36ac9ce8d --- /dev/null +++ b/ctf/g_svcmds.c @@ -0,0 +1,48 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "g_local.h" + + +void Svcmd_Test_f (void) +{ + gi.cprintf (NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); +} + +/* +================= +ServerCommand + +ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void ServerCommand (void) +{ + char *cmd; + + cmd = gi.argv(1); + if (Q_stricmp (cmd, "test") == 0) + Svcmd_Test_f (); + else + gi.cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd); +} + diff --git a/ctf/g_target.c b/ctf/g_target.c new file mode 100644 index 000000000..3296a247f --- /dev/null +++ b/ctf/g_target.c @@ -0,0 +1,809 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + +/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) +Fire an origin based temp entity event to the clients. +"style" type byte +*/ +void Use_Target_Tent (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (ent->style); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); +} + +void SP_target_temp_entity (edict_t *ent) +{ + ent->use = Use_Target_Tent; +} + + +//========================================================== + +//========================================================== + +/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable +"noise" wav file to play +"attenuation" +-1 = none, send to whole level +1 = normal fighting sounds +2 = idle sound level +3 = ambient sound level +"volume" 0.0 to 1.0 + +Normal sounds play each time the target is used. The reliable flag can be set for crucial voiceovers. + +Looped sounds are allways atten 3 / vol 1, and the use function toggles it on/off. +Multiple identical looping sounds will just increase volume without any speed cost. +*/ +void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator) +{ + int chan; + + if (ent->spawnflags & 3) + { // looping sound toggles + if (ent->s.sound) + ent->s.sound = 0; // turn it off + else + ent->s.sound = ent->noise_index; // start it + } + else + { // normal sound + if (ent->spawnflags & 4) + chan = CHAN_VOICE|CHAN_RELIABLE; + else + chan = CHAN_VOICE; + // use a positioned_sound, because this entity won't normally be + // sent to any clients because it is invisible + gi.positioned_sound (ent->s.origin, ent, chan, ent->noise_index, ent->volume, ent->attenuation, 0); + } +} + +void SP_target_speaker (edict_t *ent) +{ + char buffer[MAX_QPATH]; + + if(!st.noise) + { + gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); + return; + } + if (!strstr (st.noise, ".wav")) + Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise); + else + strncpy (buffer, st.noise, sizeof(buffer)); + ent->noise_index = gi.soundindex (buffer); + + if (!ent->volume) + ent->volume = 1.0; + + if (!ent->attenuation) + ent->attenuation = 1.0; + else if (ent->attenuation == -1) // use -1 so 0 defaults to 1 + ent->attenuation = 0; + + // check for prestarted looping sound + if (ent->spawnflags & 1) + ent->s.sound = ent->noise_index; + + ent->use = Use_Target_Speaker; + + // must link the entity so we get areas and clusters so + // the server can determine who to send updates to + gi.linkentity (ent); +} + + +//========================================================== + +void Use_Target_Help (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->spawnflags & 1) + strncpy (game.helpmessage1, ent->message, sizeof(game.helpmessage2)-1); + else + strncpy (game.helpmessage2, ent->message, sizeof(game.helpmessage1)-1); + + game.helpchanged++; +} + +/*QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 +When fired, the "message" key becomes the current personal computer string, and the message light will be set on all clients status bars. +*/ +void SP_target_help(edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + if (!ent->message) + { + gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + ent->use = Use_Target_Help; +} + +//========================================================== + +/*QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) +Counts a secret found. +These are single use targets. +*/ +void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_secrets++; + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_secret (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_secret; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_secrets++; + // map bug hack + if (!stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624) + ent->message = "You have found a secret area."; +} + +//========================================================== + +/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) +Counts a goal completed. +These are single use targets. +*/ +void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator) +{ + gi.sound (ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); + + level.found_goals++; + + if (level.found_goals == level.total_goals) + gi.configstring (CS_CDTRACK, "0"); + + G_UseTargets (ent, activator); + G_FreeEdict (ent); +} + +void SP_target_goal (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->use = use_target_goal; + if (!st.noise) + st.noise = "misc/secret.wav"; + ent->noise_index = gi.soundindex (st.noise); + ent->svflags = SVF_NOCLIENT; + level.total_goals++; +} + +//========================================================== + + +/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) +Spawns an explosion temporary entity when used. + +"delay" wait this long before going off +"dmg" how much radius damage should be done, defaults to 0 +*/ +void target_explosion_explode (edict_t *self) +{ + float save; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PHS); + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + save = self->delay; + self->delay = 0; + G_UseTargets (self, self->activator); + self->delay = save; +} + +void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (!self->delay) + { + target_explosion_explode (self); + return; + } + + self->think = target_explosion_explode; + self->nextthink = level.time + self->delay; +} + +void SP_target_explosion (edict_t *ent) +{ + ent->use = use_target_explosion; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) +Changes level to "map" when fired +*/ +void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator) +{ + if (level.intermissiontime) + return; // allready activated + + if (!deathmatch->value && !coop->value) + { + if (g_edicts[1].health <= 0) + return; + } + + // if noexit, do a ton of damage to other + if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world) + { + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); + return; + } + + // if multiplayer, let everyone know who hit the exit + if (deathmatch->value) + { + if (activator && activator->client) + gi.bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname); + } + + // if going to a new unit, clear cross triggers + if (strstr(self->map, "*")) + game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); + + BeginIntermission (self); +} + +void SP_target_changelevel (edict_t *ent) +{ + if (!ent->map) + { + gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + // ugly hack because *SOMEBODY* screwed up their map + if((stricmp(level.mapname, "fact1") == 0) && (stricmp(ent->map, "fact3") == 0)) + ent->map = "fact3$secret1"; + + ent->use = use_target_changelevel; + ent->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) +Creates a particle splash effect when used. + +Set "sounds" to one of the following: + 1) sparks + 2) blue water + 3) brown water + 4) slime + 5) lava + 6) blood + +"count" how many pixels in the splash +"dmg" if set, does a radius damage at this location when it splashes + useful for lava/sparks +*/ + +void use_target_splash (edict_t *self, edict_t *other, edict_t *activator) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (self->count); + gi.WritePosition (self->s.origin); + gi.WriteDir (self->movedir); + gi.WriteByte (self->sounds); + gi.multicast (self->s.origin, MULTICAST_PVS); + + if (self->dmg) + T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH); +} + +void SP_target_splash (edict_t *self) +{ + self->use = use_target_splash; + G_SetMovedir (self->s.angles, self->movedir); + + if (!self->count) + self->count = 32; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) +Set target to the type of entity you want spawned. +Useful for spawning monsters and gibs in the factory levels. + +For monsters: + Set direction to the facing you want it to have. + +For gibs: + Set direction if you want it moving and + speed how fast it should be moving otherwise it + will just be dropped +*/ +void ED_CallSpawn (edict_t *ent); + +void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + ent = G_Spawn(); + ent->classname = self->target; + VectorCopy (self->s.origin, ent->s.origin); + VectorCopy (self->s.angles, ent->s.angles); + ED_CallSpawn (ent); + gi.unlinkentity (ent); + KillBox (ent); + gi.linkentity (ent); + if (self->speed) + VectorCopy (self->movedir, ent->velocity); +} + +void SP_target_spawner (edict_t *self) +{ + self->use = use_target_spawner; + self->svflags = SVF_NOCLIENT; + if (self->speed) + { + G_SetMovedir (self->s.angles, self->movedir); + VectorScale (self->movedir, self->speed, self->movedir); + } +} + +//========================================================== + +/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS +Fires a blaster bolt in the set direction when triggered. + +dmg default is 15 +speed default is 1000 +*/ + +void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator) +{ + int effect; + + if (self->spawnflags & 2) + effect = 0; + else if (self->spawnflags & 1) + effect = EF_HYPERBLASTER; + else + effect = EF_BLASTER; + + fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); + gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); +} + +void SP_target_blaster (edict_t *self) +{ + self->use = use_target_blaster; + G_SetMovedir (self->s.angles, self->movedir); + self->noise_index = gi.soundindex ("weapons/laser2.wav"); + + if (!self->dmg) + self->dmg = 15; + if (!self->speed) + self->speed = 1000; + + self->svflags = SVF_NOCLIENT; +} + + +//========================================================== + +/*QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Once this trigger is touched/used, any trigger_crosslevel_target with the same trigger number is automatically used when a level is started within the same unit. It is OK to check multiple triggers. Message, delay, target, and killtarget also work. +*/ +void trigger_crosslevel_trigger_use (edict_t *self, edict_t *other, edict_t *activator) +{ + game.serverflags |= self->spawnflags; + G_FreeEdict (self); +} + +void SP_target_crosslevel_trigger (edict_t *self) +{ + self->svflags = SVF_NOCLIENT; + self->use = trigger_crosslevel_trigger_use; +} + +/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 +Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and +killtarget also work. + +"delay" delay before using targets if the trigger has been activated (default 1) +*/ +void target_crosslevel_target_think (edict_t *self) +{ + if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets (self, self); + G_FreeEdict (self); + } +} + +void SP_target_crosslevel_target (edict_t *self) +{ + if (! self->delay) + self->delay = 1; + self->svflags = SVF_NOCLIENT; + + self->think = target_crosslevel_target_think; + self->nextthink = level.time + self->delay; +} + +//========================================================== + +/*QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT +When triggered, fires a laser. You can either set a target +or a direction. +*/ + +void target_laser_think (edict_t *self) +{ + edict_t *ignore; + vec3_t start; + vec3_t end; + trace_t tr; + vec3_t point; + vec3_t last_movedir; + int count; + + if (self->spawnflags & 0x80000000) + count = 8; + else + count = 4; + + if (self->enemy) + { + VectorCopy (self->movedir, last_movedir); + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point); + VectorSubtract (point, self->s.origin, self->movedir); + VectorNormalize (self->movedir); + if (!VectorCompare(self->movedir, last_movedir)) + self->spawnflags |= 0x80000000; + } + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, self->movedir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) + T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); + + // if we hit something that's not a monster or player or is immune to lasers, we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + if (self->spawnflags & 0x80000000) + { + self->spawnflags &= ~0x80000000; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (count); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + VectorCopy (tr.endpos, self->s.old_origin); + + self->nextthink = level.time + FRAMETIME; +} + +void target_laser_on (edict_t *self) +{ + if (!self->activator) + self->activator = self; + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + target_laser_think (self); +} + +void target_laser_off (edict_t *self) +{ + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void target_laser_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + if (self->spawnflags & 1) + target_laser_off (self); + else + target_laser_on (self); +} + +void target_laser_start (edict_t *self) +{ + edict_t *ent; + + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + self->s.renderfx |= RF_BEAM|RF_TRANSLUCENT; + self->s.modelindex = 1; // must be non-zero + + // set the beam diameter + if (self->spawnflags & 64) + self->s.frame = 16; + else + self->s.frame = 4; + + // set the color + if (self->spawnflags & 2) + self->s.skinnum = 0xf2f2f0f0; + else if (self->spawnflags & 4) + self->s.skinnum = 0xd0d1d2d3; + else if (self->spawnflags & 8) + self->s.skinnum = 0xf3f3f1f1; + else if (self->spawnflags & 16) + self->s.skinnum = 0xdcdddedf; + else if (self->spawnflags & 32) + self->s.skinnum = 0xe0e1e2e3; + + if (!self->enemy) + { + if (self->target) + { + ent = G_Find (NULL, FOFS(targetname), self->target); + if (!ent) + gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target); + self->enemy = ent; + } + else + { + G_SetMovedir (self->s.angles, self->movedir); + } + } + self->use = target_laser_use; + self->think = target_laser_think; + + if (!self->dmg) + self->dmg = 1; + + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + gi.linkentity (self); + + if (self->spawnflags & 1) + target_laser_on (self); + else + target_laser_off (self); +} + +void SP_target_laser (edict_t *self) +{ + // let everything else get spawned before we start firing + self->think = target_laser_start; + self->nextthink = level.time + 1; +} + +//========================================================== + +/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE +speed How many seconds the ramping will take +message two letters; starting lightlevel and ending lightlevel +*/ + +void target_lightramp_think (edict_t *self) +{ + char style[2]; + + style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + style[1] = 0; + gi.configstring (CS_LIGHTS+self->enemy->style, style); + + if ((level.time - self->timestamp) < self->speed) + { + self->nextthink = level.time + FRAMETIME; + } + else if (self->spawnflags & 1) + { + char temp; + + temp = self->movedir[0]; + self->movedir[0] = self->movedir[1]; + self->movedir[1] = temp; + self->movedir[2] *= -1; + } +} + +void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!self->enemy) + { + edict_t *e; + + // check all the targets + e = NULL; + while (1) + { + e = G_Find (e, FOFS(targetname), self->target); + if (!e) + break; + if (strcmp(e->classname, "light") != 0) + { + gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); + gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin)); + } + else + { + self->enemy = e; + } + } + + if (!self->enemy) + { + gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + } + + self->timestamp = level.time; + target_lightramp_think (self); +} + +void SP_target_lightramp (edict_t *self) +{ + if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1]) + { + gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->svflags |= SVF_NOCLIENT; + self->use = target_lightramp_use; + self->think = target_lightramp_think; + + self->movedir[0] = self->message[0] - 'a'; + self->movedir[1] = self->message[1] - 'a'; + self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); +} + +//========================================================== + +/*QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) +When triggered, this initiates a level-wide earthquake. +All players and monsters are affected. +"speed" severity of the quake (default:200) +"count" duration of the quake (default:5) +*/ + +void target_earthquake_think (edict_t *self) +{ + int i; + edict_t *e; + + if (self->last_move_time < level.time) + { + gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + self->last_move_time = level.time + 0.5; + } + + for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++) + { + if (!e->inuse) + continue; + if (!e->client) + continue; + if (!e->groundentity) + continue; + + e->groundentity = NULL; + e->velocity[0] += crandom()* 150; + e->velocity[1] += crandom()* 150; + e->velocity[2] = self->speed * (100.0 / e->mass); + } + + if (level.time < self->timestamp) + self->nextthink = level.time + FRAMETIME; +} + +void target_earthquake_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->timestamp = level.time + self->count; + self->nextthink = level.time + FRAMETIME; + self->activator = activator; + self->last_move_time = 0; +} + +void SP_target_earthquake (edict_t *self) +{ + if (!self->targetname) + gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->count) + self->count = 5; + + if (!self->speed) + self->speed = 200; + + self->svflags |= SVF_NOCLIENT; + self->think = target_earthquake_think; + self->use = target_earthquake_use; + + self->noise_index = gi.soundindex ("world/quake.wav"); +} diff --git a/ctf/g_trigger.c b/ctf/g_trigger.c new file mode 100644 index 000000000..53a1b6c73 --- /dev/null +++ b/ctf/g_trigger.c @@ -0,0 +1,598 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +void InitTrigger (edict_t *self) +{ + if (!VectorCompare (self->s.angles, vec3_origin)) + G_SetMovedir (self->s.angles, self->movedir); + + self->solid = SOLID_TRIGGER; + self->movetype = MOVETYPE_NONE; + gi.setmodel (self, self->model); + self->svflags = SVF_NOCLIENT; +} + + +// the wait time has passed, so set back up for another activation +void multi_wait (edict_t *ent) +{ + ent->nextthink = 0; +} + + +// the trigger was just activated +// ent->activator should be set to the activator so it can be held through a delay +// so wait for the delay time before firing +void multi_trigger (edict_t *ent) +{ + if (ent->nextthink) + return; // already been triggered + + G_UseTargets (ent, ent->activator); + + if (ent->wait > 0) + { + ent->think = multi_wait; + ent->nextthink = level.time + ent->wait; + } + else + { // we can't just remove (self) here, because this is a touch function + // called while looping through area links... + ent->touch = NULL; + ent->nextthink = level.time + FRAMETIME; + ent->think = G_FreeEdict; + } +} + +void Use_Multi (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->activator = activator; + multi_trigger (ent); +} + +void Touch_Multi (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if(other->client) + { + if (self->spawnflags & 2) + return; + } + else if (other->svflags & SVF_MONSTER) + { + if (!(self->spawnflags & 1)) + return; + } + else + return; + + if (!VectorCompare(self->movedir, vec3_origin)) + { + vec3_t forward; + + AngleVectors(other->s.angles, forward, NULL, NULL); + if (_DotProduct(forward, self->movedir) < 0) + return; + } + + self->activator = other; + multi_trigger (self); +} + +/*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED +Variable sized repeatable trigger. Must be targeted at one or more entities. +If "delay" is set, the trigger waits some time after activating before firing. +"wait" : Seconds between triggerings. (.2 default) +sounds +1) secret +2) beep beep +3) large switch +4) +set "message" to text string +*/ +void trigger_enable (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_TRIGGER; + self->use = Use_Multi; + gi.linkentity (self); +} + +void SP_trigger_multiple (edict_t *ent) +{ + if (ent->sounds == 1) + ent->noise_index = gi.soundindex ("misc/secret.wav"); + else if (ent->sounds == 2) + ent->noise_index = gi.soundindex ("misc/talk.wav"); + else if (ent->sounds == 3) + ent->noise_index = gi.soundindex ("misc/trigger1.wav"); + + if (!ent->wait) + ent->wait = 0.2; + ent->touch = Touch_Multi; + ent->movetype = MOVETYPE_NONE; + ent->svflags |= SVF_NOCLIENT; + + + if (ent->spawnflags & 4) + { + ent->solid = SOLID_NOT; + ent->use = trigger_enable; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->use = Use_Multi; + } + + if (!VectorCompare(ent->s.angles, vec3_origin)) + G_SetMovedir (ent->s.angles, ent->movedir); + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + + +/*QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED +Triggers once, then removes itself. +You must set the key "target" to the name of another object in the level that has a matching "targetname". + +If TRIGGERED, this trigger must be triggered before it is live. + +sounds + 1) secret + 2) beep beep + 3) large switch + 4) + +"message" string to be displayed when triggered +*/ + +void SP_trigger_once(edict_t *ent) +{ + // make old maps work because I messed up on flag assignments here + // triggered was on bit 1 when it should have been on bit 4 + if (ent->spawnflags & 1) + { + vec3_t v; + + VectorMA (ent->mins, 0.5, ent->size, v); + ent->spawnflags &= ~1; + ent->spawnflags |= 4; + gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); + } + + ent->wait = -1; + SP_trigger_multiple (ent); +} + +/*QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) +This fixed size trigger cannot be touched, it can only be fired by other events. +*/ +void trigger_relay_use (edict_t *self, edict_t *other, edict_t *activator) +{ + G_UseTargets (self, activator); +} + +void SP_trigger_relay (edict_t *self) +{ + self->use = trigger_relay_use; +} + + +/* +============================================================================== + +trigger_key + +============================================================================== +*/ + +/*QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) +A relay trigger that only fires it's targets if player has the proper key. +Use "item" to specify the required key, for example "key_data_cd" +*/ +void trigger_key_use (edict_t *self, edict_t *other, edict_t *activator) +{ + int index; + + if (!self->item) + return; + if (!activator->client) + return; + + index = ITEM_INDEX(self->item); + if (!activator->client->pers.inventory[index]) + { + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + gi.centerprintf (activator, "You need the %s", self->item->pickup_name); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keytry.wav"), 1, ATTN_NORM, 0); + return; + } + + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/keyuse.wav"), 1, ATTN_NORM, 0); + if (coop->value) + { + int player; + edict_t *ent; + + if (strcmp(self->item->classname, "key_power_cube") == 0) + { + int cube; + + for (cube = 0; cube < 8; cube++) + if (activator->client->pers.power_cubes & (1 << cube)) + break; + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + if (ent->client->pers.power_cubes & (1 << cube)) + { + ent->client->pers.inventory[index]--; + ent->client->pers.power_cubes &= ~(1 << cube); + } + } + } + else + { + for (player = 1; player <= game.maxclients; player++) + { + ent = &g_edicts[player]; + if (!ent->inuse) + continue; + if (!ent->client) + continue; + ent->client->pers.inventory[index] = 0; + } + } + } + else + { + activator->client->pers.inventory[index]--; + } + + G_UseTargets (self, activator); + + self->use = NULL; +} + +void SP_trigger_key (edict_t *self) +{ + if (!st.item) + { + gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); + return; + } + self->item = FindItemByClassname (st.item); + + if (!self->item) + { + gi.dprintf("item %s not found for trigger_key at %s\n", st.item, vtos(self->s.origin)); + return; + } + + if (!self->target) + { + gi.dprintf("%s at %s has no target\n", self->classname, vtos(self->s.origin)); + return; + } + + gi.soundindex ("misc/keytry.wav"); + gi.soundindex ("misc/keyuse.wav"); + + self->use = trigger_key_use; +} + + +/* +============================================================================== + +trigger_counter + +============================================================================== +*/ + +/*QUAKED trigger_counter (.5 .5 .5) ? nomessage +Acts as an intermediary for an action that takes multiple inputs. + +If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished. + +After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself. +*/ + +void trigger_counter_use(edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->count == 0) + return; + + self->count--; + + if (self->count) + { + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "%i more to go...", self->count); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + return; + } + + if (! (self->spawnflags & 1)) + { + gi.centerprintf(activator, "Sequence completed!"); + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + self->activator = activator; + multi_trigger (self); +} + +void SP_trigger_counter (edict_t *self) +{ + self->wait = -1; + if (!self->count) + self->count = 2; + + self->use = trigger_counter_use; +} + + +/* +============================================================================== + +trigger_always + +============================================================================== +*/ + +/*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) +This trigger will always fire. It is activated by the world. +*/ +void SP_trigger_always (edict_t *ent) +{ + // we must have some delay to make sure our use targets are present + if (ent->delay < 0.2) + ent->delay = 0.2; + G_UseTargets(ent, ent); +} + + +/* +============================================================================== + +trigger_push + +============================================================================== +*/ + +#define PUSH_ONCE 1 + +static int windsound; + +void trigger_push_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (strcmp(other->classname, "grenade") == 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + } + else if (other->health > 0) + { + VectorScale (self->movedir, self->speed * 10, other->velocity); + + if (other->client) + { + // don't take falling damage immediately from this + VectorCopy (other->velocity, other->client->oldvelocity); + if (other->fly_sound_debounce_time < level.time) + { + other->fly_sound_debounce_time = level.time + 1.5; + gi.sound (other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); + } + } + } + if (self->spawnflags & PUSH_ONCE) + G_FreeEdict (self); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE +Pushes the player +"speed" defaults to 1000 +*/ +void SP_trigger_push (edict_t *self) +{ + InitTrigger (self); + windsound = gi.soundindex ("misc/windfly.wav"); + self->touch = trigger_push_touch; + if (!self->speed) + self->speed = 1000; + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_hurt + +============================================================================== +*/ + +/*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW +Any entity that touches this will be hurt. + +It does dmg points of damage each server frame + +SILENT supresses playing the sound +SLOW changes the damage rate to once per second +NO_PROTECTION *nothing* stops the damage + +"dmg" default 5 (whole numbers only) + +*/ +void hurt_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + self->solid = SOLID_TRIGGER; + else + self->solid = SOLID_NOT; + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + + +void hurt_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int dflags; + + if (!other->takedamage) + return; + + if (self->timestamp > level.time) + return; + + if (self->spawnflags & 16) + self->timestamp = level.time + 1; + else + self->timestamp = level.time + FRAMETIME; + + if (!(self->spawnflags & 4)) + { + if ((level.framenum % 10) == 0) + gi.sound (other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); + } + + if (self->spawnflags & 8) + dflags = DAMAGE_NO_PROTECTION; + else + dflags = 0; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); +} + +void SP_trigger_hurt (edict_t *self) +{ + InitTrigger (self); + + self->noise_index = gi.soundindex ("world/electro.wav"); + self->touch = hurt_touch; + + if (!self->dmg) + self->dmg = 5; + + if (self->spawnflags & 1) + self->solid = SOLID_NOT; + else + self->solid = SOLID_TRIGGER; + + if (self->spawnflags & 2) + self->use = hurt_use; + + gi.linkentity (self); +} + + +/* +============================================================================== + +trigger_gravity + +============================================================================== +*/ + +/*QUAKED trigger_gravity (.5 .5 .5) ? +Changes the touching entites gravity to +the value of "gravity". 1.0 is standard +gravity for the level. +*/ + +void trigger_gravity_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + other->gravity = self->gravity; +} + +void SP_trigger_gravity (edict_t *self) +{ + if (st.gravity == 0) + { + gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + InitTrigger (self); + self->gravity = atoi(st.gravity); + self->touch = trigger_gravity_touch; +} + + +/* +============================================================================== + +trigger_monsterjump + +============================================================================== +*/ + +/*QUAKED trigger_monsterjump (.5 .5 .5) ? +Walking monsters that touch this will jump in the direction of the trigger's angle +"speed" default to 200, the speed thrown forward +"height" default to 200, the speed thrown upwards +*/ + +void trigger_monsterjump_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->flags & (FL_FLY | FL_SWIM) ) + return; + if (other->svflags & SVF_DEADMONSTER) + return; + if ( !(other->svflags & SVF_MONSTER)) + return; + +// set XY even if not on ground, so the jump will clear lips + other->velocity[0] = self->movedir[0] * self->speed; + other->velocity[1] = self->movedir[1] * self->speed; + + if (!other->groundentity) + return; + + other->groundentity = NULL; + other->velocity[2] = self->movedir[2]; +} + +void SP_trigger_monsterjump (edict_t *self) +{ + if (!self->speed) + self->speed = 200; + if (!st.height) + st.height = 200; + if (self->s.angles[YAW] == 0) + self->s.angles[YAW] = 360; + InitTrigger (self); + self->touch = trigger_monsterjump_touch; + self->movedir[2] = st.height; +} + diff --git a/ctf/g_utils.c b/ctf/g_utils.c new file mode 100644 index 000000000..9dc7f9b28 --- /dev/null +++ b/ctf/g_utils.c @@ -0,0 +1,570 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_utils.c -- misc utility functions for game module + +#include "g_local.h" + + +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result) +{ + result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; + result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; + result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + distance[2]; +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +edict_t *G_Find (edict_t *from, int fieldofs, char *match) +{ + char *s; + + if (!from) + from = g_edicts; + else + from++; + + for ( ; from < &g_edicts[globals.num_edicts] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (origin, radius) +================= +*/ +edict_t *findradius (edict_t *from, vec3_t org, float rad) +{ + vec3_t eorg; + int j; + + if (!from) + from = g_edicts; + else + from++; + for ( ; from < &g_edicts[globals.num_edicts]; from++) + { + if (!from->inuse) + continue; + if (from->solid == SOLID_NOT) + continue; + for (j=0 ; j<3 ; j++) + eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); + if (VectorLength(eorg) > rad) + continue; + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the edict after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +#define MAXCHOICES 8 + +edict_t *G_PickTarget (char *targetname) +{ + edict_t *ent = NULL; + int num_choices = 0; + edict_t *choice[MAXCHOICES]; + + if (!targetname) + { + gi.dprintf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + gi.dprintf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + + +void Think_Delay (edict_t *ent) +{ + G_UseTargets (ent, ent->activator); + G_FreeEdict (ent); +} + +/* +============================== +G_UseTargets + +the global "activator" should be set to the entity that initiated the firing. + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Centerprints any self.message to the activator. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets (edict_t *ent, edict_t *activator) +{ + edict_t *t; + +// +// check for a delay +// + if (ent->delay) + { + // create a temp object to fire at a later time + t = G_Spawn(); + t->classname = "DelayedUse"; + t->nextthink = level.time + ent->delay; + t->think = Think_Delay; + t->activator = activator; + if (!activator) + gi.dprintf ("Think_Delay with no activator\n"); + t->message = ent->message; + t->target = ent->target; + t->killtarget = ent->killtarget; + return; + } + + +// +// print the message +// + if ((ent->message) && !(activator->svflags & SVF_MONSTER)) + { + gi.centerprintf (activator, "%s", ent->message); + if (ent->noise_index) + gi.sound (activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); + else + gi.sound (activator, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); + } + +// +// kill killtargets +// + if (ent->killtarget) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->killtarget))) + { + G_FreeEdict (t); + if (!ent->inuse) + { + gi.dprintf("entity was removed while using killtargets\n"); + return; + } + } + } + +// gi.dprintf("TARGET: activating %s\n", ent->target); + +// +// fire targets +// + if (ent->target) + { + t = NULL; + while ((t = G_Find (t, FOFS(targetname), ent->target))) + { + // doors fire area portals in a specific way + if (!Q_stricmp(t->classname, "func_areaportal") && + (!Q_stricmp(ent->classname, "func_door") || !Q_stricmp(ent->classname, "func_door_rotating"))) + continue; + + if (t == ent) + { + gi.dprintf ("WARNING: Entity used itself.\n"); + } + else + { + if (t->use) + t->use (t, ent, activator); + } + if (!ent->inuse) + { + gi.dprintf("entity was removed while using targets\n"); + return; + } + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv (float x, float y, float z) +{ + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos (vec3_t v) +{ + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +vec3_t VEC_UP = {0, -1, 0}; +vec3_t MOVEDIR_UP = {0, 0, 1}; +vec3_t VEC_DOWN = {0, -2, 0}; +vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void G_SetMovedir (vec3_t angles, vec3_t movedir) +{ + if (VectorCompare (angles, VEC_UP)) + { + VectorCopy (MOVEDIR_UP, movedir); + } + else if (VectorCompare (angles, VEC_DOWN)) + { + VectorCopy (MOVEDIR_DOWN, movedir); + } + else + { + AngleVectors (angles, movedir, NULL, NULL); + } + + VectorClear (angles); +} + + +float vectoyaw (vec3_t vec) +{ + float yaw; + + if (/* vec[YAW] == 0 && */ vec[PITCH] == 0) + { + yaw = 0; + if (vec[YAW] > 0) + yaw = 90; + else if (vec[YAW] < 0) + yaw = -90; + } + else + { + yaw = (int) (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + } + + return yaw; +} + + +void vectoangles (vec3_t value1, vec3_t angles) +{ + float forward; + float yaw, pitch; + + if (value1[1] == 0 && value1[0] == 0) + { + yaw = 0; + if (value1[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + if (value1[0]) + yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); + else if (value1[1] > 0) + yaw = 90; + else + yaw = -90; + if (yaw < 0) + yaw += 360; + + forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); + pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; +} + +char *G_CopyString (char *in) +{ + char *out; + + out = gi.TagMalloc (strlen(in)+1, TAG_LEVEL); + strcpy (out, in); + return out; +} + + +void G_InitEdict (edict_t *e) +{ + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; +} + +/* +================= +G_Spawn + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +edict_t *G_Spawn (void) +{ + int i; + edict_t *e; + + e = &g_edicts[(int)maxclients->value+1]; + for ( i=maxclients->value+1 ; iinuse && ( e->freetime < 2 || level.time - e->freetime > 0.5 ) ) + { + G_InitEdict (e); + return e; + } + } + + if (i == game.maxentities) + gi.error ("ED_Alloc: no free edicts"); + + globals.num_edicts++; + G_InitEdict (e); + return e; +} + +/* +================= +G_FreeEdict + +Marks the edict as free +================= +*/ +void G_FreeEdict (edict_t *ed) +{ + gi.unlinkentity (ed); // unlink from world + + if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) + { +// gi.dprintf("tried to free special edict\n"); + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = false; +} + + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + // dead things don't activate triggers! + if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) + return; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_TRIGGERS); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (!hit->touch) + continue; + hit->touch (hit, ent, NULL, NULL); + } +} + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids (edict_t *ent) +{ + int i, num; + edict_t *touch[MAX_EDICTS], *hit; + + num = gi.BoxEdicts (ent->absmin, ent->absmax, touch + , MAX_EDICTS, AREA_SOLID); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for (i=0 ; iinuse) + continue; + if (ent->touch) + ent->touch (hit, ent, NULL, NULL); + if (!ent->inuse) + break; + } +} + + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox (edict_t *ent) +{ + trace_t tr; + + while (1) + { + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, NULL, MASK_PLAYERSOLID); + if (!tr.ent) + break; + + // nail it + T_Damage (tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + + // if we didn't kill it, fail + if (tr.ent->solid) + return false; + } + + return true; // all clear +} diff --git a/ctf/g_weapon.c b/ctf/g_weapon.c new file mode 100644 index 000000000..fc3e421ce --- /dev/null +++ b/ctf/g_weapon.c @@ -0,0 +1,919 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +/* +================= +check_dodge + +This is a support routine used when a client is firing +a non-instant attack weapon. It checks to see if a +monster's dodge function should be called. +================= +*/ +static void check_dodge (edict_t *self, vec3_t start, vec3_t dir, int speed) +{ + vec3_t end; + vec3_t v; + trace_t tr; + float eta; + + // easy mode only ducks one quarter the time + if (skill->value == 0) + { + if (random() > 0.25) + return; + } + VectorMA (start, 8192, dir, end); + tr = gi.trace (start, NULL, NULL, end, self, MASK_SHOT); + if ((tr.ent) && (tr.ent->svflags & SVF_MONSTER) && (tr.ent->health > 0) && (tr.ent->monsterinfo.dodge) && infront(tr.ent, self)) + { + VectorSubtract (tr.endpos, start, v); + eta = (VectorLength(v) - tr.ent->maxs[0]) / speed; + tr.ent->monsterinfo.dodge (tr.ent, self, eta); + } +} + + +/* +================= +fire_hit + +Used for all impact (hit/punch/slash) attacks +================= +*/ +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick) +{ + trace_t tr; + vec3_t forward, right, up; + vec3_t v; + vec3_t point; + float range; + vec3_t dir; + + //see if enemy is in range + VectorSubtract (self->enemy->s.origin, self->s.origin, dir); + range = VectorLength(dir); + if (range > aim[0]) + return false; + + if (aim[1] > self->mins[0] && aim[1] < self->maxs[0]) + { + // the hit is straight on so back the range up to the edge of their bbox + range -= self->enemy->maxs[0]; + } + else + { + // this is a side hit so adjust the "right" value out to the edge of their bbox + if (aim[1] < 0) + aim[1] = self->enemy->mins[0]; + else + aim[1] = self->enemy->maxs[0]; + } + + VectorMA (self->s.origin, range, dir, point); + + tr = gi.trace (self->s.origin, NULL, NULL, point, self, MASK_SHOT); + if (tr.fraction < 1) + { + if (!tr.ent->takedamage) + return false; + // if it will hit any client/monster then hit the one we wanted to hit + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + tr.ent = self->enemy; + } + + AngleVectors(self->s.angles, forward, right, up); + VectorMA (self->s.origin, range, forward, point); + VectorMA (point, aim[1], right, point); + VectorMA (point, aim[2], up, point); + VectorSubtract (point, self->enemy->s.origin, dir); + + // do the damage + T_Damage (tr.ent, self, self, dir, point, vec3_origin, damage, kick/2, DAMAGE_NO_KNOCKBACK, MOD_HIT); + + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + return false; + + // do our special form of knockback here + VectorMA (self->enemy->absmin, 0.5, self->enemy->size, v); + VectorSubtract (v, point, v); + VectorNormalize (v); + VectorMA (self->enemy->velocity, kick, v, self->enemy->velocity); + if (self->enemy->velocity[2] > 0) + self->enemy->groundentity = NULL; + return true; +} + + +/* +================= +fire_lead + +This is an internal support routine used for bullet/pellet based weapons. +================= +*/ +static void fire_lead (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int te_impact, int hspread, int vspread, int mod) +{ + trace_t tr; + vec3_t dir; + vec3_t forward, right, up; + vec3_t end; + float r; + float u; + vec3_t water_start; + qboolean water = false; + int content_mask = MASK_SHOT | MASK_WATER; + + tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT); + if (!(tr.fraction < 1.0)) + { + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + r = crandom()*hspread; + u = crandom()*vspread; + VectorMA (start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + if (gi.pointcontents (start) & MASK_WATER) + { + water = true; + VectorCopy (start, water_start); + content_mask &= ~MASK_WATER; + } + + tr = gi.trace (start, NULL, NULL, end, self, content_mask); + + // see if we hit water + if (tr.contents & MASK_WATER) + { + int color; + + water = true; + VectorCopy (tr.endpos, water_start); + + if (!VectorCompare (start, tr.endpos)) + { + if (tr.contents & CONTENTS_WATER) + { + if (strcmp(tr.surface->name, "*brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else + color = SPLASH_UNKNOWN; + + if (color != SPLASH_UNKNOWN) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_SPLASH); + gi.WriteByte (8); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (color); + gi.multicast (tr.endpos, MULTICAST_PVS); + } + + // change bullet's course when it enters water + VectorSubtract (end, start, dir); + vectoangles (dir, dir); + AngleVectors (dir, forward, right, up); + r = crandom()*hspread*2; + u = crandom()*vspread*2; + VectorMA (water_start, 8192, forward, end); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + } + + // re-trace ignoring water this time + tr = gi.trace (water_start, NULL, NULL, end, self, MASK_SHOT); + } + } + + // send gun puff / flash + if (!((tr.surface) && (tr.surface->flags & SURF_SKY))) + { + if (tr.fraction < 1.0) + { + if (tr.ent->takedamage) + { + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, DAMAGE_BULLET, mod); + } + else + { + if (strncmp (tr.surface->name, "sky", 3) != 0) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (te_impact); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.multicast (tr.endpos, MULTICAST_PVS); + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); + } + } + } + } + + // if went through water, determine where the end and make a bubble trail + if (water) + { + vec3_t pos; + + VectorSubtract (tr.endpos, water_start, dir); + VectorNormalize (dir); + VectorMA (tr.endpos, -2, dir, pos); + if (gi.pointcontents (pos) & MASK_WATER) + VectorCopy (pos, tr.endpos); + else + tr = gi.trace (pos, NULL, NULL, water_start, tr.ent, MASK_WATER); + + VectorAdd (water_start, tr.endpos, pos); + VectorScale (pos, 0.5, pos); + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BUBBLETRAIL); + gi.WritePosition (water_start); + gi.WritePosition (tr.endpos); + gi.multicast (pos, MULTICAST_PVS); + } +} + + +/* +================= +fire_bullet + +Fires a single round. Used for machinegun and chaingun. Would be fine for +pistols, rifles, etc.... +================= +*/ +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod) +{ + fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod); +} + + +/* +================= +fire_shotgun + +Shoots shotgun pellets. Used by shotgun and super shotgun. +================= +*/ +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod) +{ + int i; + + for (i = 0; i < count; i++) + fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod); +} + + +/* +================= +fire_blaster + +Fires a single blaster bolt. Used by the blaster and hyper blaster. +================= +*/ +void blaster_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + int mod; + + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + if (other->takedamage) + { + if (self->spawnflags & 1) + mod = MOD_HYPERBLASTER; + else + mod = MOD_BLASTER; + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, DAMAGE_ENERGY, mod); + } + else + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BLASTER); + gi.WritePosition (self->s.origin); + if (!plane) + gi.WriteDir (vec3_origin); + else + gi.WriteDir (plane->normal); + gi.multicast (self->s.origin, MULTICAST_PVS); + } + + G_FreeEdict (self); +} + +void fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect, qboolean hyper) +{ + edict_t *bolt; + trace_t tr; + + VectorNormalize (dir); + + bolt = G_Spawn(); + bolt->svflags = SVF_PROJECTILE; // special net code is used for projectiles + VectorCopy (start, bolt->s.origin); + VectorCopy (start, bolt->s.old_origin); + vectoangles (dir, bolt->s.angles); + VectorScale (dir, speed, bolt->velocity); + bolt->movetype = MOVETYPE_FLYMISSILE; + bolt->clipmask = MASK_SHOT; + bolt->solid = SOLID_BBOX; + bolt->s.effects |= effect; + VectorClear (bolt->mins); + VectorClear (bolt->maxs); + bolt->s.modelindex = gi.modelindex ("models/objects/laser/tris.md2"); + bolt->s.sound = gi.soundindex ("misc/lasfly.wav"); + bolt->owner = self; + bolt->touch = blaster_touch; + bolt->nextthink = level.time + 2; + bolt->think = G_FreeEdict; + bolt->dmg = damage; + bolt->classname = "bolt"; + if (hyper) + bolt->spawnflags = 1; + gi.linkentity (bolt); + + if (self->client) + check_dodge (self, bolt->s.origin, dir, speed); + + tr = gi.trace (self->s.origin, NULL, NULL, bolt->s.origin, bolt, MASK_SHOT); + if (tr.fraction < 1.0) + { + VectorMA (bolt->s.origin, -10, dir, bolt->s.origin); + bolt->touch (bolt, tr.ent, NULL, NULL); + } +} + + +/* +================= +fire_grenade +================= +*/ +static void Grenade_Explode (edict_t *ent) +{ + vec3_t origin; + int mod; + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + //FIXME: if we are onground then raise our Z just a bit since we are a point? + if (ent->enemy) + { + float points; + vec3_t v; + vec3_t dir; + + VectorAdd (ent->enemy->mins, ent->enemy->maxs, v); + VectorMA (ent->enemy->s.origin, 0.5, v, v); + VectorSubtract (ent->s.origin, v, v); + points = ent->dmg - 0.5 * VectorLength (v); + VectorSubtract (ent->enemy->s.origin, ent->s.origin, dir); + if (ent->spawnflags & 1) + mod = MOD_HANDGRENADE; + else + mod = MOD_GRENADE; + T_Damage (ent->enemy, ent, ent->owner, dir, ent->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + + if (ent->spawnflags & 2) + mod = MOD_HELD_GRENADE; + else if (ent->spawnflags & 1) + mod = MOD_HG_SPLASH; + else + mod = MOD_G_SPLASH; + T_RadiusDamage(ent, ent->owner, ent->dmg, ent->enemy, ent->dmg_radius, mod); + + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + } + else + { + if (ent->groundentity) + gi.WriteByte (TE_GRENADE_EXPLOSION); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + } + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +static void Grenade_Touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (!other->takedamage) + { + if (ent->spawnflags & 1) + { + if (random() > 0.5) + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb1a.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/hgrenb2a.wav"), 1, ATTN_NORM, 0); + } + else + { + gi.sound (ent, CHAN_VOICE, gi.soundindex ("weapons/grenlb1b.wav"), 1, ATTN_NORM, 0); + } + return; + } + + ent->enemy = other; + Grenade_Explode (ent); +} + +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "grenade"; + + gi.linkentity (grenade); +} + +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held) +{ + edict_t *grenade; + vec3_t dir; + vec3_t forward, right, up; + + vectoangles (aimdir, dir); + AngleVectors (dir, forward, right, up); + + grenade = G_Spawn(); + VectorCopy (start, grenade->s.origin); + VectorScale (aimdir, speed, grenade->velocity); + VectorMA (grenade->velocity, 200 + crandom() * 10.0, up, grenade->velocity); + VectorMA (grenade->velocity, crandom() * 10.0, right, grenade->velocity); + VectorSet (grenade->avelocity, 300, 300, 300); + grenade->movetype = MOVETYPE_BOUNCE; + grenade->clipmask = MASK_SHOT; + grenade->solid = SOLID_BBOX; + grenade->s.effects |= EF_GRENADE; + VectorClear (grenade->mins); + VectorClear (grenade->maxs); + grenade->s.modelindex = gi.modelindex ("models/objects/grenade2/tris.md2"); + grenade->owner = self; + grenade->touch = Grenade_Touch; + grenade->nextthink = level.time + timer; + grenade->think = Grenade_Explode; + grenade->dmg = damage; + grenade->dmg_radius = damage_radius; + grenade->classname = "hgrenade"; + if (held) + grenade->spawnflags = 3; + else + grenade->spawnflags = 1; + grenade->s.sound = gi.soundindex("weapons/hgrenc1b.wav"); + + if (timer <= 0.0) + Grenade_Explode (grenade); + else + { + gi.sound (self, CHAN_WEAPON, gi.soundindex ("weapons/hgrent1a.wav"), 1, ATTN_NORM, 0); + gi.linkentity (grenade); + } +} + + +/* +================= +fire_rocket +================= +*/ +void rocket_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t origin; + int n; + + if (other == ent->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (ent); + return; + } + + if (ent->owner->client) + PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); + + // calculate position for the explosion entity + VectorMA (ent->s.origin, -0.02, ent->velocity, origin); + + if (other->takedamage) + { + T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); + } + else + { + // don't throw any debris in net games + if (!deathmatch->value && !coop->value) + { + if ((surf) && !(surf->flags & (SURF_WARP|SURF_TRANS33|SURF_TRANS66|SURF_FLOWING))) + { + n = rand() % 5; + while(n--) + ThrowDebris (ent, "models/objects/debris2/tris.md2", 2, ent->s.origin); + } + } + } + + T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH); + + gi.WriteByte (svc_temp_entity); + if (ent->waterlevel) + gi.WriteByte (TE_ROCKET_EXPLOSION_WATER); + else + gi.WriteByte (TE_ROCKET_EXPLOSION); + gi.WritePosition (origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + + G_FreeEdict (ent); +} + +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage) +{ + edict_t *rocket; + + rocket = G_Spawn(); + VectorCopy (start, rocket->s.origin); + VectorCopy (dir, rocket->movedir); + vectoangles (dir, rocket->s.angles); + VectorScale (dir, speed, rocket->velocity); + rocket->movetype = MOVETYPE_FLYMISSILE; + rocket->clipmask = MASK_SHOT; + rocket->solid = SOLID_BBOX; + rocket->s.effects |= EF_ROCKET; + VectorClear (rocket->mins); + VectorClear (rocket->maxs); + rocket->s.modelindex = gi.modelindex ("models/objects/rocket/tris.md2"); + rocket->owner = self; + rocket->touch = rocket_touch; + rocket->nextthink = level.time + 8000/speed; + rocket->think = G_FreeEdict; + rocket->dmg = damage; + rocket->radius_dmg = radius_damage; + rocket->dmg_radius = damage_radius; + rocket->s.sound = gi.soundindex ("weapons/rockfly.wav"); + rocket->classname = "rocket"; + + if (self->client) + check_dodge (self, rocket->s.origin, dir, speed); + + gi.linkentity (rocket); +} + + +/* +================= +fire_rail +================= +*/ +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick) +{ + vec3_t from; + vec3_t end; + trace_t tr; + edict_t *ignore; + int mask; + qboolean water; + + VectorMA (start, 8192, aimdir, end); + VectorCopy (start, from); + ignore = self; + water = false; + mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA; + while (ignore) + { + tr = gi.trace (from, NULL, NULL, end, ignore, mask); + + if (tr.contents & (CONTENTS_SLIME|CONTENTS_LAVA)) + { + mask &= ~(CONTENTS_SLIME|CONTENTS_LAVA); + water = true; + } + else + { + if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client)) + ignore = tr.ent; + else + ignore = NULL; + + if ((tr.ent != self) && (tr.ent->takedamage)) + T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN); + } + + VectorCopy (tr.endpos, from); + } + + // send gun puff / flash + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); +// gi.multicast (start, MULTICAST_PHS); + if (water) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_RAILTRAIL); + gi.WritePosition (start); + gi.WritePosition (tr.endpos); + gi.multicast (tr.endpos, MULTICAST_PHS); + } + + if (self->client) + PlayerNoise(self, tr.endpos, PNOISE_IMPACT); +} + + +/* +================= +fire_bfg +================= +*/ +void bfg_explode (edict_t *self) +{ + edict_t *ent; + float points; + vec3_t v; + float dist; + + if (self->s.frame == 0) + { + // the BFG effect + ent = NULL; + while ((ent = findradius(ent, self->s.origin, self->dmg_radius)) != NULL) + { + if (!ent->takedamage) + continue; + if (ent == self->owner) + continue; + if (!CanDamage (ent, self)) + continue; + if (!CanDamage (ent, self->owner)) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (self->s.origin, v, v); + dist = VectorLength(v); + points = self->radius_dmg * (1.0 - sqrt(dist/self->dmg_radius)); + if (ent == self->owner) + points = points * 0.5; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_EXPLOSION); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PHS); + T_Damage (ent, self, self->owner, self->velocity, ent->s.origin, vec3_origin, (int)points, 0, DAMAGE_ENERGY, MOD_BFG_EFFECT); + } + } + + self->nextthink = level.time + FRAMETIME; + self->s.frame++; + if (self->s.frame == 5) + self->think = G_FreeEdict; +} + +void bfg_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == self->owner) + return; + + if (surf && (surf->flags & SURF_SKY)) + { + G_FreeEdict (self); + return; + } + + if (self->owner->client) + PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT); + + // core explosion - prevents firing it into the wall/floor + if (other->takedamage) + T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, 200, 0, 0, MOD_BFG_BLAST); + T_RadiusDamage(self, self->owner, 200, other, 100, MOD_BFG_BLAST); + + gi.sound (self, CHAN_VOICE, gi.soundindex ("weapons/bfg__x1b.wav"), 1, ATTN_NORM, 0); + self->solid = SOLID_NOT; + self->touch = NULL; + VectorMA (self->s.origin, -1 * FRAMETIME, self->velocity, self->s.origin); + VectorClear (self->velocity); + self->s.modelindex = gi.modelindex ("sprites/s_bfg3.sp2"); + self->s.frame = 0; + self->s.sound = 0; + self->s.effects &= ~EF_ANIM_ALLFAST; + self->think = bfg_explode; + self->nextthink = level.time + FRAMETIME; + self->enemy = other; + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_BIGEXPLOSION); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); +} + + +void bfg_think (edict_t *self) +{ + edict_t *ent; + edict_t *ignore; + vec3_t point; + vec3_t dir; + vec3_t start; + vec3_t end; + int dmg; + trace_t tr; + + if (deathmatch->value) + dmg = 5; + else + dmg = 10; + + ent = NULL; + while ((ent = findradius(ent, self->s.origin, 256)) != NULL) + { + if (ent == self) + continue; + + if (ent == self->owner) + continue; + + if (!ent->takedamage) + continue; + + if (!(ent->svflags & SVF_MONSTER) && (!ent->client) && (strcmp(ent->classname, "misc_explobox") != 0)) + continue; + +//ZOID + //don't target players in CTF + if (ctf->value && ent->client && + self->owner->client && + ent->client->resp.ctf_team == self->owner->client->resp.ctf_team) + continue; +//ZOID + + VectorMA (ent->absmin, 0.5, ent->size, point); + + VectorSubtract (point, self->s.origin, dir); + VectorNormalize (dir); + + ignore = self; + VectorCopy (self->s.origin, start); + VectorMA (start, 2048, dir, end); + while(1) + { + tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + + if (!tr.ent) + break; + + // hurt it if we can + if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER) && (tr.ent != self->owner)) + T_Damage (tr.ent, self, self->owner, dir, tr.endpos, vec3_origin, dmg, 1, DAMAGE_ENERGY, MOD_BFG_LASER); + + // if we hit something that's not a monster or player we're done + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + { + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_LASER_SPARKS); + gi.WriteByte (4); + gi.WritePosition (tr.endpos); + gi.WriteDir (tr.plane.normal); + gi.WriteByte (self->s.skinnum); + gi.multicast (tr.endpos, MULTICAST_PVS); + break; + } + + ignore = tr.ent; + VectorCopy (tr.endpos, start); + } + + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BFG_LASER); + gi.WritePosition (self->s.origin); + gi.WritePosition (tr.endpos); + gi.multicast (self->s.origin, MULTICAST_PHS); + } + + self->nextthink = level.time + FRAMETIME; +} + + +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius) +{ + edict_t *bfg; + + bfg = G_Spawn(); + VectorCopy (start, bfg->s.origin); + VectorCopy (dir, bfg->movedir); + vectoangles (dir, bfg->s.angles); + VectorScale (dir, speed, bfg->velocity); + bfg->movetype = MOVETYPE_FLYMISSILE; + bfg->clipmask = MASK_SHOT; + bfg->solid = SOLID_BBOX; + bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST; + VectorClear (bfg->mins); + VectorClear (bfg->maxs); + bfg->s.modelindex = gi.modelindex ("sprites/s_bfg1.sp2"); + bfg->owner = self; + bfg->touch = bfg_touch; + bfg->nextthink = level.time + 8000/speed; + bfg->think = G_FreeEdict; + bfg->radius_dmg = damage; + bfg->dmg_radius = damage_radius; + bfg->classname = "bfg blast"; + bfg->s.sound = gi.soundindex ("weapons/bfg__l1a.wav"); + + bfg->think = bfg_think; + bfg->nextthink = level.time + FRAMETIME; + bfg->teammaster = bfg; + bfg->teamchain = NULL; + + if (self->client) + check_dodge (self, bfg->s.origin, dir, speed); + + gi.linkentity (bfg); +} diff --git a/ctf/game.h b/ctf/game.h new file mode 100644 index 000000000..318624fa0 --- /dev/null +++ b/ctf/game.h @@ -0,0 +1,242 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// game.h -- game dll information visible to server + +#define GAME_API_VERSION 3 + +// edict->svflags + +#define SVF_NOCLIENT 0x00000001 // don't send entity to clients, even if it has effects +#define SVF_DEADMONSTER 0x00000002 // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER 0x00000004 // treat as CONTENTS_MONSTER for collision +//ZOID +#define SVF_PROJECTILE 0x00000008 // entity is simple projectile, used for network optimization +// if an entity is projectile, the model index/x/y/z/pitch/yaw are sent, encoded into +// seven (or eight) bytes. This is to speed up projectiles. Currently, only the +// hyperblaster makes use of this. use for items that are moving with a constant +// velocity that don't change direction or model +//ZOID + +// edict->solid values + +typedef enum +{ +SOLID_NOT, // no interaction with other objects +SOLID_TRIGGER, // only touch when inside, after moving +SOLID_BBOX, // touch on edge +SOLID_BSP // bsp clip, touch on edge +} solid_t; + +//=============================================================== + +// link_t is only used for entity area links now +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +#define MAX_ENT_CLUSTERS 16 + + +typedef struct edict_s edict_t; +typedef struct gclient_s gclient_t; + + +#ifndef GAME_INCLUDE + +struct gclient_s +{ + player_state_t ps; // communicated by server to clients + int ping; + // the game dll can add anything it wants after + // this point in the structure +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; // SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + // the game dll can add anything it wants after + // this point in the structure +}; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// +typedef struct +{ + // special messages + void (*bprintf) (int printlevel, char *fmt, ...); + void (*dprintf) (char *fmt, ...); + void (*cprintf) (edict_t *ent, int printlevel, char *fmt, ...); + void (*centerprintf) (edict_t *ent, char *fmt, ...); + void (*sound) (edict_t *ent, int channel, int soundindex, float volume, float attenuation, float timeofs); + void (*positioned_sound) (vec3_t origin, edict_t *ent, int channel, int soundinedex, float volume, float attenuation, float timeofs); + + // config strings hold all the index strings, the lightstyles, + // and misc data like the sky definition and cdtrack. + // All of the current configstrings are sent to clients when + // they connect, and changes are sent to all connected clients. + void (*configstring) (int num, char *string); + + void (*error) (char *fmt, ...); + + // the *index functions create configstrings and some internal server state + int (*modelindex) (char *name); + int (*soundindex) (char *name); + int (*imageindex) (char *name); + + void (*setmodel) (edict_t *ent, char *name); + + // collision detection + trace_t (*trace) (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passent, int contentmask); + int (*pointcontents) (vec3_t point); + qboolean (*inPVS) (vec3_t p1, vec3_t p2); + qboolean (*inPHS) (vec3_t p1, vec3_t p2); + void (*SetAreaPortalState) (int portalnum, qboolean open); + qboolean (*AreasConnected) (int area1, int area2); + + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity) (edict_t *ent); + void (*unlinkentity) (edict_t *ent); // call before removing an interactive edict + int (*BoxEdicts) (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype); + void (*Pmove) (pmove_t *pmove); // player movement code common with client prediction + + // network messaging + void (*multicast) (vec3_t origin, multicast_t to); + void (*unicast) (edict_t *ent, qboolean reliable); + void (*WriteChar) (int c); + void (*WriteByte) (int c); + void (*WriteShort) (int c); + void (*WriteLong) (int c); + void (*WriteFloat) (float f); + void (*WriteString) (char *s); + void (*WritePosition) (vec3_t pos); // some fractional bits + void (*WriteDir) (vec3_t pos); // single byte encoded, very coarse + void (*WriteAngle) (float f); + + // managed memory allocation + void *(*TagMalloc) (int size, int tag); + void (*TagFree) (void *block); + void (*FreeTags) (int tag); + + // console variable interaction + cvar_t *(*cvar) (char *var_name, char *value, int flags); + cvar_t *(*cvar_set) (char *var_name, char *value); + cvar_t *(*cvar_forceset) (char *var_name, char *value); + + // ClientCommand and ServerCommand parameter access + int (*argc) (void); + char *(*argv) (int n); + char *(*args) (void); // concatenation of all argv >= 1 + + // add commands to the server console as if they were typed in + // for map changing, etc + void (*AddCommandString) (char *text); + + void (*DebugGraph) (float value, int color); +} game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct +{ + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) (void); + void (*Shutdown) (void); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (char *mapname, char *entstring, char *spawnpoint); + + // Read/Write Game is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called on a loadgame. + void (*WriteGame) (char *filename, qboolean autosave); + void (*ReadGame) (char *filename); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities + void (*WriteLevel) (char *filename); + void (*ReadLevel) (char *filename); + + qboolean (*ClientConnect) (edict_t *ent, char *userinfo); + void (*ClientBegin) (edict_t *ent); + void (*ClientUserinfoChanged) (edict_t *ent, char *userinfo); + void (*ClientDisconnect) (edict_t *ent); + void (*ClientCommand) (edict_t *ent); + void (*ClientThink) (edict_t *ent, usercmd_t *cmd); + + void (*RunFrame) (void); + + // ServerCommand will be called when an "sv " command is issued on the + // server console. + // The game can issue gi.argc() / gi.argv() commands to get the rest + // of the parameters + void (*ServerCommand) (void); + + // + // global variables shared between game and server + // + + // The edict array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + struct edict_s *edicts; + int edict_size; + int num_edicts; // current number, <= max_edicts + int max_edicts; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); diff --git a/ctf/layout.txt b/ctf/layout.txt new file mode 100644 index 000000000..db97662fa --- /dev/null +++ b/ctf/layout.txt @@ -0,0 +1,12 @@ +01234567890123456789012345678901234567890123456789012345678901234567890123456789 + +Name Score Kills Deaths BaseDef CarrierDef Efficiency +0123456789012345 01234 01234 012345 0123456 0123456789 0123456789 +>BC>Zoid 110 40 10 5 10 75% + +Name |Score|Kills|Deaths|BaseDef|CarrierDef|Efficiency| +----------------+-----+-----+------+-------+----------+----------+ +0123456789012345|01234|01234|012345|0123456|0123456789|0123456789| +>BC>Zoid | 110| 40| 10| 5| 10| 75%| + +%-16.16s|%5d|%5d|%6d|%7d|%10d|%10d| diff --git a/ctf/m_move.c b/ctf/m_move.c new file mode 100644 index 000000000..229cfcd7e --- /dev/null +++ b/ctf/m_move.c @@ -0,0 +1,556 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// m_move.c -- monster movement + +#include "g_local.h" + +#define STEPSIZE 18 + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +int c_yes, c_no; + +qboolean M_CheckBottom (edict_t *ent) +{ + vec3_t mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + VectorAdd (ent->s.origin, ent->mins, mins); + VectorAdd (ent->s.origin, ent->maxs, maxs); + +// if all of the points under the corners are solid world, don't bother +// with the tougher checks +// the corners must be within 16 of the midpoint + start[2] = mins[2] - 1; + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + if (gi.pointcontents (start) != CONTENTS_SOLID) + goto realcheck; + } + + c_yes++; + return true; // we got out easy + +realcheck: + c_no++; +// +// check it for real... +// + start[2] = mins[2]; + +// the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0])*0.5; + start[1] = stop[1] = (mins[1] + maxs[1])*0.5; + stop[2] = start[2] - 2*STEPSIZE; + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0) + return false; + mid = bottom = trace.endpos[2]; + +// the corners must be within 16 of the midpoint + for (x=0 ; x<=1 ; x++) + for (y=0 ; y<=1 ; y++) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + trace = gi.trace (start, vec3_origin, vec3_origin, stop, ent, MASK_MONSTERSOLID); + + if (trace.fraction != 1.0 && trace.endpos[2] > bottom) + bottom = trace.endpos[2]; + if (trace.fraction == 1.0 || mid - trace.endpos[2] > STEPSIZE) + return false; + } + + c_yes++; + return true; +} + + +/* +============= +SV_movestep + +Called by monster program code. +The move will be adjusted for slopes and stairs, but if the move isn't +possible, no move is done, false is returned, and +pr_global_struct->trace_normal is set to the normal of the blocking wall +============= +*/ +//FIXME since we need to test end position contents here, can we avoid doing +//it again later in catagorize position? +qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) +{ + float dz; + vec3_t oldorg, neworg, end; + trace_t trace; + int i; + float stepsize; + vec3_t test; + int contents; + +// try the move + VectorCopy (ent->s.origin, oldorg); + VectorAdd (ent->s.origin, move, neworg); + +// flying monsters don't step up + if ( ent->flags & (FL_SWIM | FL_FLY) ) + { + // try one move with vertical motion, then one without + for (i=0 ; i<2 ; i++) + { + VectorAdd (ent->s.origin, move, neworg); + if (i == 0 && ent->enemy) + { + if (!ent->goalentity) + ent->goalentity = ent->enemy; + dz = ent->s.origin[2] - ent->goalentity->s.origin[2]; + if (ent->goalentity->client) + { + if (dz > 40) + neworg[2] -= 8; + if (!((ent->flags & FL_SWIM) && (ent->waterlevel < 2))) + if (dz < 30) + neworg[2] += 8; + } + else + { + if (dz > 8) + neworg[2] -= 8; + else if (dz > 0) + neworg[2] -= dz; + else if (dz < -8) + neworg[2] += 8; + else + neworg[2] += dz; + } + } + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, neworg, ent, MASK_MONSTERSOLID); + + // fly monsters don't enter water voluntarily + if (ent->flags & FL_FLY) + { + if (!ent->waterlevel) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (contents & MASK_WATER) + return false; + } + } + + // swim monsters don't exit water voluntarily + if (ent->flags & FL_SWIM) + { + if (ent->waterlevel < 2) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + if (!(contents & MASK_WATER)) + return false; + } + } + + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + + if (!ent->enemy) + break; + } + + return false; + } + +// push down from a step height above the wished position + if (!(ent->monsterinfo.aiflags & AI_NOSTEP)) + stepsize = STEPSIZE; + else + stepsize = 1; + + neworg[2] += stepsize; + VectorCopy (neworg, end); + end[2] -= stepsize*2; + + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.allsolid) + return false; + + if (trace.startsolid) + { + neworg[2] -= stepsize; + trace = gi.trace (neworg, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + if (trace.allsolid || trace.startsolid) + return false; + } + + + // don't go in to water + if (ent->waterlevel == 0) + { + test[0] = trace.endpos[0]; + test[1] = trace.endpos[1]; + test[2] = trace.endpos[2] + ent->mins[2] + 1; + contents = gi.pointcontents(test); + + if (contents & MASK_WATER) + return false; + } + + if (trace.fraction == 1) + { + // if monster had the ground pulled out, go ahead and fall + if ( ent->flags & FL_PARTIALGROUND ) + { + VectorAdd (ent->s.origin, move, ent->s.origin); + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + ent->groundentity = NULL; + return true; + } + + return false; // walked off an edge + } + +// check point traces down for dangling corners + VectorCopy (trace.endpos, ent->s.origin); + + if (!M_CheckBottom (ent)) + { + if ( ent->flags & FL_PARTIALGROUND ) + { // entity had floor mostly pulled out from underneath it + // and is trying to correct + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; + } + VectorCopy (oldorg, ent->s.origin); + return false; + } + + if ( ent->flags & FL_PARTIALGROUND ) + { + ent->flags &= ~FL_PARTIALGROUND; + } + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + +// the move is ok + if (relink) + { + gi.linkentity (ent); + G_TouchTriggers (ent); + } + return true; +} + + +//============================================================================ + +/* +=============== +M_ChangeYaw + +=============== +*/ +void M_ChangeYaw (edict_t *ent) +{ + float ideal; + float current; + float move; + float speed; + + current = anglemod(ent->s.angles[YAW]); + ideal = ent->ideal_yaw; + + if (current == ideal) + return; + + move = ideal - current; + speed = ent->yaw_speed; + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + if (move > 0) + { + if (move > speed) + move = speed; + } + else + { + if (move < -speed) + move = -speed; + } + + ent->s.angles[YAW] = anglemod (current + move); +} + + +/* +====================== +SV_StepDirection + +Turns to the movement direction, and walks the current distance if +facing it. + +====================== +*/ +qboolean SV_StepDirection (edict_t *ent, float yaw, float dist) +{ + vec3_t move, oldorigin; + float delta; + + ent->ideal_yaw = yaw; + M_ChangeYaw (ent); + + yaw = yaw*M_PI*2 / 360; + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + VectorCopy (ent->s.origin, oldorigin); + if (SV_movestep (ent, move, false)) + { + delta = ent->s.angles[YAW] - ent->ideal_yaw; + if (delta > 45 && delta < 315) + { // not turned far enough, so don't take the step + VectorCopy (oldorigin, ent->s.origin); + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return true; + } + gi.linkentity (ent); + G_TouchTriggers (ent); + return false; +} + +/* +====================== +SV_FixCheckBottom + +====================== +*/ +void SV_FixCheckBottom (edict_t *ent) +{ + ent->flags |= FL_PARTIALGROUND; +} + + + +/* +================ +SV_NewChaseDir + +================ +*/ +#define DI_NODIR -1 +void SV_NewChaseDir (edict_t *actor, edict_t *enemy, float dist) +{ + float deltax,deltay; + float d[3]; + float tdir, olddir, turnaround; + + //FIXME: how did we get here with no enemy + if (!enemy) + return; + + olddir = anglemod( (int)(actor->ideal_yaw/45)*45 ); + turnaround = anglemod(olddir - 180); + + deltax = enemy->s.origin[0] - actor->s.origin[0]; + deltay = enemy->s.origin[1] - actor->s.origin[1]; + if (deltax>10) + d[1]= 0; + else if (deltax<-10) + d[1]= 180; + else + d[1]= DI_NODIR; + if (deltay<-10) + d[2]= 270; + else if (deltay>10) + d[2]= 90; + else + d[2]= DI_NODIR; + +// try direct route + if (d[1] != DI_NODIR && d[2] != DI_NODIR) + { + if (d[1] == 0) + tdir = d[2] == 90 ? 45 : 315; + else + tdir = d[2] == 90 ? 135 : 215; + + if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) + return; + } + +// try other directions + if ( ((rand()&3) & 1) || abs(deltay)>abs(deltax)) + { + tdir=d[1]; + d[1]=d[2]; + d[2]=tdir; + } + + if (d[1]!=DI_NODIR && d[1]!=turnaround + && SV_StepDirection(actor, d[1], dist)) + return; + + if (d[2]!=DI_NODIR && d[2]!=turnaround + && SV_StepDirection(actor, d[2], dist)) + return; + +/* there is no direct path to the player, so pick another direction */ + + if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) + return; + + if (rand()&1) /*randomly determine direction of search*/ + { + for (tdir=0 ; tdir<=315 ; tdir += 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + else + { + for (tdir=315 ; tdir >=0 ; tdir -= 45) + if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) + return; + } + + if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) + return; + + actor->ideal_yaw = olddir; // can't move + +// if a bridge was pulled out from underneath a monster, it may not have +// a valid standing position at all + + if (!M_CheckBottom (actor)) + SV_FixCheckBottom (actor); +} + +/* +====================== +SV_CloseEnough + +====================== +*/ +qboolean SV_CloseEnough (edict_t *ent, edict_t *goal, float dist) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (goal->absmin[i] > ent->absmax[i] + dist) + return false; + if (goal->absmax[i] < ent->absmin[i] - dist) + return false; + } + return true; +} + + +/* +====================== +M_MoveToGoal +====================== +*/ +void M_MoveToGoal (edict_t *ent, float dist) +{ + edict_t *goal; + + goal = ent->goalentity; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return; + +// if the next step hits the enemy, return immediately + if (ent->enemy && SV_CloseEnough (ent, ent->enemy, dist) ) + return; + +// bump around... + if ( (rand()&3)==1 || !SV_StepDirection (ent, ent->ideal_yaw, dist)) + { + if (ent->inuse) + SV_NewChaseDir (ent, goal, dist); + } +} + + +/* +=============== +M_walkmove +=============== +*/ +qboolean M_walkmove (edict_t *ent, float yaw, float dist) +{ + vec3_t move; + + if (!ent->groundentity && !(ent->flags & (FL_FLY|FL_SWIM))) + return false; + + yaw = yaw*M_PI*2 / 360; + + move[0] = cos(yaw)*dist; + move[1] = sin(yaw)*dist; + move[2] = 0; + + return SV_movestep(ent, move, true); +} diff --git a/ctf/m_player.h b/ctf/m_player.h new file mode 100644 index 000000000..001bd8fd4 --- /dev/null +++ b/ctf/m_player.h @@ -0,0 +1,225 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// G:\quake2\baseq2\models/player_x/frames + +// This file generated by qdata - Do NOT Modify + +#define FRAME_stand01 0 +#define FRAME_stand02 1 +#define FRAME_stand03 2 +#define FRAME_stand04 3 +#define FRAME_stand05 4 +#define FRAME_stand06 5 +#define FRAME_stand07 6 +#define FRAME_stand08 7 +#define FRAME_stand09 8 +#define FRAME_stand10 9 +#define FRAME_stand11 10 +#define FRAME_stand12 11 +#define FRAME_stand13 12 +#define FRAME_stand14 13 +#define FRAME_stand15 14 +#define FRAME_stand16 15 +#define FRAME_stand17 16 +#define FRAME_stand18 17 +#define FRAME_stand19 18 +#define FRAME_stand20 19 +#define FRAME_stand21 20 +#define FRAME_stand22 21 +#define FRAME_stand23 22 +#define FRAME_stand24 23 +#define FRAME_stand25 24 +#define FRAME_stand26 25 +#define FRAME_stand27 26 +#define FRAME_stand28 27 +#define FRAME_stand29 28 +#define FRAME_stand30 29 +#define FRAME_stand31 30 +#define FRAME_stand32 31 +#define FRAME_stand33 32 +#define FRAME_stand34 33 +#define FRAME_stand35 34 +#define FRAME_stand36 35 +#define FRAME_stand37 36 +#define FRAME_stand38 37 +#define FRAME_stand39 38 +#define FRAME_stand40 39 +#define FRAME_run1 40 +#define FRAME_run2 41 +#define FRAME_run3 42 +#define FRAME_run4 43 +#define FRAME_run5 44 +#define FRAME_run6 45 +#define FRAME_attack1 46 +#define FRAME_attack2 47 +#define FRAME_attack3 48 +#define FRAME_attack4 49 +#define FRAME_attack5 50 +#define FRAME_attack6 51 +#define FRAME_attack7 52 +#define FRAME_attack8 53 +#define FRAME_pain101 54 +#define FRAME_pain102 55 +#define FRAME_pain103 56 +#define FRAME_pain104 57 +#define FRAME_pain201 58 +#define FRAME_pain202 59 +#define FRAME_pain203 60 +#define FRAME_pain204 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_jump1 66 +#define FRAME_jump2 67 +#define FRAME_jump3 68 +#define FRAME_jump4 69 +#define FRAME_jump5 70 +#define FRAME_jump6 71 +#define FRAME_flip01 72 +#define FRAME_flip02 73 +#define FRAME_flip03 74 +#define FRAME_flip04 75 +#define FRAME_flip05 76 +#define FRAME_flip06 77 +#define FRAME_flip07 78 +#define FRAME_flip08 79 +#define FRAME_flip09 80 +#define FRAME_flip10 81 +#define FRAME_flip11 82 +#define FRAME_flip12 83 +#define FRAME_salute01 84 +#define FRAME_salute02 85 +#define FRAME_salute03 86 +#define FRAME_salute04 87 +#define FRAME_salute05 88 +#define FRAME_salute06 89 +#define FRAME_salute07 90 +#define FRAME_salute08 91 +#define FRAME_salute09 92 +#define FRAME_salute10 93 +#define FRAME_salute11 94 +#define FRAME_taunt01 95 +#define FRAME_taunt02 96 +#define FRAME_taunt03 97 +#define FRAME_taunt04 98 +#define FRAME_taunt05 99 +#define FRAME_taunt06 100 +#define FRAME_taunt07 101 +#define FRAME_taunt08 102 +#define FRAME_taunt09 103 +#define FRAME_taunt10 104 +#define FRAME_taunt11 105 +#define FRAME_taunt12 106 +#define FRAME_taunt13 107 +#define FRAME_taunt14 108 +#define FRAME_taunt15 109 +#define FRAME_taunt16 110 +#define FRAME_taunt17 111 +#define FRAME_wave01 112 +#define FRAME_wave02 113 +#define FRAME_wave03 114 +#define FRAME_wave04 115 +#define FRAME_wave05 116 +#define FRAME_wave06 117 +#define FRAME_wave07 118 +#define FRAME_wave08 119 +#define FRAME_wave09 120 +#define FRAME_wave10 121 +#define FRAME_wave11 122 +#define FRAME_point01 123 +#define FRAME_point02 124 +#define FRAME_point03 125 +#define FRAME_point04 126 +#define FRAME_point05 127 +#define FRAME_point06 128 +#define FRAME_point07 129 +#define FRAME_point08 130 +#define FRAME_point09 131 +#define FRAME_point10 132 +#define FRAME_point11 133 +#define FRAME_point12 134 +#define FRAME_crstnd01 135 +#define FRAME_crstnd02 136 +#define FRAME_crstnd03 137 +#define FRAME_crstnd04 138 +#define FRAME_crstnd05 139 +#define FRAME_crstnd06 140 +#define FRAME_crstnd07 141 +#define FRAME_crstnd08 142 +#define FRAME_crstnd09 143 +#define FRAME_crstnd10 144 +#define FRAME_crstnd11 145 +#define FRAME_crstnd12 146 +#define FRAME_crstnd13 147 +#define FRAME_crstnd14 148 +#define FRAME_crstnd15 149 +#define FRAME_crstnd16 150 +#define FRAME_crstnd17 151 +#define FRAME_crstnd18 152 +#define FRAME_crstnd19 153 +#define FRAME_crwalk1 154 +#define FRAME_crwalk2 155 +#define FRAME_crwalk3 156 +#define FRAME_crwalk4 157 +#define FRAME_crwalk5 158 +#define FRAME_crwalk6 159 +#define FRAME_crattak1 160 +#define FRAME_crattak2 161 +#define FRAME_crattak3 162 +#define FRAME_crattak4 163 +#define FRAME_crattak5 164 +#define FRAME_crattak6 165 +#define FRAME_crattak7 166 +#define FRAME_crattak8 167 +#define FRAME_crattak9 168 +#define FRAME_crpain1 169 +#define FRAME_crpain2 170 +#define FRAME_crpain3 171 +#define FRAME_crpain4 172 +#define FRAME_crdeath1 173 +#define FRAME_crdeath2 174 +#define FRAME_crdeath3 175 +#define FRAME_crdeath4 176 +#define FRAME_crdeath5 177 +#define FRAME_death101 178 +#define FRAME_death102 179 +#define FRAME_death103 180 +#define FRAME_death104 181 +#define FRAME_death105 182 +#define FRAME_death106 183 +#define FRAME_death201 184 +#define FRAME_death202 185 +#define FRAME_death203 186 +#define FRAME_death204 187 +#define FRAME_death205 188 +#define FRAME_death206 189 +#define FRAME_death301 190 +#define FRAME_death302 191 +#define FRAME_death303 192 +#define FRAME_death304 193 +#define FRAME_death305 194 +#define FRAME_death306 195 +#define FRAME_death307 196 +#define FRAME_death308 197 + +#define MODEL_SCALE 1.000000 + + diff --git a/ctf/p_client.c b/ctf/p_client.c new file mode 100644 index 000000000..f77cf100e --- /dev/null +++ b/ctf/p_client.c @@ -0,0 +1,1726 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" +#include "m_player.h" + +void ClientUserinfoChanged (edict_t *ent, char *userinfo); + +void SP_misc_teleporter_dest (edict_t *ent); + +// +// Gross, ugly, disgustuing hack section +// + +// this function is an ugly as hell hack to fix some map flaws +// +// the coop spawn spots on some maps are SNAFU. There are coop spots +// with the wrong targetname as well as spots with no name at all +// +// we use carnal knowledge of the maps to fix the coop spot targetnames to match +// that of the nearest named single player spot + +static void SP_FixCoopSpots (edict_t *self) +{ + edict_t *spot; + vec3_t d; + + spot = NULL; + + while(1) + { + spot = G_Find(spot, FOFS(classname), "info_player_start"); + if (!spot) + return; + if (!spot->targetname) + continue; + VectorSubtract(self->s.origin, spot->s.origin, d); + if (VectorLength(d) < 384) + { + if ((!self->targetname) || stricmp(self->targetname, spot->targetname) != 0) + { +// gi.dprintf("FixCoopSpots changed %s at %s targetname from %s to %s\n", self->classname, vtos(self->s.origin), self->targetname, spot->targetname); + self->targetname = spot->targetname; + } + return; + } + } +} + +// now if that one wasn't ugly enough for you then try this one on for size +// some maps don't have any coop spots at all, so we need to create them +// where they should have been + +static void SP_CreateCoopSpots (edict_t *self) +{ + edict_t *spot; + + if(stricmp(level.mapname, "security") == 0) + { + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 - 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 64; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + spot = G_Spawn(); + spot->classname = "info_player_coop"; + spot->s.origin[0] = 188 + 128; + spot->s.origin[1] = -164; + spot->s.origin[2] = 80; + spot->targetname = "jail3"; + spot->s.angles[1] = 90; + + return; + } +} + + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +The normal starting point for a level. +*/ +void SP_info_player_start(edict_t *self) +{ + if (!coop->value) + return; + if(stricmp(level.mapname, "security") == 0) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_CreateCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for deathmatch games +*/ +void SP_info_player_deathmatch(edict_t *self) +{ + if (!deathmatch->value) + { + G_FreeEdict (self); + return; + } + SP_misc_teleporter_dest (self); +} + +/*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) +potential spawning position for coop games +*/ + +void SP_info_player_coop(edict_t *self) +{ + if (!coop->value) + { + G_FreeEdict (self); + return; + } + + if((stricmp(level.mapname, "jail2") == 0) || + (stricmp(level.mapname, "jail4") == 0) || + (stricmp(level.mapname, "mine1") == 0) || + (stricmp(level.mapname, "mine2") == 0) || + (stricmp(level.mapname, "mine3") == 0) || + (stricmp(level.mapname, "mine4") == 0) || + (stricmp(level.mapname, "lab") == 0) || + (stricmp(level.mapname, "boss1") == 0) || + (stricmp(level.mapname, "fact3") == 0) || + (stricmp(level.mapname, "biggun") == 0) || + (stricmp(level.mapname, "space") == 0) || + (stricmp(level.mapname, "command") == 0) || + (stricmp(level.mapname, "power2") == 0) || + (stricmp(level.mapname, "strike") == 0)) + { + // invoke one of our gross, ugly, disgusting hacks + self->think = SP_FixCoopSpots; + self->nextthink = level.time + FRAMETIME; + } +} + + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The deathmatch intermission point will be at one of these +Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' +*/ +void SP_info_player_intermission(void) +{ +} + + +//======================================================================= + + +void player_pain (edict_t *self, edict_t *other, float kick, int damage) +{ + // player pain is handled at the end of the frame in P_DamageFeedback +} + + +qboolean IsFemale (edict_t *ent) +{ + char *info; + + if (!ent->client) + return false; + + info = Info_ValueForKey (ent->client->pers.userinfo, "skin"); + if (info[0] == 'f' || info[0] == 'F') + return true; + return false; +} + + +void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + int mod; + char *message; + char *message2; + qboolean ff; + + + if (coop->value && attacker->client) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch->value || coop->value) + { + ff = meansOfDeath & MOD_FRIENDLY_FIRE; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = NULL; + message2 = ""; + + 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_EXPLOSIVE: + case MOD_BARREL: + message = "blew up"; + break; + case MOD_EXIT: + message = "found a way out"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TARGET_BLASTER: + message = "got blasted"; + break; + case MOD_BOMB: + case MOD_SPLASH: + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + } + if (attacker == self) + { + switch (mod) + { + case MOD_HELD_GRENADE: + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH: + case MOD_G_SPLASH: + if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH: + if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST: + message = "should have used a smaller gun"; + break; + default: + if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message) + { + gi.bprintf (PRINT_MEDIUM, "%s %s.\n", self->client->pers.netname, message); + if (deathmatch->value) + self->client->resp.score--; + self->enemy = NULL; + return; + } + + self->enemy = attacker; + if (attacker && attacker->client) + { + switch (mod) + { + case MOD_BLASTER: + message = "was blasted by"; + break; + case MOD_SHOTGUN: + message = "was gunned down by"; + break; + case MOD_SSHOTGUN: + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN: + message = "was machinegunned by"; + break; + case MOD_CHAINGUN: + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE: + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH: + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET: + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH: + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER: + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN: + message = "was railed by"; + break; + case MOD_BFG_LASER: + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST: + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT: + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE: + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH: + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE: + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; +//ZOID + case MOD_GRAPPLE: + message = "was caught by"; + message2 = "'s grapple"; + break; +//ZOID + + } + if (message) + { + gi.bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2); + if (deathmatch->value) + { + if (ff) + attacker->client->resp.score--; + else + attacker->client->resp.score++; + } + return; + } + } + } + + gi.bprintf (PRINT_MEDIUM,"%s died.\n", self->client->pers.netname); + if (deathmatch->value) + self->client->resp.score--; +} + + +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +void TossClientWeapon (edict_t *self) +{ + gitem_t *item; + edict_t *drop; + qboolean quad; + float spread; + + if (!deathmatch->value) + return; + + item = self->client->pers.weapon; + if (! self->client->pers.inventory[self->client->ammo_index] ) + item = NULL; + if (item && (strcmp (item->pickup_name, "Blaster") == 0)) + item = NULL; + + if (!((int)(dmflags->value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self->client->quad_framenum > (level.framenum + 10)); + + if (item && quad) + spread = 22.5; + else + spread = 0.0; + + if (item) + { + self->client->v_angle[YAW] -= spread; + drop = Drop_Item (self, item); + self->client->v_angle[YAW] += spread; + drop->spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) + { + self->client->v_angle[YAW] += spread; + drop = Drop_Item (self, FindItemByClassname ("item_quad")); + self->client->v_angle[YAW] -= spread; + drop->spawnflags |= DROPPED_PLAYER_ITEM; + + drop->touch = Touch_Item; + drop->nextthink = level.time + (self->client->quad_framenum - level.framenum) * FRAMETIME; + drop->think = G_FreeEdict; + } +} + + +/* +================== +LookAtKiller +================== +*/ +void LookAtKiller (edict_t *self, edict_t *inflictor, edict_t *attacker) +{ + vec3_t dir; + + if (attacker && attacker != world && attacker != self) + { + VectorSubtract (attacker->s.origin, self->s.origin, dir); + } + else if (inflictor && inflictor != world && inflictor != self) + { + VectorSubtract (inflictor->s.origin, self->s.origin, dir); + } + else + { + self->client->killer_yaw = self->s.angles[YAW]; + return; + } + + if (dir[0]) + self->client->killer_yaw = 180/M_PI*atan2(dir[1], dir[0]); + else { + self->client->killer_yaw = 0; + if (dir[1] > 0) + self->client->killer_yaw = 90; + else if (dir[1] < 0) + self->client->killer_yaw = -90; + } + if (self->client->killer_yaw < 0) + self->client->killer_yaw += 360; +} + +/* +================== +player_die +================== +*/ +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + VectorClear (self->avelocity); + + self->takedamage = DAMAGE_YES; + self->movetype = MOVETYPE_TOSS; + + self->s.modelindex2 = 0; // remove linked weapon model +//ZOID + self->s.modelindex3 = 0; // remove linked ctf flag +//ZOID + + self->s.angles[0] = 0; + self->s.angles[2] = 0; + + self->s.sound = 0; + self->client->weapon_sound = 0; + + self->maxs[2] = -8; + +// self->solid = SOLID_NOT; + self->svflags |= SVF_DEADMONSTER; + + if (!self->deadflag) + { + self->client->respawn_time = level.time + 1.0; + LookAtKiller (self, inflictor, attacker); + self->client->ps.pmove.pm_type = PM_DEAD; + ClientObituary (self, inflictor, attacker); +//ZOID + // if at start and same team, clear + if (ctf->value && meansOfDeath == MOD_TELEFRAG && + self->client->resp.ctf_state < 2 && + self->client->resp.ctf_team == attacker->client->resp.ctf_team) { + attacker->client->resp.score--; + self->client->resp.ctf_state = 0; + } + + CTFFragBonuses(self, inflictor, attacker); +//ZOID + TossClientWeapon (self); +//ZOID + CTFPlayerResetGrapple(self); + CTFDeadDropFlag(self); + CTFDeadDropTech(self); +//ZOID + if (deathmatch->value && !self->client->showscores) + Cmd_Help_f (self); // show scores + } + + // remove powerups + self->client->quad_framenum = 0; + self->client->invincible_framenum = 0; + self->client->breather_framenum = 0; + self->client->enviro_framenum = 0; + + // clear inventory + memset(self->client->pers.inventory, 0, sizeof(self->client->pers.inventory)); + + if (self->health < -40) + { // gib + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead (self, damage); +//ZOID + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = 0; +//ZOID + self->takedamage = DAMAGE_NO; + } + else + { // normal death + if (!self->deadflag) + { + static int i; + + i = (i+1)%3; + // start a death animation + self->client->anim_priority = ANIM_DEATH; + if (self->client->ps.pmove.pm_flags & PMF_DUCKED) + { + self->s.frame = FRAME_crdeath1-1; + self->client->anim_end = FRAME_crdeath5; + } + else switch (i) + { + case 0: + self->s.frame = FRAME_death101-1; + self->client->anim_end = FRAME_death106; + break; + case 1: + self->s.frame = FRAME_death201-1; + self->client->anim_end = FRAME_death206; + break; + case 2: + self->s.frame = FRAME_death301-1; + self->client->anim_end = FRAME_death308; + break; + } + gi.sound (self, CHAN_VOICE, gi.soundindex(va("*death%i.wav", (rand()%4)+1)), 1, ATTN_NORM, 0); + } + } + + self->deadflag = DEAD_DEAD; + + gi.linkentity (self); +} + +//======================================================================= + +/* +============== +InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void InitClientPersistant (gclient_t *client) +{ + gitem_t *item; + + memset (&client->pers, 0, sizeof(client->pers)); + + item = FindItem("Blaster"); + client->pers.selected_item = ITEM_INDEX(item); + client->pers.inventory[client->pers.selected_item] = 1; + + client->pers.weapon = item; +//ZOID + client->pers.lastweapon = item; +//ZOID + +//ZOID + item = FindItem("Grapple"); + client->pers.inventory[ITEM_INDEX(item)] = 1; +//ZOID + + client->pers.health = 100; + client->pers.max_health = 100; + + client->pers.max_bullets = 200; + client->pers.max_shells = 100; + client->pers.max_rockets = 50; + client->pers.max_grenades = 50; + client->pers.max_cells = 200; + client->pers.max_slugs = 50; + + client->pers.connected = true; +} + + +void InitClientResp (gclient_t *client) +{ +//ZOID + int ctf_team = client->resp.ctf_team; + qboolean id_state = client->resp.id_state; +//ZOID + + memset (&client->resp, 0, sizeof(client->resp)); + +//ZOID + client->resp.ctf_team = ctf_team; + client->resp.id_state = id_state; +//ZOID + + client->resp.enterframe = level.framenum; + client->resp.coop_respawn = client->pers; + +//ZOID + if (ctf->value && client->resp.ctf_team < CTF_TEAM1) + CTFAssignTeam(client); +//ZOID +} + +/* +================== +SaveClientData + +Some information that should be persistant, like health, +is still stored in the edict structure, so it needs to +be mirrored out to the client structure before all the +edicts are wiped. +================== +*/ +void SaveClientData (void) +{ + int i; + edict_t *ent; + + for (i=0 ; iinuse) + continue; + game.clients[i].pers.health = ent->health; + game.clients[i].pers.max_health = ent->max_health; + game.clients[i].pers.powerArmorActive = (ent->flags & FL_POWER_ARMOR); + if (coop->value) + game.clients[i].pers.score = ent->client->resp.score; + } +} + +void FetchClientEntData (edict_t *ent) +{ + ent->health = ent->client->pers.health; + ent->max_health = ent->client->pers.max_health; + if (ent->client->pers.powerArmorActive) + ent->flags |= FL_POWER_ARMOR; + if (coop->value) + ent->client->resp.score = ent->client->pers.score; +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot (edict_t *spot) +{ + edict_t *player; + float bestplayerdistance; + vec3_t v; + int n; + float playerdistance; + + + bestplayerdistance = 9999999; + + for (n = 1; n <= maxclients->value; n++) + { + player = &g_edicts[n]; + + if (!player->inuse) + continue; + + if (player->health <= 0) + continue; + + VectorSubtract (spot->s.origin, player->s.origin, v); + playerdistance = VectorLength (v); + + if (playerdistance < bestplayerdistance) + bestplayerdistance = playerdistance; + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +edict_t *SelectRandomDeathmatchSpawnPoint (void) +{ + edict_t *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + count++; + range = PlayersRangeFromSpot(spot); + if (range < range1) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if (!count) + return NULL; + + if (count <= 2) + { + spot1 = spot2 = NULL; + } + else + count -= 2; + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_Find (spot, FOFS(classname), "info_player_deathmatch"); + if (spot == spot1 || spot == spot2) + selection++; + } while(selection--); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +edict_t *SelectFarthestDeathmatchSpawnPoint (void) +{ + edict_t *bestspot; + float bestdistance, bestplayerdistance; + edict_t *spot; + + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) + { + bestplayerdistance = PlayersRangeFromSpot (spot); + + if (bestplayerdistance > bestdistance) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if (bestspot) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + + return spot; +} + +edict_t *SelectDeathmatchSpawnPoint (void) +{ + if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST) + return SelectFarthestDeathmatchSpawnPoint (); + else + return SelectRandomDeathmatchSpawnPoint (); +} + + +edict_t *SelectCoopSpawnPoint (edict_t *ent) +{ + int index; + edict_t *spot = NULL; + char *target; + + index = ent->client - game.clients; + + // player 0 starts in normal player spawn point + if (!index) + return NULL; + + spot = NULL; + + // assume there are four coop spots at each spawnpoint + while (1) + { + spot = G_Find (spot, FOFS(classname), "info_player_coop"); + if (!spot) + return NULL; // we didn't have enough... + + target = spot->targetname; + if (!target) + target = ""; + if ( Q_stricmp(game.spawnpoint, target) == 0 ) + { // this is a coop spawn point for one of the clients here + index--; + if (!index) + return spot; // this is it + } + } + + + return spot; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, coop start, etc +============ +*/ +void SelectSpawnPoint (edict_t *ent, vec3_t origin, vec3_t angles) +{ + edict_t *spot = NULL; + + if (deathmatch->value) +//ZOID + if (ctf->value) + spot = SelectCTFSpawnPoint(ent); + else +//ZOID + spot = SelectDeathmatchSpawnPoint (); + else if (coop->value) + spot = SelectCoopSpawnPoint (ent); + + // find a single player start spot + if (!spot) + { + while ((spot = G_Find (spot, FOFS(classname), "info_player_start")) != NULL) + { + if (!game.spawnpoint[0] && !spot->targetname) + break; + + if (!game.spawnpoint[0] || !spot->targetname) + continue; + + if (Q_stricmp(game.spawnpoint, spot->targetname) == 0) + break; + } + + if (!spot) + { + if (!game.spawnpoint[0]) + { // there wasn't a spawnpoint without a target, so use any + spot = G_Find (spot, FOFS(classname), "info_player_start"); + } + if (!spot) + gi.error ("Couldn't find spawn point %s\n", game.spawnpoint); + } + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); +} + +//====================================================================== + + +void InitBodyQue (void) +{ + int i; + edict_t *ent; + + level.body_que = 0; + for (i=0; iclassname = "bodyque"; + } +} + +void body_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health < -40) + { + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + self->s.origin[2] -= 48; + ThrowClientHead (self, damage); + self->takedamage = DAMAGE_NO; + } +} + +void CopyToBodyQue (edict_t *ent) +{ + edict_t *body; + + + // grab a body que and cycle to the next one + body = &g_edicts[(int)maxclients->value + level.body_que + 1]; + level.body_que = (level.body_que + 1) % BODY_QUEUE_SIZE; + + // FIXME: send an effect on the removed body + + gi.unlinkentity (ent); + + gi.unlinkentity (body); + body->s = ent->s; + body->s.number = body - g_edicts; + + body->svflags = ent->svflags; + VectorCopy (ent->mins, body->mins); + VectorCopy (ent->maxs, body->maxs); + VectorCopy (ent->absmin, body->absmin); + VectorCopy (ent->absmax, body->absmax); + VectorCopy (ent->size, body->size); + body->solid = ent->solid; + body->clipmask = ent->clipmask; + body->owner = ent->owner; + body->movetype = ent->movetype; + + body->die = body_die; + body->takedamage = DAMAGE_YES; + + gi.linkentity (body); +} + + +void respawn (edict_t *self) +{ + if (deathmatch->value || coop->value) + { + if (self->movetype != MOVETYPE_NOCLIP) + CopyToBodyQue (self); + self->svflags &= ~SVF_NOCLIENT; + PutClientInServer (self); + + // add a teleportation effect + self->s.event = EV_PLAYER_TELEPORT; + + // hold in place briefly + self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; + self->client->ps.pmove.pm_time = 14; + + self->client->respawn_time = level.time; + + return; + } + + // restart the entire server + gi.AddCommandString ("menu_loadgame\n"); +} + +//============================================================== + + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void PutClientInServer (edict_t *ent) +{ + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + client_persistant_t saved; + client_respawn_t resp; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + SelectSpawnPoint (ent, spawn_origin, spawn_angles); + + index = ent-g_edicts-1; + client = ent->client; + + // deathmatch wipes most client data every spawn + if (deathmatch->value) + { + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + InitClientPersistant (client); + ClientUserinfoChanged (ent, userinfo); + } + else if (coop->value) + { + int n; + char userinfo[MAX_INFO_STRING]; + + resp = client->resp; + memcpy (userinfo, client->pers.userinfo, sizeof(userinfo)); + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + resp.coop_respawn.inventory[n] = client->pers.inventory[n]; + } + client->pers = resp.coop_respawn; + ClientUserinfoChanged (ent, userinfo); + if (resp.score > client->pers.score) + client->pers.score = resp.score; + } + else + { + memset (&resp, 0, sizeof(resp)); + } + + // clear everything but the persistant data + saved = client->pers; + memset (client, 0, sizeof(*client)); + client->pers = saved; + if (client->pers.health <= 0) + InitClientPersistant(client); + client->resp = resp; + + // copy some data from the client to the entity + FetchClientEntData (ent); + + // clear entity values + ent->groundentity = NULL; + ent->client = &game.clients[index]; + ent->takedamage = DAMAGE_AIM; + ent->movetype = MOVETYPE_WALK; + ent->viewheight = 22; + ent->inuse = true; + ent->classname = "player"; + ent->mass = 200; + ent->solid = SOLID_BBOX; + ent->deadflag = DEAD_NO; + ent->air_finished = level.time + 12; + ent->clipmask = MASK_PLAYERSOLID; + ent->model = "players/male/tris.md2"; + ent->pain = player_pain; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags &= ~FL_NO_KNOCKBACK; + ent->svflags &= ~SVF_DEADMONSTER; + + VectorCopy (mins, ent->mins); + VectorCopy (maxs, ent->maxs); + VectorClear (ent->velocity); + + // clear playerstate values + memset (&ent->client->ps, 0, sizeof(client->ps)); + + client->ps.pmove.origin[0] = spawn_origin[0]*8; + client->ps.pmove.origin[1] = spawn_origin[1]*8; + client->ps.pmove.origin[2] = spawn_origin[2]*8; +//ZOID + client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; +//ZOID + + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + client->ps.fov = 90; + } + else + { + client->ps.fov = atoi(Info_ValueForKey(client->pers.userinfo, "fov")); + if (client->ps.fov < 1) + client->ps.fov = 90; + else if (client->ps.fov > 160) + client->ps.fov = 160; + } + + client->ps.gunindex = gi.modelindex(client->pers.weapon->view_model); + + // clear entity state values + ent->s.effects = 0; + ent->s.skinnum = ent - g_edicts - 1; + ent->s.modelindex = 255; // will use the skin specified model + ent->s.modelindex2 = 255; // custom gun model + // sknum is player num and weapon number + // weapon number will be added in changeweapon + ent->s.skinnum = ent - g_edicts - 1; + + ent->s.frame = 0; + VectorCopy (spawn_origin, ent->s.origin); + ent->s.origin[2] += 1; // make sure off ground + VectorCopy (ent->s.origin, ent->s.old_origin); + + // set the delta angle + for (i=0 ; i<3 ; i++) + client->ps.pmove.delta_angles[i] = ANGLE2SHORT(spawn_angles[i] - client->resp.cmd_angles[i]); + + ent->s.angles[PITCH] = 0; + ent->s.angles[YAW] = spawn_angles[YAW]; + ent->s.angles[ROLL] = 0; + VectorCopy (ent->s.angles, client->ps.viewangles); + VectorCopy (ent->s.angles, client->v_angle); + +//ZOID + if (CTFStartClient(ent)) + return; +//ZOID + + if (!KillBox (ent)) + { // could't spawn in? + } + + gi.linkentity (ent); + + // force the current weapon up + client->newweapon = client->pers.weapon; + ChangeWeapon (ent); +} + +/* +===================== +ClientBeginDeathmatch + +A client has just connected to the server in +deathmatch mode, so clear everything out before starting them. +===================== +*/ +void ClientBeginDeathmatch (edict_t *ent) +{ + G_InitEdict (ent); + + InitClientResp (ent->client); + + // locate ent at a spawn point + PutClientInServer (ent); + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + + +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +void ClientBegin (edict_t *ent) +{ + int i; + + ent->client = game.clients + (ent - g_edicts - 1); + + if (deathmatch->value) + { + ClientBeginDeathmatch (ent); + return; + } + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == true) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(ent->client->ps.viewangles[i]); + } + else + { + // a spawn point will completely reinitialize the entity + // except for the persistant data that was initialized at + // ClientConnect() time + G_InitEdict (ent); + ent->classname = "player"; + InitClientResp (ent->client); + PutClientInServer (ent); + } + + if (level.intermissiontime) + { + MoveClientToIntermission (ent); + } + else + { + // send effect if in a multiplayer game + if (game.maxclients > 1) + { + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGIN); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname); + } + } + + // make sure all view stuff is valid + ClientEndServerFrame (ent); +} + +/* +=========== +ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +void ClientUserinfoChanged (edict_t *ent, char *userinfo) +{ + char *s; + int playernum; + + // check for malformed or illegal info strings + if (!Info_Validate(userinfo)) + { + strcpy (userinfo, "\\name\\badinfo\\skin\\male/grunt"); + } + + // set name + s = Info_ValueForKey (userinfo, "name"); + strncpy (ent->client->pers.netname, s, sizeof(ent->client->pers.netname)-1); + + // set skin + s = Info_ValueForKey (userinfo, "skin"); + + playernum = ent-g_edicts-1; + + // combine name and skin into a configstring +//ZOID + if (ctf->value) + CTFAssignSkin(ent, s); + else +//ZOID + gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", ent->client->pers.netname, s) ); + + // fov + if (deathmatch->value && ((int)dmflags->value & DF_FIXED_FOV)) + { + ent->client->ps.fov = 90; + } + else + { + ent->client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); + if (ent->client->ps.fov < 1) + ent->client->ps.fov = 90; + else if (ent->client->ps.fov > 160) + ent->client->ps.fov = 160; + } + + // handedness + s = Info_ValueForKey (userinfo, "hand"); + if (strlen(s)) + { + ent->client->pers.hand = atoi(s); + } + + // save off the userinfo in case we want to check something later + strncpy (ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)-1); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +The game can refuse entrance to a client by returning false. +If the client is allowed, the connection process will continue +and eventually get to ClientBegin() +Changing levels will NOT cause this to be called again, but +loadgames will. +============ +*/ +qboolean ClientConnect (edict_t *ent, char *userinfo) +{ + char *value; + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if (*password->string && strcmp(password->string, "none") && + strcmp(password->string, value)) { + Info_SetValueForKey(userinfo, "rejmsg", "Password required or incorrect."); + return false; + } + + // they can connect + ent->client = game.clients + (ent - g_edicts - 1); + + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if (ent->inuse == false) + { + // clear the respawning variables +//ZOID -- force team join + ent->client->resp.ctf_team = -1; + ent->client->resp.id_state = false; +//ZOID + InitClientResp (ent->client); + if (!game.autosaved || !ent->client->pers.weapon) + InitClientPersistant (ent->client); + } + + ClientUserinfoChanged (ent, userinfo); + + if (game.maxclients > 1) + gi.dprintf ("%s connected\n", ent->client->pers.netname); + + ent->client->pers.connected = true; + return true; +} + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. +============ +*/ +void ClientDisconnect (edict_t *ent) +{ + int playernum; + + if (!ent->client) + return; + + gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname); + +//ZOID + CTFDeadDropFlag(ent); + CTFDeadDropTech(ent); +//ZOID + + // send effect + gi.WriteByte (svc_muzzleflash); + gi.WriteShort (ent-g_edicts); + gi.WriteByte (MZ_LOGOUT); + gi.multicast (ent->s.origin, MULTICAST_PVS); + + gi.unlinkentity (ent); + ent->s.modelindex = 0; + ent->solid = SOLID_NOT; + ent->inuse = false; + ent->classname = "disconnected"; + ent->client->pers.connected = false; + + playernum = ent-g_edicts-1; + gi.configstring (CS_PLAYERSKINS+playernum, ""); +} + + +//============================================================== + + +edict_t *pm_passent; + +// pmove doesn't need to know about passent and contentmask +trace_t PM_trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end) +{ + if (pm_passent->health > 0) + return gi.trace (start, mins, maxs, end, pm_passent, MASK_PLAYERSOLID); + else + return gi.trace (start, mins, maxs, end, pm_passent, MASK_DEADSOLID); +} + +unsigned CheckBlock (void *b, int c) +{ + int v,i; + v = 0; + for (i=0 ; is, sizeof(pm->s)); + c2 = CheckBlock (&pm->cmd, sizeof(pm->cmd)); + Com_Printf ("sv %3i:%i %i\n", pm->cmd.impulse, c1, c2); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void ClientThink (edict_t *ent, usercmd_t *ucmd) +{ + gclient_t *client; + edict_t *other; + int i, j; + pmove_t pm; + + level.current_entity = ent; + client = ent->client; + + if (level.intermissiontime) + { + client->ps.pmove.pm_type = PM_FREEZE; + // can exit intermission after five seconds + if (level.time > level.intermissiontime + 5.0 + && (ucmd->buttons & BUTTON_ANY) ) + level.exitintermission = true; + return; + } + + pm_passent = ent; + +//ZOID + if (ent->client->chase_target) { + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + return; + } +//ZOID + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + + if (ent->movetype == MOVETYPE_NOCLIP) + client->ps.pmove.pm_type = PM_SPECTATOR; + else if (ent->s.modelindex != 255) + client->ps.pmove.pm_type = PM_GIB; + else if (ent->deadflag) + client->ps.pmove.pm_type = PM_DEAD; + else + client->ps.pmove.pm_type = PM_NORMAL; + + client->ps.pmove.gravity = sv_gravity->value; + pm.s = client->ps.pmove; + + for (i=0 ; i<3 ; i++) + { + pm.s.origin[i] = ent->s.origin[i]*8; + pm.s.velocity[i] = ent->velocity[i]*8; + } + + if (memcmp(&client->old_pmove, &pm.s, sizeof(pm.s))) + { + pm.snapinitial = true; +// gi.dprintf ("pmove changed!\n"); + } + + pm.cmd = *ucmd; + + pm.trace = PM_trace; // adds default parms + pm.pointcontents = gi.pointcontents; + + // perform a pmove + gi.Pmove (&pm); + + // save results of pmove + client->ps.pmove = pm.s; + client->old_pmove = pm.s; + + for (i=0 ; i<3 ; i++) + { + ent->s.origin[i] = pm.s.origin[i]*0.125; + ent->velocity[i] = pm.s.velocity[i]*0.125; + } + + VectorCopy (pm.mins, ent->mins); + VectorCopy (pm.maxs, ent->maxs); + + client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]); + client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]); + client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); + + if (ent->groundentity && !pm.groundentity && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) + { + gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0); + PlayerNoise(ent, ent->s.origin, PNOISE_SELF); + } + + ent->viewheight = pm.viewheight; + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + ent->groundentity = pm.groundentity; + if (pm.groundentity) + ent->groundentity_linkcount = pm.groundentity->linkcount; + + if (ent->deadflag) + { + client->ps.viewangles[ROLL] = 40; + client->ps.viewangles[PITCH] = -15; + client->ps.viewangles[YAW] = client->killer_yaw; + } + else + { + VectorCopy (pm.viewangles, client->v_angle); + VectorCopy (pm.viewangles, client->ps.viewangles); + } + +//ZOID + if (client->ctf_grapple) + CTFGrapplePull(client->ctf_grapple); +//ZOID + + gi.linkentity (ent); + + if (ent->movetype != MOVETYPE_NOCLIP) + G_TouchTriggers (ent); + + // touch other objects + for (i=0 ; itouch) + continue; + other->touch (other, ent, NULL, NULL); + } + + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // save light level the player is standing on for + // monster sighting AI + ent->light_level = ucmd->lightlevel; + + // fire weapon from final position if needed + if (client->latched_buttons & BUTTON_ATTACK +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + { + if (!client->weapon_thunk) + { + client->weapon_thunk = true; + Think_Weapon (ent); + } + } + +//ZOID +//regen tech + CTFApplyRegeneration(ent); +//ZOID + +//ZOID + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && other->client->chase_target == ent) + UpdateChaseCam(other); + } + + if (client->menudirty && client->menutime <= level.time) { + PMenu_Do_Update(ent); + gi.unicast (ent, true); + client->menutime = level.time; + client->menudirty = false; + } +//ZOID +} + + +/* +============== +ClientBeginServerFrame + +This will be called once for each server frame, before running +any other entities in the world. +============== +*/ +void ClientBeginServerFrame (edict_t *ent) +{ + gclient_t *client; + int buttonMask; + + if (level.intermissiontime) + return; + + client = ent->client; + + // run weapon animations if it hasn't been done by a ucmd_t + if (!client->weapon_thunk +//ZOID + && ent->movetype != MOVETYPE_NOCLIP +//ZOID + ) + Think_Weapon (ent); + else + client->weapon_thunk = false; + + if (ent->deadflag) + { + // wait for any button just going down + if ( level.time > client->respawn_time) + { + // in deathmatch, only wait for attack button + if (deathmatch->value) + buttonMask = BUTTON_ATTACK; + else + buttonMask = -1; + + if ( ( client->latched_buttons & buttonMask ) || + (deathmatch->value && ((int)dmflags->value & DF_FORCE_RESPAWN) ) || + CTFMatchOn()) + { + respawn(ent); + client->latched_buttons = 0; + } + } + return; + } + + // add player trail so monsters can follow + if (!deathmatch->value) + if (!visible (ent, PlayerTrail_LastSpot() ) ) + PlayerTrail_Add (ent->s.old_origin); + + client->latched_buttons = 0; +} diff --git a/ctf/p_hud.c b/ctf/p_hud.c new file mode 100644 index 000000000..fdccf2af9 --- /dev/null +++ b/ctf/p_hud.c @@ -0,0 +1,544 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + + +/* +====================================================================== + +INTERMISSION + +====================================================================== +*/ + +void MoveClientToIntermission (edict_t *ent) +{ + if (deathmatch->value || coop->value) + ent->client->showscores = true; + VectorCopy (level.intermission_origin, ent->s.origin); + ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8; + ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8; + ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8; + VectorCopy (level.intermission_angle, ent->client->ps.viewangles); + ent->client->ps.pmove.pm_type = PM_FREEZE; + ent->client->ps.gunindex = 0; + ent->client->ps.blend[3] = 0; + ent->client->ps.rdflags &= ~RDF_UNDERWATER; + + // clean up powerup info + ent->client->quad_framenum = 0; + ent->client->invincible_framenum = 0; + ent->client->breather_framenum = 0; + ent->client->enviro_framenum = 0; + ent->client->grenade_blew_up = false; + ent->client->grenade_time = 0; + + ent->viewheight = 0; + ent->s.modelindex = 0; + ent->s.modelindex2 = 0; + ent->s.modelindex3 = 0; + ent->s.modelindex = 0; + ent->s.effects = 0; + ent->s.sound = 0; + ent->solid = SOLID_NOT; + + // add the layout + + if (deathmatch->value || coop->value) + { + DeathmatchScoreboardMessage (ent, NULL); + gi.unicast (ent, true); + } + +} + +void BeginIntermission (edict_t *targ) +{ + int i, n; + edict_t *ent, *client; + + if (level.intermissiontime) + return; // allready activated + +//ZOID + if (deathmatch->value && ctf->value) + CTFCalcScores(); +//ZOID + + game.autosaved = false; + + // respawn any dead clients + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + if (client->health <= 0) + respawn(client); + } + + level.intermissiontime = level.time; + level.changemap = targ->map; + + if (strstr(level.changemap, "*")) + { + if (coop->value) + { + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + // strip players of all keys between units + for (n = 0; n < MAX_ITEMS; n++) + { + if (itemlist[n].flags & IT_KEY) + client->client->pers.inventory[n] = 0; + } + } + } + } + else + { + if (!deathmatch->value) + { + level.exitintermission = 1; // go immediately to the next level + return; + } + } + + level.exitintermission = 0; + + // find an intermission spot + ent = G_Find (NULL, FOFS(classname), "info_player_intermission"); + if (!ent) + { // the map creator forgot to put in an intermission point... + ent = G_Find (NULL, FOFS(classname), "info_player_start"); + if (!ent) + ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch"); + } + else + { // chose one of four spots + i = rand() & 3; + while (i--) + { + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + if (!ent) // wrap around the list + ent = G_Find (ent, FOFS(classname), "info_player_intermission"); + } + } + + VectorCopy (ent->s.origin, level.intermission_origin); + VectorCopy (ent->s.angles, level.intermission_angle); + + // move all clients to the intermission point + for (i=0 ; ivalue ; i++) + { + client = g_edicts + 1 + i; + if (!client->inuse) + continue; + MoveClientToIntermission (client); + } +} + + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer) +{ + char entry[1024]; + char string[1400]; + int stringlength; + int i, j, k; + int sorted[MAX_CLIENTS]; + int sortedscores[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t *cl; + edict_t *cl_ent; + char *tag; + +//ZOID + if (ctf->value) { + CTFScoreboardMessage (ent, killer); + return; + } +//ZOID + + // sort the clients by score + total = 0; + for (i=0 ; iinuse) + continue; + score = game.clients[i].resp.score; + for (j=0 ; j sortedscores[j]) + break; + } + for (k=total ; k>j ; k--) + { + sorted[k] = sorted[k-1]; + sortedscores[k] = sortedscores[k-1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string[0] = 0; + + stringlength = strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i=0 ; i=6) ? 160 : 0; + y = 32 + 32 * (i%6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = NULL; + if (tag) + { + Com_sprintf (entry, sizeof(entry), + "xv %i yv %i picn %s ",x+32, y, tag); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + Com_sprintf (entry, sizeof(entry), + "client %i %i %i %i %i %i ", + x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + + +/* +================== +DeathmatchScoreboard + +Draw instead of help message. +Note that it isn't that hard to overflow the 1400 byte message limit! +================== +*/ +void DeathmatchScoreboard (edict_t *ent) +{ + DeathmatchScoreboardMessage (ent, ent->enemy); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Score_f + +Display the scoreboard +================== +*/ +void Cmd_Score_f (edict_t *ent) +{ + ent->client->showinventory = false; + ent->client->showhelp = false; +//ZOID + if (ent->client->menu) + PMenu_Close(ent); +//ZOID + + if (!deathmatch->value && !coop->value) + return; + + if (ent->client->showscores) + { + ent->client->showscores = false; + ent->client->update_chase = true; + return; + } + + ent->client->showscores = true; + + DeathmatchScoreboard (ent); +} + + +/* +================== +HelpComputer + +Draw help computer. +================== +*/ +void HelpComputer (edict_t *ent) +{ + char string[1024]; + char *sk; + + if (skill->value == 0) + sk = "easy"; + else if (skill->value == 1) + sk = "medium"; + else if (skill->value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + Com_sprintf (string, sizeof(string), + "xv 32 yv 8 picn help " // background + "xv 202 yv 12 string2 \"%s\" " // skill + "xv 0 yv 24 cstring2 \"%s\" " // level name + "xv 0 yv 54 cstring2 \"%s\" " // help 1 + "xv 0 yv 110 cstring2 \"%s\" " // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ", + sk, + level.level_name, + game.helpmessage1, + game.helpmessage2, + level.killed_monsters, level.total_monsters, + level.found_goals, level.total_goals, + level.found_secrets, level.total_secrets); + + gi.WriteByte (svc_layout); + gi.WriteString (string); + gi.unicast (ent, true); +} + + +/* +================== +Cmd_Help_f + +Display the current help message +================== +*/ +void Cmd_Help_f (edict_t *ent) +{ + // this is for backwards compatability + if (deathmatch->value) + { + Cmd_Score_f (ent); + return; + } + + ent->client->showinventory = false; + ent->client->showscores = false; + + if (ent->client->showhelp && (ent->client->resp.game_helpchanged == game.helpchanged)) + { + ent->client->showhelp = false; + return; + } + + ent->client->showhelp = true; + ent->client->resp.helpchanged = 0; + HelpComputer (ent); +} + + +//======================================================================= + +/* +=============== +G_SetStats +=============== +*/ +void G_SetStats (edict_t *ent) +{ + gitem_t *item; + int index, cells; + int power_armor_type; + + // + // health + // + ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health; + ent->client->ps.stats[STAT_HEALTH] = ent->health; + + // + // ammo + // + if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */) + { + ent->client->ps.stats[STAT_AMMO_ICON] = 0; + ent->client->ps.stats[STAT_AMMO] = 0; + } + else + { + item = &itemlist[ent->client->ammo_index]; + ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index]; + } + + // + // armor + // + power_armor_type = PowerArmorType (ent); + if (power_armor_type) + { + cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))]; + if (cells == 0) + { // ran out of cells for power armor + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + power_armor_type = 0;; + } + } + + index = ArmorIndex (ent); + if (power_armor_type && (!index || (level.framenum & 8) ) ) + { // flash between power armor and other armor icon + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield"); + ent->client->ps.stats[STAT_ARMOR] = cells; + } + else if (index) + { + item = GetItemByIndex (index); + ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon); + ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index]; + } + else + { + ent->client->ps.stats[STAT_ARMOR_ICON] = 0; + ent->client->ps.stats[STAT_ARMOR] = 0; + } + + // + // pickup message + // + if (level.time > ent->client->pickup_msg_time) + { + ent->client->ps.stats[STAT_PICKUP_ICON] = 0; + ent->client->ps.stats[STAT_PICKUP_STRING] = 0; + } + + // + // timers + // + if (ent->client->quad_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10; + } + else if (ent->client->invincible_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10; + } + else if (ent->client->enviro_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10; + } + else if (ent->client->breather_framenum > level.framenum) + { + ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather"); + ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10; + } + else + { + ent->client->ps.stats[STAT_TIMER_ICON] = 0; + ent->client->ps.stats[STAT_TIMER] = 0; + } + + // + // selected item + // + if (ent->client->pers.selected_item == -1) + ent->client->ps.stats[STAT_SELECTED_ICON] = 0; + else + ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon); + + ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item; + + // + // layouts + // + ent->client->ps.stats[STAT_LAYOUTS] = 0; + + if (deathmatch->value) + { + if (ent->client->pers.health <= 0 || level.intermissiontime + || ent->client->showscores) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + else + { + if (ent->client->showscores || ent->client->showhelp) + ent->client->ps.stats[STAT_LAYOUTS] |= 1; + if (ent->client->showinventory && ent->client->pers.health > 0) + ent->client->ps.stats[STAT_LAYOUTS] |= 2; + } + + // + // frags + // + ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score; + + // + // help icon / current weapon if not shown + // + if (ent->client->resp.helpchanged && (level.framenum&8) ) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help"); + else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91) + && ent->client->pers.weapon) + ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon); + else + ent->client->ps.stats[STAT_HELPICON] = 0; + +//ZOID + SetCTFStats(ent); +//ZOID +} + diff --git a/ctf/p_menu.c b/ctf/p_menu.c new file mode 100644 index 000000000..fe61e803b --- /dev/null +++ b/ctf/p_menu.c @@ -0,0 +1,256 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + +// Note that the pmenu entries are duplicated +// this is so that a static set of pmenu entries can be used +// for multiple clients and changed without interference +// note that arg will be freed when the menu is closed, it must be allocated memory +pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + int i; + + if (!ent->client) + return NULL; + + if (ent->client->menu) { + gi.dprintf("warning, ent already has a menu\n"); + PMenu_Close(ent); + } + + hnd = malloc(sizeof(*hnd)); + + hnd->arg = arg; + hnd->entries = malloc(sizeof(pmenu_t) * num); + memcpy(hnd->entries, entries, sizeof(pmenu_t) * num); + // duplicate the strings since they may be from static memory + for (i = 0; i < num; i++) + if (entries[i].text) + hnd->entries[i].text = strdup(entries[i].text); + + hnd->num = num; + + if (cur < 0 || !entries[cur].SelectFunc) { + for (i = 0, p = entries; i < num; i++, p++) + if (p->SelectFunc) + break; + } else + i = cur; + + if (i >= num) + hnd->cur = -1; + else + hnd->cur = i; + + ent->client->showscores = true; + ent->client->inmenu = true; + ent->client->menu = hnd; + + PMenu_Do_Update(ent); + gi.unicast (ent, true); + + return hnd; +} + +void PMenu_Close(edict_t *ent) +{ + int i; + pmenuhnd_t *hnd; + + if (!ent->client->menu) + return; + + hnd = ent->client->menu; + for (i = 0; i < hnd->num; i++) + if (hnd->entries[i].text) + free(hnd->entries[i].text); + free(hnd->entries); + if (hnd->arg) + free(hnd->arg); + free(hnd); + ent->client->menu = NULL; + ent->client->showscores = false; +} + +// only use on pmenu's that have been called with PMenu_Open +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc) +{ + if (entry->text) + free(entry->text); + entry->text = strdup(text); + entry->align = align; + entry->SelectFunc = SelectFunc; +} + +void PMenu_Do_Update(edict_t *ent) +{ + char string[1400]; + int i; + pmenu_t *p; + int x; + pmenuhnd_t *hnd; + char *t; + qboolean alt = false; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + strcpy(string, "xv 32 yv 8 picn inventory "); + + for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) { + if (!p->text || !*(p->text)) + continue; // blank line + t = p->text; + if (*t == '*') { + alt = true; + t++; + } + sprintf(string + strlen(string), "yv %d ", 32 + i * 8); + if (p->align == PMENU_ALIGN_CENTER) + x = 196/2 - strlen(t)*4 + 64; + else if (p->align == PMENU_ALIGN_RIGHT) + x = 64 + (196 - strlen(t)*8); + else + x = 64; + + sprintf(string + strlen(string), "xv %d ", + x - ((hnd->cur == i) ? 8 : 0)); + + if (hnd->cur == i) + sprintf(string + strlen(string), "string2 \"\x0d%s\" ", t); + else if (alt) + sprintf(string + strlen(string), "string2 \"%s\" ", t); + else + sprintf(string + strlen(string), "string \"%s\" ", t); + alt = false; + } + + gi.WriteByte (svc_layout); + gi.WriteString (string); +} + +void PMenu_Update(edict_t *ent) +{ + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + if (level.time - ent->client->menutime >= 1.0) { + // been a second or more since last update, update now + PMenu_Do_Update(ent); + gi.unicast (ent, true); + ent->client->menutime = level.time; + ent->client->menudirty = false; + } + ent->client->menutime = level.time + 0.2; + ent->client->menudirty = true; +} + +void PMenu_Next(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + i++, p++; + if (i == hnd->num) + i = 0, p = hnd->entries; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Prev(edict_t *ent) +{ + pmenuhnd_t *hnd; + int i; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + i = hnd->cur; + p = hnd->entries + hnd->cur; + do { + if (i == 0) { + i = hnd->num - 1; + p = hnd->entries + i; + } else + i--, p--; + if (p->SelectFunc) + break; + } while (i != hnd->cur); + + hnd->cur = i; + + PMenu_Update(ent); +} + +void PMenu_Select(edict_t *ent) +{ + pmenuhnd_t *hnd; + pmenu_t *p; + + if (!ent->client->menu) { + gi.dprintf("warning: ent has no menu\n"); + return; + } + + hnd = ent->client->menu; + + if (hnd->cur < 0) + return; // no selectable entries + + p = hnd->entries + hnd->cur; + + if (p->SelectFunc) + p->SelectFunc(ent, hnd); +} diff --git a/ctf/p_menu.h b/ctf/p_menu.h new file mode 100644 index 000000000..d16cc1597 --- /dev/null +++ b/ctf/p_menu.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +enum { + PMENU_ALIGN_LEFT, + PMENU_ALIGN_CENTER, + PMENU_ALIGN_RIGHT +}; + +typedef struct pmenuhnd_s { + struct pmenu_s *entries; + int cur; + int num; + void *arg; +} pmenuhnd_t; + +typedef void (*SelectFunc_t)(edict_t *ent, pmenuhnd_t *hnd); + +typedef struct pmenu_s { + char *text; + int align; + SelectFunc_t SelectFunc; +} pmenu_t; + +pmenuhnd_t *PMenu_Open(edict_t *ent, pmenu_t *entries, int cur, int num, void *arg); +void PMenu_Close(edict_t *ent); +void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc); +void PMenu_Do_Update(edict_t *ent); +void PMenu_Update(edict_t *ent); +void PMenu_Next(edict_t *ent); +void PMenu_Prev(edict_t *ent); +void PMenu_Select(edict_t *ent); diff --git a/ctf/p_trail.c b/ctf/p_trail.c new file mode 100644 index 000000000..d968682f2 --- /dev/null +++ b/ctf/p_trail.c @@ -0,0 +1,146 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +/* +============================================================================== + +PLAYER TRAIL + +============================================================================== + +This is a circular list containing the a list of points of where +the player has been recently. It is used by monsters for pursuit. + +.origin the spot +.owner forward link +.aiment backward link +*/ + + +#define TRAIL_LENGTH 8 + +edict_t *trail[TRAIL_LENGTH]; +int trail_head; +qboolean trail_active = false; + +#define NEXT(n) (((n) + 1) & (TRAIL_LENGTH - 1)) +#define PREV(n) (((n) - 1) & (TRAIL_LENGTH - 1)) + + +void PlayerTrail_Init (void) +{ + int n; + + if (deathmatch->value /* FIXME || coop */) + return; + + for (n = 0; n < TRAIL_LENGTH; n++) + { + trail[n] = G_Spawn(); + trail[n]->classname = "player_trail"; + } + + trail_head = 0; + trail_active = true; +} + + +void PlayerTrail_Add (vec3_t spot) +{ + vec3_t temp; + + if (!trail_active) + return; + + VectorCopy (spot, trail[trail_head]->s.origin); + + trail[trail_head]->timestamp = level.time; + + VectorSubtract (spot, trail[PREV(trail_head)]->s.origin, temp); + trail[trail_head]->s.angles[1] = vectoyaw (temp); + + trail_head = NEXT(trail_head); +} + + +void PlayerTrail_New (vec3_t spot) +{ + if (!trail_active) + return; + + PlayerTrail_Init (); + PlayerTrail_Add (spot); +} + + +edict_t *PlayerTrail_PickFirst (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + if (visible(self, trail[marker])) + { + return trail[marker]; + } + + if (visible(self, trail[PREV(marker)])) + { + return trail[PREV(marker)]; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_PickNext (edict_t *self) +{ + int marker; + int n; + + if (!trail_active) + return NULL; + + for (marker = trail_head, n = TRAIL_LENGTH; n; n--) + { + if(trail[marker]->timestamp <= self->monsterinfo.trail_time) + marker = NEXT(marker); + else + break; + } + + return trail[marker]; +} + +edict_t *PlayerTrail_LastSpot (void) +{ + return trail[PREV(trail_head)]; +} diff --git a/game/g_ai.c b/game/g_ai.c new file mode 100644 index 000000000..c5ea318e6 --- /dev/null +++ b/game/g_ai.c @@ -0,0 +1,1117 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_ai.c + +#include "g_local.h" + +qboolean FindTarget (edict_t *self); +extern cvar_t *maxclients; + +qboolean ai_checkattack (edict_t *self, float dist); + +qboolean enemy_vis; +qboolean enemy_infront; +int enemy_range; +float enemy_yaw; + +//============================================================================ + + +/* +================= +AI_SetSightClient + +Called once each frame to set level.sight_client to the +player to be checked for in findtarget. + +If all clients are either dead or in notarget, sight_client +will be null. + +In coop games, sight_client will cycle between the clients. +================= +*/ +void AI_SetSightClient (void) +{ + edict_t *ent; + int start, check; + + if (level.sight_client == NULL) + start = 1; + else + start = level.sight_client - g_edicts; + + check = start; + while (1) + { + check++; + if (check > game.maxclients) + check = 1; + ent = &g_edicts[check]; + if (ent->inuse + && ent->health > 0 + && !(ent->flags & FL_NOTARGET) ) + { + level.sight_client = ent; + return; // got one + } + if (check == start) + { + level.sight_client = NULL; + return; // nobody to see + } + } +} + +//============================================================================ + +/* +============= +ai_move + +Move the specified distance at current facing. +This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward +============== +*/ +void ai_move (edict_t *self, float dist) +{ + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_stand + +Used for standing around and looking for players +Distance is for slight position adjustments needed by the animations +============== +*/ +void ai_stand (edict_t *self, float dist) +{ + vec3_t v; + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + if (self->enemy) + { + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + { + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.run (self); + } + M_ChangeYaw (self); + ai_checkattack (self, 0); + } + else + FindTarget (self); + return; + } + + if (FindTarget (self)) + return; + + if (level.time > self->monsterinfo.pausetime) + { + self->monsterinfo.walk (self); + return; + } + + if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.idle (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_walk + +The monster is walking it's beat +============= +*/ +void ai_walk (edict_t *self, float dist) +{ + M_MoveToGoal (self, dist); + + // check for noticing a player + if (FindTarget (self)) + return; + + if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time)) + { + if (self->monsterinfo.idle_time) + { + self->monsterinfo.search (self); + self->monsterinfo.idle_time = level.time + 15 + random() * 15; + } + else + { + self->monsterinfo.idle_time = level.time + random() * 15; + } + } +} + + +/* +============= +ai_charge + +Turns towards target and advances +Use this call with a distnace of 0 to replace ai_face +============== +*/ +void ai_charge (edict_t *self, float dist) +{ + vec3_t v; + + VectorSubtract (self->enemy->s.origin, self->s.origin, v); + self->ideal_yaw = vectoyaw(v); + M_ChangeYaw (self); + + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); +} + + +/* +============= +ai_turn + +don't move, but turn towards ideal_yaw +Distance is for slight position adjustments needed by the animations +============= +*/ +void ai_turn (edict_t *self, float dist) +{ + if (dist) + M_walkmove (self, self->s.angles[YAW], dist); + + if (FindTarget (self)) + return; + + M_ChangeYaw (self); +} + + +/* + +.enemy +Will be world if not currently angry at anyone. + +.movetarget +The next path spot to walk toward. If .enemy, ignore .movetarget. +When an enemy is killed, the monster will try to return to it's path. + +.hunt_time +Set to time + something when the player is in sight, but movement straight for +him is blocked. This causes the monster to use wall following code for +movement direction instead of sighting on the player. + +.ideal_yaw +A yaw angle of the intended direction, which will be turned towards at up +to 45 deg / state. If the enemy is in view and hunt_time is not active, +this will be the exact line towards the enemy. + +.pausetime +A monster will leave it's stand state and head towards it's .movetarget when +time > .pausetime. + +walkmove(angle, speed) primitive is all or nothing +*/ + +/* +============= +range + +returns the range catagorization of an entity reletive to self +0 melee range, will become hostile even if back is turned +1 visibility and infront, or visibility and show hostile +2 infront and show hostile +3 only triggered by damage +============= +*/ +int range (edict_t *self, edict_t *other) +{ + vec3_t v; + float len; + + VectorSubtract (self->s.origin, other->s.origin, v); + len = VectorLength (v); + if (len < MELEE_DISTANCE) + return RANGE_MELEE; + if (len < 500) + return RANGE_NEAR; + if (len < 1000) + return RANGE_MID; + return RANGE_FAR; +} + +/* +============= +visible + +returns 1 if the entity is visible to self, even if not infront () +============= +*/ +qboolean visible (edict_t *self, edict_t *other) +{ + vec3_t spot1; + vec3_t spot2; + trace_t trace; + + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (other->s.origin, spot2); + spot2[2] += other->viewheight; + trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE); + + if (trace.fraction == 1.0) + return true; + return false; +} + + +/* +============= +infront + +returns 1 if the entity is in front (in sight) of self +============= +*/ +qboolean infront (edict_t *self, edict_t *other) +{ + vec3_t vec; + float dot; + vec3_t forward; + + AngleVectors (self->s.angles, forward, NULL, NULL); + VectorSubtract (other->s.origin, self->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + + if (dot > 0.3) + return true; + return false; +} + + +//============================================================================ + +void HuntTarget (edict_t *self) +{ + vec3_t vec; + + self->goalentity = self->enemy; + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + self->monsterinfo.stand (self); + else + self->monsterinfo.run (self); + VectorSubtract (self->enemy->s.origin, self->s.origin, vec); + self->ideal_yaw = vectoyaw(vec); + // wait a while before first attack + if (!(self->monsterinfo.aiflags & AI_STAND_GROUND)) + AttackFinished (self, 1); +} + +void FoundTarget (edict_t *self) +{ + // let other monsters see this monster for a while + if (self->enemy->client) + { + level.sight_entity = self; + level.sight_entity_framenum = level.framenum; + level.sight_entity->light_level = 128; + } + + self->show_hostile = level.time + 1; // wake up other monsters + + VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + + if (!self->combattarget) + { + HuntTarget (self); + return; + } + + self->goalentity = self->movetarget = G_PickTarget(self->combattarget); + if (!self->movetarget) + { + self->goalentity = self->movetarget = self->enemy; + HuntTarget (self); + gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget); + return; + } + + // clear out our combattarget, these are a one shot deal + self->combattarget = NULL; + self->monsterinfo.aiflags |= AI_COMBAT_POINT; + + // clear the targetname, that point is ours! + self->movetarget->targetname = NULL; + self->monsterinfo.pausetime = 0; + + // run for it + self->monsterinfo.run (self); +} + + +/* +=========== +FindTarget + +Self is currently not attacking anything, so try to find a target + +Returns TRUE if an enemy was sighted + +When a player fires a missile, the point of impact becomes a fakeplayer so +that monsters that see the impact will respond as if they had seen the +player. + +To avoid spending too much time, only a single client (or fakeclient) is +checked each frame. This means multi player games will have slightly +slower noticing monsters. +============ +*/ +qboolean FindTarget (edict_t *self) +{ + edict_t *client; + qboolean heardit; + int r; + + if (self->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (self->goalentity && self->goalentity->inuse && self->goalentity->classname) + { + if (strcmp(self->goalentity->classname, "target_actor") == 0) + return false; + } + + //FIXME look for monsters? + return false; + } + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + +// if the first spawnflag bit is set, the monster will only wake up on +// really seeing the player, not another monster getting angry or hearing +// something + +// revised behavior so they will wake up if they "see" a player make a noise +// but not weapon impact/explosion noises + + heardit = false; + if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sight_entity; + if (client->enemy == self->enemy) + { + return false; + } + } + else if (level.sound_entity_framenum >= (level.framenum - 1)) + { + client = level.sound_entity; + heardit = true; + } + else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) ) + { + client = level.sound2_entity; + heardit = true; + } + else + { + client = level.sight_client; + if (!client) + return false; // no clients to get mad at + } + + // if the entity went away, forget it + if (!client->inuse) + return false; + + if (client == self->enemy) + return true; // JDC false; + + if (client->client) + { + if (client->flags & FL_NOTARGET) + return false; + } + else if (client->svflags & SVF_MONSTER) + { + if (!client->enemy) + return false; + if (client->enemy->flags & FL_NOTARGET) + return false; + } + else if (heardit) + { + if (client->owner->flags & FL_NOTARGET) + return false; + } + else + return false; + + if (!heardit) + { + r = range (self, client); + + if (r == RANGE_FAR) + return false; + +// this is where we would check invisibility + + // is client in an spot too dark to be seen? + if (client->light_level <= 5) + return false; + + if (!visible (self, client)) + { + return false; + } + + if (r == RANGE_NEAR) + { + if (client->show_hostile < level.time && !infront (self, client)) + { + return false; + } + } + else if (r == RANGE_MID) + { + if (!infront (self, client)) + { + return false; + } + } + + self->enemy = client; + + if (strcmp(self->enemy->classname, "player_noise") != 0) + { + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + if (!self->enemy->client) + { + self->enemy = self->enemy->enemy; + if (!self->enemy->client) + { + self->enemy = NULL; + return false; + } + } + } + } + else // heardit + { + vec3_t temp; + + if (self->spawnflags & 1) + { + if (!visible (self, client)) + return false; + } + else + { + if (!gi.inPHS(self->s.origin, client->s.origin)) + return false; + } + + VectorSubtract (client->s.origin, self->s.origin, temp); + + if (VectorLength(temp) > 1000) // too far to hear + { + return false; + } + + // check area portals - if they are different and not connected then we can't hear it + if (client->areanum != self->areanum) + if (!gi.AreasConnected(self->areanum, client->areanum)) + return false; + + self->ideal_yaw = vectoyaw(temp); + M_ChangeYaw (self); + + // hunt the sound for a bit; hopefully find the real player + self->monsterinfo.aiflags |= AI_SOUND_TARGET; + self->enemy = client; + } + +// +// got one +// + FoundTarget (self); + + if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight)) + self->monsterinfo.sight (self, self->enemy); + + return true; +} + + +//============================================================================= + +/* +============ +FacingIdeal + +============ +*/ +qboolean FacingIdeal(edict_t *self) +{ + float delta; + + delta = anglemod(self->s.angles[YAW] - self->ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; +} + + +//============================================================================= + +qboolean M_CheckAttack (edict_t *self) +{ + vec3_t spot1, spot2; + float chance; + trace_t tr; + + if (self->enemy->health > 0) + { + // see if any entities are in the way of the shot + VectorCopy (self->s.origin, spot1); + spot1[2] += self->viewheight; + VectorCopy (self->enemy->s.origin, spot2); + spot2[2] += self->enemy->viewheight; + + tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self->enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) + { + // don't always melee in easy mode + if (skill->value == 0 && (rand()&3) ) + return false; + if (self->monsterinfo.melee) + self->monsterinfo.attack_state = AS_MELEE; + else + self->monsterinfo.attack_state = AS_MISSILE; + return true; + } + +// missile attack + if (!self->monsterinfo.attack) + return false; + + if (level.time < self->monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + chance = 0.4; + } + else if (enemy_range == RANGE_MELEE) + { + chance = 0.2; + } + else if (enemy_range == RANGE_NEAR) + { + chance = 0.1; + } + else if (enemy_range == RANGE_MID) + { + chance = 0.02; + } + else + { + return false; + } + + if (skill->value == 0) + chance *= 0.5; + else if (skill->value >= 2) + chance *= 2; + + if (random () < chance) + { + self->monsterinfo.attack_state = AS_MISSILE; + self->monsterinfo.attack_finished = level.time + 2*random(); + return true; + } + + if (self->flags & FL_FLY) + { + if (random() < 0.3) + self->monsterinfo.attack_state = AS_SLIDING; + else + self->monsterinfo.attack_state = AS_STRAIGHT; + } + + return false; +} + + +/* +============= +ai_run_melee + +Turn and close until within an angle to launch a melee attack +============= +*/ +void ai_run_melee(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.melee (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +} + + +/* +============= +ai_run_missile + +Turn in place until within an angle to launch a missile attack +============= +*/ +void ai_run_missile(edict_t *self) +{ + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (FacingIdeal(self)) + { + self->monsterinfo.attack (self); + self->monsterinfo.attack_state = AS_STRAIGHT; + } +}; + + +/* +============= +ai_run_slide + +Strafe sideways, but stay at aproximately the same range +============= +*/ +void ai_run_slide(edict_t *self, float distance) +{ + float ofs; + + self->ideal_yaw = enemy_yaw; + M_ChangeYaw (self); + + if (self->monsterinfo.lefty) + ofs = 90; + else + ofs = -90; + + if (M_walkmove (self, self->ideal_yaw + ofs, distance)) + return; + + self->monsterinfo.lefty = 1 - self->monsterinfo.lefty; + M_walkmove (self, self->ideal_yaw - ofs, distance); +} + + +/* +============= +ai_checkattack + +Decides if we're going to attack or do something else +used by ai_run and ai_stand +============= +*/ +qboolean ai_checkattack (edict_t *self, float dist) +{ + vec3_t temp; + qboolean hesDeadJim; + +// this causes monsters to run blindly to the combat point w/o firing + if (self->goalentity) + { + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + return false; + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + if ((level.time - self->enemy->teleport_time) > 5.0) + { + if (self->goalentity == self->enemy) + if (self->movetarget) + self->goalentity = self->movetarget; + else + self->goalentity = NULL; + self->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) + self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else + { + self->show_hostile = level.time + 1; + return false; + } + } + } + + enemy_vis = false; + +// see if the enemy is dead + hesDeadJim = false; + if ((!self->enemy) || (!self->enemy->inuse)) + { + hesDeadJim = true; + } + else if (self->monsterinfo.aiflags & AI_MEDIC) + { + if (self->enemy->health > 0) + { + hesDeadJim = true; + self->monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else + { + if (self->monsterinfo.aiflags & AI_BRUTAL) + { + if (self->enemy->health <= -80) + hesDeadJim = true; + } + else + { + if (self->enemy->health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) + { + self->enemy = NULL; + // FIXME: look all around for other targets + if (self->oldenemy && self->oldenemy->health > 0) + { + self->enemy = self->oldenemy; + self->oldenemy = NULL; + HuntTarget (self); + } + else + { + if (self->movetarget) + { + self->goalentity = self->movetarget; + self->monsterinfo.walk (self); + } + else + { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self->monsterinfo.pausetime = level.time + 100000000; + self->monsterinfo.stand (self); + } + return true; + } + } + + self->show_hostile = level.time + 1; // wake up other monsters + +// check knowledge of enemy + enemy_vis = visible(self, self->enemy); + if (enemy_vis) + { + self->monsterinfo.search_time = level.time + 5; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + } + +// look for other coop players here +// if (coop && self->monsterinfo.search_time < level.time) +// { +// if (FindTarget (self)) +// return true; +// } + + enemy_infront = infront(self, self->enemy); + enemy_range = range(self, self->enemy); + VectorSubtract (self->enemy->s.origin, self->s.origin, temp); + enemy_yaw = vectoyaw(temp); + + + // JDC self->ideal_yaw = enemy_yaw; + + if (self->monsterinfo.attack_state == AS_MISSILE) + { + ai_run_missile (self); + return true; + } + if (self->monsterinfo.attack_state == AS_MELEE) + { + ai_run_melee (self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self->monsterinfo.checkattack (self); +} + + +/* +============= +ai_run + +The monster has an enemy it is trying to kill +============= +*/ +void ai_run (edict_t *self, float dist) +{ + vec3_t v; + edict_t *tempgoal; + edict_t *save; + qboolean new; + edict_t *marker; + float d1, d2; + trace_t tr; + vec3_t v_forward, v_right; + float left, center, right; + vec3_t left_target, right_target; + + // if we're going to a combat point, just proceed + if (self->monsterinfo.aiflags & AI_COMBAT_POINT) + { + M_MoveToGoal (self, dist); + return; + } + + if (self->monsterinfo.aiflags & AI_SOUND_TARGET) + { + VectorSubtract (self->s.origin, self->enemy->s.origin, v); + if (VectorLength(v) < 64) + { + self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self->monsterinfo.stand (self); + return; + } + + M_MoveToGoal (self, dist); + + if (!FindTarget (self)) + return; + } + + if (ai_checkattack (self, dist)) + return; + + if (self->monsterinfo.attack_state == AS_SLIDING) + { + ai_run_slide (self, dist); + return; + } + + if (enemy_vis) + { +// if (self.aiflags & AI_LOST_SIGHT) +// dprint("regained sight\n"); + M_MoveToGoal (self, dist); + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop->value) + { // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget (self)) + return; + } + + if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20))) + { + M_MoveToGoal (self, dist); + self->monsterinfo.search_time = 0; +// dprint("search timeout\n"); + return; + } + + save = self->goalentity; + tempgoal = G_Spawn(); + self->goalentity = tempgoal; + + new = false; + + if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first +// dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new = true; + } + + if (self->monsterinfo.aiflags & AI_PURSUE_NEXT) + { + self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT; +// dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self->monsterinfo.search_time = level.time + 5; + + if (self->monsterinfo.aiflags & AI_PURSUE_TEMP) + { +// dprint("was temp goal; retrying original\n"); + self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = NULL; + VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting); + new = true; + } + else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) + { + self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail_PickFirst (self); + } + else + { + marker = PlayerTrail_PickNext (self); + } + + if (marker) + { + VectorCopy (marker->s.origin, self->monsterinfo.last_sighting); + self->monsterinfo.trail_time = marker->timestamp; + self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW]; +// dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + +// debug_drawline(self.origin, self.last_sighting, 52); + new = true; + } + } + + VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v); + d1 = VectorLength(v); + if (d1 <= dist) + { + self->monsterinfo.aiflags |= AI_PURSUE_NEXT; + dist = d1; + } + + VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin); + + if (new) + { +// gi.dprintf("checking for course correction\n"); + + tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + d1 = VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center+1)/2); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); + AngleVectors(self->s.angles, v_forward, v_right, NULL); + + VectorSet(v, d2, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID); + left = tr.fraction; + + VectorSet(v, d2, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); + tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1*center)/d2; + if (left >= center && left > right) + { + if (left < 1) + { + VectorSet(v, d2 * left * 0.5, -16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (left_target, self->goalentity->s.origin); + VectorCopy (left_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted left\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + VectorSet(v, d2 * right * 0.5, 16, 0); + G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target); +// gi.dprintf("incomplete path, go part way and adjust again\n"); + } + VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal); + self->monsterinfo.aiflags |= AI_PURSUE_TEMP; + VectorCopy (right_target, self->goalentity->s.origin); + VectorCopy (right_target, self->monsterinfo.last_sighting); + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v); +// gi.dprintf("adjusted right\n"); +// debug_drawline(self.origin, self.last_sighting, 152); + } + } +// else gi.dprintf("course was fine\n"); + } + + M_MoveToGoal (self, dist); + + G_FreeEdict(tempgoal); + + if (self) + self->goalentity = save; +} diff --git a/game/g_chase.c b/game/g_chase.c new file mode 100644 index 000000000..b486a4e87 --- /dev/null +++ b/game/g_chase.c @@ -0,0 +1,175 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + +void UpdateChaseCam(edict_t *ent) +{ + vec3_t o, ownerv, goal; + edict_t *targ; + vec3_t forward, right; + trace_t trace; + int i; + vec3_t oldgoal; + vec3_t angles; + + // is our chase target gone? + if (!ent->client->chase_target->inuse + || ent->client->chase_target->client->resp.spectator) { + edict_t *old = ent->client->chase_target; + ChaseNext(ent); + if (ent->client->chase_target == old) { + ent->client->chase_target = NULL; + ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent->client->chase_target; + + VectorCopy(targ->s.origin, ownerv); + VectorCopy(ent->s.origin, oldgoal); + + ownerv[2] += targ->viewheight; + + VectorCopy(targ->client->v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + AngleVectors (angles, forward, right, NULL); + VectorNormalize(forward); + VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ->s.origin[2] + 20) + o[2] = targ->s.origin[2] + 20; + + // jump animation lifts + if (!targ->groundentity) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + VectorCopy(trace.endpos, goal); + + VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ->deadflag) + ent->client->ps.pmove.pm_type = PM_DEAD; + else + ent->client->ps.pmove.pm_type = PM_FREEZE; + + VectorCopy(goal, ent->s.origin); + for (i=0 ; i<3 ; i++) + ent->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(targ->client->v_angle[i] - ent->client->resp.cmd_angles[i]); + + if (targ->deadflag) { + ent->client->ps.viewangles[ROLL] = 40; + ent->client->ps.viewangles[PITCH] = -15; + ent->client->ps.viewangles[YAW] = targ->client->killer_yaw; + } else { + VectorCopy(targ->client->v_angle, ent->client->ps.viewangles); + VectorCopy(targ->client->v_angle, ent->client->v_angle); + } + + ent->viewheight = 0; + ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); +} + +void ChaseNext(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i++; + if (i > maxclients->value) + i = 1; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void ChasePrev(edict_t *ent) +{ + int i; + edict_t *e; + + if (!ent->client->chase_target) + return; + + i = ent->client->chase_target - g_edicts; + do { + i--; + if (i < 1) + i = maxclients->value; + e = g_edicts + i; + if (!e->inuse) + continue; + if (!e->client->resp.spectator) + break; + } while (e != ent->client->chase_target); + + ent->client->chase_target = e; + ent->client->update_chase = true; +} + +void GetChaseTarget(edict_t *ent) +{ + int i; + edict_t *other; + + for (i = 1; i <= maxclients->value; i++) { + other = g_edicts + i; + if (other->inuse && !other->client->resp.spectator) { + ent->client->chase_target = other; + ent->client->update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); +} + diff --git a/game/g_cmds.c b/game/g_cmds.c new file mode 100644 index 000000000..412c2a61c --- /dev/null +++ b/game/g_cmds.c @@ -0,0 +1,992 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" +#include "m_player.h" + + +char *ClientTeam (edict_t *ent) +{ + char *p; + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + strcpy(value, Info_ValueForKey (ent->client->pers.userinfo, "skin")); + p = strchr(value, '/'); + if (!p) + return value; + + if ((int)(dmflags->value) & DF_MODELTEAMS) + { + *p = 0; + return value; + } + + // if ((int)(dmflags->value) & DF_SKINTEAMS) + return ++p; +} + +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2) +{ + char ent1Team [512]; + char ent2Team [512]; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + return false; + + strcpy (ent1Team, ClientTeam (ent1)); + strcpy (ent2Team, ClientTeam (ent2)); + + if (strcmp(ent1Team, ent2Team) == 0) + return true; + return false; +} + + +void SelectNextItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChaseNext(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void SelectPrevItem (edict_t *ent, int itflags) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + + cl = ent->client; + + if (cl->chase_target) { + ChasePrev(ent); + return; + } + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (cl->pers.selected_item + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (!(it->flags & itflags)) + continue; + + cl->pers.selected_item = index; + return; + } + + cl->pers.selected_item = -1; +} + +void ValidateSelectedItem (edict_t *ent) +{ + gclient_t *cl; + + cl = ent->client; + + if (cl->pers.inventory[cl->pers.selected_item]) + return; // valid + + SelectNextItem (ent, -1); +} + + +//================================================================================= + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (edict_t *ent) +{ + char *name; + gitem_t *it; + int index; + int i; + qboolean give_all; + edict_t *it_ent; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + name = gi.args(); + + if (Q_stricmp(name, "all") == 0) + give_all = true; + else + give_all = false; + + if (give_all || Q_stricmp(gi.argv(1), "health") == 0) + { + if (gi.argc() == 3) + ent->health = atoi(gi.argv(2)); + else + ent->health = ent->max_health; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_WEAPON)) + continue; + ent->client->pers.inventory[i] += 1; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for (i=0 ; ipickup) + continue; + if (!(it->flags & IT_AMMO)) + continue; + Add_Ammo (ent, it, 1000); + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + gitem_armor_t *info; + + it = FindItem("Jacket Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Combat Armor"); + ent->client->pers.inventory[ITEM_INDEX(it)] = 0; + + it = FindItem("Body Armor"); + info = (gitem_armor_t *)it->info; + ent->client->pers.inventory[ITEM_INDEX(it)] = info->max_count; + + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "Power Shield") == 0) + { + it = FindItem("Power Shield"); + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + + if (!give_all) + return; + } + + if (give_all) + { + for (i=0 ; ipickup) + continue; + if (it->flags & (IT_ARMOR|IT_WEAPON|IT_AMMO)) + continue; + ent->client->pers.inventory[i] = 1; + } + return; + } + + it = FindItem (name); + if (!it) + { + name = gi.argv(1); + it = FindItem (name); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item\n"); + return; + } + } + + if (!it->pickup) + { + gi.cprintf (ent, PRINT_HIGH, "non-pickup item\n"); + return; + } + + index = ITEM_INDEX(it); + + if (it->flags & IT_AMMO) + { + if (gi.argc() == 3) + ent->client->pers.inventory[index] = atoi(gi.argv(2)); + else + ent->client->pers.inventory[index] += it->quantity; + } + else + { + it_ent = G_Spawn(); + it_ent->classname = it->classname; + SpawnItem (it_ent, it); + Touch_Item (it_ent, ent, NULL, NULL); + if (it_ent->inuse) + G_FreeEdict(it_ent); + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f (edict_t *ent) +{ + char *msg; + + if (deathmatch->value && !sv_cheats->value) + { + gi.cprintf (ent, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n"); + return; + } + + if (ent->movetype == MOVETYPE_NOCLIP) + { + ent->movetype = MOVETYPE_WALK; + msg = "noclip OFF\n"; + } + else + { + ent->movetype = MOVETYPE_NOCLIP; + msg = "noclip ON\n"; + } + + gi.cprintf (ent, PRINT_HIGH, msg); +} + + +/* +================== +Cmd_Use_f + +Use an inventory item +================== +*/ +void Cmd_Use_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->use (ent, it); +} + + +/* +================== +Cmd_Drop_f + +Drop an inventory item +================== +*/ +void Cmd_Drop_f (edict_t *ent) +{ + int index; + gitem_t *it; + char *s; + + s = gi.args(); + it = FindItem (s); + if (!it) + { + gi.cprintf (ent, PRINT_HIGH, "unknown item: %s\n", s); + return; + } + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + index = ITEM_INDEX(it); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "Out of item: %s\n", s); + return; + } + + it->drop (ent, it); +} + + +/* +================= +Cmd_Inven_f +================= +*/ +void Cmd_Inven_f (edict_t *ent) +{ + int i; + gclient_t *cl; + + cl = ent->client; + + cl->showscores = false; + cl->showhelp = false; + + if (cl->showinventory) + { + cl->showinventory = false; + return; + } + + cl->showinventory = true; + + gi.WriteByte (svc_inventory); + for (i=0 ; ipers.inventory[i]); + } + gi.unicast (ent, true); +} + +/* +================= +Cmd_InvUse_f +================= +*/ +void Cmd_InvUse_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to use.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->use) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not usable.\n"); + return; + } + it->use (ent, it); +} + +/* +================= +Cmd_WeapPrev_f +================= +*/ +void Cmd_WeapPrev_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapNext_f +================= +*/ +void Cmd_WeapNext_f (edict_t *ent) +{ + gclient_t *cl; + int i, index; + gitem_t *it; + int selected_weapon; + + cl = ent->client; + + if (!cl->pers.weapon) + return; + + selected_weapon = ITEM_INDEX(cl->pers.weapon); + + // scan for the next valid one + for (i=1 ; i<=MAX_ITEMS ; i++) + { + index = (selected_weapon + MAX_ITEMS - i)%MAX_ITEMS; + if (!cl->pers.inventory[index]) + continue; + it = &itemlist[index]; + if (!it->use) + continue; + if (! (it->flags & IT_WEAPON) ) + continue; + it->use (ent, it); + if (cl->pers.weapon == it) + return; // successful + } +} + +/* +================= +Cmd_WeapLast_f +================= +*/ +void Cmd_WeapLast_f (edict_t *ent) +{ + gclient_t *cl; + int index; + gitem_t *it; + + cl = ent->client; + + if (!cl->pers.weapon || !cl->pers.lastweapon) + return; + + index = ITEM_INDEX(cl->pers.lastweapon); + if (!cl->pers.inventory[index]) + return; + it = &itemlist[index]; + if (!it->use) + return; + if (! (it->flags & IT_WEAPON) ) + return; + it->use (ent, it); +} + +/* +================= +Cmd_InvDrop_f +================= +*/ +void Cmd_InvDrop_f (edict_t *ent) +{ + gitem_t *it; + + ValidateSelectedItem (ent); + + if (ent->client->pers.selected_item == -1) + { + gi.cprintf (ent, PRINT_HIGH, "No item to drop.\n"); + return; + } + + it = &itemlist[ent->client->pers.selected_item]; + if (!it->drop) + { + gi.cprintf (ent, PRINT_HIGH, "Item is not dropable.\n"); + return; + } + it->drop (ent, it); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f (edict_t *ent) +{ + if((level.time - ent->client->respawn_time) < 5) + return; + ent->flags &= ~FL_GODMODE; + ent->health = 0; + meansOfDeath = MOD_SUICIDE; + player_die (ent, ent, ent, 100000, vec3_origin); +} + +/* +================= +Cmd_PutAway_f +================= +*/ +void Cmd_PutAway_f (edict_t *ent) +{ + ent->client->showscores = false; + ent->client->showhelp = false; + ent->client->showinventory = false; +} + + +int PlayerSort (void const *a, void const *b) +{ + int anum, bnum; + + anum = *(int *)a; + bnum = *(int *)b; + + anum = game.clients[anum].ps.stats[STAT_FRAGS]; + bnum = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum < bnum) + return -1; + if (anum > bnum) + return 1; + return 0; +} + +/* +================= +Cmd_Players_f +================= +*/ +void Cmd_Players_f (edict_t *ent) +{ + int i; + int count; + char small[64]; + char large[1280]; + int index[256]; + + count = 0; + for (i = 0 ; i < maxclients->value ; i++) + if (game.clients[i].pers.connected) + { + index[count] = i; + count++; + } + + // sort by frags + qsort (index, count, sizeof(index[0]), PlayerSort); + + // print information + large[0] = 0; + + for (i = 0 ; i < count ; i++) + { + Com_sprintf (small, sizeof(small), "%3i %s\n", + game.clients[index[i]].ps.stats[STAT_FRAGS], + game.clients[index[i]].pers.netname); + if (strlen (small) + strlen(large) > sizeof(large) - 100 ) + { // can't print all of them in one packet + strcat (large, "...\n"); + break; + } + strcat (large, small); + } + + gi.cprintf (ent, PRINT_HIGH, "%s\n%i players\n", large, count); +} + +/* +================= +Cmd_Wave_f +================= +*/ +void Cmd_Wave_f (edict_t *ent) +{ + int i; + + i = atoi (gi.argv(1)); + + // can't wave when ducked + if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) + return; + + if (ent->client->anim_priority > ANIM_WAVE) + return; + + ent->client->anim_priority = ANIM_WAVE; + + switch (i) + { + case 0: + gi.cprintf (ent, PRINT_HIGH, "flipoff\n"); + ent->s.frame = FRAME_flip01-1; + ent->client->anim_end = FRAME_flip12; + break; + case 1: + gi.cprintf (ent, PRINT_HIGH, "salute\n"); + ent->s.frame = FRAME_salute01-1; + ent->client->anim_end = FRAME_salute11; + break; + case 2: + gi.cprintf (ent, PRINT_HIGH, "taunt\n"); + ent->s.frame = FRAME_taunt01-1; + ent->client->anim_end = FRAME_taunt17; + break; + case 3: + gi.cprintf (ent, PRINT_HIGH, "wave\n"); + ent->s.frame = FRAME_wave01-1; + ent->client->anim_end = FRAME_wave11; + break; + case 4: + default: + gi.cprintf (ent, PRINT_HIGH, "point\n"); + ent->s.frame = FRAME_point01-1; + ent->client->anim_end = FRAME_point12; + break; + } +} + +/* +================== +Cmd_Say_f +================== +*/ +void Cmd_Say_f (edict_t *ent, qboolean team, qboolean arg0) +{ + int i, j; + edict_t *other; + char *p; + char text[2048]; + gclient_t *cl; + + if (gi.argc () < 2 && !arg0) + return; + + if (!((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) + team = false; + + if (team) + Com_sprintf (text, sizeof(text), "(%s): ", ent->client->pers.netname); + else + Com_sprintf (text, sizeof(text), "%s: ", ent->client->pers.netname); + + if (arg0) + { + strcat (text, gi.argv(0)); + strcat (text, " "); + strcat (text, gi.args()); + } + else + { + p = gi.args(); + + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + strcat(text, p); + } + + // don't let text be too long for malicious reasons + if (strlen(text) > 150) + text[150] = 0; + + strcat(text, "\n"); + + if (flood_msgs->value) { + cl = ent->client; + + if (level.time < cl->flood_locktill) { + gi.cprintf(ent, PRINT_HIGH, "You can't talk for %d more seconds\n", + (int)(cl->flood_locktill - level.time)); + return; + } + i = cl->flood_whenhead - flood_msgs->value + 1; + if (i < 0) + i = (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])) + i; + if (cl->flood_when[i] && + level.time - cl->flood_when[i] < flood_persecond->value) { + cl->flood_locktill = level.time + flood_waitdelay->value; + gi.cprintf(ent, PRINT_CHAT, "Flood protection: You can't talk for %d seconds.\n", + (int)flood_waitdelay->value); + return; + } + cl->flood_whenhead = (cl->flood_whenhead + 1) % + (sizeof(cl->flood_when)/sizeof(cl->flood_when[0])); + cl->flood_when[cl->flood_whenhead] = level.time; + } + + if (dedicated->value) + gi.cprintf(NULL, PRINT_CHAT, "%s", text); + + for (j = 1; j <= game.maxclients; j++) + { + other = &g_edicts[j]; + if (!other->inuse) + continue; + if (!other->client) + continue; + if (team) + { + if (!OnSameTeam(ent, other)) + continue; + } + gi.cprintf(other, PRINT_CHAT, "%s", text); + } +} + +void Cmd_PlayerList_f(edict_t *ent) +{ + int i; + char st[80]; + char text[1400]; + edict_t *e2; + + // connect time, ping, score, name + *text = 0; + for (i = 0, e2 = g_edicts + 1; i < maxclients->value; i++, e2++) { + if (!e2->inuse) + continue; + + sprintf(st, "%02d:%02d %4d %3d %s%s\n", + (level.framenum - e2->client->resp.enterframe) / 600, + ((level.framenum - e2->client->resp.enterframe) % 600)/10, + e2->client->ping, + e2->client->resp.score, + e2->client->pers.netname, + e2->client->resp.spectator ? " (spectator)" : ""); + if (strlen(text) + strlen(st) > sizeof(text) - 50) { + sprintf(text+strlen(text), "And more...\n"); + gi.cprintf(ent, PRINT_HIGH, "%s", text); + return; + } + strcat(text, st); + } + gi.cprintf(ent, PRINT_HIGH, "%s", text); +} + + +/* +================= +ClientCommand +================= +*/ +void ClientCommand (edict_t *ent) +{ + char *cmd; + + if (!ent->client) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Q_stricmp (cmd, "players") == 0) + { + Cmd_Players_f (ent); + return; + } + if (Q_stricmp (cmd, "say") == 0) + { + Cmd_Say_f (ent, false, false); + return; + } + if (Q_stricmp (cmd, "say_team") == 0) + { + Cmd_Say_f (ent, true, false); + return; + } + if (Q_stricmp (cmd, "score") == 0) + { + Cmd_Score_f (ent); + return; + } + if (Q_stricmp (cmd, "help") == 0) + { + Cmd_Help_f (ent); + return; + } + + if (level.intermissiontime) + return; + + if (Q_stricmp (cmd, "use") == 0) + Cmd_Use_f (ent); + else if (Q_stricmp (cmd, "drop") == 0) + Cmd_Drop_f (ent); + else if (Q_stricmp (cmd, "give") == 0) + Cmd_Give_f (ent); + else if (Q_stricmp (cmd, "god") == 0) + Cmd_God_f (ent); + else if (Q_stricmp (cmd, "notarget") == 0) + Cmd_Notarget_f (ent); + else if (Q_stricmp (cmd, "noclip") == 0) + Cmd_Noclip_f (ent); + else if (Q_stricmp (cmd, "inven") == 0) + Cmd_Inven_f (ent); + else if (Q_stricmp (cmd, "invnext") == 0) + SelectNextItem (ent, -1); + else if (Q_stricmp (cmd, "invprev") == 0) + SelectPrevItem (ent, -1); + else if (Q_stricmp (cmd, "invnextw") == 0) + SelectNextItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invprevw") == 0) + SelectPrevItem (ent, IT_WEAPON); + else if (Q_stricmp (cmd, "invnextp") == 0) + SelectNextItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invprevp") == 0) + SelectPrevItem (ent, IT_POWERUP); + else if (Q_stricmp (cmd, "invuse") == 0) + Cmd_InvUse_f (ent); + else if (Q_stricmp (cmd, "invdrop") == 0) + Cmd_InvDrop_f (ent); + else if (Q_stricmp (cmd, "weapprev") == 0) + Cmd_WeapPrev_f (ent); + else if (Q_stricmp (cmd, "weapnext") == 0) + Cmd_WeapNext_f (ent); + else if (Q_stricmp (cmd, "weaplast") == 0) + Cmd_WeapLast_f (ent); + else if (Q_stricmp (cmd, "kill") == 0) + Cmd_Kill_f (ent); + else if (Q_stricmp (cmd, "putaway") == 0) + Cmd_PutAway_f (ent); + else if (Q_stricmp (cmd, "wave") == 0) + Cmd_Wave_f (ent); + else if (Q_stricmp(cmd, "playerlist") == 0) + Cmd_PlayerList_f(ent); + else // anything that doesn't match a command will be a chat + Cmd_Say_f (ent, false, true); +} diff --git a/game/g_combat.c b/game/g_combat.c new file mode 100644 index 000000000..a921bdbf5 --- /dev/null +++ b/game/g_combat.c @@ -0,0 +1,576 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_combat.c + +#include "g_local.h" + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean CanDamage (edict_t *targ, edict_t *inflictor) +{ + vec3_t dest; + trace_t trace; + +// bmodels need special checking because their origin is 0,0,0 + if (targ->movetype == MOVETYPE_PUSH) + { + VectorAdd (targ->absmin, targ->absmax, dest); + VectorScale (dest, 0.5, dest); + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + if (trace.ent == targ) + return true; + return false; + } + + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, targ->s.origin, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] += 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] += 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + VectorCopy (targ->s.origin, dest); + dest[0] -= 15.0; + dest[1] -= 15.0; + trace = gi.trace (inflictor->s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID); + if (trace.fraction == 1.0) + return true; + + + return false; +} + + +/* +============ +Killed +============ +*/ +void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + if (targ->health < -999) + targ->health = -999; + + targ->enemy = attacker; + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { +// targ->svflags |= SVF_DEADMONSTER; // now treat as a different content type + if (!(targ->monsterinfo.aiflags & AI_GOOD_GUY)) + { + level.killed_monsters++; + if (coop->value && attacker->client) + attacker->client->resp.score++; + // medics won't heal monsters that they kill themselves + if (strcmp(attacker->classname, "monster_medic") == 0) + targ->owner = attacker; + } + } + + if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE) + { // doors, triggers, etc + targ->die (targ, inflictor, attacker, damage, point); + return; + } + + if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD)) + { + targ->touch = NULL; + monster_death_use (targ); + } + + targ->die (targ, inflictor, attacker, damage, point); +} + + +/* +================ +SpawnDamage +================ +*/ +void SpawnDamage (int type, vec3_t origin, vec3_t normal, int damage) +{ + if (damage > 255) + damage = 255; + gi.WriteByte (svc_temp_entity); + gi.WriteByte (type); +// gi.WriteByte (damage); + gi.WritePosition (origin); + gi.WriteDir (normal); + gi.multicast (origin, MULTICAST_PVS); +} + + +/* +============ +T_Damage + +targ entity that is being damaged +inflictor entity that is causing the damage +attacker entity that caused the inflictor to damage targ + example: targ=monster, inflictor=rocket, attacker=player + +dir direction of the attack +point point at which the damage is being inflicted +normal normal vector from that point +damage amount of damage being inflicted +knockback force to be applied against targ as a result of the damage + +dflags these flags are used to control how T_Damage works + DAMAGE_RADIUS damage was indirect (from a nearby explosion) + DAMAGE_NO_ARMOR armor does not protect from this damage + DAMAGE_ENERGY damage is from an energy based weapon + DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles + DAMAGE_BULLET damage is from a bullet (used for ricochets) + DAMAGE_NO_PROTECTION kills godmode, armor, everything +============ +*/ +static int CheckPowerArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int dflags) +{ + gclient_t *client; + int save; + int power_armor_type; + int index; + int damagePerCell; + int pa_te_type; + int power; + int power_used; + + if (!damage) + return 0; + + client = ent->client; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + if (client) + { + power_armor_type = PowerArmorType (ent); + if (power_armor_type != POWER_ARMOR_NONE) + { + index = ITEM_INDEX(FindItem("Cells")); + power = client->pers.inventory[index]; + } + } + else if (ent->svflags & SVF_MONSTER) + { + power_armor_type = ent->monsterinfo.power_armor_type; + power = ent->monsterinfo.power_armor_power; + } + else + return 0; + + if (power_armor_type == POWER_ARMOR_NONE) + return 0; + if (!power) + return 0; + + if (power_armor_type == POWER_ARMOR_SCREEN) + { + vec3_t vec; + float dot; + vec3_t forward; + + // only works if damage point is in front + AngleVectors (ent->s.angles, forward, NULL, NULL); + VectorSubtract (point, ent->s.origin, vec); + VectorNormalize (vec); + dot = DotProduct (vec, forward); + if (dot <= 0.3) + return 0; + + damagePerCell = 1; + pa_te_type = TE_SCREEN_SPARKS; + damage = damage / 3; + } + else + { + damagePerCell = 2; + pa_te_type = TE_SHIELD_SPARKS; + damage = (2 * damage) / 3; + } + + save = power * damagePerCell; + if (!save) + return 0; + if (save > damage) + save = damage; + + SpawnDamage (pa_te_type, point, normal, save); + ent->powerarmor_time = level.time + 0.2; + + power_used = save / damagePerCell; + + if (client) + client->pers.inventory[index] -= power_used; + else + ent->monsterinfo.power_armor_power -= power_used; + return save; +} + +static int CheckArmor (edict_t *ent, vec3_t point, vec3_t normal, int damage, int te_sparks, int dflags) +{ + gclient_t *client; + int save; + int index; + gitem_t *armor; + + if (!damage) + return 0; + + client = ent->client; + + if (!client) + return 0; + + if (dflags & DAMAGE_NO_ARMOR) + return 0; + + index = ArmorIndex (ent); + if (!index) + return 0; + + armor = GetItemByIndex (index); + + if (dflags & DAMAGE_ENERGY) + save = ceil(((gitem_armor_t *)armor->info)->energy_protection*damage); + else + save = ceil(((gitem_armor_t *)armor->info)->normal_protection*damage); + if (save >= client->pers.inventory[index]) + save = client->pers.inventory[index]; + + if (!save) + return 0; + + client->pers.inventory[index] -= save; + SpawnDamage (te_sparks, point, normal, save); + + return save; +} + +void M_ReactToDamage (edict_t *targ, edict_t *attacker) +{ + if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) + return; + + if (attacker == targ || attacker == targ->enemy) + return; + + // if we are a good guy monster and our attacker is a player + // or another good guy, do not get mad at them + if (targ->monsterinfo.aiflags & AI_GOOD_GUY) + { + if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + } + + // we now know that we are not both good guys + + // if attacker is a client, get mad at them because he's good and we're not + if (attacker->client) + { + targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET; + + // this can only happen in coop (both new and old enemies are clients) + // only switch if can't see the current enemy + if (targ->enemy && targ->enemy->client) + { + if (visible(targ, targ->enemy)) + { + targ->oldenemy = attacker; + return; + } + targ->oldenemy = targ->enemy; + } + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + return; + } + + // it's the same base (walk/swim/fly) type and a different classname and it's not a tank + // (they spray too much), get mad at them + if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && + (strcmp (targ->classname, attacker->classname) != 0) && + (strcmp(attacker->classname, "monster_tank") != 0) && + (strcmp(attacker->classname, "monster_supertank") != 0) && + (strcmp(attacker->classname, "monster_makron") != 0) && + (strcmp(attacker->classname, "monster_jorg") != 0) ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // if they *meant* to shoot us, then shoot back + else if (attacker->enemy == targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } + // otherwise get mad at whoever they are mad at (help our buddy) unless it is us! + else if (attacker->enemy && attacker->enemy != targ) + { + if (targ->enemy && targ->enemy->client) + targ->oldenemy = targ->enemy; + targ->enemy = attacker->enemy; + if (!(targ->monsterinfo.aiflags & AI_DUCKED)) + FoundTarget (targ); + } +} + +qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker) +{ + //FIXME make the next line real and uncomment this block + // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team)) + return false; +} + +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod) +{ + gclient_t *client; + int take; + int save; + int asave; + int psave; + int te_sparks; + + if (!targ->takedamage) + return; + + // friendly fire avoidance + // if enabled you can't hurt teammates (but you can hurt yourself) + // knockback still occurs + if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value)) + { + if (OnSameTeam (targ, attacker)) + { + if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) + damage = 0; + else + mod |= MOD_FRIENDLY_FIRE; + } + } + meansOfDeath = mod; + + // easy mode takes half damage + if (skill->value == 0 && deathmatch->value == 0 && targ->client) + { + damage *= 0.5; + if (!damage) + damage = 1; + } + + client = targ->client; + + if (dflags & DAMAGE_BULLET) + te_sparks = TE_BULLET_SPARKS; + else + te_sparks = TE_SPARKS; + + VectorNormalize(dir); + +// bonus damage for suprising a monster + if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) + damage *= 2; + + if (targ->flags & FL_NO_KNOCKBACK) + knockback = 0; + +// figure momentum add + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP)) + { + vec3_t kvel; + float mass; + + if (targ->mass < 50) + mass = 50; + else + mass = targ->mass; + + if (targ->client && attacker == targ) + VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... + else + VectorScale (dir, 500.0 * (float)knockback / mass, kvel); + + VectorAdd (targ->velocity, kvel, targ->velocity); + } + } + + take = damage; + save = 0; + + // check for godmode + if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) ) + { + take = 0; + save = damage; + SpawnDamage (te_sparks, point, normal, save); + } + + // check for invincibility + if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION)) + { + if (targ->pain_debounce_time < level.time) + { + gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0); + targ->pain_debounce_time = level.time + 2; + } + take = 0; + save = damage; + } + + psave = CheckPowerArmor (targ, point, normal, take, dflags); + take -= psave; + + asave = CheckArmor (targ, point, normal, take, te_sparks, dflags); + take -= asave; + + //treat cheat/powerup savings the same as armor + asave += save; + + // team damage avoidance + if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker)) + return; + +// do the damage + if (take) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + SpawnDamage (TE_BLOOD, point, normal, take); + else + SpawnDamage (te_sparks, point, normal, take); + + + targ->health = targ->health - take; + + if (targ->health <= 0) + { + if ((targ->svflags & SVF_MONSTER) || (client)) + targ->flags |= FL_NO_KNOCKBACK; + Killed (targ, inflictor, attacker, take, point); + return; + } + } + + if (targ->svflags & SVF_MONSTER) + { + M_ReactToDamage (targ, attacker); + if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) + { + targ->pain (targ, attacker, knockback, take); + // nightmare mode monsters don't go into pain frames often + if (skill->value == 3) + targ->pain_debounce_time = level.time + 5; + } + } + else if (client) + { + if (!(targ->flags & FL_GODMODE) && (take)) + targ->pain (targ, attacker, knockback, take); + } + else if (take) + { + if (targ->pain) + targ->pain (targ, attacker, knockback, take); + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + if (client) + { + client->damage_parmor += psave; + client->damage_armor += asave; + client->damage_blood += take; + client->damage_knockback += knockback; + VectorCopy (point, client->damage_from); + } +} + + +/* +============ +T_RadiusDamage +============ +*/ +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod) +{ + float points; + edict_t *ent = NULL; + vec3_t v; + vec3_t dir; + + while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) + { + if (ent == ignore) + continue; + if (!ent->takedamage) + continue; + + VectorAdd (ent->mins, ent->maxs, v); + VectorMA (ent->s.origin, 0.5, v, v); + VectorSubtract (inflictor->s.origin, v, v); + points = damage - 0.5 * VectorLength (v); + if (ent == attacker) + points = points * 0.5; + if (points > 0) + { + if (CanDamage (ent, inflictor)) + { + VectorSubtract (ent->s.origin, inflictor->s.origin, dir); + T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod); + } + } + } +} diff --git a/game/g_func.c b/game/g_func.c new file mode 100644 index 000000000..48703a3cf --- /dev/null +++ b/game/g_func.c @@ -0,0 +1,2048 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + +/* +========================================================= + + PLATS + + movement options: + + linear + smooth start, hard stop + smooth start, smooth stop + + start + end + acceleration + speed + deceleration + begin sound + end sound + target fired when reaching end + wait at end + + object characteristics that use move segments + --------------------------------------------- + movetype_push, or movetype_stop + action when touched + action when blocked + action when used + disabled? + auto trigger spawning + + +========================================================= +*/ + +#define PLAT_LOW_TRIGGER 1 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 + + +// +// Support routines for movement (changes in origin using velocity) +// + +void Move_Done (edict_t *ent) +{ + VectorClear (ent->velocity); + ent->moveinfo.endfunc (ent); +} + +void Move_Final (edict_t *ent) +{ + if (ent->moveinfo.remaining_distance == 0) + { + Move_Done (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + + ent->think = Move_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void Move_Begin (edict_t *ent) +{ + float frames; + + if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) + { + Move_Final (ent); + return; + } + VectorScale (ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); + frames = floor((ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->nextthink = level.time + (frames * FRAMETIME); + ent->think = Move_Final; +} + +void Think_AccelMove (edict_t *ent); + +void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*)) +{ + VectorClear (ent->velocity); + VectorSubtract (dest, ent->s.origin, ent->moveinfo.dir); + ent->moveinfo.remaining_distance = VectorNormalize (ent->moveinfo.dir); + ent->moveinfo.endfunc = func; + + if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel) + { + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + Move_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = Move_Begin; + } + } + else + { + // accelerative + ent->moveinfo.current_speed = 0; + ent->think = Think_AccelMove; + ent->nextthink = level.time + FRAMETIME; + } +} + + +// +// Support routines for angular movement (changes in angle using avelocity) +// + +void AngleMove_Done (edict_t *ent) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc (ent); +} + +void AngleMove_Final (edict_t *ent) +{ + vec3_t move; + + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, move); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, move); + + if (VectorCompare (move, vec3_origin)) + { + AngleMove_Done (ent); + return; + } + + VectorScale (move, 1.0/FRAMETIME, ent->avelocity); + + ent->think = AngleMove_Done; + ent->nextthink = level.time + FRAMETIME; +} + +void AngleMove_Begin (edict_t *ent) +{ + vec3_t destdelta; + float len; + float traveltime; + float frames; + + // set destdelta to the vector needed to move + if (ent->moveinfo.state == STATE_UP) + VectorSubtract (ent->moveinfo.end_angles, ent->s.angles, destdelta); + else + VectorSubtract (ent->moveinfo.start_angles, ent->s.angles, destdelta); + + // calculate length of vector + len = VectorLength (destdelta); + + // divide by speed to get time to reach dest + traveltime = len / ent->moveinfo.speed; + + if (traveltime < FRAMETIME) + { + AngleMove_Final (ent); + return; + } + + frames = floor(traveltime / FRAMETIME); + + // scale the destdelta vector by the time spent traveling to get velocity + VectorScale (destdelta, 1.0 / traveltime, ent->avelocity); + + // set nextthink to trigger a think when dest is reached + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; +} + +void AngleMove_Calc (edict_t *ent, void(*func)(edict_t*)) +{ + VectorClear (ent->avelocity); + ent->moveinfo.endfunc = func; + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) + { + AngleMove_Begin (ent); + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } +} + + +/* +============== +Think_AccelMove + +The team has completed a frame of movement, so +change the speed for the next frame +============== +*/ +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) +{ + float accel_dist; + float decel_dist; + + moveinfo->move_speed = moveinfo->speed; + + if (moveinfo->remaining_distance < moveinfo->accel) + { + moveinfo->current_speed = moveinfo->remaining_distance; + return; + } + + accel_dist = AccelerationDistance (moveinfo->speed, moveinfo->accel); + decel_dist = AccelerationDistance (moveinfo->speed, moveinfo->decel); + + if ((moveinfo->remaining_distance - accel_dist - decel_dist) < 0) + { + float f; + + f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + decel_dist = AccelerationDistance (moveinfo->move_speed, moveinfo->decel); + } + + moveinfo->decel_distance = decel_dist; +}; + +void plat_Accelerate (moveinfo_t *moveinfo) +{ + // are we decelerating? + if (moveinfo->remaining_distance <= moveinfo->decel_distance) + { + if (moveinfo->remaining_distance < moveinfo->decel_distance) + { + if (moveinfo->next_speed) + { + moveinfo->current_speed = moveinfo->next_speed; + moveinfo->next_speed = 0; + return; + } + if (moveinfo->current_speed > moveinfo->decel) + moveinfo->current_speed -= moveinfo->decel; + } + return; + } + + // are we at full speed and need to start decelerating during this move? + if (moveinfo->current_speed == moveinfo->move_speed) + if ((moveinfo->remaining_distance - moveinfo->current_speed) < moveinfo->decel_distance) + { + float p1_distance; + float p2_distance; + float distance; + + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / moveinfo->move_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = moveinfo->move_speed; + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // are we accelerating? + if (moveinfo->current_speed < moveinfo->speed) + { + float old_speed; + float p1_distance; + float p1_speed; + float p2_distance; + float distance; + + old_speed = moveinfo->current_speed; + + // figure simple acceleration up to move_speed + moveinfo->current_speed += moveinfo->accel; + if (moveinfo->current_speed > moveinfo->speed) + moveinfo->current_speed = moveinfo->speed; + + // are we accelerating throughout this entire move? + if ((moveinfo->remaining_distance - moveinfo->current_speed) >= moveinfo->decel_distance) + return; + + // during this move we will accelrate from current_speed to move_speed + // and cross over the decel_distance; figure the average speed for the + // entire move + p1_distance = moveinfo->remaining_distance - moveinfo->decel_distance; + p1_speed = (old_speed + moveinfo->move_speed) / 2.0; + p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); + distance = p1_distance + p2_distance; + moveinfo->current_speed = (p1_speed * (p1_distance / distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); + return; + } + + // we are at constant velocity (move_speed) + return; +}; + +void Think_AccelMove (edict_t *ent) +{ + ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; + + if (ent->moveinfo.current_speed == 0) // starting or blocked + plat_CalcAcceleratedMove(&ent->moveinfo); + + plat_Accelerate (&ent->moveinfo); + + // will the entire move complete on next frame? + if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed) + { + Move_Final (ent); + return; + } + + VectorScale (ent->moveinfo.dir, ent->moveinfo.current_speed*10, ent->velocity); + ent->nextthink = level.time + FRAMETIME; + ent->think = Think_AccelMove; +} + + +void plat_go_down (edict_t *ent); + +void plat_hit_top (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_TOP; + + ent->think = plat_go_down; + ent->nextthink = level.time + 3; +} + +void plat_hit_bottom (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); + ent->s.sound = 0; + } + ent->moveinfo.state = STATE_BOTTOM; +} + +void plat_go_down (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_DOWN; + Move_Calc (ent, ent->moveinfo.end_origin, plat_hit_bottom); +} + +void plat_go_up (edict_t *ent) +{ + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + gi.sound (ent, CHAN_NO_PHS_ADD+CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->s.sound = ent->moveinfo.sound_middle; + } + ent->moveinfo.state = STATE_UP; + Move_Calc (ent, ent->moveinfo.start_origin, plat_hit_top); +} + +void plat_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + plat_go_down (self); + else if (self->moveinfo.state == STATE_DOWN) + plat_go_up (self); +} + + +void Use_Plat (edict_t *ent, edict_t *other, edict_t *activator) +{ + if (ent->think) + return; // already down + plat_go_down (ent); +} + + +void Touch_Plat_Center (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + ent = ent->enemy; // now point at the plat, not the trigger + if (ent->moveinfo.state == STATE_BOTTOM) + plat_go_up (ent); + else if (ent->moveinfo.state == STATE_TOP) + ent->nextthink = level.time + 1; // the player is still on the plat, so delay going down +} + +void plat_spawn_inside_trigger (edict_t *ent) +{ + edict_t *trigger; + vec3_t tmin, tmax; + +// +// middle trigger +// + trigger = G_Spawn(); + trigger->touch = Touch_Plat_Center; + trigger->movetype = MOVETYPE_NONE; + trigger->solid = SOLID_TRIGGER; + trigger->enemy = ent; + + tmin[0] = ent->mins[0] + 25; + tmin[1] = ent->mins[1] + 25; + tmin[2] = ent->mins[2]; + + tmax[0] = ent->maxs[0] - 25; + tmax[1] = ent->maxs[1] - 25; + tmax[2] = ent->maxs[2] + 8; + + tmin[2] = tmax[2] - (ent->pos1[2] - ent->pos2[2] + st.lip); + + if (ent->spawnflags & PLAT_LOW_TRIGGER) + tmax[2] = tmin[2] + 8; + + if (tmax[0] - tmin[0] <= 0) + { + tmin[0] = (ent->mins[0] + ent->maxs[0]) *0.5; + tmax[0] = tmin[0] + 1; + } + if (tmax[1] - tmin[1] <= 0) + { + tmin[1] = (ent->mins[1] + ent->maxs[1]) *0.5; + tmax[1] = tmin[1] + 1; + } + + VectorCopy (tmin, trigger->mins); + VectorCopy (tmax, trigger->maxs); + + gi.linkentity (trigger); +} + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat. + +"speed" overrides default 200. +"accel" overrides default 500 +"lip" overrides default 8 pixel lip + +If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ +void SP_func_plat (edict_t *ent) +{ + VectorClear (ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel (ent, ent->model); + + ent->blocked = plat_blocked; + + if (!ent->speed) + ent->speed = 20; + else + ent->speed *= 0.1; + + if (!ent->accel) + ent->accel = 5; + else + ent->accel *= 0.1; + + if (!ent->decel) + ent->decel = 5; + else + ent->decel *= 0.1; + + if (!ent->dmg) + ent->dmg = 2; + + if (!st.lip) + st.lip = 8; + + // pos1 is the top position, pos2 is the bottom + VectorCopy (ent->s.origin, ent->pos1); + VectorCopy (ent->s.origin, ent->pos2); + if (st.height) + ent->pos2[2] -= st.height; + else + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + + ent->use = Use_Plat; + + plat_spawn_inside_trigger (ent); // the "start moving" trigger + + if (ent->targetname) + { + ent->moveinfo.state = STATE_UP; + } + else + { + VectorCopy (ent->pos2, ent->s.origin); + gi.linkentity (ent); + ent->moveinfo.state = STATE_BOTTOM; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav"); +} + +//==================================================================== + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +STOP mean it will stop moving instead of pushing entities +*/ + +void rotating_blocked (edict_t *self, edict_t *other) +{ + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (self->avelocity[0] || self->avelocity[1] || self->avelocity[2]) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void rotating_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!VectorCompare (self->avelocity, vec3_origin)) + { + self->s.sound = 0; + VectorClear (self->avelocity); + self->touch = NULL; + } + else + { + self->s.sound = self->moveinfo.sound_middle; + VectorScale (self->movedir, self->speed, self->avelocity); + if (self->spawnflags & 16) + self->touch = rotating_touch; + } +} + +void SP_func_rotating (edict_t *ent) +{ + ent->solid = SOLID_BSP; + if (ent->spawnflags & 32) + ent->movetype = MOVETYPE_STOP; + else + ent->movetype = MOVETYPE_PUSH; + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & 4) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & 8) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & 2) + VectorNegate (ent->movedir, ent->movedir); + + if (!ent->speed) + ent->speed = 100; + if (!ent->dmg) + ent->dmg = 2; + +// ent->moveinfo.sound_middle = "doors/hydro1.wav"; + + ent->use = rotating_use; + if (ent->dmg) + ent->blocked = rotating_blocked; + + if (ent->spawnflags & 1) + ent->use (ent, NULL, NULL); + + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 128) + ent->s.effects |= EF_ANIM_ALLFAST; + + gi.setmodel (ent, ent->model); + gi.linkentity (ent); +} + +/* +====================================================================== + +BUTTONS + +====================================================================== +*/ + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +1) silent +2) steam metal +3) wooden clunk +4) metallic click +5) in-out +*/ + +void button_done (edict_t *self) +{ + self->moveinfo.state = STATE_BOTTOM; + self->s.effects &= ~EF_ANIM23; + self->s.effects |= EF_ANIM01; +} + +void button_return (edict_t *self) +{ + self->moveinfo.state = STATE_DOWN; + + Move_Calc (self, self->moveinfo.start_origin, button_done); + + self->s.frame = 0; + + if (self->health) + self->takedamage = DAMAGE_YES; +} + +void button_wait (edict_t *self) +{ + self->moveinfo.state = STATE_TOP; + self->s.effects &= ~EF_ANIM01; + self->s.effects |= EF_ANIM23; + + G_UseTargets (self, self->activator); + self->s.frame = 1; + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = button_return; + } +} + +void button_fire (edict_t *self) +{ + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + return; + + self->moveinfo.state = STATE_UP; + if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + Move_Calc (self, self->moveinfo.end_origin, button_wait); +} + +void button_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + button_fire (self); +} + +void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (other->health <= 0) + return; + + self->activator = other; + button_fire (self); +} + +void button_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->activator = attacker; + self->health = self->max_health; + self->takedamage = DAMAGE_NO; + button_fire (self); +} + +void SP_func_button (edict_t *ent) +{ + vec3_t abs_movedir; + float dist; + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_STOP; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + if (ent->sounds != 1) + ent->moveinfo.sound_start = gi.soundindex ("switches/butn2.wav"); + + if (!ent->speed) + ent->speed = 40; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 4; + + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + dist = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, dist, ent->movedir, ent->pos2); + + ent->use = button_use; + ent->s.effects |= EF_ANIM01; + + if (ent->health) + { + ent->max_health = ent->health; + ent->die = button_killed; + ent->takedamage = DAMAGE_YES; + } + else if (! ent->targetname) + ent->touch = button_touch; + + ent->moveinfo.state = STATE_BOTTOM; + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + gi.linkentity (ent); +} + +/* +====================================================================== + +DOORS + + spawn a trigger surrounding the entire team unless it is + already targeted by another + +====================================================================== +*/ + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST +TOGGLE wait in both the start and end states for a trigger event. +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void door_use_areaportals (edict_t *self, qboolean open) +{ + edict_t *t = NULL; + + if (!self->target) + return; + + while ((t = G_Find (t, FOFS(targetname), self->target))) + { + if (Q_stricmp(t->classname, "func_areaportal") == 0) + { + gi.SetAreaPortalState (t->style, open); + } + } +} + +void door_go_down (edict_t *self); + +void door_hit_top (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_TOP; + if (self->spawnflags & DOOR_TOGGLE) + return; + if (self->moveinfo.wait >= 0) + { + self->think = door_go_down; + self->nextthink = level.time + self->moveinfo.wait; + } +} + +void door_hit_bottom (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + self->moveinfo.state = STATE_BOTTOM; + door_use_areaportals (self, false); +} + +void door_go_down (edict_t *self) +{ + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + if (self->max_health) + { + self->takedamage = DAMAGE_YES; + self->health = self->max_health; + } + + self->moveinfo.state = STATE_DOWN; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.start_origin, door_hit_bottom); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_bottom); +} + +void door_go_up (edict_t *self, edict_t *activator) +{ + if (self->moveinfo.state == STATE_UP) + return; // already going up + + if (self->moveinfo.state == STATE_TOP) + { // reset top wait time + if (self->moveinfo.wait >= 0) + self->nextthink = level.time + self->moveinfo.wait; + return; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + self->moveinfo.state = STATE_UP; + if (strcmp(self->classname, "func_door") == 0) + Move_Calc (self, self->moveinfo.end_origin, door_hit_top); + else if (strcmp(self->classname, "func_door_rotating") == 0) + AngleMove_Calc (self, door_hit_top); + + G_UseTargets (self, activator); + door_use_areaportals (self, true); +} + +void door_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *ent; + + if (self->flags & FL_TEAMSLAVE) + return; + + if (self->spawnflags & DOOR_TOGGLE) + { + if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) + { + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_down (ent); + } + return; + } + } + + // trigger all paired doors + for (ent = self ; ent ; ent = ent->teamchain) + { + ent->message = NULL; + ent->touch = NULL; + door_go_up (ent, activator); + } +}; + +void Touch_DoorTrigger (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other->health <= 0) + return; + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + return; + + if ((self->owner->spawnflags & DOOR_NOMONSTER) && (other->svflags & SVF_MONSTER)) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 1.0; + + door_use (self->owner, other, other); +} + +void Think_CalcMoveSpeed (edict_t *self) +{ + edict_t *ent; + float min; + float time; + float newspeed; + float ratio; + float dist; + + if (self->flags & FL_TEAMSLAVE) + return; // only the team master does this + + // find the smallest distance any member of the team will be moving + min = fabs(self->moveinfo.distance); + for (ent = self->teamchain; ent; ent = ent->teamchain) + { + dist = fabs(ent->moveinfo.distance); + if (dist < min) + min = dist; + } + + time = min / self->moveinfo.speed; + + // adjust speeds so they will all complete at the same time + for (ent = self; ent; ent = ent->teamchain) + { + newspeed = fabs(ent->moveinfo.distance) / time; + ratio = newspeed / ent->moveinfo.speed; + if (ent->moveinfo.accel == ent->moveinfo.speed) + ent->moveinfo.accel = newspeed; + else + ent->moveinfo.accel *= ratio; + if (ent->moveinfo.decel == ent->moveinfo.speed) + ent->moveinfo.decel = newspeed; + else + ent->moveinfo.decel *= ratio; + ent->moveinfo.speed = newspeed; + } +} + +void Think_SpawnDoorTrigger (edict_t *ent) +{ + edict_t *other; + vec3_t mins, maxs; + + if (ent->flags & FL_TEAMSLAVE) + return; // only the team leader spawns a trigger + + VectorCopy (ent->absmin, mins); + VectorCopy (ent->absmax, maxs); + + for (other = ent->teamchain ; other ; other=other->teamchain) + { + AddPointToBounds (other->absmin, mins, maxs); + AddPointToBounds (other->absmax, mins, maxs); + } + + // expand + mins[0] -= 60; + mins[1] -= 60; + maxs[0] += 60; + maxs[1] += 60; + + other = G_Spawn (); + VectorCopy (mins, other->mins); + VectorCopy (maxs, other->maxs); + other->owner = ent; + other->solid = SOLID_TRIGGER; + other->movetype = MOVETYPE_NONE; + other->touch = Touch_DoorTrigger; + gi.linkentity (other); + + if (ent->spawnflags & DOOR_START_OPEN) + door_use_areaportals (ent, true); + + Think_CalcMoveSpeed (ent); +} + +void door_blocked (edict_t *self, edict_t *other) +{ + edict_t *ent; + + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->spawnflags & DOOR_CRUSHER) + return; + + +// if a door has a negative wait, it would never come back if blocked, +// so let it just squash the object to death real fast + if (self->moveinfo.wait >= 0) + { + if (self->moveinfo.state == STATE_DOWN) + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_up (ent, ent->activator); + } + else + { + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + door_go_down (ent); + } + } +} + +void door_killed (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + edict_t *ent; + + for (ent = self->teammaster ; ent ; ent = ent->teamchain) + { + ent->health = ent->max_health; + ent->takedamage = DAMAGE_NO; + } + door_use (self->teammaster, attacker, attacker); +} + +void door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (!other->client) + return; + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 5.0; + + gi.centerprintf (other, "%s", self->message); + gi.sound (other, CHAN_AUTO, gi.soundindex ("misc/talk1.wav"), 1, ATTN_NORM, 0); +} + +void SP_func_door (edict_t *ent) +{ + vec3_t abs_movedir; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + G_SetMovedir (ent->s.angles, ent->movedir); + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (deathmatch->value) + ent->speed *= 2; + + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!st.lip) + st.lip = 8; + if (!ent->dmg) + ent->dmg = 2; + + // calculate second position + VectorCopy (ent->s.origin, ent->pos1); + abs_movedir[0] = fabs(ent->movedir[0]); + abs_movedir[1] = fabs(ent->movedir[1]); + abs_movedir[2] = fabs(ent->movedir[2]); + ent->moveinfo.distance = abs_movedir[0] * ent->size[0] + abs_movedir[1] * ent->size[1] + abs_movedir[2] * ent->size[2] - st.lip; + VectorMA (ent->pos1, ent->moveinfo.distance, ent->movedir, ent->pos2); + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.origin); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.origin, ent->pos1); + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->pos1, ent->moveinfo.start_origin); + VectorCopy (ent->s.angles, ent->moveinfo.start_angles); + VectorCopy (ent->pos2, ent->moveinfo.end_origin); + VectorCopy (ent->s.angles, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + if (ent->spawnflags & 64) + ent->s.effects |= EF_ANIM_ALLFAST; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN the door to moves to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). +NOMONSTER monsters will not trigger this door + +You need to have an origin brush as part of this entity. The center of that brush will be +the point around which it is rotated. It will rotate around the Z axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +1) silent +2) light +3) medium +4) heavy +*/ + +void SP_func_door_rotating (edict_t *ent) +{ + VectorClear (ent->s.angles); + + // set the axis of rotation + VectorClear(ent->movedir); + if (ent->spawnflags & DOOR_X_AXIS) + ent->movedir[2] = 1.0; + else if (ent->spawnflags & DOOR_Y_AXIS) + ent->movedir[0] = 1.0; + else // Z_AXIS + ent->movedir[1] = 1.0; + + // check for reverse rotation + if (ent->spawnflags & DOOR_REVERSE) + VectorNegate (ent->movedir, ent->movedir); + + if (!st.distance) + { + gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); + st.distance = 90; + } + + VectorCopy (ent->s.angles, ent->pos1); + VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); + ent->moveinfo.distance = st.distance; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_blocked; + ent->use = door_use; + + if (!ent->speed) + ent->speed = 100; + if (!ent->accel) + ent->accel = ent->speed; + if (!ent->decel) + ent->decel = ent->speed; + + if (!ent->wait) + ent->wait = 3; + if (!ent->dmg) + ent->dmg = 2; + + if (ent->sounds != 1) + { + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + } + + // if it starts open, switch the positions + if (ent->spawnflags & DOOR_START_OPEN) + { + VectorCopy (ent->pos2, ent->s.angles); + VectorCopy (ent->pos1, ent->pos2); + VectorCopy (ent->s.angles, ent->pos1); + VectorNegate (ent->movedir, ent->movedir); + } + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + + if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->moveinfo.state = STATE_BOTTOM; + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy (ent->s.origin, ent->moveinfo.start_origin); + VectorCopy (ent->pos1, ent->moveinfo.start_angles); + VectorCopy (ent->s.origin, ent->moveinfo.end_origin); + VectorCopy (ent->pos2, ent->moveinfo.end_angles); + + if (ent->spawnflags & 16) + ent->s.effects |= EF_ANIM_ALL; + + // to simplify logic elsewhere, make non-teamed doors into a team of one + if (!ent->team) + ent->teammaster = ent; + + gi.linkentity (ent); + + ent->nextthink = level.time + FRAMETIME; + if (ent->health || ent->targetname) + ent->think = Think_CalcMoveSpeed; + else + ent->think = Think_SpawnDoorTrigger; +} + + +/*QUAKED func_water (0 .5 .8) ? START_OPEN +func_water is a moveable water brush. It must be targeted to operate. Use a non-water texture at your own risk. + +START_OPEN causes the water to move to its destination when spawned and operate in reverse. + +"angle" determines the opening direction (up or down only) +"speed" movement speed (25 default) +"wait" wait before returning (-1 default, -1 = TOGGLE) +"lip" lip remaining at end of move (0 default) +"sounds" (yes, these need to be changed) +0) no sound +1) water +2) lava +*/ + +void SP_func_water (edict_t *self) +{ + vec3_t abs_movedir; + + G_SetMovedir (self->s.angles, self->movedir); + self->movetype = MOVETYPE_PUSH; + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + switch (self->sounds) + { + default: + break; + + case 1: // water + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + + case 2: // lava + self->moveinfo.sound_start = gi.soundindex ("world/mov_watr.wav"); + self->moveinfo.sound_end = gi.soundindex ("world/stp_watr.wav"); + break; + } + + // calculate second position + VectorCopy (self->s.origin, self->pos1); + abs_movedir[0] = fabs(self->movedir[0]); + abs_movedir[1] = fabs(self->movedir[1]); + abs_movedir[2] = fabs(self->movedir[2]); + self->moveinfo.distance = abs_movedir[0] * self->size[0] + abs_movedir[1] * self->size[1] + abs_movedir[2] * self->size[2] - st.lip; + VectorMA (self->pos1, self->moveinfo.distance, self->movedir, self->pos2); + + // if it starts open, switch the positions + if (self->spawnflags & DOOR_START_OPEN) + { + VectorCopy (self->pos2, self->s.origin); + VectorCopy (self->pos1, self->pos2); + VectorCopy (self->s.origin, self->pos1); + } + + VectorCopy (self->pos1, self->moveinfo.start_origin); + VectorCopy (self->s.angles, self->moveinfo.start_angles); + VectorCopy (self->pos2, self->moveinfo.end_origin); + VectorCopy (self->s.angles, self->moveinfo.end_angles); + + self->moveinfo.state = STATE_BOTTOM; + + if (!self->speed) + self->speed = 25; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + + if (!self->wait) + self->wait = -1; + self->moveinfo.wait = self->wait; + + self->use = door_use; + + if (self->wait == -1) + self->spawnflags |= DOOR_TOGGLE; + + self->classname = "func_door"; + + gi.linkentity (self); +} + + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +/*QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +noise looping sound to play when the train is in motion + +*/ +void train_next (edict_t *self); + +void train_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + + if (!self->dmg) + return; + self->touch_debounce_time = level.time + 0.5; + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void train_wait (edict_t *self) +{ + if (self->target_ent->pathtarget) + { + char *savetarget; + edict_t *ent; + + ent = self->target_ent; + savetarget = ent->target; + ent->target = ent->pathtarget; + G_UseTargets (ent, self->activator); + ent->target = savetarget; + + // make sure we didn't get killed by a killtarget + if (!self->inuse) + return; + } + + if (self->moveinfo.wait) + { + if (self->moveinfo.wait > 0) + { + self->nextthink = level.time + self->moveinfo.wait; + self->think = train_next; + } + else if (self->spawnflags & TRAIN_TOGGLE) // && wait < 0 + { + train_next (self); + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_end) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_end, 1, ATTN_STATIC, 0); + self->s.sound = 0; + } + } + else + { + train_next (self); + } + +} + +void train_next (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + qboolean first; + + first = true; +again: + if (!self->target) + { +// gi.dprintf ("train_next: no next target\n"); + return; + } + + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_next: bad target %s\n", self->target); + return; + } + + self->target = ent->target; + + // check for a teleport path_corner + if (ent->spawnflags & 1) + { + if (!first) + { + gi.dprintf ("connected teleport path_corners, see %s at %s\n", ent->classname, vtos(ent->s.origin)); + return; + } + first = false; + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + VectorCopy (self->s.origin, self->s.old_origin); + self->s.event = EV_OTHER_TELEPORT; + gi.linkentity (self); + goto again; + } + + self->moveinfo.wait = ent->wait; + self->target_ent = ent; + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + gi.sound (self, CHAN_NO_PHS_ADD+CHAN_VOICE, self->moveinfo.sound_start, 1, ATTN_STATIC, 0); + self->s.sound = self->moveinfo.sound_middle; + } + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void train_resume (edict_t *self) +{ + edict_t *ent; + vec3_t dest; + + ent = self->target_ent; + + VectorSubtract (ent->s.origin, self->mins, dest); + self->moveinfo.state = STATE_TOP; + VectorCopy (self->s.origin, self->moveinfo.start_origin); + VectorCopy (dest, self->moveinfo.end_origin); + Move_Calc (self, dest, train_wait); + self->spawnflags |= TRAIN_START_ON; +} + +void func_train_find (edict_t *self) +{ + edict_t *ent; + + if (!self->target) + { + gi.dprintf ("train_find: no target\n"); + return; + } + ent = G_PickTarget (self->target); + if (!ent) + { + gi.dprintf ("train_find: target %s not found\n", self->target); + return; + } + self->target = ent->target; + + VectorSubtract (ent->s.origin, self->mins, self->s.origin); + gi.linkentity (self); + + // if not triggered, start immediately + if (!self->targetname) + self->spawnflags |= TRAIN_START_ON; + + if (self->spawnflags & TRAIN_START_ON) + { + self->nextthink = level.time + FRAMETIME; + self->think = train_next; + self->activator = self; + } +} + +void train_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + if (self->spawnflags & TRAIN_START_ON) + { + if (!(self->spawnflags & TRAIN_TOGGLE)) + return; + self->spawnflags &= ~TRAIN_START_ON; + VectorClear (self->velocity); + self->nextthink = 0; + } + else + { + if (self->target_ent) + train_resume(self); + else + train_next(self); + } +} + +void SP_func_train (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + + VectorClear (self->s.angles); + self->blocked = train_blocked; + if (self->spawnflags & TRAIN_BLOCK_STOPS) + self->dmg = 0; + else + { + if (!self->dmg) + self->dmg = 100; + } + self->solid = SOLID_BSP; + gi.setmodel (self, self->model); + + if (st.noise) + self->moveinfo.sound_middle = gi.soundindex (st.noise); + + if (!self->speed) + self->speed = 100; + + self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed; + + self->use = train_use; + + gi.linkentity (self); + + if (self->target) + { + // start trains on the second frame, to make sure their targets have had + // a chance to spawn + self->nextthink = level.time + FRAMETIME; + self->think = func_train_find; + } + else + { + gi.dprintf ("func_train without a target at %s\n", vtos(self->absmin)); + } +} + + +/*QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) +*/ +void trigger_elevator_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *target; + + if (self->movetarget->nextthink) + { +// gi.dprintf("elevator busy\n"); + return; + } + + if (!other->pathtarget) + { + gi.dprintf("elevator used with no pathtarget\n"); + return; + } + + target = G_PickTarget (other->pathtarget); + if (!target) + { + gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + return; + } + + self->movetarget->target_ent = target; + train_resume (self->movetarget); +} + +void trigger_elevator_init (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("trigger_elevator has no target\n"); + return; + } + self->movetarget = G_PickTarget (self->target); + if (!self->movetarget) + { + gi.dprintf("trigger_elevator unable to find target %s\n", self->target); + return; + } + if (strcmp(self->movetarget->classname, "func_train") != 0) + { + gi.dprintf("trigger_elevator target %s is not a train\n", self->target); + return; + } + + self->use = trigger_elevator_use; + self->svflags = SVF_NOCLIENT; + +} + +void SP_trigger_elevator (edict_t *self) +{ + self->think = trigger_elevator_init; + self->nextthink = level.time + FRAMETIME; +} + + +/*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON +"wait" base time between triggering all targets, default is 1 +"random" wait variance, default is 0 + +so, the basic time between firing is a random time between +(wait - random) and (wait + random) + +"delay" delay before first firing when turned on, default is 0 + +"pausetime" additional delay used only the very first time + and only if spawned with START_ON + +These can used but not touched. +*/ +void func_timer_think (edict_t *self) +{ + G_UseTargets (self, self->activator); + self->nextthink = level.time + self->wait + crandom() * self->random; +} + +void func_timer_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->activator = activator; + + // if on, turn it off + if (self->nextthink) + { + self->nextthink = 0; + return; + } + + // turn it on + if (self->delay) + self->nextthink = level.time + self->delay; + else + func_timer_think (self); +} + +void SP_func_timer (edict_t *self) +{ + if (!self->wait) + self->wait = 1.0; + + self->use = func_timer_use; + self->think = func_timer_think; + + if (self->random >= self->wait) + { + self->random = self->wait - FRAMETIME; + gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + } + + if (self->spawnflags & 1) + { + self->nextthink = level.time + 1.0 + st.pausetime + self->delay + self->wait + crandom() * self->random; + self->activator = self; + } + + self->svflags = SVF_NOCLIENT; +} + + +/*QUAKED func_conveyor (0 .5 .8) ? START_ON TOGGLE +Conveyors are stationary brushes that move what's on them. +The brush should be have a surface with at least one current content enabled. +speed default 100 +*/ + +void func_conveyor_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & 1) + { + self->speed = 0; + self->spawnflags &= ~1; + } + else + { + self->speed = self->count; + self->spawnflags |= 1; + } + + if (!(self->spawnflags & 2)) + self->count = 0; +} + +void SP_func_conveyor (edict_t *self) +{ + if (!self->speed) + self->speed = 100; + + if (!(self->spawnflags & 1)) + { + self->count = self->speed; + self->speed = 0; + } + + self->use = func_conveyor_use; + + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + gi.linkentity (self); +} + + +/*QUAKED func_door_secret (0 .5 .8) ? always_shoot 1st_left 1st_down +A secret door. Slide back and then to the side. + +open_once doors never closes +1st_left 1st move is left of arrow +1st_down 1st move is down from arrow +always_shoot door is shootebale even if targeted + +"angle" determines the direction +"dmg" damage to inflic when blocked (default 2) +"wait" how long to hold in the open position (default 5, -1 means hold) +*/ + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1 (edict_t *self); +void door_secret_move2 (edict_t *self); +void door_secret_move3 (edict_t *self); +void door_secret_move4 (edict_t *self); +void door_secret_move5 (edict_t *self); +void door_secret_move6 (edict_t *self); +void door_secret_done (edict_t *self); + +void door_secret_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // make sure we're not already moving + if (!VectorCompare(self->s.origin, vec3_origin)) + return; + + Move_Calc (self, self->pos1, door_secret_move1); + door_use_areaportals (self, true); +} + +void door_secret_move1 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move2; +} + +void door_secret_move2 (edict_t *self) +{ + Move_Calc (self, self->pos2, door_secret_move3); +} + +void door_secret_move3 (edict_t *self) +{ + if (self->wait == -1) + return; + self->nextthink = level.time + self->wait; + self->think = door_secret_move4; +} + +void door_secret_move4 (edict_t *self) +{ + Move_Calc (self, self->pos1, door_secret_move5); +} + +void door_secret_move5 (edict_t *self) +{ + self->nextthink = level.time + 1.0; + self->think = door_secret_move6; +} + +void door_secret_move6 (edict_t *self) +{ + Move_Calc (self, vec3_origin, door_secret_done); +} + +void door_secret_done (edict_t *self) +{ + if (!(self->targetname) || (self->spawnflags & SECRET_ALWAYS_SHOOT)) + { + self->health = 0; + self->takedamage = DAMAGE_YES; + } + door_use_areaportals (self, false); +} + +void door_secret_blocked (edict_t *self, edict_t *other) +{ + if (!(other->svflags & SVF_MONSTER) && (!other->client) ) + { + // give it a chance to go away on it's own terms (like gibs) + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH); + // if it's still there, nuke it + if (other) + BecomeExplosion1 (other); + return; + } + + if (level.time < self->touch_debounce_time) + return; + self->touch_debounce_time = level.time + 0.5; + + T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void door_secret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + door_secret_use (self, attacker, attacker); +} + +void SP_func_door_secret (edict_t *ent) +{ + vec3_t forward, right, up; + float side; + float width; + float length; + + ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_BSP; + gi.setmodel (ent, ent->model); + + ent->blocked = door_secret_blocked; + ent->use = door_secret_use; + + if (!(ent->targetname) || (ent->spawnflags & SECRET_ALWAYS_SHOOT)) + { + ent->health = 0; + ent->takedamage = DAMAGE_YES; + ent->die = door_secret_die; + } + + if (!ent->dmg) + ent->dmg = 2; + + if (!ent->wait) + ent->wait = 5; + + ent->moveinfo.accel = + ent->moveinfo.decel = + ent->moveinfo.speed = 50; + + // calculate positions + AngleVectors (ent->s.angles, forward, right, up); + VectorClear (ent->s.angles); + side = 1.0 - (ent->spawnflags & SECRET_1ST_LEFT); + if (ent->spawnflags & SECRET_1ST_DOWN) + width = fabs(DotProduct(up, ent->size)); + else + width = fabs(DotProduct(right, ent->size)); + length = fabs(DotProduct(forward, ent->size)); + if (ent->spawnflags & SECRET_1ST_DOWN) + VectorMA (ent->s.origin, -1 * width, up, ent->pos1); + else + VectorMA (ent->s.origin, side * width, right, ent->pos1); + VectorMA (ent->pos1, length, forward, ent->pos2); + + if (ent->health) + { + ent->takedamage = DAMAGE_YES; + ent->die = door_killed; + ent->max_health = ent->health; + } + else if (ent->targetname && ent->message) + { + gi.soundindex ("misc/talk.wav"); + ent->touch = door_touch; + } + + ent->classname = "func_door"; + + gi.linkentity (ent); +} + + +/*QUAKED func_killbox (1 0 0) ? +Kills everything inside when fired, irrespective of protection. +*/ +void use_killbox (edict_t *self, edict_t *other, edict_t *activator) +{ + KillBox (self); +} + +void SP_func_killbox (edict_t *ent) +{ + gi.setmodel (ent, ent->model); + ent->use = use_killbox; + ent->svflags = SVF_NOCLIENT; +} + diff --git a/game/g_items.c b/game/g_items.c new file mode 100644 index 000000000..e81e835ed --- /dev/null +++ b/game/g_items.c @@ -0,0 +1,2216 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +qboolean Pickup_Weapon (edict_t *ent, edict_t *other); +void Use_Weapon (edict_t *ent, gitem_t *inv); +void Drop_Weapon (edict_t *ent, gitem_t *inv); + +void Weapon_Blaster (edict_t *ent); +void Weapon_Shotgun (edict_t *ent); +void Weapon_SuperShotgun (edict_t *ent); +void Weapon_Machinegun (edict_t *ent); +void Weapon_Chaingun (edict_t *ent); +void Weapon_HyperBlaster (edict_t *ent); +void Weapon_RocketLauncher (edict_t *ent); +void Weapon_Grenade (edict_t *ent); +void Weapon_GrenadeLauncher (edict_t *ent); +void Weapon_Railgun (edict_t *ent); +void Weapon_BFG (edict_t *ent); + +gitem_armor_t jacketarmor_info = { 25, 50, .30, .00, ARMOR_JACKET}; +gitem_armor_t combatarmor_info = { 50, 100, .60, .30, ARMOR_COMBAT}; +gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; +static int power_screen_index; +static int power_shield_index; + +#define HEALTH_IGNORE_MAX 1 +#define HEALTH_TIMED 2 + +void Use_Quad (edict_t *ent, gitem_t *item); +static int quad_drop_timeout_hack; + +//====================================================================== + +/* +=============== +GetItemByIndex +=============== +*/ +gitem_t *GetItemByIndex (int index) +{ + if (index == 0 || index >= game.num_items) + return NULL; + + return &itemlist[index]; +} + + +/* +=============== +FindItemByClassname + +=============== +*/ +gitem_t *FindItemByClassname (char *classname) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; iclassname) + continue; + if (!Q_stricmp(it->classname, classname)) + return it; + } + + return NULL; +} + +/* +=============== +FindItem + +=============== +*/ +gitem_t *FindItem (char *pickup_name) +{ + int i; + gitem_t *it; + + it = itemlist; + for (i=0 ; ipickup_name) + continue; + if (!Q_stricmp(it->pickup_name, pickup_name)) + return it; + } + + return NULL; +} + +//====================================================================== + +void DoRespawn (edict_t *ent) +{ + if (ent->team) + { + edict_t *master; + int count; + int choice; + + master = ent->teammaster; + + for (count = 0, ent = master; ent; ent = ent->chain, count++) + ; + + choice = rand() % count; + + for (count = 0, ent = master; count < choice; ent = ent->chain, count++) + ; + } + + ent->svflags &= ~SVF_NOCLIENT; + ent->solid = SOLID_TRIGGER; + gi.linkentity (ent); + + // send an effect + ent->s.event = EV_ITEM_RESPAWN; +} + +void SetRespawn (edict_t *ent, float delay) +{ + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->nextthink = level.time + delay; + ent->think = DoRespawn; + gi.linkentity (ent); +} + + +//====================================================================== + +qboolean Pickup_Powerup (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + if ((skill->value == 1 && quantity >= 2) || (skill->value >= 2 && quantity >= 1)) + return false; + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + return false; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + if (((int)dmflags->value & DF_INSTANT_ITEMS) || ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM))) + { + if ((ent->item->use == Use_Quad) && (ent->spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (ent->nextthink - level.time) / FRAMETIME; + ent->item->use (other, ent->item); + } + } + + return true; +} + +void Drop_General (edict_t *ent, gitem_t *item) +{ + Drop_Item (ent, item); + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +qboolean Pickup_Adrenaline (edict_t *ent, edict_t *other) +{ + if (!deathmatch->value) + other->max_health += 1; + + if (other->health < other->max_health) + other->health = other->max_health; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_AncientHead (edict_t *ent, edict_t *other) +{ + other->max_health += 2; + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Bandolier (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 250) + other->client->pers.max_bullets = 250; + if (other->client->pers.max_shells < 150) + other->client->pers.max_shells = 150; + if (other->client->pers.max_cells < 250) + other->client->pers.max_cells = 250; + if (other->client->pers.max_slugs < 75) + other->client->pers.max_slugs = 75; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +qboolean Pickup_Pack (edict_t *ent, edict_t *other) +{ + gitem_t *item; + int index; + + if (other->client->pers.max_bullets < 300) + other->client->pers.max_bullets = 300; + if (other->client->pers.max_shells < 200) + other->client->pers.max_shells = 200; + if (other->client->pers.max_rockets < 100) + other->client->pers.max_rockets = 100; + if (other->client->pers.max_grenades < 100) + other->client->pers.max_grenades = 100; + if (other->client->pers.max_cells < 300) + other->client->pers.max_cells = 300; + if (other->client->pers.max_slugs < 100) + other->client->pers.max_slugs = 100; + + item = FindItem("Bullets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_bullets) + other->client->pers.inventory[index] = other->client->pers.max_bullets; + } + + item = FindItem("Shells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_shells) + other->client->pers.inventory[index] = other->client->pers.max_shells; + } + + item = FindItem("Cells"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_cells) + other->client->pers.inventory[index] = other->client->pers.max_cells; + } + + item = FindItem("Grenades"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_grenades) + other->client->pers.inventory[index] = other->client->pers.max_grenades; + } + + item = FindItem("Rockets"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_rockets) + other->client->pers.inventory[index] = other->client->pers.max_rockets; + } + + item = FindItem("Slugs"); + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + if (other->client->pers.inventory[index] > other->client->pers.max_slugs) + other->client->pers.inventory[index] = other->client->pers.max_slugs; + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, ent->item->quantity); + + return true; +} + +//====================================================================== + +void Use_Quad (edict_t *ent, gitem_t *item) +{ + int timeout; + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (quad_drop_timeout_hack) + { + timeout = quad_drop_timeout_hack; + quad_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quad_framenum > level.framenum) + ent->client->quad_framenum += timeout; + else + ent->client->quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Breather (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->breather_framenum > level.framenum) + ent->client->breather_framenum += 300; + else + ent->client->breather_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Envirosuit (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->enviro_framenum > level.framenum) + ent->client->enviro_framenum += 300; + else + ent->client->enviro_framenum = level.framenum + 300; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Invulnerability (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + + if (ent->client->invincible_framenum > level.framenum) + ent->client->invincible_framenum += 300; + else + ent->client->invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +void Use_Silencer (edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem (ent); + ent->client->silencer_shots += 30; + +// gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +//====================================================================== + +qboolean Pickup_Key (edict_t *ent, edict_t *other) +{ + if (coop->value) + { + if (strcmp(ent->classname, "key_power_cube") == 0) + { + if (other->client->pers.power_cubes & ((ent->spawnflags & 0x0000ff00)>> 8)) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + other->client->pers.power_cubes |= ((ent->spawnflags & 0x0000ff00) >> 8); + } + else + { + if (other->client->pers.inventory[ITEM_INDEX(ent->item)]) + return false; + other->client->pers.inventory[ITEM_INDEX(ent->item)] = 1; + } + return true; + } + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + return true; +} + +//====================================================================== + +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count) +{ + int index; + int max; + + if (!ent->client) + return false; + + if (item->tag == AMMO_BULLETS) + max = ent->client->pers.max_bullets; + else if (item->tag == AMMO_SHELLS) + max = ent->client->pers.max_shells; + else if (item->tag == AMMO_ROCKETS) + max = ent->client->pers.max_rockets; + else if (item->tag == AMMO_GRENADES) + max = ent->client->pers.max_grenades; + else if (item->tag == AMMO_CELLS) + max = ent->client->pers.max_cells; + else if (item->tag == AMMO_SLUGS) + max = ent->client->pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent->client->pers.inventory[index] == max) + return false; + + ent->client->pers.inventory[index] += count; + + if (ent->client->pers.inventory[index] > max) + ent->client->pers.inventory[index] = max; + + return true; +} + +qboolean Pickup_Ammo (edict_t *ent, edict_t *other) +{ + int oldcount; + int count; + qboolean weapon; + + weapon = (ent->item->flags & IT_WEAPON); + if ( (weapon) && ( (int)dmflags->value & DF_INFINITE_AMMO ) ) + count = 1000; + else if (ent->count) + count = ent->count; + else + count = ent->item->quantity; + + oldcount = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (!Add_Ammo (other, ent->item, count)) + return false; + + if (weapon && !oldcount) + { + if (other->client->pers.weapon != ent->item && ( !deathmatch->value || other->client->pers.weapon == FindItem("blaster") ) ) + other->client->newweapon = ent->item; + } + + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + SetRespawn (ent, 30); + return true; +} + +void Drop_Ammo (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + int index; + + index = ITEM_INDEX(item); + dropped = Drop_Item (ent, item); + if (ent->client->pers.inventory[index] >= item->quantity) + dropped->count = item->quantity; + else + dropped->count = ent->client->pers.inventory[index]; + + if (ent->client->pers.weapon && + ent->client->pers.weapon->tag == AMMO_GRENADES && + item->tag == AMMO_GRENADES && + ent->client->pers.inventory[index] - dropped->count <= 0) { + gi.cprintf (ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + ent->client->pers.inventory[index] -= dropped->count; + ValidateSelectedItem (ent); +} + + +//====================================================================== + +void MegaHealth_think (edict_t *self) +{ + if (self->owner->health > self->owner->max_health) + { + self->nextthink = level.time + 1; + self->owner->health -= 1; + return; + } + + if (!(self->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (self, 20); + else + G_FreeEdict (self); +} + +qboolean Pickup_Health (edict_t *ent, edict_t *other) +{ + if (!(ent->style & HEALTH_IGNORE_MAX)) + if (other->health >= other->max_health) + return false; + + other->health += ent->count; + + if (!(ent->style & HEALTH_IGNORE_MAX)) + { + if (other->health > other->max_health) + other->health = other->max_health; + } + + if (ent->style & HEALTH_TIMED) + { + ent->think = MegaHealth_think; + ent->nextthink = level.time + 5; + ent->owner = other; + ent->flags |= FL_RESPAWN; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + } + else + { + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 30); + } + + return true; +} + +//====================================================================== + +int ArmorIndex (edict_t *ent) +{ + if (!ent->client) + return 0; + + if (ent->client->pers.inventory[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent->client->pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent->client->pers.inventory[body_armor_index] > 0) + return body_armor_index; + + return 0; +} + +qboolean Pickup_Armor (edict_t *ent, edict_t *other) +{ + int old_armor_index; + gitem_armor_t *oldinfo; + gitem_armor_t *newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t *)ent->item->info; + + old_armor_index = ArmorIndex (other); + + // handle armor shards specially + if (ent->item->tag == ARMOR_SHARD) + { + if (!old_armor_index) + other->client->pers.inventory[jacket_armor_index] = 2; + else + other->client->pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (!old_armor_index) + { + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newinfo->base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = &jacketarmor_info; + else if (old_armor_index == combat_armor_index) + oldinfo = &combatarmor_info; + else // (old_armor_index == body_armor_index) + oldinfo = &bodyarmor_info; + + if (newinfo->normal_protection > oldinfo->normal_protection) + { + // calc new armor values + salvage = oldinfo->normal_protection / newinfo->normal_protection; + salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + newcount = newinfo->base_count + salvagecount; + if (newcount > newinfo->max_count) + newcount = newinfo->max_count; + + // zero count of old armor so it goes away + other->client->pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other->client->pers.inventory[ITEM_INDEX(ent->item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo->normal_protection / oldinfo->normal_protection; + salvagecount = salvage * newinfo->base_count; + newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo->max_count) + newcount = oldinfo->max_count; + + // if we're already maxed out then we don't need the new armor + if (other->client->pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other->client->pers.inventory[old_armor_index] = newcount; + } + } + + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) + SetRespawn (ent, 20); + + return true; +} + +//====================================================================== + +int PowerArmorType (edict_t *ent) +{ + if (!ent->client) + return POWER_ARMOR_NONE; + + if (!(ent->flags & FL_POWER_ARMOR)) + return POWER_ARMOR_NONE; + + if (ent->client->pers.inventory[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent->client->pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; +} + +void Use_PowerArmor (edict_t *ent, gitem_t *item) +{ + int index; + + if (ent->flags & FL_POWER_ARMOR) + { + ent->flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else + { + index = ITEM_INDEX(FindItem("cells")); + if (!ent->client->pers.inventory[index]) + { + gi.cprintf (ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent->flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } +} + +qboolean Pickup_PowerArmor (edict_t *ent, edict_t *other) +{ + int quantity; + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM) ) + SetRespawn (ent, ent->item->quantity); + // auto-use for DM only if we didn't already have one + if (!quantity) + ent->item->use (other, ent->item); + } + + return true; +} + +void Drop_PowerArmor (edict_t *ent, gitem_t *item) +{ + if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[ITEM_INDEX(item)] == 1)) + Use_PowerArmor (ent, item); + Drop_General (ent, item); +} + +//====================================================================== + +/* +=============== +Touch_Item +=============== +*/ +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + qboolean taken; + + if (!other->client) + return; + if (other->health < 1) + return; // dead people can't pickup + if (!ent->item->pickup) + return; // not a grabbable item? + + taken = ent->item->pickup(ent, other); + + if (taken) + { + // flash the screen + other->client->bonus_alpha = 0.25; + + // show icon and name on status bar + other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS+ITEM_INDEX(ent->item); + other->client->pickup_msg_time = level.time + 3.0; + + // change selected item + if (ent->item->use) + other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + + if (ent->item->pickup == Pickup_Health) + { + if (ent->count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent->count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent->count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent->item->pickup_sound) + { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (!(ent->spawnflags & ITEM_TARGETS_USED)) + { + G_UseTargets (ent, other); + ent->spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) + { + if (ent->flags & FL_RESPAWN) + ent->flags &= ~FL_RESPAWN; + else + G_FreeEdict (ent); + } +} + +//====================================================================== + +static void drop_temp_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + if (other == ent->owner) + return; + + Touch_Item (ent, other, plane, surf); +} + +static void drop_make_touchable (edict_t *ent) +{ + ent->touch = Touch_Item; + if (deathmatch->value) + { + ent->nextthink = level.time + 29; + ent->think = G_FreeEdict; + } +} + +edict_t *Drop_Item (edict_t *ent, gitem_t *item) +{ + edict_t *dropped; + vec3_t forward, right; + vec3_t offset; + + dropped = G_Spawn(); + + dropped->classname = item->classname; + dropped->item = item; + dropped->spawnflags = DROPPED_ITEM; + dropped->s.effects = item->world_model_flags; + dropped->s.renderfx = RF_GLOW; + VectorSet (dropped->mins, -15, -15, -15); + VectorSet (dropped->maxs, 15, 15, 15); + gi.setmodel (dropped, dropped->item->world_model); + dropped->solid = SOLID_TRIGGER; + dropped->movetype = MOVETYPE_TOSS; + dropped->touch = drop_temp_touch; + dropped->owner = ent; + + if (ent->client) + { + trace_t trace; + + AngleVectors (ent->client->v_angle, forward, right, NULL); + VectorSet(offset, 24, 0, -16); + G_ProjectSource (ent->s.origin, offset, forward, right, dropped->s.origin); + trace = gi.trace (ent->s.origin, dropped->mins, dropped->maxs, + dropped->s.origin, ent, CONTENTS_SOLID); + VectorCopy (trace.endpos, dropped->s.origin); + } + else + { + AngleVectors (ent->s.angles, forward, right, NULL); + VectorCopy (ent->s.origin, dropped->s.origin); + } + + VectorScale (forward, 100, dropped->velocity); + dropped->velocity[2] = 300; + + dropped->think = drop_make_touchable; + dropped->nextthink = level.time + 1; + + gi.linkentity (dropped); + + return dropped; +} + +void Use_Item (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->svflags &= ~SVF_NOCLIENT; + ent->use = NULL; + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + } + else + { + ent->solid = SOLID_TRIGGER; + ent->touch = Touch_Item; + } + + gi.linkentity (ent); +} + +//====================================================================== + +/* +================ +droptofloor +================ +*/ +void droptofloor (edict_t *ent) +{ + trace_t tr; + vec3_t dest; + float *v; + + v = tv(-15,-15,-15); + VectorCopy (v, ent->mins); + v = tv(15,15,15); + VectorCopy (v, ent->maxs); + + if (ent->model) + gi.setmodel (ent, ent->model); + else + gi.setmodel (ent, ent->item->world_model); + ent->solid = SOLID_TRIGGER; + ent->movetype = MOVETYPE_TOSS; + ent->touch = Touch_Item; + + v = tv(0,0,-128); + VectorAdd (ent->s.origin, v, dest); + + tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) + { + gi.dprintf ("droptofloor: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin)); + G_FreeEdict (ent); + return; + } + + VectorCopy (tr.endpos, ent->s.origin); + + if (ent->team) + { + ent->flags &= ~FL_TEAMSLAVE; + ent->chain = ent->teamchain; + ent->teamchain = NULL; + + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + if (ent == ent->teammaster) + { + ent->nextthink = level.time + FRAMETIME; + ent->think = DoRespawn; + } + } + + if (ent->spawnflags & ITEM_NO_TOUCH) + { + ent->solid = SOLID_BBOX; + ent->touch = NULL; + ent->s.effects &= ~EF_ROTATE; + ent->s.renderfx &= ~RF_GLOW; + } + + if (ent->spawnflags & ITEM_TRIGGER_SPAWN) + { + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; + ent->use = Use_Item; + } + + gi.linkentity (ent); +} + + +/* +=============== +PrecacheItem + +Precaches all data needed for a given item. +This will be called for each item spawned in a level, +and for each item in each client's inventory. +=============== +*/ +void PrecacheItem (gitem_t *it) +{ + char *s, *start; + char data[MAX_QPATH]; + int len; + gitem_t *ammo; + + if (!it) + return; + + if (it->pickup_sound) + gi.soundindex (it->pickup_sound); + if (it->world_model) + gi.modelindex (it->world_model); + if (it->view_model) + gi.modelindex (it->view_model); + if (it->icon) + gi.imageindex (it->icon); + + // parse everything for its ammo + if (it->ammo && it->ammo[0]) + { + ammo = FindItem (it->ammo); + if (ammo != it) + PrecacheItem (ammo); + } + + // parse the space seperated precache string for other items + s = it->precaches; + if (!s || !s[0]) + return; + + while (*s) + { + start = s; + while (*s && *s != ' ') + s++; + + len = s-start; + if (len >= MAX_QPATH || len < 5) + gi.error ("PrecacheItem: %s has bad precache string", it->classname); + memcpy (data, start, len); + data[len] = 0; + if (*s) + s++; + + // determine type based on extension + if (!strcmp(data+len-3, "md2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "sp2")) + gi.modelindex (data); + else if (!strcmp(data+len-3, "wav")) + gi.soundindex (data); + if (!strcmp(data+len-3, "pcx")) + gi.imageindex (data); + } +} + +/* +============ +SpawnItem + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void SpawnItem (edict_t *ent, gitem_t *item) +{ + PrecacheItem (item); + + if (ent->spawnflags) + { + if (strcmp(ent->classname, "key_power_cube") != 0) + { + ent->spawnflags = 0; + gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin)); + } + } + + // some items will be prevented in deathmatch + if (deathmatch->value) + { + if ( (int)dmflags->value & DF_NO_ARMOR ) + { + if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_ITEMS ) + { + if (item->pickup == Pickup_Powerup) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_NO_HEALTH ) + { + if (item->pickup == Pickup_Health || item->pickup == Pickup_Adrenaline || item->pickup == Pickup_AncientHead) + { + G_FreeEdict (ent); + return; + } + } + if ( (int)dmflags->value & DF_INFINITE_AMMO ) + { + if ( (item->flags == IT_AMMO) || (strcmp(ent->classname, "weapon_bfg") == 0) ) + { + G_FreeEdict (ent); + return; + } + } + } + + if (coop->value && (strcmp(ent->classname, "key_power_cube") == 0)) + { + ent->spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop->value) && (item->flags & IT_STAY_COOP)) + { + item->drop = NULL; + } + + ent->item = item; + ent->nextthink = level.time + 2 * FRAMETIME; // items start after other solids + ent->think = droptofloor; + ent->s.effects = item->world_model_flags; + ent->s.renderfx = RF_GLOW; + if (ent->model) + gi.modelindex (ent->model); +} + +//====================================================================== + +gitem_t itemlist[] = +{ + { + NULL + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_body", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/body/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_bodyarmor", +/* pickup */ "Body Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &bodyarmor_info, + ARMOR_BODY, +/* precache */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_combat", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_combatarmor", +/* pickup */ "Combat Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &combatarmor_info, + ARMOR_COMBAT, +/* precache */ "" + }, + +/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_jacket", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Jacket Armor", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + &jacketarmor_info, + ARMOR_JACKET, +/* precache */ "" + }, + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_armor_shard", + Pickup_Armor, + NULL, + NULL, + NULL, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_jacketarmor", +/* pickup */ "Armor Shard", +/* width */ 3, + 0, + NULL, + IT_ARMOR, + 0, + NULL, + ARMOR_SHARD, +/* precache */ "" + }, + + +/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_screen", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powerscreen", +/* pickup */ "Power Screen", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + NULL, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_powershield", +/* pickup */ "Power Shield", +/* width */ 0, + 60, + NULL, + IT_ARMOR, + 0, + NULL, + 0, +/* precache */ "misc/power2.wav misc/power1.wav" + }, + + + // + // WEAPONS + // + +/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) +always owned, never in the world +*/ + { + "weapon_blaster", + NULL, + Use_Weapon, + NULL, + Weapon_Blaster, + "misc/w_pkup.wav", + NULL, 0, + "models/weapons/v_blast/tris.md2", +/* icon */ "w_blaster", +/* pickup */ "Blaster", + 0, + 0, + NULL, + IT_WEAPON|IT_STAY_COOP, + WEAP_BLASTER, + NULL, + 0, +/* precache */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_shotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", EF_ROTATE, + "models/weapons/v_shotg/tris.md2", +/* icon */ "w_shotgun", +/* pickup */ "Shotgun", + 0, + 1, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SHOTGUN, + NULL, + 0, +/* precache */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + +/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_supershotgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", +/* icon */ "w_sshotgun", +/* pickup */ "Super Shotgun", + 0, + 2, + "Shells", + IT_WEAPON|IT_STAY_COOP, + WEAP_SUPERSHOTGUN, + NULL, + 0, +/* precache */ "weapons/sshotf1b.wav" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_machinegun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", EF_ROTATE, + "models/weapons/v_machn/tris.md2", +/* icon */ "w_machinegun", +/* pickup */ "Machinegun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_MACHINEGUN, + NULL, + 0, +/* precache */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_chaingun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", EF_ROTATE, + "models/weapons/v_chain/tris.md2", +/* icon */ "w_chaingun", +/* pickup */ "Chaingun", + 0, + 1, + "Bullets", + IT_WEAPON|IT_STAY_COOP, + WEAP_CHAINGUN, + NULL, + 0, +/* precache */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_grenades", + Pickup_Ammo, + Use_Weapon, + Drop_Ammo, + Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", 0, + "models/weapons/v_handgr/tris.md2", +/* icon */ "a_grenades", +/* pickup */ "Grenades", +/* width */ 3, + 5, + "grenades", + IT_AMMO|IT_WEAPON, + WEAP_GRENADES, + NULL, + AMMO_GRENADES, +/* precache */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav " + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_grenadelauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", EF_ROTATE, + "models/weapons/v_launch/tris.md2", +/* icon */ "w_glauncher", +/* pickup */ "Grenade Launcher", + 0, + 1, + "Grenades", + IT_WEAPON|IT_STAY_COOP, + WEAP_GRENADELAUNCHER, + NULL, + 0, +/* precache */ "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_rocketlauncher", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", EF_ROTATE, + "models/weapons/v_rocket/tris.md2", +/* icon */ "w_rlauncher", +/* pickup */ "Rocket Launcher", + 0, + 1, + "Rockets", + IT_WEAPON|IT_STAY_COOP, + WEAP_ROCKETLAUNCHER, + NULL, + 0, +/* precache */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + +/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_hyperblaster", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", +/* icon */ "w_hyperblaster", +/* pickup */ "HyperBlaster", + 0, + 1, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_HYPERBLASTER, + NULL, + 0, +/* precache */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_railgun", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", EF_ROTATE, + "models/weapons/v_rail/tris.md2", +/* icon */ "w_railgun", +/* pickup */ "Railgun", + 0, + 1, + "Slugs", + IT_WEAPON|IT_STAY_COOP, + WEAP_RAILGUN, + NULL, + 0, +/* precache */ "weapons/rg_hum.wav" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "weapon_bfg", + Pickup_Weapon, + Use_Weapon, + Drop_Weapon, + Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", EF_ROTATE, + "models/weapons/v_bfg/tris.md2", +/* icon */ "w_bfg", +/* pickup */ "BFG10K", + 0, + 50, + "Cells", + IT_WEAPON|IT_STAY_COOP, + WEAP_BFG, + NULL, + 0, +/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_shells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", 0, + NULL, +/* icon */ "a_shells", +/* pickup */ "Shells", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SHELLS, +/* precache */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_bullets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", 0, + NULL, +/* icon */ "a_bullets", +/* pickup */ "Bullets", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_BULLETS, +/* precache */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_cells", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", 0, + NULL, +/* icon */ "a_cells", +/* pickup */ "Cells", +/* width */ 3, + 50, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_CELLS, +/* precache */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_rockets", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", 0, + NULL, +/* icon */ "a_rockets", +/* pickup */ "Rockets", +/* width */ 3, + 5, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_ROCKETS, +/* precache */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "ammo_slugs", + Pickup_Ammo, + NULL, + Drop_Ammo, + NULL, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", 0, + NULL, +/* icon */ "a_slugs", +/* pickup */ "Slugs", +/* width */ 3, + 10, + NULL, + IT_AMMO, + 0, + NULL, + AMMO_SLUGS, +/* precache */ "" + }, + + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/quaddama/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_quad", +/* pickup */ "Quad Damage", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/damage.wav items/damage2.wav items/damage3.wav" + }, + +/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/invulner/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_invulnerability", +/* pickup */ "Invulnerability", +/* width */ 2, + 300, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + +/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/silencer/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_silencer", +/* pickup */ "Silencer", +/* width */ 2, + 60, + NULL, + IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/breather/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_rebreather", +/* pickup */ "Rebreather", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/enviro/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_envirosuit", +/* pickup */ "Environment Suit", +/* width */ 2, + 60, + NULL, + IT_STAY_COOP|IT_POWERUP, + 0, + NULL, + 0, +/* precache */ "items/airout.wav" + }, + +/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) +Special item that gives +2 to maximum health +*/ + { + "item_ancient_head", + Pickup_AncientHead, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/c_head/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_fixme", +/* pickup */ "Ancient Head", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) +gives +1 to maximum health +*/ + { + "item_adrenaline", + Pickup_Adrenaline, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/adrenal/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_adrenaline", +/* pickup */ "Adrenaline", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_bandolier", + Pickup_Bandolier, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/band/tris.md2", EF_ROTATE, + NULL, +/* icon */ "p_bandolier", +/* pickup */ "Bandolier", +/* width */ 2, + 60, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ + { + "item_pack", + Pickup_Pack, + NULL, + NULL, + NULL, + "items/pkup.wav", + "models/items/pack/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_pack", +/* pickup */ "Ammo Pack", +/* width */ 2, + 180, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "" + }, + + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) +key for computer centers +*/ + { + "key_data_cd", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", EF_ROTATE, + NULL, + "k_datacd", + "Data CD", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH +warehouse circuits +*/ + { + "key_power_cube", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/power/tris.md2", EF_ROTATE, + NULL, + "k_powercube", + "Power Cube", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the entrance of jail3 +*/ + { + "key_pyramid", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", EF_ROTATE, + NULL, + "k_pyramid", + "Pyramid Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) +key for the city computer +*/ + { + "key_data_spinner", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", EF_ROTATE, + NULL, + "k_dataspin", + "Data Spinner", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) +security pass for the security level +*/ + { + "key_pass", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", EF_ROTATE, + NULL, + "k_security", + "Security Pass", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - blue +*/ + { + "key_blue_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/key/tris.md2", EF_ROTATE, + NULL, + "k_bluekey", + "Blue Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) +normal door key - red +*/ + { + "key_red_key", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", EF_ROTATE, + NULL, + "k_redkey", + "Red Key", + 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_commander_head", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", EF_GIB, + NULL, +/* icon */ "k_comhead", +/* pickup */ "Commander's Head", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + +/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) +tank commander's head +*/ + { + "key_airstrike_target", + Pickup_Key, + NULL, + Drop_General, + NULL, + "items/pkup.wav", + "models/items/keys/target/tris.md2", EF_ROTATE, + NULL, +/* icon */ "i_airstrike", +/* pickup */ "Airstrike Marker", +/* width */ 2, + 0, + NULL, + IT_STAY_COOP|IT_KEY, + 0, + NULL, + 0, +/* precache */ "" + }, + + { + NULL, + Pickup_Health, + NULL, + NULL, + NULL, + "items/pkup.wav", + NULL, 0, + NULL, +/* icon */ "i_health", +/* pickup */ "Health", +/* width */ 3, + 0, + NULL, + 0, + 0, + NULL, + 0, +/* precache */ "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav" + }, + + // end of list marker + {NULL} +}; + + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/medium/tris.md2"; + self->count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); +} + +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_small (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/stimpack/tris.md2"; + self->count = 2; + SpawnItem (self, FindItem ("Health")); + self->style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); +} + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_large (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/healing/large/tris.md2"; + self->count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); +} + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) +*/ +void SP_item_health_mega (edict_t *self) +{ + if ( deathmatch->value && ((int)dmflags->value & DF_NO_HEALTH) ) + { + G_FreeEdict (self); + return; + } + + self->model = "models/items/mega_h/tris.md2"; + self->count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self->style = HEALTH_IGNORE_MAX|HEALTH_TIMED; +} + + +void InitItems (void) +{ + game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; +} + + + +/* +=============== +SetItemNames + +Called by worldspawn +=============== +*/ +void SetItemNames (void) +{ + int i; + gitem_t *it; + + for (i=0 ; ipickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); +} diff --git a/game/g_local.h b/game/g_local.h new file mode 100644 index 000000000..78605e59b --- /dev/null +++ b/game/g_local.h @@ -0,0 +1,1113 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_local.h -- local definitions for game module + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and edict_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "game.h" + +// the "gameversion" client command will print this plus compile date +#define GAMEVERSION "baseq2" + +// protocol bytes that can be directly added to messages +#define svc_muzzleflash 1 +#define svc_muzzleflash2 2 +#define svc_temp_entity 3 +#define svc_layout 4 +#define svc_inventory 5 +#define svc_stufftext 11 + +//================================================================== + +// view pitching times +#define DAMAGE_TIME 0.5 +#define FALL_TIME 0.3 + + +// edict->spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_NOT_COOP 0x00001000 + +// edict->flags +#define FL_FLY 0x00000001 +#define FL_SWIM 0x00000002 // implied immunity to drowining +#define FL_IMMUNE_LASER 0x00000004 +#define FL_INWATER 0x00000008 +#define FL_GODMODE 0x00000010 +#define FL_NOTARGET 0x00000020 +#define FL_IMMUNE_SLIME 0x00000040 +#define FL_IMMUNE_LAVA 0x00000080 +#define FL_PARTIALGROUND 0x00000100 // not all corners are valid +#define FL_WATERJUMP 0x00000200 // player jumping out of water +#define FL_TEAMSLAVE 0x00000400 // not the first on the team +#define FL_NO_KNOCKBACK 0x00000800 +#define FL_POWER_ARMOR 0x00001000 // power armor (if any) is active +#define FL_RESPAWN 0x80000000 // used for item respawning + + +#define FRAMETIME 0.1 + +// memory tags to allow dynamic memory to be cleaned up +#define TAG_GAME 765 // clear when unloading the dll +#define TAG_LEVEL 766 // clear when loading a new level + + +#define MELEE_DISTANCE 80 + +#define BODY_QUEUE_SIZE 8 + +typedef enum +{ + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this +} damage_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_ACTIVATING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +typedef enum +{ + AMMO_BULLETS, + AMMO_SHELLS, + AMMO_ROCKETS, + AMMO_GRENADES, + AMMO_CELLS, + AMMO_SLUGS +} ammo_t; + + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +//range +#define RANGE_MELEE 0 +#define RANGE_NEAR 1 +#define RANGE_MID 2 +#define RANGE_FAR 3 + +//gib types +#define GIB_ORGANIC 0 +#define GIB_METALLIC 1 + +//monster ai flags +#define AI_STAND_GROUND 0x00000001 +#define AI_TEMP_STAND_GROUND 0x00000002 +#define AI_SOUND_TARGET 0x00000004 +#define AI_LOST_SIGHT 0x00000008 +#define AI_PURSUIT_LAST_SEEN 0x00000010 +#define AI_PURSUE_NEXT 0x00000020 +#define AI_PURSUE_TEMP 0x00000040 +#define AI_HOLD_FRAME 0x00000080 +#define AI_GOOD_GUY 0x00000100 +#define AI_BRUTAL 0x00000200 +#define AI_NOSTEP 0x00000400 +#define AI_DUCKED 0x00000800 +#define AI_COMBAT_POINT 0x00001000 +#define AI_MEDIC 0x00002000 +#define AI_RESURRECTING 0x00004000 + +//monster attack state +#define AS_STRAIGHT 1 +#define AS_SLIDING 2 +#define AS_MELEE 3 +#define AS_MISSILE 4 + +// armor types +#define ARMOR_NONE 0 +#define ARMOR_JACKET 1 +#define ARMOR_COMBAT 2 +#define ARMOR_BODY 3 +#define ARMOR_SHARD 4 + +// power armor types +#define POWER_ARMOR_NONE 0 +#define POWER_ARMOR_SCREEN 1 +#define POWER_ARMOR_SHIELD 2 + +// handedness values +#define RIGHT_HANDED 0 +#define LEFT_HANDED 1 +#define CENTER_HANDED 2 + + +// game.serverflags values +#define SFL_CROSS_TRIGGER_1 0x00000001 +#define SFL_CROSS_TRIGGER_2 0x00000002 +#define SFL_CROSS_TRIGGER_3 0x00000004 +#define SFL_CROSS_TRIGGER_4 0x00000008 +#define SFL_CROSS_TRIGGER_5 0x00000010 +#define SFL_CROSS_TRIGGER_6 0x00000020 +#define SFL_CROSS_TRIGGER_7 0x00000040 +#define SFL_CROSS_TRIGGER_8 0x00000080 +#define SFL_CROSS_TRIGGER_MASK 0x000000ff + + +// noise types for PlayerNoise +#define PNOISE_SELF 0 +#define PNOISE_WEAPON 1 +#define PNOISE_IMPACT 2 + + +// edict->movetype values +typedef enum +{ +MOVETYPE_NONE, // never moves +MOVETYPE_NOCLIP, // origin and angles change with no interaction +MOVETYPE_PUSH, // no clip to world, push on box contact +MOVETYPE_STOP, // no clip to world, stops on box contact + +MOVETYPE_WALK, // gravity +MOVETYPE_STEP, // gravity, special edge handling +MOVETYPE_FLY, +MOVETYPE_TOSS, // gravity +MOVETYPE_FLYMISSILE, // extra size to monsters +MOVETYPE_BOUNCE +} movetype_t; + + + +typedef struct +{ + int base_count; + int max_count; + float normal_protection; + float energy_protection; + int armor; +} gitem_armor_t; + + +// gitem_t->flags +#define IT_WEAPON 1 // use makes active weapon +#define IT_AMMO 2 +#define IT_ARMOR 4 +#define IT_STAY_COOP 8 +#define IT_KEY 16 +#define IT_POWERUP 32 + +// gitem_t->weapmodel for weapons indicates model index +#define WEAP_BLASTER 1 +#define WEAP_SHOTGUN 2 +#define WEAP_SUPERSHOTGUN 3 +#define WEAP_MACHINEGUN 4 +#define WEAP_CHAINGUN 5 +#define WEAP_GRENADES 6 +#define WEAP_GRENADELAUNCHER 7 +#define WEAP_ROCKETLAUNCHER 8 +#define WEAP_HYPERBLASTER 9 +#define WEAP_RAILGUN 10 +#define WEAP_BFG 11 + +typedef struct gitem_s +{ + char *classname; // spawning name + qboolean (*pickup)(struct edict_s *ent, struct edict_s *other); + void (*use)(struct edict_s *ent, struct gitem_s *item); + void (*drop)(struct edict_s *ent, struct gitem_s *item); + void (*weaponthink)(struct edict_s *ent); + char *pickup_sound; + char *world_model; + int world_model_flags; + char *view_model; + + // client side info + char *icon; + char *pickup_name; // for printing on pickup + int count_width; // number of digits to display by icon + + int quantity; // for ammo how much, for weapons how much is used per shot + char *ammo; // for weapons + int flags; // IT_* flags + + int weapmodel; // weapon model index (for weapons) + + void *info; + int tag; + + char *precaches; // string of all models, sounds, and images this item will use +} gitem_t; + + + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// +typedef struct +{ + char helpmessage1[512]; + char helpmessage2[512]; + int helpchanged; // flash F1 icon if non 0, play sound + // and increment only if 1, 2, or 3 + + gclient_t *clients; // [maxclients] + + // can't store spawnpoint in level, because + // it would get overwritten by the savegame restore + char spawnpoint[512]; // needed for coop respawns + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + // cross level triggers + int serverflags; + + // items + int num_items; + + qboolean autosaved; +} game_locals_t; + + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// +typedef struct +{ + int framenum; + float time; + + char level_name[MAX_QPATH]; // the descriptive name (Outer Base, etc) + char mapname[MAX_QPATH]; // the server name (base1, etc) + char nextmap[MAX_QPATH]; // go here when fraglimit is hit + + // intermission state + float intermissiontime; // time the intermission was started + char *changemap; + int exitintermission; + vec3_t intermission_origin; + vec3_t intermission_angle; + + edict_t *sight_client; // changed once each frame for coop games + + edict_t *sight_entity; + int sight_entity_framenum; + edict_t *sound_entity; + int sound_entity_framenum; + edict_t *sound2_entity; + int sound2_entity_framenum; + + int pic_health; + + int total_secrets; + int found_secrets; + + int total_goals; + int found_goals; + + int total_monsters; + int killed_monsters; + + edict_t *current_entity; // entity running from G_RunFrame + int body_que; // dead bodies + + int power_cubes; // ugly necessity for coop +} level_locals_t; + + +// spawn_temp_t is only used to hold entity field values that +// can be set from the editor, but aren't actualy present +// in edict_t during gameplay +typedef struct +{ + // world vars + char *sky; + float skyrotate; + vec3_t skyaxis; + char *nextmap; + + int lip; + int distance; + int height; + char *noise; + float pausetime; + char *item; + char *gravity; + + float minyaw; + float maxyaw; + float minpitch; + float maxpitch; +} spawn_temp_t; + + +typedef struct +{ + // fixed data + vec3_t start_origin; + vec3_t start_angles; + vec3_t end_origin; + vec3_t end_angles; + + int sound_start; + int sound_middle; + int sound_end; + + float accel; + float speed; + float decel; + float distance; + + float wait; + + // state data + int state; + vec3_t dir; + float current_speed; + float move_speed; + float next_speed; + float remaining_distance; + float decel_distance; + void (*endfunc)(edict_t *); +} moveinfo_t; + + +typedef struct +{ + void (*aifunc)(edict_t *self, float dist); + float dist; + void (*thinkfunc)(edict_t *self); +} mframe_t; + +typedef struct +{ + int firstframe; + int lastframe; + mframe_t *frame; + void (*endfunc)(edict_t *self); +} mmove_t; + +typedef struct +{ + mmove_t *currentmove; + int aiflags; + int nextframe; + float scale; + + void (*stand)(edict_t *self); + void (*idle)(edict_t *self); + void (*search)(edict_t *self); + void (*walk)(edict_t *self); + void (*run)(edict_t *self); + void (*dodge)(edict_t *self, edict_t *other, float eta); + void (*attack)(edict_t *self); + void (*melee)(edict_t *self); + void (*sight)(edict_t *self, edict_t *other); + qboolean (*checkattack)(edict_t *self); + + float pausetime; + float attack_finished; + + vec3_t saved_goal; + float search_time; + float trail_time; + vec3_t last_sighting; + int attack_state; + int lefty; + float idle_time; + int linkcount; + + int power_armor_type; + int power_armor_power; +} monsterinfo_t; + + + +extern game_locals_t game; +extern level_locals_t level; +extern game_import_t gi; +extern game_export_t globals; +extern spawn_temp_t st; + +extern int sm_meat_index; +extern int snd_fry; + +extern int jacket_armor_index; +extern int combat_armor_index; +extern int body_armor_index; + + +// means of death +#define MOD_UNKNOWN 0 +#define MOD_BLASTER 1 +#define MOD_SHOTGUN 2 +#define MOD_SSHOTGUN 3 +#define MOD_MACHINEGUN 4 +#define MOD_CHAINGUN 5 +#define MOD_GRENADE 6 +#define MOD_G_SPLASH 7 +#define MOD_ROCKET 8 +#define MOD_R_SPLASH 9 +#define MOD_HYPERBLASTER 10 +#define MOD_RAILGUN 11 +#define MOD_BFG_LASER 12 +#define MOD_BFG_BLAST 13 +#define MOD_BFG_EFFECT 14 +#define MOD_HANDGRENADE 15 +#define MOD_HG_SPLASH 16 +#define MOD_WATER 17 +#define MOD_SLIME 18 +#define MOD_LAVA 19 +#define MOD_CRUSH 20 +#define MOD_TELEFRAG 21 +#define MOD_FALLING 22 +#define MOD_SUICIDE 23 +#define MOD_HELD_GRENADE 24 +#define MOD_EXPLOSIVE 25 +#define MOD_BARREL 26 +#define MOD_BOMB 27 +#define MOD_EXIT 28 +#define MOD_SPLASH 29 +#define MOD_TARGET_LASER 30 +#define MOD_TRIGGER_HURT 31 +#define MOD_HIT 32 +#define MOD_TARGET_BLASTER 33 +#define MOD_FRIENDLY_FIRE 0x8000000 + +extern int meansOfDeath; + + +extern edict_t *g_edicts; + +#define FOFS(x) (int)&(((edict_t *)0)->x) +#define STOFS(x) (int)&(((spawn_temp_t *)0)->x) +#define LLOFS(x) (int)&(((level_locals_t *)0)->x) +#define CLOFS(x) (int)&(((gclient_t *)0)->x) + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0 * (random() - 0.5)) + +extern cvar_t *maxentities; +extern cvar_t *deathmatch; +extern cvar_t *coop; +extern cvar_t *dmflags; +extern cvar_t *skill; +extern cvar_t *fraglimit; +extern cvar_t *timelimit; +extern cvar_t *password; +extern cvar_t *spectator_password; +extern cvar_t *g_select_empty; +extern cvar_t *dedicated; + +extern cvar_t *filterban; + +extern cvar_t *sv_gravity; +extern cvar_t *sv_maxvelocity; + +extern cvar_t *gun_x, *gun_y, *gun_z; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_rollangle; + +extern cvar_t *run_pitch; +extern cvar_t *run_roll; +extern cvar_t *bob_up; +extern cvar_t *bob_pitch; +extern cvar_t *bob_roll; + +extern cvar_t *sv_cheats; +extern cvar_t *maxclients; +extern cvar_t *maxspectators; + +extern cvar_t *flood_msgs; +extern cvar_t *flood_persecond; +extern cvar_t *flood_waitdelay; + +extern cvar_t *sv_maplist; + +#define world (&g_edicts[0]) + +// item spawnflags +#define ITEM_TRIGGER_SPAWN 0x00000001 +#define ITEM_NO_TOUCH 0x00000002 +// 6 bits reserved for editor flags +// 8 bits used as power cube id bits for coop games +#define DROPPED_ITEM 0x00010000 +#define DROPPED_PLAYER_ITEM 0x00020000 +#define ITEM_TARGETS_USED 0x00040000 + +// +// fields are needed for spawning from the entity string +// and saving / loading games +// +#define FFL_SPAWNTEMP 1 +#define FFL_NOSPAWN 2 + +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_EDICT, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_FUNCTION, + F_MMOVE, + F_IGNORE +} fieldtype_t; + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} field_t; + + +extern field_t fields[]; +extern gitem_t itemlist[]; + + +// +// g_cmds.c +// +void Cmd_Help_f (edict_t *ent); +void Cmd_Score_f (edict_t *ent); + +// +// g_items.c +// +void PrecacheItem (gitem_t *it); +void InitItems (void); +void SetItemNames (void); +gitem_t *FindItem (char *pickup_name); +gitem_t *FindItemByClassname (char *classname); +#define ITEM_INDEX(x) ((x)-itemlist) +edict_t *Drop_Item (edict_t *ent, gitem_t *item); +void SetRespawn (edict_t *ent, float delay); +void ChangeWeapon (edict_t *ent); +void SpawnItem (edict_t *ent, gitem_t *item); +void Think_Weapon (edict_t *ent); +int ArmorIndex (edict_t *ent); +int PowerArmorType (edict_t *ent); +gitem_t *GetItemByIndex (int index); +qboolean Add_Ammo (edict_t *ent, gitem_t *item, int count); +void Touch_Item (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf); + +// +// g_utils.c +// +qboolean KillBox (edict_t *ent); +void G_ProjectSource (vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t result); +edict_t *G_Find (edict_t *from, int fieldofs, char *match); +edict_t *findradius (edict_t *from, vec3_t org, float rad); +edict_t *G_PickTarget (char *targetname); +void G_UseTargets (edict_t *ent, edict_t *activator); +void G_SetMovedir (vec3_t angles, vec3_t movedir); + +void G_InitEdict (edict_t *e); +edict_t *G_Spawn (void); +void G_FreeEdict (edict_t *e); + +void G_TouchTriggers (edict_t *ent); +void G_TouchSolids (edict_t *ent); + +char *G_CopyString (char *in); + +float *tv (float x, float y, float z); +char *vtos (vec3_t v); + +float vectoyaw (vec3_t vec); +void vectoangles (vec3_t vec, vec3_t angles); + +// +// g_combat.c +// +qboolean OnSameTeam (edict_t *ent1, edict_t *ent2); +qboolean CanDamage (edict_t *targ, edict_t *inflictor); +void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod); +void T_RadiusDamage (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, int mod); + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect + +#define DEFAULT_BULLET_HSPREAD 300 +#define DEFAULT_BULLET_VSPREAD 500 +#define DEFAULT_SHOTGUN_HSPREAD 1000 +#define DEFAULT_SHOTGUN_VSPREAD 500 +#define DEFAULT_DEATHMATCH_SHOTGUN_COUNT 12 +#define DEFAULT_SHOTGUN_COUNT 12 +#define DEFAULT_SSHOTGUN_COUNT 20 + +// +// g_monster.c +// +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype); +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype); +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect); +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype); +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype); +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype); +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype); +void M_droptofloor (edict_t *ent); +void monster_think (edict_t *self); +void walkmonster_start (edict_t *self); +void swimmonster_start (edict_t *self); +void flymonster_start (edict_t *self); +void AttackFinished (edict_t *self, float time); +void monster_death_use (edict_t *self); +void M_CatagorizePosition (edict_t *ent); +qboolean M_CheckAttack (edict_t *self); +void M_FlyCheck (edict_t *self); +void M_CheckGround (edict_t *ent); + +// +// g_misc.c +// +void ThrowHead (edict_t *self, char *gibname, int damage, int type); +void ThrowClientHead (edict_t *self, int damage); +void ThrowGib (edict_t *self, char *gibname, int damage, int type); +void BecomeExplosion1(edict_t *self); + +// +// g_ai.c +// +void AI_SetSightClient (void); + +void ai_stand (edict_t *self, float dist); +void ai_move (edict_t *self, float dist); +void ai_walk (edict_t *self, float dist); +void ai_turn (edict_t *self, float dist); +void ai_run (edict_t *self, float dist); +void ai_charge (edict_t *self, float dist); +int range (edict_t *self, edict_t *other); + +void FoundTarget (edict_t *self); +qboolean infront (edict_t *self, edict_t *other); +qboolean visible (edict_t *self, edict_t *other); +qboolean FacingIdeal(edict_t *self); + +// +// g_weapon.c +// +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin); +qboolean fire_hit (edict_t *self, vec3_t aim, int damage, int kick); +void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod); +void fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int mod); +void fire_blaster (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int effect, qboolean hyper); +void fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius); +void fire_grenade2 (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, float timer, float damage_radius, qboolean held); +void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage); +void fire_rail (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick); +void fire_bfg (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius); + +// +// g_ptrail.c +// +void PlayerTrail_Init (void); +void PlayerTrail_Add (vec3_t spot); +void PlayerTrail_New (vec3_t spot); +edict_t *PlayerTrail_PickFirst (edict_t *self); +edict_t *PlayerTrail_PickNext (edict_t *self); +edict_t *PlayerTrail_LastSpot (void); + +// +// g_client.c +// +void respawn (edict_t *ent); +void BeginIntermission (edict_t *targ); +void PutClientInServer (edict_t *ent); +void InitClientPersistant (gclient_t *client); +void InitClientResp (gclient_t *client); +void InitBodyQue (void); +void ClientBeginServerFrame (edict_t *ent); + +// +// g_player.c +// +void player_pain (edict_t *self, edict_t *other, float kick, int damage); +void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + +// +// g_svcmds.c +// +void ServerCommand (void); +qboolean SV_FilterPacket (char *from); + +// +// p_view.c +// +void ClientEndServerFrame (edict_t *ent); + +// +// p_hud.c +// +void MoveClientToIntermission (edict_t *client); +void G_SetStats (edict_t *ent); +void G_SetSpectatorStats (edict_t *ent); +void G_CheckChaseStats (edict_t *ent); +void ValidateSelectedItem (edict_t *ent); +void DeathmatchScoreboardMessage (edict_t *client, edict_t *killer); + +// +// g_pweapon.c +// +void PlayerNoise(edict_t *who, vec3_t where, int type); + +// +// m_move.c +// +qboolean M_CheckBottom (edict_t *ent); +qboolean M_walkmove (edict_t *ent, float yaw, float dist); +void M_MoveToGoal (edict_t *ent, float dist); +void M_ChangeYaw (edict_t *ent); + +// +// g_phys.c +// +void G_RunEntity (edict_t *ent); + +// +// g_main.c +// +void SaveClientData (void); +void FetchClientEntData (edict_t *ent); + +// +// g_chase.c +// +void UpdateChaseCam(edict_t *ent); +void ChaseNext(edict_t *ent); +void ChasePrev(edict_t *ent); +void GetChaseTarget(edict_t *ent); + +//============================================================================ + +// client_t->anim_priority +#define ANIM_BASIC 0 // stand / run +#define ANIM_WAVE 1 +#define ANIM_JUMP 2 +#define ANIM_PAIN 3 +#define ANIM_ATTACK 4 +#define ANIM_DEATH 5 +#define ANIM_REVERSE 6 + + +// client data that stays across multiple level loads +typedef struct +{ + char userinfo[MAX_INFO_STRING]; + char netname[16]; + int hand; + + qboolean connected; // a loadgame will leave valid entities that + // just don't have a connection yet + + // values saved and restored from edicts when changing levels + int health; + int max_health; + int savedFlags; + + int selected_item; + int inventory[MAX_ITEMS]; + + // ammo capacities + int max_bullets; + int max_shells; + int max_rockets; + int max_grenades; + int max_cells; + int max_slugs; + + gitem_t *weapon; + gitem_t *lastweapon; + + int power_cubes; // used for tracking the cubes in coop games + int score; // for calculating total unit score in coop games + + int game_helpchanged; + int helpchanged; + + qboolean spectator; // client is a spectator +} client_persistant_t; + +// client data that stays across deathmatch respawns +typedef struct +{ + client_persistant_t coop_respawn; // what to set client->pers to on a respawn + int enterframe; // level.framenum the client entered the game + int score; // frags, etc + vec3_t cmd_angles; // angles sent over in the last command + + qboolean spectator; // client is a spectator +} client_respawn_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +struct gclient_s +{ + // known to server + player_state_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + client_respawn_t resp; + pmove_state_t old_pmove; // for detecting out-of-pmove changes + + qboolean showscores; // set layout stat + qboolean showinventory; // set layout stat + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + // sum up damage over an entire frame, so + // shotgun blasts give a single big kick + int damage_armor; // damage absorbed by armor + int damage_parmor; // damage absorbed by power armor + int damage_blood; // damage taken out of health + int damage_knockback; // impact damage + vec3_t damage_from; // origin for vector calculation + + float killer_yaw; // when dead, look at killer + + weaponstate_t weaponstate; + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks + float fall_time, fall_value; // for view drop on fall + float damage_alpha; + float bonus_alpha; + vec3_t damage_blend; + vec3_t v_angle; // aiming direction + float bobtime; // so off-ground doesn't change it + vec3_t oldviewangles; + vec3_t oldvelocity; + + float next_drown_time; + int old_waterlevel; + int breather_sound; + + int machinegun_shots; // for weapon raising + + // animation vars + int anim_end; + int anim_priority; + qboolean anim_duck; + qboolean anim_run; + + // powerup timers + float quad_framenum; + float invincible_framenum; + float breather_framenum; + float enviro_framenum; + + qboolean grenade_blew_up; + float grenade_time; + int silencer_shots; + int weapon_sound; + + float pickup_msg_time; + + float flood_locktill; // locked from talking + float flood_when[10]; // when messages were said + int flood_whenhead; // head pointer for when said + + float respawn_time; // can respawn when time > this + + edict_t *chase_target; // player we are chasing + qboolean update_chase; // need to update chase info? +}; + + +struct edict_s +{ + entity_state_t s; + struct gclient_s *client; // NULL if not a player + // the server expects the first part + // of gclient_s to be a player_state_t + // but the rest of it is opaque + + qboolean inuse; + int linkcount; + + // FIXME: move these fields to a server private sv_entity_t + link_t area; // linked to a division node or leaf + + int num_clusters; // if -1, use headnode instead + int clusternums[MAX_ENT_CLUSTERS]; + int headnode; // unused if num_clusters != -1 + int areanum, areanum2; + + //================================ + + int svflags; + vec3_t mins, maxs; + vec3_t absmin, absmax, size; + solid_t solid; + int clipmask; + edict_t *owner; + + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + int movetype; + int flags; + + char *model; + float freetime; // sv.time when the object was freed + + // + // only used locally in game, not by server + // + char *message; + char *classname; + int spawnflags; + + float timestamp; + + float angle; // set in qe3, -1 = up, -2 = down + char *target; + char *targetname; + char *killtarget; + char *team; + char *pathtarget; + char *deathtarget; + char *combattarget; + edict_t *target_ent; + + float speed, accel, decel; + vec3_t movedir; + vec3_t pos1, pos2; + + vec3_t velocity; + vec3_t avelocity; + int mass; + float air_finished; + float gravity; // per entity gravity multiplier (1.0 is normal) + // use for lowgrav artifact, flares + + edict_t *goalentity; + edict_t *movetarget; + float yaw_speed; + float ideal_yaw; + + float nextthink; + void (*prethink) (edict_t *ent); + void (*think)(edict_t *self); + void (*blocked)(edict_t *self, edict_t *other); //move to moveinfo? + void (*touch)(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf); + void (*use)(edict_t *self, edict_t *other, edict_t *activator); + void (*pain)(edict_t *self, edict_t *other, float kick, int damage); + void (*die)(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); + + float touch_debounce_time; // are all these legit? do we need more/less of them? + float pain_debounce_time; + float damage_debounce_time; + float fly_sound_debounce_time; //move to clientinfo + float last_move_time; + + int health; + int max_health; + int gib_health; + int deadflag; + qboolean show_hostile; + + float powerarmor_time; + + char *map; // target_changelevel + + int viewheight; // height above origin where eyesight is determined + int takedamage; + int dmg; + int radius_dmg; + float dmg_radius; + int sounds; //make this a spawntemp var? + int count; + + edict_t *chain; + edict_t *enemy; + edict_t *oldenemy; + edict_t *activator; + edict_t *groundentity; + int groundentity_linkcount; + edict_t *teamchain; + edict_t *teammaster; + + edict_t *mynoise; // can go in client only + edict_t *mynoise2; + + int noise_index; + int noise_index2; + float volume; + float attenuation; + + // timing variables + float wait; + float delay; // before firing targets + float random; + + float teleport_time; + + int watertype; + int waterlevel; + + vec3_t move_origin; + vec3_t move_angles; + + // move this to clientinfo? + int light_level; + + int style; // also used as areaportal number + + gitem_t *item; // for bonus items + + // common data blocks + moveinfo_t moveinfo; + monsterinfo_t monsterinfo; +}; + diff --git a/game/g_main.c b/game/g_main.c new file mode 100644 index 000000000..d6c3c8875 --- /dev/null +++ b/game/g_main.c @@ -0,0 +1,411 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "g_local.h" + +game_locals_t game; +level_locals_t level; +game_import_t gi; +game_export_t globals; +spawn_temp_t st; + +int sm_meat_index; +int snd_fry; +int meansOfDeath; + +edict_t *g_edicts; + +cvar_t *deathmatch; +cvar_t *coop; +cvar_t *dmflags; +cvar_t *skill; +cvar_t *fraglimit; +cvar_t *timelimit; +cvar_t *password; +cvar_t *spectator_password; +cvar_t *maxclients; +cvar_t *maxspectators; +cvar_t *maxentities; +cvar_t *g_select_empty; +cvar_t *dedicated; + +cvar_t *filterban; + +cvar_t *sv_maxvelocity; +cvar_t *sv_gravity; + +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *gun_x; +cvar_t *gun_y; +cvar_t *gun_z; + +cvar_t *run_pitch; +cvar_t *run_roll; +cvar_t *bob_up; +cvar_t *bob_pitch; +cvar_t *bob_roll; + +cvar_t *sv_cheats; + +cvar_t *flood_msgs; +cvar_t *flood_persecond; +cvar_t *flood_waitdelay; + +cvar_t *sv_maplist; + +void SpawnEntities (char *mapname, char *entities, char *spawnpoint); +void ClientThink (edict_t *ent, usercmd_t *cmd); +qboolean ClientConnect (edict_t *ent, char *userinfo); +void ClientUserinfoChanged (edict_t *ent, char *userinfo); +void ClientDisconnect (edict_t *ent); +void ClientBegin (edict_t *ent); +void ClientCommand (edict_t *ent); +void RunEntity (edict_t *ent); +void WriteGame (char *filename, qboolean autosave); +void ReadGame (char *filename); +void WriteLevel (char *filename); +void ReadLevel (char *filename); +void InitGame (void); +void G_RunFrame (void); + + +//=================================================================== + + +void ShutdownGame (void) +{ + gi.dprintf ("==== ShutdownGame ====\n"); + + gi.FreeTags (TAG_LEVEL); + gi.FreeTags (TAG_GAME); +} + + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +game_export_t *GetGameAPI (game_import_t *import) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = InitGame; + globals.Shutdown = ShutdownGame; + globals.SpawnEntities = SpawnEntities; + + globals.WriteGame = WriteGame; + globals.ReadGame = ReadGame; + globals.WriteLevel = WriteLevel; + globals.ReadLevel = ReadLevel; + + globals.ClientThink = ClientThink; + globals.ClientConnect = ClientConnect; + globals.ClientUserinfoChanged = ClientUserinfoChanged; + globals.ClientDisconnect = ClientDisconnect; + globals.ClientBegin = ClientBegin; + globals.ClientCommand = ClientCommand; + + globals.RunFrame = G_RunFrame; + + globals.ServerCommand = ServerCommand; + + globals.edict_size = sizeof(edict_t); + + return &globals; +} + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Sys_Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.error (ERR_FATAL, "%s", text); +} + +void Com_Printf (char *msg, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.dprintf ("%s", text); +} + +#endif + +//====================================================================== + + +/* +================= +ClientEndServerFrames +================= +*/ +void ClientEndServerFrames (void) +{ + int i; + edict_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse || !ent->client) + continue; + ClientEndServerFrame (ent); + } + +} + +/* +================= +CreateTargetChangeLevel + +Returns the created target changelevel +================= +*/ +edict_t *CreateTargetChangeLevel(char *map) +{ + edict_t *ent; + + ent = G_Spawn (); + ent->classname = "target_changelevel"; + Com_sprintf(level.nextmap, sizeof(level.nextmap), "%s", map); + ent->map = level.nextmap; + return ent; +} + +/* +================= +EndDMLevel + +The timelimit or fraglimit has been exceeded +================= +*/ +void EndDMLevel (void) +{ + edict_t *ent; + char *s, *t, *f; + static const char *seps = " ,\n\r"; + + // stay on same level flag + if ((int)dmflags->value & DF_SAME_LEVEL) + { + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + + // see if it's in the map list + if (*sv_maplist->string) { + s = strdup(sv_maplist->string); + f = NULL; + t = strtok(s, seps); + while (t != NULL) { + if (Q_stricmp(t, level.mapname) == 0) { + // it's in the list, go to the next one + t = strtok(NULL, seps); + if (t == NULL) { // end of list, go to first one + if (f == NULL) // there isn't a first one, same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + else + BeginIntermission (CreateTargetChangeLevel (f) ); + } else + BeginIntermission (CreateTargetChangeLevel (t) ); + free(s); + return; + } + if (!f) + f = t; + t = strtok(NULL, seps); + } + free(s); + } + + if (level.nextmap[0]) // go to a specific map + BeginIntermission (CreateTargetChangeLevel (level.nextmap) ); + else { // search for a changelevel + ent = G_Find (NULL, FOFS(classname), "target_changelevel"); + if (!ent) + { // the map designer didn't include a changelevel, + // so create a fake ent that goes back to the same level + BeginIntermission (CreateTargetChangeLevel (level.mapname) ); + return; + } + BeginIntermission (ent); + } +} + +/* +================= +CheckDMRules +================= +*/ +void CheckDMRules (void) +{ + int i; + gclient_t *cl; + + if (level.intermissiontime) + return; + + if (!deathmatch->value) + return; + + if (timelimit->value) + { + if (level.time >= timelimit->value*60) + { + gi.bprintf (PRINT_HIGH, "Timelimit hit.\n"); + EndDMLevel (); + return; + } + } + + if (fraglimit->value) + { + for (i=0 ; ivalue ; i++) + { + cl = game.clients + i; + if (!g_edicts[i+1].inuse) + continue; + + if (cl->resp.score >= fraglimit->value) + { + gi.bprintf (PRINT_HIGH, "Fraglimit hit.\n"); + EndDMLevel (); + return; + } + } + } +} + + +/* +============= +ExitLevel +============= +*/ +void ExitLevel (void) +{ + int i; + edict_t *ent; + char command [256]; + + Com_sprintf (command, sizeof(command), "gamemap \"%s\"\n", level.changemap); + gi.AddCommandString (command); + level.changemap = NULL; + level.exitintermission = 0; + level.intermissiontime = 0; + ClientEndServerFrames (); + + // clear some things before going to next level + for (i=0 ; ivalue ; i++) + { + ent = g_edicts + 1 + i; + if (!ent->inuse) + continue; + if (ent->health > ent->client->pers.max_health) + ent->health = ent->client->pers.max_health; + } + +} + +/* +================ +G_RunFrame + +Advances the world by 0.1 seconds +================ +*/ +void G_RunFrame (void) +{ + int i; + edict_t *ent; + + level.framenum++; + level.time = level.framenum*FRAMETIME; + + // choose a client for monsters to target this frame + AI_SetSightClient (); + + // exit intermissions + + if (level.exitintermission) + { + ExitLevel (); + return; + } + + // + // treat each object in turn + // even the world gets a chance to think + // + ent = &g_edicts[0]; + for (i=0 ; iinuse) + continue; + + level.current_entity = ent; + + VectorCopy (ent->s.origin, ent->s.old_origin); + + // if the ground entity moved, make sure we are still on it + if ((ent->groundentity) && (ent->groundentity->linkcount != ent->groundentity_linkcount)) + { + ent->groundentity = NULL; + if ( !(ent->flags & (FL_SWIM|FL_FLY)) && (ent->svflags & SVF_MONSTER) ) + { + M_CheckGround (ent); + } + } + + if (i > 0 && i <= maxclients->value) + { + ClientBeginServerFrame (ent); + continue; + } + + G_RunEntity (ent); + } + + // see if it is time to end a deathmatch + CheckDMRules (); + + // build the playerstate_t structures for all players + ClientEndServerFrames (); +} + diff --git a/game/g_misc.c b/game/g_misc.c new file mode 100644 index 000000000..46abe0823 --- /dev/null +++ b/game/g_misc.c @@ -0,0 +1,1876 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_misc.c + +#include "g_local.h" + + +/*QUAKED func_group (0 0 0) ? +Used to group brushes together just for editor convenience. +*/ + +//===================================================== + +void Use_Areaportal (edict_t *ent, edict_t *other, edict_t *activator) +{ + ent->count ^= 1; // toggle state +// gi.dprintf ("portalstate: %i = %i\n", ent->style, ent->count); + gi.SetAreaPortalState (ent->style, ent->count); +} + +/*QUAKED func_areaportal (0 0 0) ? + +This is a non-visible object that divides the world into +areas that are seperated when this portal is not activated. +Usually enclosed in the middle of a door. +*/ +void SP_func_areaportal (edict_t *ent) +{ + ent->use = Use_Areaportal; + ent->count = 0; // always start closed; +} + +//===================================================== + + +/* +================= +Misc functions +================= +*/ +void VelocityForDamage (int damage, vec3_t v) +{ + v[0] = 100.0 * crandom(); + v[1] = 100.0 * crandom(); + v[2] = 200.0 + 100.0 * random(); + + if (damage < 50) + VectorScale (v, 0.7, v); + else + VectorScale (v, 1.2, v); +} + +void ClipGibVelocity (edict_t *ent) +{ + if (ent->velocity[0] < -300) + ent->velocity[0] = -300; + else if (ent->velocity[0] > 300) + ent->velocity[0] = 300; + if (ent->velocity[1] < -300) + ent->velocity[1] = -300; + else if (ent->velocity[1] > 300) + ent->velocity[1] = 300; + if (ent->velocity[2] < 200) + ent->velocity[2] = 200; // always some upwards + else if (ent->velocity[2] > 500) + ent->velocity[2] = 500; +} + + +/* +================= +gibs +================= +*/ +void gib_think (edict_t *self) +{ + self->s.frame++; + self->nextthink = level.time + FRAMETIME; + + if (self->s.frame == 10) + { + self->think = G_FreeEdict; + self->nextthink = level.time + 8 + random()*10; + } +} + +void gib_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t normal_angles, right; + + if (!self->groundentity) + return; + + self->touch = NULL; + + if (plane) + { + gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/fhit3.wav"), 1, ATTN_NORM, 0); + + vectoangles (plane->normal, normal_angles); + AngleVectors (normal_angles, NULL, right, NULL); + vectoangles (right, self->s.angles); + + if (self->s.modelindex == sm_meat_index) + { + self->s.frame++; + self->think = gib_think; + self->nextthink = level.time + FRAMETIME; + } + } +} + +void gib_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowGib (edict_t *self, char *gibname, int damage, int type) +{ + edict_t *gib; + vec3_t vd; + vec3_t origin; + vec3_t size; + float vscale; + + gib = G_Spawn(); + + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + gib->s.origin[0] = origin[0] + crandom() * size[0]; + gib->s.origin[1] = origin[1] + crandom() * size[1]; + gib->s.origin[2] = origin[2] + crandom() * size[2]; + + gi.setmodel (gib, gibname); + gib->solid = SOLID_NOT; + gib->s.effects |= EF_GIB; + gib->flags |= FL_NO_KNOCKBACK; + gib->takedamage = DAMAGE_YES; + gib->die = gib_die; + + if (type == GIB_ORGANIC) + { + gib->movetype = MOVETYPE_TOSS; + gib->touch = gib_touch; + vscale = 0.5; + } + else + { + gib->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, gib->velocity); + ClipGibVelocity (gib); + gib->avelocity[0] = random()*600; + gib->avelocity[1] = random()*600; + gib->avelocity[2] = random()*600; + + gib->think = G_FreeEdict; + gib->nextthink = level.time + 10 + random()*10; + + gi.linkentity (gib); +} + +void ThrowHead (edict_t *self, char *gibname, int damage, int type) +{ + vec3_t vd; + float vscale; + + self->s.skinnum = 0; + self->s.frame = 0; + VectorClear (self->mins); + VectorClear (self->maxs); + + self->s.modelindex2 = 0; + gi.setmodel (self, gibname); + self->solid = SOLID_NOT; + self->s.effects |= EF_GIB; + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + self->svflags &= ~SVF_MONSTER; + self->takedamage = DAMAGE_YES; + self->die = gib_die; + + if (type == GIB_ORGANIC) + { + self->movetype = MOVETYPE_TOSS; + self->touch = gib_touch; + vscale = 0.5; + } + else + { + self->movetype = MOVETYPE_BOUNCE; + vscale = 1.0; + } + + VelocityForDamage (damage, vd); + VectorMA (self->velocity, vscale, vd, self->velocity); + ClipGibVelocity (self); + + self->avelocity[YAW] = crandom()*600; + + self->think = G_FreeEdict; + self->nextthink = level.time + 10 + random()*10; + + gi.linkentity (self); +} + + +void ThrowClientHead (edict_t *self, int damage) +{ + vec3_t vd; + char *gibname; + + if (rand()&1) + { + gibname = "models/objects/gibs/head2/tris.md2"; + self->s.skinnum = 1; // second skin is player + } + else + { + gibname = "models/objects/gibs/skull/tris.md2"; + self->s.skinnum = 0; + } + + self->s.origin[2] += 32; + self->s.frame = 0; + gi.setmodel (self, gibname); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 16); + + self->takedamage = DAMAGE_NO; + self->solid = SOLID_NOT; + self->s.effects = EF_GIB; + self->s.sound = 0; + self->flags |= FL_NO_KNOCKBACK; + + self->movetype = MOVETYPE_BOUNCE; + VelocityForDamage (damage, vd); + VectorAdd (self->velocity, vd, self->velocity); + + if (self->client) // bodies in the queue don't have a client anymore + { + self->client->anim_priority = ANIM_DEATH; + self->client->anim_end = self->s.frame; + } + else + { + self->think = NULL; + self->nextthink = 0; + } + + gi.linkentity (self); +} + + +/* +================= +debris +================= +*/ +void debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + G_FreeEdict (self); +} + +void ThrowDebris (edict_t *self, char *modelname, float speed, vec3_t origin) +{ + edict_t *chunk; + vec3_t v; + + chunk = G_Spawn(); + VectorCopy (origin, chunk->s.origin); + gi.setmodel (chunk, modelname); + v[0] = 100 * crandom(); + v[1] = 100 * crandom(); + v[2] = 100 + 100 * crandom(); + VectorMA (self->velocity, speed, v, chunk->velocity); + chunk->movetype = MOVETYPE_BOUNCE; + chunk->solid = SOLID_NOT; + chunk->avelocity[0] = random()*600; + chunk->avelocity[1] = random()*600; + chunk->avelocity[2] = random()*600; + chunk->think = G_FreeEdict; + chunk->nextthink = level.time + 5 + random()*5; + chunk->s.frame = 0; + chunk->flags = 0; + chunk->classname = "debris"; + chunk->takedamage = DAMAGE_YES; + chunk->die = debris_die; + gi.linkentity (chunk); +} + + +void BecomeExplosion1 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION1); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +void BecomeExplosion2 (edict_t *self) +{ + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_EXPLOSION2); + gi.WritePosition (self->s.origin); + gi.multicast (self->s.origin, MULTICAST_PVS); + + G_FreeEdict (self); +} + + +/*QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT +Target: next path corner +Pathtarget: gets used when an entity that has + this path_corner targeted touches it +*/ + +void path_corner_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t v; + edict_t *next; + + if (other->movetarget != self) + return; + + if (other->enemy) + return; + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + G_UseTargets (self, other); + self->target = savetarget; + } + + if (self->target) + next = G_PickTarget(self->target); + else + next = NULL; + + if ((next) && (next->spawnflags & 1)) + { + VectorCopy (next->s.origin, v); + v[2] += next->mins[2]; + v[2] -= other->mins[2]; + VectorCopy (v, other->s.origin); + next = G_PickTarget(next->target); + other->s.event = EV_OTHER_TELEPORT; + } + + other->goalentity = other->movetarget = next; + + if (self->wait) + { + other->monsterinfo.pausetime = level.time + self->wait; + other->monsterinfo.stand (other); + return; + } + + if (!other->movetarget) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.stand (other); + } + else + { + VectorSubtract (other->goalentity->s.origin, other->s.origin, v); + other->ideal_yaw = vectoyaw (v); + } +} + +void SP_path_corner (edict_t *self) +{ + if (!self->targetname) + { + gi.dprintf ("path_corner with no targetname at %s\n", vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + self->solid = SOLID_TRIGGER; + self->touch = path_corner_touch; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + self->svflags |= SVF_NOCLIENT; + gi.linkentity (self); +} + + +/*QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold +Makes this the target of a monster and it will head here +when first activated before going after the activator. If +hold is selected, it will stay here. +*/ +void point_combat_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *activator; + + if (other->movetarget != self) + return; + + if (self->target) + { + other->target = self->target; + other->goalentity = other->movetarget = G_PickTarget(other->target); + if (!other->goalentity) + { + gi.dprintf("%s at %s target %s does not exist\n", self->classname, vtos(self->s.origin), self->target); + other->movetarget = self; + } + self->target = NULL; + } + else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM|FL_FLY))) + { + other->monsterinfo.pausetime = level.time + 100000000; + other->monsterinfo.aiflags |= AI_STAND_GROUND; + other->monsterinfo.stand (other); + } + + if (other->movetarget == self) + { + other->target = NULL; + other->movetarget = NULL; + other->goalentity = other->enemy; + other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; + } + + if (self->pathtarget) + { + char *savetarget; + + savetarget = self->target; + self->target = self->pathtarget; + if (other->enemy && other->enemy->client) + activator = other->enemy; + else if (other->oldenemy && other->oldenemy->client) + activator = other->oldenemy; + else if (other->activator && other->activator->client) + activator = other->activator; + else + activator = other; + G_UseTargets (self, activator); + self->target = savetarget; + } +} + +void SP_point_combat (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return; + } + self->solid = SOLID_TRIGGER; + self->touch = point_combat_touch; + VectorSet (self->mins, -8, -8, -16); + VectorSet (self->maxs, 8, 8, 16); + self->svflags = SVF_NOCLIENT; + gi.linkentity (self); +}; + + +/*QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) +Just for the debugging level. Don't use +*/ +void TH_viewthing(edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 7; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_viewthing(edict_t *ent) +{ + gi.dprintf ("viewthing spawned\n"); + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.renderfx = RF_FRAMELERP; + VectorSet (ent->mins, -16, -16, -24); + VectorSet (ent->maxs, 16, 16, 32); + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + gi.linkentity (ent); + ent->nextthink = level.time + 0.5; + ent->think = TH_viewthing; + return; +} + + +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for spotlights, etc. +*/ +void SP_info_null (edict_t *self) +{ + G_FreeEdict (self); +}; + + +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) +Used as a positional target for lightning. +*/ +void SP_info_notnull (edict_t *self) +{ + VectorCopy (self->s.origin, self->absmin); + VectorCopy (self->s.origin, self->absmax); +}; + + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF +Non-displayed light. +Default light value is 300. +Default style is 0. +If targeted, will toggle between on and off. +Default _cone value is 10 (used to set size of light for spotlights) +*/ + +#define START_OFF 1 + +static void light_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->spawnflags & START_OFF) + { + gi.configstring (CS_LIGHTS+self->style, "m"); + self->spawnflags &= ~START_OFF; + } + else + { + gi.configstring (CS_LIGHTS+self->style, "a"); + self->spawnflags |= START_OFF; + } +} + +void SP_light (edict_t *self) +{ + // no targeted lights in deathmatch, because they cause global messages + if (!self->targetname || deathmatch->value) + { + G_FreeEdict (self); + return; + } + + if (self->style >= 32) + { + self->use = light_use; + if (self->spawnflags & START_OFF) + gi.configstring (CS_LIGHTS+self->style, "a"); + else + gi.configstring (CS_LIGHTS+self->style, "m"); + } +} + + +/*QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST +This is just a solid wall if not inhibited + +TRIGGER_SPAWN the wall will not be present until triggered + it will then blink in to existance; it will + kill anything that was in it's way + +TOGGLE only valid for TRIGGER_SPAWN walls + this allows the wall to be turned on and off + +START_ON only valid for TRIGGER_SPAWN walls + the wall will initially be present +*/ + +void func_wall_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + KillBox (self); + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); + + if (!(self->spawnflags & 2)) + self->use = NULL; +} + +void SP_func_wall (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + + if (self->spawnflags & 8) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 16) + self->s.effects |= EF_ANIM_ALLFAST; + + // just a wall + if ((self->spawnflags & 7) == 0) + { + self->solid = SOLID_BSP; + gi.linkentity (self); + return; + } + + // it must be TRIGGER_SPAWN + if (!(self->spawnflags & 1)) + { +// gi.dprintf("func_wall missing TRIGGER_SPAWN\n"); + self->spawnflags |= 1; + } + + // yell if the spawnflags are odd + if (self->spawnflags & 4) + { + if (!(self->spawnflags & 2)) + { + gi.dprintf("func_wall START_ON without TOGGLE\n"); + self->spawnflags |= 2; + } + } + + self->use = func_wall_use; + if (self->spawnflags & 4) + { + self->solid = SOLID_BSP; + } + else + { + self->solid = SOLID_NOT; + self->svflags |= SVF_NOCLIENT; + } + gi.linkentity (self); +} + + +/*QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST +This is solid bmodel that will fall if it's support it removed. +*/ + +void func_object_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + // only squash thing we fall on top of + if (!plane) + return; + if (plane->normal[2] < 1.0) + return; + if (other->takedamage == DAMAGE_NO) + return; + T_Damage (other, self, self, vec3_origin, self->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); +} + +void func_object_release (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->touch = func_object_touch; +} + +void func_object_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + func_object_release (self); +} + +void SP_func_object (edict_t *self) +{ + gi.setmodel (self, self->model); + + self->mins[0] += 1; + self->mins[1] += 1; + self->mins[2] += 1; + self->maxs[0] -= 1; + self->maxs[1] -= 1; + self->maxs[2] -= 1; + + if (!self->dmg) + self->dmg = 100; + + if (self->spawnflags == 0) + { + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; + self->think = func_object_release; + self->nextthink = level.time + 2 * FRAMETIME; + } + else + { + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_PUSH; + self->use = func_object_use; + self->svflags |= SVF_NOCLIENT; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + self->clipmask = MASK_MONSTERSOLID; + + gi.linkentity (self); +} + + +/*QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST +Any brush that you want to explode or break apart. If you want an +ex0plosion, set dmg and it will do a radius explosion of that amount +at the center of the bursh. + +If targeted it will not be shootable. + +health defaults to 100. + +mass defaults to 75. This determines how much debris is emitted when +it explodes. You get one large chunk per 100 of mass (up to 8) and +one small chunk per 25 of mass (up to 16). So 800 gives the most. +*/ +void func_explosive_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + vec3_t origin; + vec3_t chunkorigin; + vec3_t size; + int count; + int mass; + + // bmodel origins are (0 0 0), we need to adjust that here + VectorScale (self->size, 0.5, size); + VectorAdd (self->absmin, size, origin); + VectorCopy (origin, self->s.origin); + + self->takedamage = DAMAGE_NO; + + if (self->dmg) + T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE); + + VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity); + VectorNormalize (self->velocity); + VectorScale (self->velocity, 150, self->velocity); + + // start chunks towards the center + VectorScale (size, 0.5, size); + + mass = self->mass; + if (!mass) + mass = 75; + + // big chunks + if (mass >= 100) + { + count = mass / 100; + if (count > 8) + count = 8; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin); + } + } + + // small chunks + count = mass / 25; + if (count > 16) + count = 16; + while(count--) + { + chunkorigin[0] = origin[0] + crandom() * size[0]; + chunkorigin[1] = origin[1] + crandom() * size[1]; + chunkorigin[2] = origin[2] + crandom() * size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin); + } + + G_UseTargets (self, attacker); + + if (self->dmg) + BecomeExplosion1 (self); + else + G_FreeEdict (self); +} + +void func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +{ + func_explosive_explode (self, self, other, self->health, vec3_origin); +} + +void func_explosive_spawn (edict_t *self, edict_t *other, edict_t *activator) +{ + self->solid = SOLID_BSP; + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + KillBox (self); + gi.linkentity (self); +} + +void SP_func_explosive (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + self->movetype = MOVETYPE_PUSH; + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + + gi.setmodel (self, self->model); + + if (self->spawnflags & 1) + { + self->svflags |= SVF_NOCLIENT; + self->solid = SOLID_NOT; + self->use = func_explosive_spawn; + } + else + { + self->solid = SOLID_BSP; + if (self->targetname) + self->use = func_explosive_use; + } + + if (self->spawnflags & 2) + self->s.effects |= EF_ANIM_ALL; + if (self->spawnflags & 4) + self->s.effects |= EF_ANIM_ALLFAST; + + if (self->use != func_explosive_use) + { + if (!self->health) + self->health = 100; + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; + } + + gi.linkentity (self); +} + + +/*QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) +Large exploding box. You can override its mass (100), +health (80), and dmg (150). +*/ + +void barrel_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) + +{ + float ratio; + vec3_t v; + + if ((!other->groundentity) || (other->groundentity == self)) + return; + + ratio = (float)other->mass / (float)self->mass; + VectorSubtract (self->s.origin, other->s.origin, v); + M_walkmove (self, vectoyaw(v), 20 * ratio * FRAMETIME); +} + +void barrel_explode (edict_t *self) +{ + vec3_t org; + float spd; + vec3_t save; + + T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_BARREL); + + VectorCopy (self->s.origin, save); + VectorMA (self->absmin, 0.5, self->size, self->s.origin); + + // a few big chunks + spd = 1.5 * (float)self->dmg / 200.0; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris1/tris.md2", spd, org); + + // bottom corners + spd = 1.75 * (float)self->dmg / 200.0; + VectorCopy (self->absmin, org); + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + VectorCopy (self->absmin, org); + org[0] += self->size[0]; + org[1] += self->size[1]; + ThrowDebris (self, "models/objects/debris3/tris.md2", spd, org); + + // a bunch of little chunks + spd = 2 * self->dmg / 200; + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + org[0] = self->s.origin[0] + crandom() * self->size[0]; + org[1] = self->s.origin[1] + crandom() * self->size[1]; + org[2] = self->s.origin[2] + crandom() * self->size[2]; + ThrowDebris (self, "models/objects/debris2/tris.md2", spd, org); + + VectorCopy (save, self->s.origin); + if (self->groundentity) + BecomeExplosion2 (self); + else + BecomeExplosion1 (self); +} + +void barrel_delay (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + self->takedamage = DAMAGE_NO; + self->nextthink = level.time + 2 * FRAMETIME; + self->think = barrel_explode; + self->activator = attacker; +} + +void SP_misc_explobox (edict_t *self) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (self); + return; + } + + gi.modelindex ("models/objects/debris1/tris.md2"); + gi.modelindex ("models/objects/debris2/tris.md2"); + gi.modelindex ("models/objects/debris3/tris.md2"); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + + self->model = "models/objects/barrels/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -16, -16, 0); + VectorSet (self->maxs, 16, 16, 40); + + if (!self->mass) + self->mass = 400; + if (!self->health) + self->health = 10; + if (!self->dmg) + self->dmg = 150; + + self->die = barrel_delay; + self->takedamage = DAMAGE_YES; + self->monsterinfo.aiflags = AI_NOSTEP; + + self->touch = barrel_touch; + + self->think = M_droptofloor; + self->nextthink = level.time + 2 * FRAMETIME; + + gi.linkentity (self); +} + + +// +// miscellaneous specialty items +// + +/*QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) +*/ + +void misc_blackhole_use (edict_t *ent, edict_t *other, edict_t *activator) +{ + /* + gi.WriteByte (svc_temp_entity); + gi.WriteByte (TE_BOSSTPORT); + gi.WritePosition (ent->s.origin); + gi.multicast (ent->s.origin, MULTICAST_PVS); + */ + G_FreeEdict (ent); +} + +void misc_blackhole_think (edict_t *self) +{ + if (++self->s.frame < 19) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 0; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_blackhole (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 8); + ent->s.modelindex = gi.modelindex ("models/objects/black/tris.md2"); + ent->s.renderfx = RF_TRANSLUCENT; + ent->use = misc_blackhole_use; + ent->think = misc_blackhole_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) +*/ + +void misc_eastertank_think (edict_t *self) +{ + if (++self->s.frame < 293) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 254; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_eastertank (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, -16); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/tank/tris.md2"); + ent->s.frame = 254; + ent->think = misc_eastertank_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick_think (edict_t *self) +{ + if (++self->s.frame < 247) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 208; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 208; + ent->think = misc_easterchick_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + +/*QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) +*/ + + +void misc_easterchick2_think (edict_t *self) +{ + if (++self->s.frame < 287) + self->nextthink = level.time + FRAMETIME; + else + { + self->s.frame = 248; + self->nextthink = level.time + FRAMETIME; + } +} + +void SP_misc_easterchick2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -32, -32, 0); + VectorSet (ent->maxs, 32, 32, 32); + ent->s.modelindex = gi.modelindex ("models/monsters/bitch/tris.md2"); + ent->s.frame = 248; + ent->think = misc_easterchick2_think; + ent->nextthink = level.time + 2 * FRAMETIME; + gi.linkentity (ent); +} + + +/*QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) +Not really a monster, this is the Tank Commander's decapitated body. +There should be a item_commander_head that has this as it's target. +*/ + +void commander_body_think (edict_t *self) +{ + if (++self->s.frame < 24) + self->nextthink = level.time + FRAMETIME; + else + self->nextthink = 0; + + if (self->s.frame == 22) + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/thud.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->think = commander_body_think; + self->nextthink = level.time + FRAMETIME; + gi.sound (self, CHAN_BODY, gi.soundindex ("tank/pain.wav"), 1, ATTN_NORM, 0); +} + +void commander_body_drop (edict_t *self) +{ + self->movetype = MOVETYPE_TOSS; + self->s.origin[2] += 2; +} + +void SP_monster_commander_body (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_BBOX; + self->model = "models/monsters/commandr/tris.md2"; + self->s.modelindex = gi.modelindex (self->model); + VectorSet (self->mins, -32, -32, 0); + VectorSet (self->maxs, 32, 32, 48); + self->use = commander_body_use; + self->takedamage = DAMAGE_YES; + self->flags = FL_GODMODE; + self->s.renderfx |= RF_FRAMELERP; + gi.linkentity (self); + + gi.soundindex ("tank/thud.wav"); + gi.soundindex ("tank/pain.wav"); + + self->think = commander_body_drop; + self->nextthink = level.time + 5 * FRAMETIME; +} + + +/*QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) +The origin is the bottom of the banner. +The banner is 128 tall. +*/ +void misc_banner_think (edict_t *ent) +{ + ent->s.frame = (ent->s.frame + 1) % 16; + ent->nextthink = level.time + FRAMETIME; +} + +void SP_misc_banner (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/objects/banner/tris.md2"); + ent->s.frame = rand() % 16; + gi.linkentity (ent); + + ent->think = misc_banner_think; + ent->nextthink = level.time + FRAMETIME; +} + +/*QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED +This is the dead player model. Comes in 6 exciting different poses! +*/ +void misc_deadsoldier_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point) +{ + int n; + + if (self->health > -80) + return; + + gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n= 0; n < 4; n++) + ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowHead (self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); +} + +void SP_misc_deadsoldier (edict_t *ent) +{ + if (deathmatch->value) + { // auto-remove for deathmatch + G_FreeEdict (ent); + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex=gi.modelindex ("models/deadbods/dude/tris.md2"); + + // Defaults to frame 0 + if (ent->spawnflags & 2) + ent->s.frame = 1; + else if (ent->spawnflags & 4) + ent->s.frame = 2; + else if (ent->spawnflags & 8) + ent->s.frame = 3; + else if (ent->spawnflags & 16) + ent->s.frame = 4; + else if (ent->spawnflags & 32) + ent->s.frame = 5; + else + ent->s.frame = 0; + + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 16); + ent->deadflag = DEAD_DEAD; + ent->takedamage = DAMAGE_YES; + ent->svflags |= SVF_MONSTER|SVF_DEADMONSTER; + ent->die = misc_deadsoldier_die; + ent->monsterinfo.aiflags |= AI_GOOD_GUY; + + gi.linkentity (ent); +} + +/*QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) +This is the Viper for the flyby bombing. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast the Viper should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_viper_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_viper (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("misc_viper without a target at %s\n", vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/viper/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_viper_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) +This is a large stationary viper as seen in Paul's intro +*/ +void SP_misc_bigviper (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -176, -120, -24); + VectorSet (ent->maxs, 176, 120, 72); + ent->s.modelindex = gi.modelindex ("models/ships/bigviper/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) +"dmg" how much boom should the bomb make? +*/ +void misc_viper_bomb_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + G_UseTargets (self, self->activator); + + self->s.origin[2] = self->absmin[2] + 1; + T_RadiusDamage (self, self, self->dmg, NULL, self->dmg+40, MOD_BOMB); + BecomeExplosion2 (self); +} + +void misc_viper_bomb_prethink (edict_t *self) +{ + vec3_t v; + float diff; + + self->groundentity = NULL; + + diff = self->timestamp - level.time; + if (diff < -1.0) + diff = -1.0; + + VectorScale (self->moveinfo.dir, 1.0 + diff, v); + v[2] = diff; + + diff = self->s.angles[2]; + vectoangles (v, self->s.angles); + self->s.angles[2] = diff + 10; +} + +void misc_viper_bomb_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *viper; + + self->solid = SOLID_BBOX; + self->svflags &= ~SVF_NOCLIENT; + self->s.effects |= EF_ROCKET; + self->use = NULL; + self->movetype = MOVETYPE_TOSS; + self->prethink = misc_viper_bomb_prethink; + self->touch = misc_viper_bomb_touch; + self->activator = activator; + + viper = G_Find (NULL, FOFS(classname), "misc_viper"); + VectorScale (viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); + + self->timestamp = level.time; + VectorCopy (viper->moveinfo.dir, self->moveinfo.dir); +} + +void SP_misc_viper_bomb (edict_t *self) +{ + self->movetype = MOVETYPE_NONE; + self->solid = SOLID_NOT; + VectorSet (self->mins, -8, -8, -8); + VectorSet (self->maxs, 8, 8, 8); + + self->s.modelindex = gi.modelindex ("models/objects/bomb/tris.md2"); + + if (!self->dmg) + self->dmg = 1000; + + self->use = misc_viper_bomb_use; + self->svflags |= SVF_NOCLIENT; + + gi.linkentity (self); +} + + +/*QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) +This is a Storgg ship for the flybys. +It is trigger_spawned, so you must have something use it for it to show up. +There must be a path for it to follow once it is activated. + +"speed" How fast it should fly +*/ + +extern void train_use (edict_t *self, edict_t *other, edict_t *activator); +extern void func_train_find (edict_t *self); + +void misc_strogg_ship_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = train_use; + train_use (self, other, activator); +} + +void SP_misc_strogg_ship (edict_t *ent) +{ + if (!ent->target) + { + gi.dprintf ("%s without a target at %s\n", ent->classname, vtos(ent->absmin)); + G_FreeEdict (ent); + return; + } + + if (!ent->speed) + ent->speed = 300; + + ent->movetype = MOVETYPE_PUSH; + ent->solid = SOLID_NOT; + ent->s.modelindex = gi.modelindex ("models/ships/strogg1/tris.md2"); + VectorSet (ent->mins, -16, -16, 0); + VectorSet (ent->maxs, 16, 16, 32); + + ent->think = func_train_find; + ent->nextthink = level.time + FRAMETIME; + ent->use = misc_strogg_ship_use; + ent->svflags |= SVF_NOCLIENT; + ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; + + gi.linkentity (ent); +} + + +/*QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) +*/ +void misc_satellite_dish_think (edict_t *self) +{ + self->s.frame++; + if (self->s.frame < 38) + self->nextthink = level.time + FRAMETIME; +} + +void misc_satellite_dish_use (edict_t *self, edict_t *other, edict_t *activator) +{ + self->s.frame = 0; + self->think = misc_satellite_dish_think; + self->nextthink = level.time + FRAMETIME; +} + +void SP_misc_satellite_dish (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + VectorSet (ent->mins, -64, -64, 0); + VectorSet (ent->maxs, 64, 64, 128); + ent->s.modelindex = gi.modelindex ("models/objects/satellite/tris.md2"); + ent->use = misc_satellite_dish_use; + gi.linkentity (ent); +} + + +/*QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine1 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light1/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) +*/ +void SP_light_mine2 (edict_t *ent) +{ + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->s.modelindex = gi.modelindex ("models/objects/minelite/light2/tris.md2"); + gi.linkentity (ent); +} + + +/*QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_arm (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/arm/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_leg (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/leg/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +/*QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) +Intended for use with the target_spawner +*/ +void SP_misc_gib_head (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/gibs/head/tris.md2"); + ent->solid = SOLID_NOT; + ent->s.effects |= EF_GIB; + ent->takedamage = DAMAGE_YES; + ent->die = gib_die; + ent->movetype = MOVETYPE_TOSS; + ent->svflags |= SVF_MONSTER; + ent->deadflag = DEAD_DEAD; + ent->avelocity[0] = random()*200; + ent->avelocity[1] = random()*200; + ent->avelocity[2] = random()*200; + ent->think = G_FreeEdict; + ent->nextthink = level.time + 30; + gi.linkentity (ent); +} + +//===================================================== + +/*QUAKED target_character (0 0 1) ? +used with target_string (must be on same "team") +"count" is position in the string (starts at 1) +*/ + +void SP_target_character (edict_t *self) +{ + self->movetype = MOVETYPE_PUSH; + gi.setmodel (self, self->model); + self->solid = SOLID_BSP; + self->s.frame = 12; + gi.linkentity (self); + return; +} + + +/*QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) +*/ + +void target_string_use (edict_t *self, edict_t *other, edict_t *activator) +{ + edict_t *e; + int n, l; + char c; + + l = strlen(self->message); + for (e = self->teammaster; e; e = e->teamchain) + { + if (!e->count) + continue; + n = e->count - 1; + if (n > l) + { + e->s.frame = 12; + continue; + } + + c = self->message[n]; + if (c >= '0' && c <= '9') + e->s.frame = c - '0'; + else if (c == '-') + e->s.frame = 10; + else if (c == ':') + e->s.frame = 11; + else + e->s.frame = 12; + } +} + +void SP_target_string (edict_t *self) +{ + if (!self->message) + self->message = ""; + self->use = target_string_use; +} + + +/*QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE +target a target_string with this + +The default is to be a time of day clock + +TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" +If START_OFF, this entity must be used before it starts + +"style" 0 "xx" + 1 "xx:xx" + 2 "xx:xx:xx" +*/ + +#define CLOCK_MESSAGE_SIZE 16 + +// don't let field width of any clock messages change, or it +// could cause an overwrite after a game load + +static void func_clock_reset (edict_t *self) +{ + self->activator = NULL; + if (self->spawnflags & 1) + { + self->health = 0; + self->wait = self->count; + } + else if (self->spawnflags & 2) + { + self->health = self->count; + self->wait = 0; + } +} + +static void func_clock_format_countdown (edict_t *self) +{ + if (self->style == 0) + { + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); + return; + } + + if (self->style == 1) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", self->health / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + return; + } + + if (self->style == 2) + { + Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, self->health % 60); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + return; + } +} + +void func_clock_think (edict_t *self) +{ + if (!self->enemy) + { + self->enemy = G_Find (NULL, FOFS(targetname), self->target); + if (!self->enemy) + return; + } + + if (self->spawnflags & 1) + { + func_clock_format_countdown (self); + self->health++; + } + else if (self->spawnflags & 2) + { + func_clock_format_countdown (self); + self->health--; + } + else + { + struct tm *ltime; + time_t gmtime; + + time(&gmtime); + ltime = localtime(&gmtime); + Com_sprintf (self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", ltime->tm_hour, ltime->tm_min, ltime->tm_sec); + if (self->message[3] == ' ') + self->message[3] = '0'; + if (self->message[6] == ' ') + self->message[6] = '0'; + } + + self->enemy->message = self->message; + self->enemy->use (self->enemy, self, self); + + if (((self->spawnflags & 1) && (self->health > self->wait)) || + ((self->spawnflags & 2) && (self->health < self->wait))) + { + if (self->pathtarget) + { + char *savetarget; + char *savemessage; + + savetarget = self->target; + savemessage = self->message; + self->target = self->pathtarget; + self->message = NULL; + G_UseTargets (self, self->activator); + self->target = savetarget; + self->message = savemessage; + } + + if (!(self->spawnflags & 8)) + return; + + func_clock_reset (self); + + if (self->spawnflags & 4) + return; + } + + self->nextthink = level.time + 1; +} + +void func_clock_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (!(self->spawnflags & 8)) + self->use = NULL; + if (self->activator) + return; + self->activator = activator; + self->think (self); +} + +void SP_func_clock (edict_t *self) +{ + if (!self->target) + { + gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 2) && (!self->count)) + { + gi.dprintf("%s with no count at %s\n", self->classname, vtos(self->s.origin)); + G_FreeEdict (self); + return; + } + + if ((self->spawnflags & 1) && (!self->count)) + self->count = 60*60;; + + func_clock_reset (self); + + self->message = gi.TagMalloc (CLOCK_MESSAGE_SIZE, TAG_LEVEL); + + self->think = func_clock_think; + + if (self->spawnflags & 4) + self->use = func_clock_use; + else + self->nextthink = level.time + 1; +} + +//================================================================================= + +void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + edict_t *dest; + int i; + + if (!other->client) + return; + dest = G_Find (NULL, FOFS(targetname), self->target); + if (!dest) + { + gi.dprintf ("Couldn't find destination\n"); + return; + } + + // unlink to make sure it can't possibly interfere with KillBox + gi.unlinkentity (other); + + VectorCopy (dest->s.origin, other->s.origin); + VectorCopy (dest->s.origin, other->s.old_origin); + other->s.origin[2] += 10; + + // clear the velocity and hold them in place briefly + VectorClear (other->velocity); + other->client->ps.pmove.pm_time = 160>>3; // hold time + other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; + + // draw the teleport splash at source and on the player + self->owner->s.event = EV_PLAYER_TELEPORT; + other->s.event = EV_PLAYER_TELEPORT; + + // set angles + for (i=0 ; i<3 ; i++) + other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]); + + VectorClear (other->s.angles); + VectorClear (other->client->ps.viewangles); + VectorClear (other->client->v_angle); + + // kill anything at the destination + KillBox (other); + + gi.linkentity (other); +} + +/*QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) +Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. +*/ +void SP_misc_teleporter (edict_t *ent) +{ + edict_t *trig; + + if (!ent->target) + { + gi.dprintf ("teleporter without a target.\n"); + G_FreeEdict (ent); + return; + } + + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 1; + ent->s.effects = EF_TELEPORTER; + ent->s.sound = gi.soundindex ("world/amb10.wav"); + ent->solid = SOLID_BBOX; + + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); + + trig = G_Spawn (); + trig->touch = teleporter_touch; + trig->solid = SOLID_TRIGGER; + trig->target = ent->target; + trig->owner = ent; + VectorCopy (ent->s.origin, trig->s.origin); + VectorSet (trig->mins, -8, -8, 8); + VectorSet (trig->maxs, 8, 8, 24); + gi.linkentity (trig); + +} + +/*QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) +Point teleporters at these. +*/ +void SP_misc_teleporter_dest (edict_t *ent) +{ + gi.setmodel (ent, "models/objects/dmspot/tris.md2"); + ent->s.skinnum = 0; + ent->solid = SOLID_BBOX; +// ent->s.effects |= EF_FLIES; + VectorSet (ent->mins, -32, -32, -24); + VectorSet (ent->maxs, 32, 32, -16); + gi.linkentity (ent); +} + diff --git a/game/g_monster.c b/game/g_monster.c new file mode 100644 index 000000000..c7b954b6d --- /dev/null +++ b/game/g_monster.c @@ -0,0 +1,740 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +#include "g_local.h" + + +// +// monster weapons +// + +//FIXME mosnters should call these with a totally accurate direction +// and we can mess it up based on skill. Spread should be for normal +// and we can tighten or loosen based on skill. We could muck with +// the damages too, but I'm not sure that's such a good idea. +void monster_fire_bullet (edict_t *self, vec3_t start, vec3_t dir, int damage, int kick, int hspread, int vspread, int flashtype) +{ + fire_bullet (self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_shotgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int count, int flashtype) +{ + fire_shotgun (self, start, aimdir, damage, kick, hspread, vspread, count, MOD_UNKNOWN); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_blaster (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype, int effect) +{ + fire_blaster (self, start, dir, damage, speed, effect, false); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_grenade (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int flashtype) +{ + fire_grenade (self, start, aimdir, damage, speed, 2.5, damage+40); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int flashtype) +{ + fire_rocket (self, start, dir, damage, speed, damage+20, damage); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_railgun (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int flashtype) +{ + fire_rail (self, start, aimdir, damage, kick); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + +void monster_fire_bfg (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int speed, int kick, float damage_radius, int flashtype) +{ + fire_bfg (self, start, aimdir, damage, speed, damage_radius); + + gi.WriteByte (svc_muzzleflash2); + gi.WriteShort (self - g_edicts); + gi.WriteByte (flashtype); + gi.multicast (start, MULTICAST_PVS); +} + + + +// +// Monster utility functions +// + +static void M_FliesOff (edict_t *self) +{ + self->s.effects &= ~EF_FLIES; + self->s.sound = 0; +} + +static void M_FliesOn (edict_t *self) +{ + if (self->waterlevel) + return; + self->s.effects |= EF_FLIES; + self->s.sound = gi.soundindex ("infantry/inflies1.wav"); + self->think = M_FliesOff; + self->nextthink = level.time + 60; +} + +void M_FlyCheck (edict_t *self) +{ + if (self->waterlevel) + return; + + if (random() > 0.5) + return; + + self->think = M_FliesOn; + self->nextthink = level.time + 5 + 10 * random(); +} + +void AttackFinished (edict_t *self, float time) +{ + self->monsterinfo.attack_finished = level.time + time; +} + + +void M_CheckGround (edict_t *ent) +{ + vec3_t point; + trace_t trace; + + if (ent->flags & (FL_SWIM|FL_FLY)) + return; + + if (ent->velocity[2] > 100) + { + ent->groundentity = NULL; + return; + } + +// if the hull point one-quarter unit down is solid the entity is on ground + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] - 0.25; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); + + // check steepness + if ( trace.plane.normal[2] < 0.7 && !trace.startsolid) + { + ent->groundentity = NULL; + return; + } + +// ent->groundentity = trace.ent; +// ent->groundentity_linkcount = trace.ent->linkcount; +// if (!trace.startsolid && !trace.allsolid) +// VectorCopy (trace.endpos, ent->s.origin); + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, ent->s.origin); + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + ent->velocity[2] = 0; + } +} + + +void M_CatagorizePosition (edict_t *ent) +{ + vec3_t point; + int cont; + +// +// get waterlevel +// + point[0] = ent->s.origin[0]; + point[1] = ent->s.origin[1]; + point[2] = ent->s.origin[2] + ent->mins[2] + 1; + cont = gi.pointcontents (point); + + if (!(cont & MASK_WATER)) + { + ent->waterlevel = 0; + ent->watertype = 0; + return; + } + + ent->watertype = cont; + ent->waterlevel = 1; + point[2] += 26; + cont = gi.pointcontents (point); + if (!(cont & MASK_WATER)) + return; + + ent->waterlevel = 2; + point[2] += 22; + cont = gi.pointcontents (point); + if (cont & MASK_WATER) + ent->waterlevel = 3; +} + + +void M_WorldEffects (edict_t *ent) +{ + int dmg; + + if (ent->health > 0) + { + if (!(ent->flags & FL_SWIM)) + { + if (ent->waterlevel < 3) + { + ent->air_finished = level.time + 12; + } + else if (ent->air_finished < level.time) + { // drown! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + else + { + if (ent->waterlevel > 0) + { + ent->air_finished = level.time + 9; + } + else if (ent->air_finished < level.time) + { // suffocate! + if (ent->pain_debounce_time < level.time) + { + dmg = 2 + 2 * floor(level.time - ent->air_finished); + if (dmg > 15) + dmg = 15; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); + ent->pain_debounce_time = level.time + 1; + } + } + } + } + + if (ent->waterlevel == 0) + { + if (ent->flags & FL_INWATER) + { + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); + ent->flags &= ~FL_INWATER; + } + return; + } + + if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 0.2; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 10*ent->waterlevel, 0, 0, MOD_LAVA); + } + } + if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) + { + if (ent->damage_debounce_time < level.time) + { + ent->damage_debounce_time = level.time + 1; + T_Damage (ent, world, world, vec3_origin, ent->s.origin, vec3_origin, 4*ent->waterlevel, 0, 0, MOD_SLIME); + } + } + + if ( !(ent->flags & FL_INWATER) ) + { + if (!(ent->svflags & SVF_DEADMONSTER)) + { + if (ent->watertype & CONTENTS_LAVA) + if (random() <= 0.5) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); + else + gi.sound (ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_SLIME) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + else if (ent->watertype & CONTENTS_WATER) + gi.sound (ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); + } + + ent->flags |= FL_INWATER; + ent->damage_debounce_time = 0; + } +} + + +void M_droptofloor (edict_t *ent) +{ + vec3_t end; + trace_t trace; + + ent->s.origin[2] += 1; + VectorCopy (ent->s.origin, end); + end[2] -= 256; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); + + if (trace.fraction == 1 || trace.allsolid) + return; + + VectorCopy (trace.endpos, ent->s.origin); + + gi.linkentity (ent); + M_CheckGround (ent); + M_CatagorizePosition (ent); +} + + +void M_SetEffects (edict_t *ent) +{ + ent->s.effects &= ~(EF_COLOR_SHELL|EF_POWERSCREEN); + ent->s.renderfx &= ~(RF_SHELL_RED|RF_SHELL_GREEN|RF_SHELL_BLUE); + + if (ent->monsterinfo.aiflags & AI_RESURRECTING) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_RED; + } + + if (ent->health <= 0) + return; + + if (ent->powerarmor_time > level.time) + { + if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) + { + ent->s.effects |= EF_POWERSCREEN; + } + else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) + { + ent->s.effects |= EF_COLOR_SHELL; + ent->s.renderfx |= RF_SHELL_GREEN; + } + } +} + + +void M_MoveFrame (edict_t *self) +{ + mmove_t *move; + int index; + + move = self->monsterinfo.currentmove; + self->nextthink = level.time + FRAMETIME; + + if ((self->monsterinfo.nextframe) && (self->monsterinfo.nextframe >= move->firstframe) && (self->monsterinfo.nextframe <= move->lastframe)) + { + self->s.frame = self->monsterinfo.nextframe; + self->monsterinfo.nextframe = 0; + } + else + { + if (self->s.frame == move->lastframe) + { + if (move->endfunc) + { + move->endfunc (self); + + // regrab move, endfunc is very likely to change it + move = self->monsterinfo.currentmove; + + // check for death + if (self->svflags & SVF_DEADMONSTER) + return; + } + } + + if (self->s.frame < move->firstframe || self->s.frame > move->lastframe) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + self->s.frame = move->firstframe; + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->s.frame++; + if (self->s.frame > move->lastframe) + self->s.frame = move->firstframe; + } + } + } + + index = self->s.frame - move->firstframe; + if (move->frame[index].aifunc) + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + move->frame[index].aifunc (self, move->frame[index].dist * self->monsterinfo.scale); + else + move->frame[index].aifunc (self, 0); + + if (move->frame[index].thinkfunc) + move->frame[index].thinkfunc (self); +} + + +void monster_think (edict_t *self) +{ + M_MoveFrame (self); + if (self->linkcount != self->monsterinfo.linkcount) + { + self->monsterinfo.linkcount = self->linkcount; + M_CheckGround (self); + } + M_CatagorizePosition (self); + M_WorldEffects (self); + M_SetEffects (self); +} + + +/* +================ +monster_use + +Using a monster makes it angry at the current activator +================ +*/ +void monster_use (edict_t *self, edict_t *other, edict_t *activator) +{ + if (self->enemy) + return; + if (self->health <= 0) + return; + if (activator->flags & FL_NOTARGET) + return; + if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) + return; + +// delay reaction so if the monster is teleported, its sound is still heard + self->enemy = activator; + FoundTarget (self); +} + + +void monster_start_go (edict_t *self); + + +void monster_triggered_spawn (edict_t *self) +{ + self->s.origin[2] += 1; + KillBox (self); + + self->solid = SOLID_BBOX; + self->movetype = MOVETYPE_STEP; + self->svflags &= ~SVF_NOCLIENT; + self->air_finished = level.time + 12; + gi.linkentity (self); + + monster_start_go (self); + + if (self->enemy && !(self->spawnflags & 1) && !(self->enemy->flags & FL_NOTARGET)) + { + FoundTarget (self); + } + else + { + self->enemy = NULL; + } +} + +void monster_triggered_spawn_use (edict_t *self, edict_t *other, edict_t *activator) +{ + // we have a one frame delay here so we don't telefrag the guy who activated us + self->think = monster_triggered_spawn; + self->nextthink = level.time + FRAMETIME; + if (activator->client) + self->enemy = activator; + self->use = monster_use; +} + +void monster_triggered_start (edict_t *self) +{ + self->solid = SOLID_NOT; + self->movetype = MOVETYPE_NONE; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; + self->use = monster_triggered_spawn_use; +} + + +/* +================ +monster_death_use + +When a monster dies, it fires all of its targets with the current +enemy as activator. +================ +*/ +void monster_death_use (edict_t *self) +{ + self->flags &= ~(FL_FLY|FL_SWIM); + self->monsterinfo.aiflags &= AI_GOOD_GUY; + + if (self->item) + { + Drop_Item (self, self->item); + self->item = NULL; + } + + if (self->deathtarget) + self->target = self->deathtarget; + + if (!self->target) + return; + + G_UseTargets (self, self->enemy); +} + + +//============================================================================ + +qboolean monster_start (edict_t *self) +{ + if (deathmatch->value) + { + G_FreeEdict (self); + return false; + } + + if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) + { + self->spawnflags &= ~4; + self->spawnflags |= 1; +// gi.dprintf("fixed spawnflags on %s at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!(self->monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self->nextthink = level.time + FRAMETIME; + self->svflags |= SVF_MONSTER; + self->s.renderfx |= RF_FRAMELERP; + self->takedamage = DAMAGE_AIM; + self->air_finished = level.time + 12; + self->use = monster_use; + self->max_health = self->health; + self->clipmask = MASK_MONSTERSOLID; + + self->s.skinnum = 0; + self->deadflag = DEAD_NO; + self->svflags &= ~SVF_DEADMONSTER; + + if (!self->monsterinfo.checkattack) + self->monsterinfo.checkattack = M_CheckAttack; + VectorCopy (self->s.origin, self->s.old_origin); + + if (st.item) + { + self->item = FindItemByClassname (st.item); + if (!self->item) + gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); + } + + // randomize what frame they start on + if (self->monsterinfo.currentmove) + self->s.frame = self->monsterinfo.currentmove->firstframe + (rand() % (self->monsterinfo.currentmove->lastframe - self->monsterinfo.currentmove->firstframe + 1)); + + return true; +} + +void monster_start_go (edict_t *self) +{ + vec3_t v; + + if (self->health <= 0) + return; + + // check for target to combat_point and change to combattarget + if (self->target) + { + qboolean notcombat; + qboolean fixup; + edict_t *target; + + target = NULL; + notcombat = false; + fixup = false; + while ((target = G_Find (target, FOFS(targetname), self->target)) != NULL) + { + if (strcmp(target->classname, "point_combat") == 0) + { + self->combattarget = self->target; + fixup = true; + } + else + { + notcombat = true; + } + } + if (notcombat && self->combattarget) + gi.dprintf("%s at %s has target with mixed types\n", self->classname, vtos(self->s.origin)); + if (fixup) + self->target = NULL; + } + + // validate combattarget + if (self->combattarget) + { + edict_t *target; + + target = NULL; + while ((target = G_Find (target, FOFS(targetname), self->combattarget)) != NULL) + { + if (strcmp(target->classname, "point_combat") != 0) + { + gi.dprintf("%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", + self->classname, (int)self->s.origin[0], (int)self->s.origin[1], (int)self->s.origin[2], + self->combattarget, target->classname, (int)target->s.origin[0], (int)target->s.origin[1], + (int)target->s.origin[2]); + } + } + } + + if (self->target) + { + self->goalentity = self->movetarget = G_PickTarget(self->target); + if (!self->movetarget) + { + gi.dprintf ("%s can't find target %s at %s\n", self->classname, self->target, vtos(self->s.origin)); + self->target = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + else if (strcmp (self->movetarget->classname, "path_corner") == 0) + { + VectorSubtract (self->goalentity->s.origin, self->s.origin, v); + self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); + self->monsterinfo.walk (self); + self->target = NULL; + } + else + { + self->goalentity = self->movetarget = NULL; + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + } + else + { + self->monsterinfo.pausetime = 100000000; + self->monsterinfo.stand (self); + } + + self->think = monster_think; + self->nextthink = level.time + FRAMETIME; +} + + +void walkmonster_start_go (edict_t *self) +{ + if (!(self->spawnflags & 2) && level.time < 1) + { + M_droptofloor (self); + + if (self->groundentity) + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + } + + if (!self->yaw_speed) + self->yaw_speed = 20; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void walkmonster_start (edict_t *self) +{ + self->think = walkmonster_start_go; + monster_start (self); +} + + +void flymonster_start_go (edict_t *self) +{ + if (!M_walkmove (self, 0, 0)) + gi.dprintf ("%s in solid at %s\n", self->classname, vtos(self->s.origin)); + + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 25; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + + +void flymonster_start (edict_t *self) +{ + self->flags |= FL_FLY; + self->think = flymonster_start_go; + monster_start (self); +} + + +void swimmonster_start_go (edict_t *self) +{ + if (!self->yaw_speed) + self->yaw_speed = 10; + self->viewheight = 10; + + monster_start_go (self); + + if (self->spawnflags & 2) + monster_triggered_start (self); +} + +void swimmonster_start (edict_t *self) +{ + self->flags |= FL_SWIM; + self->think = swimmonster_start_go; + monster_start (self); +} diff --git a/game/g_phys.c b/game/g_phys.c new file mode 100644 index 000000000..8cbd1bc4c --- /dev/null +++ b/game/g_phys.c @@ -0,0 +1,961 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// g_phys.c + +#include "g_local.h" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + + +/* +============ +SV_TestEntityPosition + +============ +*/ +edict_t *SV_TestEntityPosition (edict_t *ent) +{ + trace_t trace; + int mask; + + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask); + + if (trace.startsolid) + return g_edicts; + + return NULL; +} + + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity (edict_t *ent) +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + if (ent->velocity[i] > sv_maxvelocity->value) + ent->velocity[i] = sv_maxvelocity->value; + else if (ent->velocity[i] < -sv_maxvelocity->value) + ent->velocity[i] = -sv_maxvelocity->value; + } +} + +/* +============= +SV_RunThink + +Runs thinking code for this frame if necessary +============= +*/ +qboolean SV_RunThink (edict_t *ent) +{ + float thinktime; + + thinktime = ent->nextthink; + if (thinktime <= 0) + return true; + if (thinktime > level.time+0.001) + return true; + + ent->nextthink = 0; + if (!ent->think) + gi.error ("NULL ent->think"); + ent->think (ent); + + return false; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact (edict_t *e1, trace_t *trace) +{ + edict_t *e2; +// cplane_t backplane; + + e2 = trace->ent; + + if (e1->touch && e1->solid != SOLID_NOT) + e1->touch (e1, e2, &trace->plane, trace->surface); + + if (e2->touch && e2->solid != SOLID_NOT) + e2->touch (e2, e1, NULL, NULL); +} + + +/* +================== +ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int ClipVelocity (vec3_t in, vec3_t normal, vec3_t out, float overbounce) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if (normal[2] > 0) + blocked |= 1; // floor + if (!normal[2]) + blocked |= 2; // step + + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + return blocked; +} + + +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 +int SV_FlyMove (edict_t *ent, float time, int mask) +{ + edict_t *hit; + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + vec3_t end; + float time_left; + int blocked; + + numbumps = 4; + + blocked = 0; + VectorCopy (ent->velocity, original_velocity); + VectorCopy (ent->velocity, primal_velocity); + numplanes = 0; + + time_left = time; + + ent->groundentity = NULL; + for (bumpcount=0 ; bumpcounts.origin[i] + time_left * ent->velocity[i]; + + trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, end, ent, mask); + + if (trace.allsolid) + { // entity is trapped in another solid + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + if (trace.fraction > 0) + { // actually covered some distance + VectorCopy (trace.endpos, ent->s.origin); + VectorCopy (ent->velocity, original_velocity); + numplanes = 0; + } + + if (trace.fraction == 1) + break; // moved the entire distance + + hit = trace.ent; + + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + if ( hit->solid == SOLID_BSP) + { + ent->groundentity = hit; + ent->groundentity_linkcount = hit->linkcount; + } + } + if (!trace.plane.normal[2]) + { + blocked |= 2; // step + } + +// +// run the impact function +// + SV_Impact (ent, &trace); + if (!ent->inuse) + break; // removed by the impact function + + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if (numplanes >= MAX_CLIP_PLANES) + { // this shouldn't really happen + VectorCopy (vec3_origin, ent->velocity); + return 3; + } + + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + +// +// modify original_velocity so it parallels all of the clip planes +// + for (i=0 ; ivelocity); + } + else + { // go along the crease + if (numplanes != 2) + { +// gi.dprintf ("clip velocity, numplanes == %i\n",numplanes); + VectorCopy (vec3_origin, ent->velocity); + return 7; + } + CrossProduct (planes[0], planes[1], dir); + d = DotProduct (dir, ent->velocity); + VectorScale (dir, d, ent->velocity); + } + +// +// if original velocity is against the original velocity, stop dead +// to avoid tiny occilations in sloping corners +// + if (DotProduct (ent->velocity, primal_velocity) <= 0) + { + VectorCopy (vec3_origin, ent->velocity); + return blocked; + } + } + + return blocked; +} + + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity (edict_t *ent) +{ + ent->velocity[2] -= ent->gravity * sv_gravity->value * FRAMETIME; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity (edict_t *ent, vec3_t push) +{ + trace_t trace; + vec3_t start; + vec3_t end; + int mask; + + VectorCopy (ent->s.origin, start); + VectorAdd (start, push, end); + +retry: + if (ent->clipmask) + mask = ent->clipmask; + else + mask = MASK_SOLID; + + trace = gi.trace (start, ent->mins, ent->maxs, end, ent, mask); + + VectorCopy (trace.endpos, ent->s.origin); + gi.linkentity (ent); + + if (trace.fraction != 1.0) + { + SV_Impact (ent, &trace); + + // if the pushed entity went away and the pusher is still there + if (!trace.ent->inuse && ent->inuse) + { + // move the pusher back and try again + VectorCopy (start, ent->s.origin); + gi.linkentity (ent); + goto retry; + } + } + + if (ent->inuse) + G_TouchTriggers (ent); + + return trace; +} + + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + float deltayaw; +} pushed_t; +pushed_t pushed[MAX_EDICTS], *pushed_p; + +edict_t *obstacle; + +/* +============ +SV_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean SV_Push (edict_t *pusher, vec3_t move, vec3_t amove) +{ + int i, e; + edict_t *check, *block; + vec3_t mins, maxs; + pushed_t *p; + vec3_t org, org2, move2, forward, right, up; + + // clamp the move to 1/8 units, so the position will + // be accurate for client side prediction + for (i=0 ; i<3 ; i++) + { + float temp; + temp = move[i]*8.0; + if (temp > 0.0) + temp += 0.5; + else + temp -= 0.5; + move[i] = 0.125 * (int)temp; + } + + // find the bounding box + for (i=0 ; i<3 ; i++) + { + mins[i] = pusher->absmin[i] + move[i]; + maxs[i] = pusher->absmax[i] + move[i]; + } + +// we need this for pushing things later + VectorSubtract (vec3_origin, amove, org); + AngleVectors (org, forward, right, up); + +// save the pusher's original position + pushed_p->ent = pusher; + VectorCopy (pusher->s.origin, pushed_p->origin); + VectorCopy (pusher->s.angles, pushed_p->angles); + if (pusher->client) + pushed_p->deltayaw = pusher->client->ps.pmove.delta_angles[YAW]; + pushed_p++; + +// move the pusher to it's final position + VectorAdd (pusher->s.origin, move, pusher->s.origin); + VectorAdd (pusher->s.angles, amove, pusher->s.angles); + gi.linkentity (pusher); + +// see if any solid entities are inside the final position + check = g_edicts+1; + for (e = 1; e < globals.num_edicts; e++, check++) + { + if (!check->inuse) + continue; + if (check->movetype == MOVETYPE_PUSH + || check->movetype == MOVETYPE_STOP + || check->movetype == MOVETYPE_NONE + || check->movetype == MOVETYPE_NOCLIP) + continue; + + if (!check->area.prev) + continue; // not linked in anywhere + + // if the entity is standing on the pusher, it will definitely be moved + if (check->groundentity != pusher) + { + // see if the ent needs to be tested + if ( check->absmin[0] >= maxs[0] + || check->absmin[1] >= maxs[1] + || check->absmin[2] >= maxs[2] + || check->absmax[0] <= mins[0] + || check->absmax[1] <= mins[1] + || check->absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if (!SV_TestEntityPosition (check)) + continue; + } + + if ((pusher->movetype == MOVETYPE_PUSH) || (check->groundentity == pusher)) + { + // move this entity + pushed_p->ent = check; + VectorCopy (check->s.origin, pushed_p->origin); + VectorCopy (check->s.angles, pushed_p->angles); + pushed_p++; + + // try moving the contacted entity + VectorAdd (check->s.origin, move, check->s.origin); + if (check->client) + { // FIXME: doesn't rotate monsters? + check->client->ps.pmove.delta_angles[YAW] += amove[YAW]; + } + + // figure movement due to the pusher's amove + VectorSubtract (check->s.origin, pusher->s.origin, org); + org2[0] = DotProduct (org, forward); + org2[1] = -DotProduct (org, right); + org2[2] = DotProduct (org, up); + VectorSubtract (org2, org, move2); + VectorAdd (check->s.origin, move2, check->s.origin); + + // may have pushed them off an edge + if (check->groundentity != pusher) + check->groundentity = NULL; + + block = SV_TestEntityPosition (check); + if (!block) + { // pushed ok + gi.linkentity (check); + // impact? + continue; + } + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + VectorSubtract (check->s.origin, move, check->s.origin); + block = SV_TestEntityPosition (check); + if (!block) + { + pushed_p--; + continue; + } + } + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for (p=pushed_p-1 ; p>=pushed ; p--) + { + VectorCopy (p->origin, p->ent->s.origin); + VectorCopy (p->angles, p->ent->s.angles); + if (p->ent->client) + { + p->ent->client->ps.pmove.delta_angles[YAW] = p->deltayaw; + } + gi.linkentity (p->ent); + } + return false; + } + +//FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for (p=pushed_p-1 ; p>=pushed ; p--) + G_TouchTriggers (p->ent); + + return true; +} + +/* +================ +SV_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void SV_Physics_Pusher (edict_t *ent) +{ + vec3_t move, amove; + edict_t *part, *mv; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out +//retry: + pushed_p = pushed; + for (part = ent ; part ; part=part->teamchain) + { + if (part->velocity[0] || part->velocity[1] || part->velocity[2] || + part->avelocity[0] || part->avelocity[1] || part->avelocity[2] + ) + { // object is moving + VectorScale (part->velocity, FRAMETIME, move); + VectorScale (part->avelocity, FRAMETIME, amove); + + if (!SV_Push (part, move, amove)) + break; // move was blocked + } + } + if (pushed_p > &pushed[MAX_EDICTS]) + gi.error (ERR_FATAL, "pushed_p > &pushed[MAX_EDICTS], memory corrupted"); + + if (part) + { + // the move failed, bump all nextthink times and back out moves + for (mv = ent ; mv ; mv=mv->teamchain) + { + if (mv->nextthink > 0) + mv->nextthink += FRAMETIME; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if (part->blocked) + part->blocked (part, obstacle); +#if 0 + // if the pushed entity went away and the pusher is still there + if (!obstacle->inuse && part->inuse) + goto retry; +#endif + } + else + { + // the move succeeded, so call all think functions + for (part = ent ; part ; part=part->teamchain) + { + SV_RunThink (part); + } + } +} + +//================================================================== + +/* +============= +SV_Physics_None + +Non moving objects can only think +============= +*/ +void SV_Physics_None (edict_t *ent) +{ +// regular thinking + SV_RunThink (ent); +} + +/* +============= +SV_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip (edict_t *ent) +{ +// regular thinking + if (!SV_RunThink (ent)) + return; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + VectorMA (ent->s.origin, FRAMETIME, ent->velocity, ent->s.origin); + + gi.linkentity (ent); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss (edict_t *ent) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *slave; + qboolean wasinwater; + qboolean isinwater; + vec3_t old_origin; + +// regular thinking + SV_RunThink (ent); + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE) + return; + + if (ent->velocity[2] > 0) + ent->groundentity = NULL; + +// check for the groundentity going away + if (ent->groundentity) + if (!ent->groundentity->inuse) + ent->groundentity = NULL; + +// if onground, return without moving + if ( ent->groundentity ) + return; + + VectorCopy (ent->s.origin, old_origin); + + SV_CheckVelocity (ent); + +// add gravity + if (ent->movetype != MOVETYPE_FLY + && ent->movetype != MOVETYPE_FLYMISSILE) + SV_AddGravity (ent); + +// move angles + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + +// move origin + VectorScale (ent->velocity, FRAMETIME, move); + trace = SV_PushEntity (ent, move); + if (!ent->inuse) + return; + + if (trace.fraction < 1) + { + if (ent->movetype == MOVETYPE_BOUNCE) + backoff = 1.5; + else + backoff = 1; + + ClipVelocity (ent->velocity, trace.plane.normal, ent->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + if (ent->velocity[2] < 60 || ent->movetype != MOVETYPE_BOUNCE ) + { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + VectorCopy (vec3_origin, ent->velocity); + VectorCopy (vec3_origin, ent->avelocity); + } + } + +// if (ent->touch) +// ent->touch (ent, trace.ent, &trace.plane, trace.surface); + } + +// check for water transition + wasinwater = (ent->watertype & MASK_WATER); + ent->watertype = gi.pointcontents (ent->s.origin); + isinwater = ent->watertype & MASK_WATER; + + if (isinwater) + ent->waterlevel = 1; + else + ent->waterlevel = 0; + + if (!wasinwater && isinwater) + gi.positioned_sound (old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + else if (wasinwater && !isinwater) + gi.positioned_sound (ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0); + +// move teamslaves + for (slave = ent->teamchain; slave; slave = slave->teamchain) + { + VectorCopy (ent->s.origin, slave->s.origin); + gi.linkentity (slave); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +//FIXME: hacked in for E3 demo +#define sv_stopspeed 100 +#define sv_friction 6 +#define sv_waterfriction 1 + +void SV_AddRotationalFriction (edict_t *ent) +{ + int n; + float adjustment; + + VectorMA (ent->s.angles, FRAMETIME, ent->avelocity, ent->s.angles); + adjustment = FRAMETIME * sv_stopspeed * sv_friction; + for (n = 0; n < 3; n++) + { + if (ent->avelocity[n] > 0) + { + ent->avelocity[n] -= adjustment; + if (ent->avelocity[n] < 0) + ent->avelocity[n] = 0; + } + else + { + ent->avelocity[n] += adjustment; + if (ent->avelocity[n] > 0) + ent->avelocity[n] = 0; + } + } +} + +void SV_Physics_Step (edict_t *ent) +{ + qboolean wasonground; + qboolean hitsound = false; + float *vel; + float speed, newspeed, control; + float friction; + edict_t *groundentity; + int mask; + + // airborn monsters should always check for ground + if (!ent->groundentity) + M_CheckGround (ent); + + groundentity = ent->groundentity; + + SV_CheckVelocity (ent); + + if (groundentity) + wasonground = true; + else + wasonground = false; + + if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + SV_AddRotationalFriction (ent); + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if (! wasonground) + if (!(ent->flags & FL_FLY)) + if (!((ent->flags & FL_SWIM) && (ent->waterlevel > 2))) + { + if (ent->velocity[2] < sv_gravity->value*-0.1) + hitsound = true; + if (ent->waterlevel == 0) + SV_AddGravity (ent); + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_FLY) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + friction = sv_friction/3; + newspeed = speed - (FRAMETIME * control * friction); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ((ent->flags & FL_SWIM) && (ent->velocity[2] != 0)) + { + speed = fabs(ent->velocity[2]); + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - (FRAMETIME * control * sv_waterfriction * ent->waterlevel); + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + ent->velocity[2] *= newspeed; + } + + if (ent->velocity[2] || ent->velocity[1] || ent->velocity[0]) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ((wasonground) || (ent->flags & (FL_SWIM|FL_FLY))) + if (!(ent->health <= 0.0 && !M_CheckBottom(ent))) + { + vel = ent->velocity; + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1]); + if (speed) + { + friction = sv_friction; + + control = speed < sv_stopspeed ? sv_stopspeed : speed; + newspeed = speed - FRAMETIME*control*friction; + + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + vel[0] *= newspeed; + vel[1] *= newspeed; + } + } + + if (ent->svflags & SVF_MONSTER) + mask = MASK_MONSTERSOLID; + else + mask = MASK_SOLID; + SV_FlyMove (ent, FRAMETIME, mask); + + gi.linkentity (ent); + G_TouchTriggers (ent); + if (!ent->inuse) + return; + + if (ent->groundentity) + if (!wasonground) + if (hitsound) + gi.sound (ent, 0, gi.soundindex("world/land.wav"), 1, 1, 0); + } + +// regular thinking + SV_RunThink (ent); +} + +//============================================================================ +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity (edict_t *ent) +{ + if (ent->prethink) + ent->prethink (ent); + + switch ( (int)ent->movetype) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + SV_Physics_None (ent); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip (ent); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + SV_Physics_Toss (ent); + break; + default: + gi.error ("SV_Physics: bad movetype %i", (int)ent->movetype); + } +}